Pārlūkot izejas kodu

Merge branch 'master' of http://git.cc-lotus.info/new_train/train-center

guhongwei 5 gadi atpakaļ
vecāks
revīzija
7fab10ad3e
75 mainītis faili ar 8861 papildinājumiem un 363 dzēšanām
  1. 3 0
      package.json
  2. 2 0
      src/main.js
  3. 43 12
      src/plugins/dateM.js
  4. 16 0
      src/plugins/moment.js
  5. 2 0
      src/plugins/other.js
  6. 422 223
      src/router/index.js
  7. 26 2
      src/store/index.js
  8. 0 1
      src/views/Itembank/detail.vue
  9. 7 2
      src/views/bedroom/detail.vue
  10. 23 2
      src/views/bedroom/index.vue
  11. 3 4
      src/views/director/index.vue
  12. 1 0
      src/views/duty/index.vue
  13. 80 0
      src/views/home.vue
  14. 81 0
      src/views/home/iscome.vue
  15. 98 0
      src/views/home/leave.vue
  16. 80 0
      src/views/home/school.vue
  17. 1 4
      src/views/index.vue
  18. 109 0
      src/views/leave/index.vue
  19. 242 0
      src/views/lesson/detail.vue
  20. 73 0
      src/views/lesson/index.vue
  21. 4 1
      src/views/location/detail.vue
  22. 2 2
      src/views/location/index.vue
  23. 507 0
      src/views/new-plan/arrange/arrange.vue
  24. 212 0
      src/views/new-plan/arrange/director-arrange.vue
  25. 684 0
      src/views/new-plan/arrange/school-arrange.vue
  26. 129 0
      src/views/new-plan/arrange/school-num.vue
  27. 176 0
      src/views/new-plan/arrange/school-time.vue
  28. 86 0
      src/views/new-plan/class/bedroom.vue
  29. 243 0
      src/views/new-plan/class/classes.vue
  30. 151 0
      src/views/new-plan/class/index-class.vue
  31. 129 0
      src/views/new-plan/class/lesson.vue
  32. 353 0
      src/views/new-plan/class/lesson/lesson-table.vue
  33. 97 0
      src/views/new-plan/class/lesson/teacher.vue
  34. 247 0
      src/views/new-plan/class/name-list.vue
  35. 173 0
      src/views/new-plan/class/setting.vue
  36. 78 0
      src/views/new-plan/class/setting/detail.vue
  37. 226 0
      src/views/new-plan/index.vue
  38. 130 0
      src/views/new-plan/parts/event.vue
  39. 116 0
      src/views/new-plan/template.vue
  40. 2 0
      src/views/plan/detail.vue
  41. 0 2
      src/views/plan/lesson-plan.vue
  42. 18 1
      src/views/questionnaire/detail.vue
  43. 2 0
      src/views/questionnaire/index.vue
  44. 1 1
      src/views/register/login.vue
  45. 101 0
      src/views/school/detail.vue
  46. 79 0
      src/views/school/index.vue
  47. 70 0
      src/views/setting/system-setting.vue
  48. 103 0
      src/views/statistics/charts/line-bar.vue
  49. 97 0
      src/views/statistics/charts/pie.vue
  50. 57 0
      src/views/statistics/detail.vue
  51. 68 0
      src/views/statistics/index.vue
  52. 110 36
      src/views/student/index.vue
  53. 17 1
      src/views/subject/detail.vue
  54. 1 0
      src/views/subject/index.vue
  55. 0 1
      src/views/teacher/detail.vue
  56. 49 50
      src/views/test/detail.vue
  57. 124 0
      src/views/train-batch/index.vue
  58. 131 0
      src/views/train-plan/attendance.vue
  59. 189 0
      src/views/train-plan/bedroom.vue
  60. 249 0
      src/views/train-plan/classes.vue
  61. 88 0
      src/views/train-plan/leave.vue
  62. 205 0
      src/views/train-plan/lesson.vue
  63. 85 0
      src/views/train-plan/parts/class-info.vue
  64. 369 0
      src/views/train-plan/parts/class-table.vue
  65. 142 0
      src/views/train-plan/parts/lesson-table.vue
  66. 97 0
      src/views/train-plan/parts/teacher.vue
  67. 133 0
      src/views/train-plan/parts/term-class-from.vue
  68. 82 0
      src/views/train-plan/parts/term-lesson-form.vue
  69. 104 0
      src/views/train-plan/parts/term-lesson-table.vue
  70. 123 0
      src/views/train-plan/print.vue
  71. 146 0
      src/views/train-plan/quest.vue
  72. 30 0
      src/views/train-plan/remind.vue
  73. 138 0
      src/views/train-plan/score.vue
  74. 589 0
      src/views/train-plan/term-lesson.vue
  75. 7 18
      vue.config.js

+ 3 - 0
package.json

@@ -11,10 +11,13 @@
     "@fullcalendar/core": "^4.3.1",
     "@fullcalendar/daygrid": "^4.3.0",
     "@fullcalendar/interaction": "^4.3.0",
+    "@fullcalendar/list": "^4.4.2",
+    "@fullcalendar/timegrid": "^4.4.2",
     "@fullcalendar/vue": "^4.3.1",
     "@stomp/stompjs": "^5.4.3",
     "axios": "^0.19.1",
     "core-js": "^3.4.4",
+    "echarts": "^4.7.0",
     "element-ui": "^2.13.0",
     "jsonwebtoken": "^8.5.1",
     "lodash": "^4.17.15",

+ 2 - 0
src/main.js

@@ -7,6 +7,8 @@ import '@/plugins/axios';
 import '@/plugins/check-res';
 import '@/plugins/element';
 import '@/plugins/setting';
+import '@/plugins/other';
+import '@/plugins/moment';
 import '@/plugins/dateM';
 import InitStomp from '@/plugins/stomp';
 

+ 43 - 12
src/plugins/dateM.js

@@ -1,22 +1,53 @@
 import Vue from 'vue';
 import _ from 'lodash';
-const vm = new Vue({});
+var moment = require('moment');
 const Plugin = {
   install(Vue, options) {
-    Vue.prototype.$fullDateString = (date, type) => {
-      let res;
-      let dStr = date.toLocaleDateString();
-      let arr = dStr.split('/');
-      let year = parseInt(arr[0]);
-      let month = parseInt(arr[1]) >= 10 ? parseInt(arr[1]) : `0${parseInt(arr[1])}`;
-      let day = parseInt(arr[2]) >= 10 ? parseInt(arr[2]) : `0${parseInt(arr[2])}`;
-      res = `${year}-${month}-${day}`;
-      if (type === 'datetime') {
-        let tStr = date.toLocaleTimeString();
-        res = `${res} ${tStr}`;
+    Vue.prototype.$checkDate = (startStr, endStr, vacation = []) => {
+      let res = true;
+      let cbt = (time, vs, ve) => {
+        let r = moment(time).isBetween(vs, ve, null, '[]');
+        if (r) res = false;
+        return r;
+      };
+      for (const vac of vacation) {
+        if (moment(startStr).isBefore(vac.start)) {
+          if (cbt(endStr, vac.start, vac.end)) {
+            res = vac;
+            break;
+          }
+        } else {
+          if (cbt(startStr, vac.start, vac.end)) {
+            res = vac;
+            break;
+          }
+        }
       }
       return res;
     };
+    Vue.prototype.$tqInRange = (start, end, ranges) => {
+      let res = { result: false };
+      //开始时间
+      for (const range of ranges) {
+        let { start: s, end: e } = range;
+        //开始时间在范围开始前=>说明检测的时间段不在这个范围内;继续循环
+        if (moment(start).isBefore(s)) continue;
+        //开始时间是否在范围内=>true:说明在,继续检查end在不在范围内;false:不在范围内;继续循环
+        if (!moment(start).isBetween(s, e, null, '[]')) continue;
+        //结束时间是否在范围内=>true:说明检测的时间段在该范围内,break;false=>说明不行;继续循环
+        if (moment(end).isBetween(s, e, null, '[]')) {
+          res.result = true;
+          res.checkData = { start, end };
+          res.range = range;
+        }
+      }
+      return res;
+    };
+    Vue.prototype.$plusDay = (date, day = 1) => {
+      return moment(date)
+        .add(day, 'days')
+        .format('YYYY-MM-DD');
+    };
   },
 };
 Vue.use(Plugin);

+ 16 - 0
src/plugins/moment.js

@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import moment from 'moment';
+
+const vm = new Vue({});
+const Plugin = {
+  install(Vue, options) {
+    // 4. 添加实例方法
+    Vue.prototype.$moment = moment;
+    Vue.prototype.$addZero = data => {
+      if (data * 1 < 10) return `0${data}`;
+      else return data;
+    };
+  },
+};
+
+Vue.use(Plugin);

+ 2 - 0
src/plugins/other.js

@@ -0,0 +1,2 @@
+import Vue from 'vue';
+Vue.config.jhAppKey = 'ed73fa73956ff995bad705d664002595';

+ 422 - 223
src/router/index.js

@@ -5,12 +5,397 @@ import { Notification } from 'element-ui';
 
 Vue.use(VueRouter);
 
+const system = [
+  // 缺少培训批次
+  {
+    path: '/train/batch/index',
+    name: 'train_batch_index',
+    meta: { title: '培训批次管理' },
+    component: () => import('@/views/train-batch/index.vue'),
+  },
+  {
+    path: '/dept/index',
+    name: 'dept_index',
+    meta: { title: '部门', sub: '管理' },
+    component: () => import('@/views/dept/index.vue'),
+  },
+  {
+    path: '/dept/detail',
+    name: 'dept_detail',
+    meta: { title: '部门', sub: '详情' },
+    component: () => import('@/views/dept/detail.vue'),
+  },
+  {
+    path: '/director/index',
+    name: 'director_index',
+    meta: { title: '班主任', sub: '管理' },
+    component: () => import('@/views/director/index.vue'),
+  },
+  {
+    path: '/director/detail',
+    name: 'director_detail',
+    meta: { title: '班主任', sub: '详情' },
+    component: () => import('@/views/director/detail.vue'),
+  },
+  {
+    path: '/teacher/detail',
+    name: 'teacher_detail',
+    meta: { title: '教师', sub: '详情' },
+    component: () => import('@/views/teacher/detail.vue'),
+  },
+  {
+    path: '/teacher/index',
+    name: 'teacher_index',
+    meta: { title: '教师', sub: '管理' },
+    component: () => import('@/views/teacher/index.vue'),
+  },
+  {
+    path: '/teacher/means',
+    name: 'teacher_means',
+    component: () => import('@/views/teacher/means.vue'),
+  },
+  {
+    path: '/teacher/verify',
+    name: 'teacher_verify',
+    meta: { title: '教师', sub: '审核页' },
+    component: () => import('@/views/teacher/verify.vue'),
+  },
+  {
+    path: '/school/index',
+    name: 'school_index',
+    meta: { title: '学校管理', sub: '' },
+    component: () => import('@/views/school/index.vue'),
+  },
+  {
+    path: '/school/detail',
+    name: 'school_detail',
+    meta: { title: '学校信息', sub: '' },
+    component: () => import('@/views/school/detail.vue'),
+  },
+  {
+    path: '/location/index',
+    name: 'location_index',
+    meta: { title: '地点', sub: '管理' },
+    component: () => import('@/views/location/index.vue'),
+  },
+  {
+    path: '/location/detail',
+    name: 'location_detail',
+    meta: { title: '地点', sub: '详情' },
+    component: () => import('@/views/location/detail.vue'),
+  },
+  {
+    path: '/bedroom/index',
+    name: 'bedroom_index',
+    meta: { title: '寝室', sub: '管理' },
+    component: () => import('@/views/bedroom/index.vue'),
+  },
+  {
+    path: '/bedroom/detail',
+    name: 'bedroom_detail',
+    meta: { title: '寝室', sub: '详情' },
+    component: () => import('@/views/bedroom/detail.vue'),
+  },
+  {
+    path: '/subject/index',
+    name: 'subject_index',
+    meta: { title: '科目', sub: '管理' },
+    component: () => import('@/views/subject/index.vue'),
+  },
+  {
+    path: '/subject/detail',
+    name: 'subject_detail',
+    meta: { title: '科目', sub: '详情' },
+    component: () => import('@/views/subject/detail.vue'),
+  },
+  {
+    path: '/work/detail',
+    name: 'work_detail',
+    meta: { title: '作业', sub: '详情' },
+    component: () => import('@/views/work/detail.vue'),
+  },
+  {
+    path: '/work/index',
+    name: 'work_index',
+    meta: { title: '作业', sub: '管理' },
+    component: () => import('@/views/work/index.vue'),
+  },
+  {
+    path: '/work/look',
+    name: 'work_look',
+    meta: { title: '作业题', sub: '管理' },
+    component: () => import('@/views/work/look.vue'),
+  },
+  {
+    path: '/duty/index',
+    name: 'duty_index',
+    meta: { title: '职责', sub: '说明' },
+    component: () => import('@/views/duty/index.vue'),
+  },
+  {
+    path: '/lesson/index',
+    name: 'lesson_index',
+    meta: { title: '课程模板管理', sub: '' },
+    component: () => import('@/views/lesson/index.vue'),
+  },
+  {
+    path: '/lesson/detail',
+    name: 'lesson_detail',
+    meta: { title: '课程模板', sub: '' },
+    component: () => import('@/views/lesson/detail.vue'),
+  },
+  {
+    path: '/questionnaire/index',
+    name: 'questionnaire_index',
+    meta: { title: '问卷', sub: '管理' },
+    component: () => import('@/views/questionnaire/index.vue'),
+  },
+  {
+    path: '/questionnaire/detail',
+    name: 'questionnaire_detail',
+    meta: { title: '问卷', sub: '详情' },
+    component: () => import('@/views/questionnaire/detail.vue'),
+  },
+
+  {
+    path: '/questionstate/index',
+    name: 'questionstate_index',
+    meta: { title: '问卷进度管理', sub: '' },
+    component: () => import('@/views/questionstate/index.vue'),
+  },
+  {
+    path: '/itembank/detail',
+    name: 'itembank_detail',
+    meta: { title: '题库', sub: '详情' },
+    component: () => import('@/views/Itembank/detail.vue'),
+  },
+  {
+    path: '/itembank/index',
+    name: 'itembank_index',
+    meta: { title: '题库', sub: '管理' },
+    component: () => import('@/views/Itembank/index.vue'),
+  },
+  // {
+  //   path: '/classes/index',
+  //   name: 'classes_index',
+  //   meta: { title: '班级', sub: '管理' },
+  //   component: () => import('@/views/classes/index.vue'),
+  // },
+  // {
+  //   path: '/classes/detail',
+  //   name: 'classes_detail',
+  //   meta: { title: '班级', sub: '详情' },
+  //   component: () => import('@/views/classes/detail.vue'),
+  // },
+  {
+    path: '/setting',
+    name: 'setting',
+    meta: { title: '系统邮箱设置' },
+    component: () => import('@/views/setting/system-setting.vue'),
+  },
+];
+const newPlan = [
+  //缺少:当前计划日历;班主任全年安排;学校总人数设置;学校发参培时间;培训计划详表
+  {
+    path: '/plan/index',
+    name: 'newPlan_index',
+    meta: { title: '年度计划管理' },
+    component: () => import('@/views/new-plan/index.vue'),
+  },
+  {
+    path: '/plan/template',
+    name: 'newPlan_template',
+    meta: { title: '计划模板' },
+    component: () => import('@/views/new-plan/template.vue'),
+  },
+  {
+    path: '/plan/arrange',
+    name: 'newPlan_arrange',
+    meta: { title: '培训计划安排' },
+    component: () => import('@/views/new-plan/arrange/arrange.vue'),
+  },
+  {
+    path: '/plan/director',
+    name: 'newPlan_director',
+    meta: { title: '班主任计划安排' },
+    component: () => import('@/views/new-plan/arrange/director-arrange.vue'),
+  },
+  {
+    path: '/plan/school/num',
+    name: 'newPlan_school_num',
+    meta: { title: '学校人数安排' },
+    component: () => import('@/views/new-plan/arrange/school-num.vue'),
+  },
+  {
+    path: '/plan/school/time',
+    name: 'newPlan_school_time',
+    meta: { title: '学校参培时间' },
+    component: () => import('@/views/new-plan/arrange/school-time.vue'),
+  },
+  {
+    path: '/plan/school',
+    name: 'newPlan_school',
+    meta: { title: '学校计划安排' },
+    component: () => import('@/views/new-plan/arrange/school-arrange.vue'),
+  },
+  {
+    path: '/plan/classes/detail',
+    name: 'newPlan_classes_detail',
+    meta: { title: '班级安排' },
+    component: () => import('@/views/new-plan/class/classes.vue'),
+  },
+  {
+    path: '/plan/classes/lesson',
+    name: 'newPlan_classes_lesson',
+    meta: { title: '排课管理' },
+    component: () => import('@/views/new-plan/class/lesson.vue'),
+  },
+  {
+    path: '/plan/classes/setting',
+    name: 'newPlan_classes_setting',
+    meta: { title: '班级设置' },
+    component: () => import('@/views/new-plan/class/setting.vue'),
+  },
+  {
+    path: '/plan/classes/bedroom',
+    name: 'newPlan_classes_bedroom',
+    meta: { title: '分寝' },
+    component: () => import('@/views/new-plan/class/bedroom.vue'),
+  },
+  {
+    path: '/plan/classes/namelist',
+    name: 'newPlan_classes_namelist',
+    meta: { title: '班级人员' },
+    component: () => import('@/views/new-plan/class/name-list.vue'),
+  },
+];
+
+const train = [
+  //班级设置需要处理,课表管理(按魏老师给的图片做), 通知,学生管理(拿出来,加上searchBar)缺少考勤,学生成绩
+  {
+    path: '/train/plan/classes',
+    name: 'train_plan_classes',
+    meta: { title: '学生分班' },
+    component: () => import('@/views/train-plan/classes.vue'),
+  },
+  {
+    path: '/train/plan/bedroom',
+    name: 'train_plan_bedroom',
+    meta: { title: '寝室管理' },
+    component: () => import('@/views/train-plan/bedroom.vue'),
+  },
+  {
+    path: '/train/plan/quest',
+    name: 'train_plan_quest',
+    meta: { title: '非常用问卷管理' },
+    component: () => import('@/views/train-plan/quest.vue'),
+  },
+  {
+    path: '/train/plan/lesson',
+    name: 'train_plan_lesson',
+    meta: { title: '课表管理管理' },
+    component: () => import('@/views/train-plan/lesson.vue'),
+  },
+  {
+    path: '/train/plan/term/lesson',
+    name: 'train_plan_term_lesson',
+    meta: { title: '期课表' },
+    component: () => import('@/views/train-plan/term-lesson.vue'),
+  },
+  {
+    path: '/train/plan/remind',
+    name: 'train_plan_remind',
+    meta: { title: '通知' },
+    component: () => import('@/views/train-plan/remind.vue'),
+  },
+
+  {
+    path: '/student/index',
+    name: 'student_index',
+    meta: { title: '学生管理', sub: '' },
+    component: () => import('@/views/student/index.vue'),
+  },
+  {
+    path: '/student/detail',
+    name: 'student_detail',
+    meta: { title: '学生详情', sub: '' },
+    component: () => import('@/views/student/detail.vue'),
+  },
+  //报表打印(部分)
+  {
+    path: '/train/plan/print',
+    name: 'train_plan_print',
+    meta: { title: '打印报表' },
+    component: () => import('@/views/train-plan/print.vue'),
+  },
+  {
+    path: '/certificaate/index',
+    name: 'certificaate_index',
+    meta: { title: '证书', sub: '管理' },
+    component: () => import('@/views/certificaate/index.vue'),
+  },
+  {
+    path: '/certificaate/detail',
+    name: 'certificaate_detail',
+    meta: { title: '证书', sub: '详情' },
+    component: () => import('@/views/certificaate/detail.vue'),
+  },
+  {
+    path: '/certificaate/look',
+    name: 'certificaate_look',
+    meta: { title: '证书', sub: '打印' },
+    component: () => import('@/views/certificaate/look.vue'),
+  },
+  //报表打印结束
+  {
+    path: '/train/plan/leave',
+    name: 'train_plan_leave',
+    meta: { title: '请假管理' },
+    component: () => import('@/views/train-plan/leave.vue'),
+  },
+  {
+    path: '/train/plan/attendance',
+    name: 'train_plan_attendance',
+    meta: { title: '考勤管理' },
+    component: () => import('@/views/train-plan/attendance.vue'),
+  },
+  {
+    path: '/train/plan/score',
+    name: 'train_plan_score',
+    meta: { title: '学生成绩' },
+    component: () => import('@/views/train-plan/score.vue'),
+  },
+];
+
+const statistics = [
+  {
+    path: '/statistics/question/index',
+    name: 'statistics_question',
+    meta: { title: '问卷统计' },
+    component: () => import('@/views/statistics/index.vue'),
+  },
+  {
+    path: '/statistics/question/detail',
+    name: 'statistics_question_detail',
+    meta: { title: '问卷统计' },
+    component: () => import('@/views/statistics/detail.vue'),
+  },
+];
+
 const routes = [
   {
-    path: '/',
-    name: 'frame',
+    path: '',
     component: () => import('@/views/index.vue'),
     children: [
+      {
+        path: '/',
+        name: 'home',
+        component: () => import('@/views/home.vue'),
+      },
+      ...system,
+      ...newPlan,
+      ...train,
+      ...statistics,
       {
         path: '/list',
         name: 'test_list',
@@ -22,229 +407,42 @@ const routes = [
         component: () => import('@/views/test/detail.vue'),
       },
 
-      {
-        path: '/itembank/detail',
-        name: 'itembank_detail',
-        meta: { title: '题库', sub: '详情' },
-        component: () => import('@/views/Itembank/detail.vue'),
-      },
-      {
-        path: '/itembank/index',
-        name: 'itembank_index',
-        meta: { title: '题库', sub: '管理' },
-        component: () => import('@/views/Itembank/index.vue'),
-      },
-
       // {
-      //   path: '/questionstate/detail',
-      //   name: 'questionstate_detail',
-      //   meta: { title: '问卷状态', sub: '详情' },
-      //   component: () => import('@/views/questionstate/detail.vue'),
+      //   path: '/plan/index',
+      //   name: 'plan_index',
+      //   meta: { title: '计划', sub: '管理' },
+      //   component: () => import('@/views/plan/index.vue'),
+      // },
+      // {
+      //   path: '/plan/detail',
+      //   name: 'plan_detail',
+      //   meta: { title: '计划', sub: '详情' },
+      //   component: () => import('@/views/plan/detail.vue'),
+      // },
+      // {
+      //   path: '/plan/classes',
+      //   name: 'plan_classes',
+      //   meta: { title: '安排', sub: '班级' },
+      //   component: () => import('@/views/plan/classes.vue'),
+      // },
+      // {
+      //   path: '/plan/lesson',
+      //   name: 'plan_lesson',
+      //   meta: { title: '安排', sub: '课程' },
+      //   component: () => import('@/views/plan/lesson.vue'),
+      // },
+      // {
+      //   path: '/plan/msg',
+      //   name: 'plan_msg',
+      //   meta: { title: '发送', sub: '通知' },
+      //   component: () => import('@/views/plan/msg.vue'),
+      // },
+      // {
+      //   path: '/plan/question',
+      //   name: 'plan_question',
+      //   meta: { title: '问卷', sub: '管理' },
+      //   component: () => import('@/views/plan/question.vue'),
       // },
-      {
-        path: '/questionstate/index',
-        name: 'questionstate_index',
-        meta: { title: '问卷状态', sub: '管理' },
-        component: () => import('@/views/questionstate/index.vue'),
-      },
-      {
-        path: '/certificaate/index',
-        name: 'certificaate_index',
-        meta: { title: '证书', sub: '管理' },
-        component: () => import('@/views/certificaate/index.vue'),
-      },
-      {
-        path: '/certificaate/detail',
-        name: 'certificaate_detail',
-        meta: { title: '证书', sub: '详情' },
-        component: () => import('@/views/certificaate/detail.vue'),
-      },
-      {
-        path: '/certificaate/look',
-        name: 'certificaate_look',
-        meta: { title: '证书', sub: '打印' },
-        component: () => import('@/views/certificaate/look.vue'),
-      },
-
-      {
-        path: '/questionnaire/index',
-        name: 'questionnaire_index',
-        meta: { title: '问卷', sub: '管理' },
-        component: () => import('@/views/questionnaire/index.vue'),
-      },
-      {
-        path: '/questionnaire/detail',
-        name: 'questionnaire_detail',
-        meta: { title: '问卷', sub: '详情' },
-        component: () => import('@/views/questionnaire/detail.vue'),
-      },
-      {
-        path: '/teacher/detail',
-        name: 'teacher_detail',
-        meta: { title: '教师', sub: '详情' },
-        component: () => import('@/views/teacher/detail.vue'),
-      },
-      {
-        path: '/teacher/index',
-        name: 'teacher_index',
-        meta: { title: '教师', sub: '管理' },
-        component: () => import('@/views/teacher/index.vue'),
-      },
-      {
-        path: '/teacher/means',
-        name: 'teacher_means',
-        component: () => import('@/views/teacher/means.vue'),
-      },
-      {
-        path: '/teacher/verify',
-        name: 'teacher_verify',
-        meta: { title: '教师', sub: '审核页' },
-        component: () => import('@/views/teacher/verify.vue'),
-      },
-      {
-        path: '/work/detail',
-        name: 'work_detail',
-        meta: { title: '作业', sub: '详情' },
-        component: () => import('@/views/work/detail.vue'),
-      },
-      {
-        path: '/work/index',
-        name: 'work_index',
-        meta: { title: '作业', sub: '管理' },
-        component: () => import('@/views/work/index.vue'),
-      },
-      {
-        path: '/work/look',
-        name: 'work_look',
-        meta: { title: '作业题', sub: '管理' },
-        component: () => import('@/views/work/look.vue'),
-      },
-      {
-        path: '/subject/index',
-        name: 'subject_index',
-        meta: { title: '科目', sub: '管理' },
-        component: () => import('@/views/subject/index.vue'),
-      },
-      {
-        path: '/subject/detail',
-        name: 'subject_detail',
-        meta: { title: '科目', sub: '详情' },
-        component: () => import('@/views/subject/detail.vue'),
-      },
-      {
-        path: '/director/index',
-        name: 'director_index',
-        meta: { title: '班主任', sub: '管理' },
-        component: () => import('@/views/director/index.vue'),
-      },
-      {
-        path: '/director/detail',
-        name: 'director_detail',
-        meta: { title: '班主任', sub: '详情' },
-        component: () => import('@/views/director/detail.vue'),
-      },
-      {
-        path: '/dept/index',
-        name: 'dept_index',
-        meta: { title: '部门', sub: '管理' },
-        component: () => import('@/views/dept/index.vue'),
-      },
-      {
-        path: '/dept/detail',
-        name: 'dept_detail',
-        meta: { title: '部门', sub: '详情' },
-        component: () => import('@/views/dept/detail.vue'),
-      },
-      {
-        path: '/duty/index',
-        name: 'duty_index',
-        meta: { title: '职责', sub: '说明' },
-        component: () => import('@/views/duty/index.vue'),
-      },
-      {
-        path: '/location/index',
-        name: 'location_index',
-        meta: { title: '地点', sub: '管理' },
-        component: () => import('@/views/location/index.vue'),
-      },
-      {
-        path: '/location/detail',
-        name: 'location_detail',
-        meta: { title: '地点', sub: '详情' },
-        component: () => import('@/views/location/detail.vue'),
-      },
-      {
-        path: '/bedroom/index',
-        name: 'bedroom_index',
-        meta: { title: '寝室', sub: '管理' },
-        component: () => import('@/views/bedroom/index.vue'),
-      },
-      {
-        path: '/bedroom/detail',
-        name: 'bedroom_detail',
-        meta: { title: '寝室', sub: '详情' },
-        component: () => import('@/views/bedroom/detail.vue'),
-      },
-      {
-        path: '/classes/index',
-        name: 'classes_index',
-        meta: { title: '班级', sub: '管理' },
-        component: () => import('@/views/classes/index.vue'),
-      },
-      {
-        path: '/classes/detail',
-        name: 'classes_detail',
-        meta: { title: '班级', sub: '详情' },
-        component: () => import('@/views/classes/detail.vue'),
-      },
-      {
-        path: '/student/index',
-        name: 'student_index',
-        meta: { title: '学生', sub: '管理' },
-        component: () => import('@/views/student/index.vue'),
-      },
-      {
-        path: '/student/detail',
-        name: 'student_detail',
-        meta: { title: '学生', sub: '详情' },
-        component: () => import('@/views/student/detail.vue'),
-      },
-      {
-        path: '/plan/index',
-        name: 'plan_index',
-        meta: { title: '计划', sub: '管理' },
-        component: () => import('@/views/plan/index.vue'),
-      },
-      {
-        path: '/plan/detail',
-        name: 'plan_detail',
-        meta: { title: '计划', sub: '详情' },
-        component: () => import('@/views/plan/detail.vue'),
-      },
-      {
-        path: '/plan/classes',
-        name: 'plan_classes',
-        meta: { title: '安排', sub: '班级' },
-        component: () => import('@/views/plan/classes.vue'),
-      },
-      {
-        path: '/plan/lesson',
-        name: 'plan_lesson',
-        meta: { title: '安排', sub: '课程' },
-        component: () => import('@/views/plan/lesson.vue'),
-      },
-      {
-        path: '/plan/msg',
-        name: 'plan_msg',
-        meta: { title: '发送', sub: '通知' },
-        component: () => import('@/views/plan/msg.vue'),
-      },
-      {
-        path: '/plan/question',
-        name: 'plan_question',
-        meta: { title: '问卷', sub: '管理' },
-        component: () => import('@/views/plan/question.vue'),
-      },
     ],
   },
   {
@@ -275,6 +473,7 @@ const router = new VueRouter({
 });
 router.beforeEach((to, form, next) => {
   store.commit('setUser');
+  store.dispatch('setting/checkCache');
   if (to.name === 'login') {
     next();
     return;

+ 26 - 2
src/store/index.js

@@ -17,6 +17,16 @@ import school from '@frame/store/school';
 import schPlan from '@frame/store/sch-plan';
 import teaPlan from '@frame/store/tea-plan';
 import lesson from '@frame/store/lesson';
+import trainTemplate from '@frame/store/train-template';
+import leave from '@frame/store/leave';
+import util from '@frame/store/util';
+import count from '@frame/store/count';
+import setting from '@frame/store/setting';
+import trainBatch from '@frame/store/train-plan-year';
+import attendance from '@frame/store/attendance';
+import group from '@frame/store/group';
+import uploadtask from '@frame/store/uploadtask';
+
 import nation from '@frame/store/nation';
 import completion from '@frame/store/question-completion';
 import termquest from '@frame/store/termquest';
@@ -24,6 +34,10 @@ import dirPlan from '@frame/store/dir-plan';
 import login from '@frame/store/login';
 import * as ustate from '@frame/store/user/state';
 import * as umutations from '@frame/store/user/mutations';
+import * as dostate from '@frame/store/setting/state';
+import * as domutations from '@frame/store/setting/mutations';
+
+import other from '@frame/store/other';
 
 Vue.use(Vuex);
 
@@ -51,8 +65,18 @@ export default new Vuex.Store({
     termquest,
     login,
     dirPlan,
+    leave,
+    trainTemplate,
+    other,
+    util,
+    count,
+    setting,
+    trainBatch,
+    attendance,
+    group,
+    uploadtask,
   },
-  state: { ...ustate },
-  mutations: { ...umutations },
+  state: { ...ustate, ...dostate },
+  mutations: { ...umutations, ...domutations },
   actions: {},
 });

+ 0 - 1
src/views/Itembank/detail.vue

@@ -64,7 +64,6 @@
 <script>
 import detailFrame from '@frame/layout/admin/detail-frame';
 import dataForm from '@frame/components/form';
-import upload from '@frame/components/upload';
 import _ from 'lodash';
 import { createNamespacedHelpers } from 'vuex';
 const { mapActions } = createNamespacedHelpers('question');

+ 7 - 2
src/views/bedroom/detail.vue

@@ -7,6 +7,10 @@
             <el-radio label="男">男</el-radio>
             <el-radio label="女">女</el-radio>
           </template>
+          <template v-if="item.model === 'status'">
+            <el-radio label="0">启用</el-radio>
+            <el-radio label="1">禁用</el-radio>
+          </template>
           <template v-if="item.model === 'floor'">
             <el-radio v-for="i in 5" :key="i" :label="`${i}楼`">{{ `${i}楼` }}</el-radio>
           </template>
@@ -35,14 +39,15 @@ export default {
       { label: '寝室号', required: true, model: 'code' },
       { label: '人数', required: true, model: 'number' },
       { label: '批次', required: true, model: 'batch' },
-      { label: '男女限制', required: true, model: 'gender', type: 'radio' },
+      { label: '男女限制', model: 'gender', type: 'radio' },
+      { label: '状态', model: 'status', type: 'radio' },
       { label: '楼层', required: true, model: 'floor', type: 'radio' },
+      { label: '蓝牙id', required: true, model: 'ibeacon' },
     ],
     rules: {
       code: [{ required: true, message: '请输入寝室号' }],
       number: [{ required: true, message: '请输入人数' }],
       batch: [{ required: true, message: '请输入批次' }],
-      gender: [{ required: true, message: '请选择男女限制' }],
       floor: [{ required: true, message: '请选择楼层' }],
     },
   }),

+ 23 - 2
src/views/bedroom/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div id="index">
     <list-frame title="寝室管理" @query="search" :total="total" :needFilter="false" @add="$router.push({ path: '/bedroom/detail' })">
-      <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete"></data-table>
+      <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete" @use="toUse"></data-table>
     </list-frame>
   </div>
 </template>
@@ -31,6 +31,18 @@ export default {
         icon: 'el-icon-delete',
         method: 'delete',
       },
+      {
+        label: '禁用',
+        icon: 'el-icon-video-pause',
+        method: 'use',
+        display: i => i.status === '0',
+      },
+      {
+        label: '使用',
+        icon: 'el-icon-video-play',
+        method: 'use',
+        display: i => i.status === '1',
+      },
     ],
     fields: [
       { label: '寝室号', prop: 'code' },
@@ -38,6 +50,7 @@ export default {
       { label: '批次', prop: 'batch' },
       { label: '男女限制', prop: 'gender' },
       { label: '楼层', prop: 'floor' },
+      { label: '状态', prop: 'status', format: i => (i === '0' ? '使用中' : '已禁用') },
     ],
     list: [],
     total: 0,
@@ -47,7 +60,7 @@ export default {
   },
   computed: {},
   methods: {
-    ...mapActions(['query', 'delete']),
+    ...mapActions(['query', 'delete', 'update']),
     async search({ skip = 0, limit = 10, ...info } = {}) {
       const res = await this.query({ skip, limit, ...info });
       if (this.$checkRes(res)) {
@@ -63,6 +76,14 @@ export default {
       this.$checkRes(res, '删除成功', '删除失败');
       this.search();
     },
+    async toUse({ data }) {
+      console.log(data);
+      let newData = JSON.parse(JSON.stringify(data));
+      if (newData.status === '0') newData.status = '1';
+      else newData.status = '0';
+      let res = await this.update(newData);
+      if (this.$checkRes(res)) this.search();
+    },
   },
 };
 </script>

+ 3 - 4
src/views/director/index.vue

@@ -2,9 +2,8 @@
   <div id="index">
     <list-frame :title="mainTitle" @query="search" :total="total" :filter="filFields" @add="$router.push({ path: '/director/detail' })">
       <template #options="{item}">
-        <template v-if="item.model === 'dept'">
-          <el-option label="信息部" value="1"></el-option>
-          <el-option label="办公室" value="0"></el-option>
+        <template v-if="item.model === 'department'">
+          <el-option v-for="(i, index) in deptList" :key="index" :label="i.name" :value="i._id"></el-option>
         </template>
       </template>
       <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete" :toFormat="toFormat"></data-table>
@@ -43,7 +42,7 @@ export default {
     ],
     filFields: [
       { label: '姓名', model: 'name' },
-      { label: '所属部门', model: 'dept', type: 'select' },
+      { label: '所属部门', model: 'department', type: 'select' },
     ],
     fields: [
       { label: '姓名', prop: 'name' },

+ 1 - 0
src/views/duty/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div id="detail">
     <detail-frame :title="mainTitle" returns="/dept/index" v-loading="loading">
+      <p v-html="info.bzduty"></p>
       <data-form v-if="!loading" :data="info" :fields="fields" :rules="rules" @save="handleSave" :isNew="isNew"> </data-form>
     </detail-frame>
   </div>

+ 80 - 0
src/views/home.vue

@@ -0,0 +1,80 @@
+<template>
+  <div id="home">
+    <el-card v-if="!loading" shadow="hover">
+      <el-row type="flex" align="middle" justify="center" style="margin:10px">
+        <el-col :span="12">
+          <el-card shadow="hover" style="text-align:center">全年计划共计: {{ data.planstu }} 人 </el-card>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-card>
+            <iscome :istrain="data.trainstu" :nottrain="data.notrainstu"></iscome>
+          </el-card>
+        </el-col>
+        <el-col :span="12">
+          <el-card shadow="hover">
+            <school :school="data.schs"></school>
+          </el-card>
+        </el-col>
+        <el-col :span="24" style="margin-top:20px">
+          <el-card shadow="hover">
+            <leave :qj="data.levelqj" :exit="data.levelexit"></leave>
+          </el-card>
+        </el-col>
+      </el-row>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import school from './home/school.vue';
+import iscome from './home/iscome.vue';
+import leave from './home/leave.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: count } = createNamespacedHelpers('count');
+export default {
+  name: 'home',
+  props: {},
+  components: { leave, iscome, school },
+  data: function() {
+    return {
+      loading: true,
+      data: {},
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...count(['query']),
+    async search() {
+      let res = await this.query();
+      // 柱状图
+      //levelexit:退出;
+      //levelqj:请假;
+      //饼形图
+      //notrainstu:没参加培训的;
+      //trainstu:已参加学生数;
+
+      //planstu:计划总人数;
+
+      //饼形图,哪个学校多少人
+      //schstu:学校上报人数;
+      if (this.$checkRes(res)) this.$set(this, `data`, res.data);
+      this.loading = false;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 81 - 0
src/views/home/iscome.vue

@@ -0,0 +1,81 @@
+<template>
+  <div id="iscome">
+    <div id="chartPie" style="height:350px;"></div>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'iscome',
+  props: {
+    istrain: { type: Number, default: 0 },
+    nottrain: { type: Number, default: 0 },
+  },
+  components: {},
+  data: function() {
+    return {
+      myPie: null,
+    };
+  },
+  created() {
+    this.$nextTick(() => {
+      this.init();
+    });
+  },
+  methods: {
+    init(type) {
+      this.myPie = echarts.init(document.getElementById('chartPie'));
+      const option = {
+        title: { text: '当前学生参加培训情况' },
+        tooltip: { trigger: 'item', formatter: '{a} <br/>{b} : {c} ({d}%)' },
+        legend: {
+          data: ['已参加培训', '未参加培训'],
+        },
+        series: [
+          {
+            name: '统计',
+            label: {
+              show: true,
+              position: 'top',
+            },
+            type: 'pie',
+            data: [
+              { name: '已参加培训', value: this.istrain, itemStyle: { color: '#7cb5ec' } },
+              { name: '未参加培训', value: this.nottrain, itemStyle: { color: '#ffa94b' } },
+            ],
+            animationType: 'scale',
+          },
+        ],
+        toolbox: {
+          show: true,
+          feature: {
+            dataView: { readOnly: false },
+            saveAsImage: {},
+          },
+        },
+      };
+      this.myPie.setOption(option);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 98 - 0
src/views/home/leave.vue

@@ -0,0 +1,98 @@
+<template>
+  <div id="leave">
+    <div id="chartLinebar" style="height:350px;"></div>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/line';
+import 'echarts/lib/chart/bar';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'leave',
+  props: {
+    qj: { type: Number, default: 0 }, //请假
+    exit: { type: Number, default: 0 }, //退出
+  },
+  components: {},
+  data: function() {
+    return {
+      mychart: null,
+    };
+  },
+  created() {
+    this.$nextTick(() => {
+      this.init();
+    });
+  },
+  methods: {
+    init() {
+      const option = {
+        title: { text: '请假/退出统计' },
+        tooltip: { trigger: 'axis' },
+        legend: {
+          data: ['请假/退出统计'],
+        },
+        xAxis: {
+          boundaryGap: true,
+          // nameLocation: 'center',
+          data: ['请假', '退出'],
+        },
+        yAxis: {},
+        series: [
+          {
+            name: '请假',
+            step: true,
+            label: {
+              show: true,
+              position: 'top',
+            },
+            type: 'bar',
+            data: [this.qj],
+            color: ['#333ceb'],
+          },
+          {
+            name: '退出',
+            label: {
+              show: true,
+              position: 'top',
+            },
+            type: 'bar',
+            data: [this.exit],
+            color: ['#acaecce'],
+          },
+        ],
+        toolbox: {
+          show: true,
+          feature: {
+            dataView: { readOnly: false },
+            saveAsImage: {},
+            restore: {},
+            // magicType: { type: ['bar'] },
+          },
+        },
+      };
+      this.mychart = echarts.init(document.getElementById('chartLinebar'));
+      this.mychart.setOption(option);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 80 - 0
src/views/home/school.vue

@@ -0,0 +1,80 @@
+<template>
+  <div id="school">
+    <div id="schoolPie" style="height:350px;"></div>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'school',
+  props: {
+    school: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {
+      myPie: null,
+    };
+  },
+  created() {
+    this.$nextTick(() => {
+      this.init();
+    });
+  },
+  methods: {
+    init(type) {
+      let data = this.school.map(i => {
+        return { name: i.schname, value: i.schnum };
+      });
+      this.myPie = echarts.init(document.getElementById('schoolPie'));
+      const option = {
+        title: { text: '学校上报统计' },
+        tooltip: { trigger: 'item', formatter: '{a} <br/>{b} : {c} ({d}%)' },
+        legend: {
+          data: data.map(i => i.name),
+        },
+        series: [
+          {
+            name: '统计',
+            label: {
+              show: true,
+              position: 'top',
+            },
+            type: 'pie',
+            data: data,
+            animationType: 'scale',
+          },
+        ],
+        toolbox: {
+          show: true,
+          feature: {
+            dataView: { readOnly: false },
+            saveAsImage: {},
+          },
+        },
+      };
+      this.myPie.setOption(option);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 1 - 4
src/views/index.vue

@@ -14,7 +14,6 @@
 import adminMenu from '@frame/layout/admin/admin-menu.vue';
 import adminBar from '@frame/layout/admin/navBar.vue';
 import fwAdmin from '@frame/layout/admin/fw-admin.vue';
-import { devMenu } from '@frame/config/menu-config';
 export default {
   name: 'admin-index',
   metaInfo: { title: ' 双困生培训系统' },
@@ -25,9 +24,7 @@ export default {
     adminBar,
     // breadcrumb,
   },
-  data: () => ({
-    devMenu,
-  }),
+  data: () => ({}),
   created() {
     // this.login();
   },

+ 109 - 0
src/views/leave/index.vue

@@ -0,0 +1,109 @@
+<template>
+  <div id="index">
+    <list-frame :title="mainTitle" @query="search" :total="total" :needAdd="false" :needFilter="false">
+      <!-- <el-form :inline="true" size="mini">
+        <el-form-item label="选择计划">
+          <el-select v-model="searchInfo.planid" @change="getTerm" clearable>
+            <el-option v-for="(i, index) in planList" :key="index" :label="i.title" :value="i.id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="选择期">
+          <el-select v-model="searchInfo.termid" clearable>
+            <el-option v-for="(i, index) in termList" :key="index" :label="i.term" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="search">查询</el-button>
+        </el-form-item>
+      </el-form> -->
+      <data-table :fields="fields" :data="list" :opera="opera"></data-table>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('leave');
+const { mapActions: mapstudent } = createNamespacedHelpers('student');
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
+export default {
+  metaInfo: { title: '请假和退出管理' },
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+    dataTable,
+  },
+  data: () => ({
+    opera: [],
+    fields: [
+      { label: '学生名称', prop: 'stuName' },
+      { label: '开始时间', prop: 'starttime' },
+      { label: '结束时间', prop: 'endtime' },
+      { label: '请假理由', prop: 'reason' },
+
+      {
+        label: '状态',
+        prop: 'status',
+        format: item => {
+          return item === '0' ? '审核中' : item === '1' ? '通过' : '未通过';
+        },
+      },
+      { label: '拒绝原因', prop: 'refcause' },
+      {
+        label: '类型',
+        prop: 'type',
+        format: item => {
+          return item === '0' ? '请假' : '退出';
+        },
+      },
+    ],
+    searchInfo: {},
+    list: [],
+    planList: [],
+    termList: [],
+    total: 0,
+  }),
+  created() {
+    this.search();
+  },
+  computed: {
+    mainTitle() {
+      let meta = this.$route.meta;
+      let main = meta.title || '';
+      let sub = meta.sub || '';
+      return `${main}${sub}`;
+    },
+    keyWord() {
+      let meta = this.$route.meta;
+      let main = meta.title || '';
+      return main;
+    },
+  },
+  methods: {
+    ...mapActions(['query', 'delete']),
+    ...mapstudent({ lists: 'fetch' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, ...info });
+      for (const val of res.data) {
+        const stuName = await this.lists(val.studentid);
+        console.log(stuName);
+        val.stuName = stuName.data.name;
+      }
+      console.log(res.data);
+      this.$set(this, `list`, res.data);
+      this.$set(this, `total`, res.total);
+    },
+
+    getTerm(data) {
+      let term = this.planList.find(f => f.id === data);
+      if (term) this.$set(this, `termList`, term.termnum);
+      else this.$set(this, `termList`, []);
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 242 - 0
src/views/lesson/detail.vue

@@ -0,0 +1,242 @@
+<template>
+  <div id="detail">
+    <detail-frame :title="pageTitle" returns="./index">
+      <data-form :data="info" :fields="fields" :rules="rules" @save="handleSave" :isNew="isNew" :reset="false">
+        <template #radios="{item}">
+          <template v-if="item.model === 'type'">
+            <el-radio v-for="(i, index) in types" :key="index" :label="i.value">{{ i.label }}</el-radio>
+          </template>
+          <template v-if="item.model === 'allday'">
+            <el-radio label="0">全天</el-radio>
+            <el-radio label="1">半天</el-radio>
+          </template>
+        </template>
+        <template #custom="{item}">
+          <template v-if="item.model === 'lesson'">
+            <el-table :data="lessons" stripe border>
+              <el-table-column align="center" label="时间" prop="time"></el-table-column>
+              <el-table-column align="center" label="第一天" prop="day1"></el-table-column>
+              <el-table-column align="center" label="第二天" prop="day2"></el-table-column>
+              <el-table-column align="center" label="第三天" prop="day3"></el-table-column>
+              <el-table-column align="center" label="第四天" prop="day4"></el-table-column>
+              <el-table-column align="center" label="第五天" prop="day5"></el-table-column>
+              <el-table-column align="center" label="第六天" prop="day6"></el-table-column>
+              <el-table-column label="操作" align="center">
+                <template v-slot="{ row, $index }">
+                  <el-tooltip :key="$index" effect="dark" content="编辑" placement="bottom">
+                    <el-button type="text" size="mini" icon="el-icon-edit" @click="toEdit(row, $index)"></el-button>
+                  </el-tooltip>
+                </template>
+              </el-table-column>
+            </el-table>
+          </template>
+        </template>
+      </data-form>
+    </detail-frame>
+    <el-drawer :visible.sync="drawer" @close="toClose" direction="rtl" :with-header="false">
+      <el-tabs style="padding:10px;">
+        <el-tab-pane label="课程安排" style="height:93vh;">
+          <el-scrollbar style="height:100%;overflow-x:hidden">
+            <data-form :styles="{ padding: 0 }" :data="form" :fields="oFields" :rules="{}" @save="handleOsave" :isNew="false">
+              <template #radios="{item, form}">
+                <template v-if="isType(item.model, 'radio')">
+                  <el-radio v-for="(i, index) in dayType" :key="index" :label="i.label">{{ i.label }}</el-radio>
+                </template>
+              </template>
+              <template #custom="{item, form}">
+                <template v-if="item.model === 'time'">
+                  <el-time-picker
+                    is-range
+                    v-model="form.time"
+                    range-separator="至"
+                    start-placeholder="开始时间"
+                    end-placeholder="结束时间"
+                    placeholder="选择时间范围"
+                    size="mini"
+                    value-format="HH:mm"
+                    format="HH:mm"
+                  >
+                  </el-time-picker>
+                </template>
+                <template v-if="isType(item.model, 'select')">
+                  <template v-if="form[`${item.model}type`] === '活动'">
+                    <el-select v-model="form[item.model]">
+                      <el-option v-for="(i, index) in actList" :key="index" :label="i.label" :value="i.label"></el-option>
+                    </el-select>
+                  </template>
+                  <template v-else-if="form[`${item.model}type`] === '课程'">
+                    <el-select v-model="form[`${item.model}subid`]">
+                      <el-option v-for="(i, index) in subjectList" :key="index" :label="i.name" :value="i.id"></el-option>
+                    </el-select>
+                  </template>
+                  <template v-else>
+                    请选择当前时间段活动类型
+                  </template>
+                </template>
+              </template>
+            </data-form>
+          </el-scrollbar>
+        </el-tab-pane>
+      </el-tabs>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataForm from '@frame/components/form';
+import { lesson as lessons } from '@frame/config/lesson-template';
+import _ from 'lodash';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: subject } = createNamespacedHelpers('subject');
+const { mapActions: lesson } = createNamespacedHelpers('lesson');
+export default {
+  name: 'detail',
+  props: {},
+  components: { detailFrame, dataForm },
+  data: () => {
+    return {
+      drawer: false,
+      info: {},
+      form: {},
+      fields: [
+        { label: '模板名称', required: true, model: 'title' },
+        { label: '类型', required: true, model: 'type', type: 'radio' },
+        { label: '最后一天时长', required: true, model: 'allday', type: 'radio' },
+        { label: '课程安排', model: 'lesson', custom: true },
+      ],
+      types: [
+        { label: '普通班', value: '0' },
+        { label: '特殊班', value: '1' },
+      ],
+      rules: {
+        title: [{ required: true, message: '请输入模板名称' }],
+        type: [{ required: true, message: '请选择模板类型' }],
+      },
+      oFields: [
+        { label: '时间', model: 'time', type: 'time', custom: true },
+        { label: '第一天类型', model: 'day1type', type: 'radio' },
+        { label: '第一天安排', model: 'day1', custom: true },
+        { label: '第二天类型', model: 'day2type', type: 'radio' },
+        { label: '第二天安排', model: 'day2', custom: true },
+        { label: '第三天类型', model: 'day3type', type: 'radio' },
+        { label: '第三天安排', model: 'day3', custom: true },
+        { label: '第四天类型', model: 'day4type', type: 'radio' },
+        { label: '第四天安排', model: 'day4', custom: true },
+        { label: '第五天类型', model: 'day5type', type: 'radio' },
+        { label: '第五天安排', model: 'day5', custom: true },
+        { label: '第六天类型', model: 'day6type', type: 'radio' },
+        { label: '第六天安排', model: 'day6', custom: true },
+      ],
+      lessons: lessons,
+      dayType: [{ label: '活动' }, { label: '课程' }],
+      actList: [
+        { label: '--' },
+        { label: '报道+开班仪式' },
+        { label: '午餐+休息' },
+        { label: '晚餐' },
+        { label: '团队组建' },
+        { label: '拓展交流' },
+        { label: '课程作业小组展示' },
+        { label: '课程作业' },
+        { label: '礼仪课小组面试' },
+        { label: '结业仪式' },
+      ],
+      subjectList: [],
+    };
+  },
+  created() {},
+  methods: {
+    ...subject({
+      getSubjectList: 'query',
+    }),
+    ...lesson(['modelFetch', 'modelCreate', 'modelUpdate']),
+    async search() {
+      const res = await this.modelFetch(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `info`, res.data);
+        let { lessons } = res.data;
+        lessons = JSON.parse(lessons);
+        console.log(lessons);
+        this.$set(this, `lessons`, lessons);
+      }
+      this.loading = false;
+    },
+    async handleSave({ isNew, data }) {
+      let res;
+      let msg;
+      data.lessons = JSON.stringify(this.lessons);
+      if (isNew) {
+        res = await this.modelCreate(data);
+        msg = `${this.pageTitle}添加成功`;
+      } else {
+        res = await this.modelUpdate(data);
+        msg = `${this.pageTitle}修改成功`;
+      }
+      if (this.$checkRes(res, msg)) this.$router.push({ path: './index' });
+    },
+    async toEdit(data, index) {
+      let newData = JSON.parse(JSON.stringify(data));
+      newData.index = index;
+      let times = newData.time.split('-');
+      if (times.length === 2) newData.time = times;
+      let subject = await this.getSubjectList({ type: this.info.type });
+      if (subject.errcode === 0) this.$set(this, `subjectList`, subject.data);
+      this.$set(this, `form`, newData);
+      this.drawer = true;
+    },
+    handleOsave({ data }) {
+      data.time = _.join(data.time, '-');
+      let index = JSON.parse(JSON.stringify(data.index));
+      let keys = Object.keys(data);
+      let midArr = keys.filter(f => /^day\d$/.test(f));
+      for (const key of midArr) {
+        if (data[`${key}type`] === '课程') data[key] = this.subjectList.find(f => f.id === data[`${key}subid`]).name;
+        else delete data[`${key}subid`];
+      }
+      data = _.omit(data, ['index']);
+      this.$set(this.lessons, index, data);
+      this.drawer = false;
+    },
+    toClose() {
+      this.drawer = false;
+      this.form = {};
+    },
+    isType(model, type) {
+      let reg;
+      if (type === 'radio') reg = /^day\dtype$/;
+      else reg = /^day\d$/;
+      return reg.test(model);
+    },
+  },
+  watch: {
+    isNew: {
+      immediate: true,
+      handler(val) {
+        if (!val) this.search();
+      },
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+    isNew() {
+      return this.$route.query.id ? false : true;
+    },
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less">
+.el-scrollbar__wrap {
+  overflow-x: auto !important;
+}
+</style>

+ 73 - 0
src/views/lesson/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <div id="index">
+    <list-frame :title="pageTitle" @query="search" :total="total" :needFilter="false" @add="$router.push({ path: '/lesson/detail' })">
+      <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete"></data-table>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: lesson } = createNamespacedHelpers('lesson');
+export default {
+  name: 'index',
+  props: {},
+  components: { listFrame, dataTable },
+  data: () => {
+    return {
+      opera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+        },
+      ],
+      fields: [
+        { label: '模板名称', prop: 'title' },
+        { label: '模板类型', prop: 'type', format: i => (i == 0 ? '普通班' : '特殊班') },
+      ],
+      list: [],
+      total: 0,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...lesson(['modelQuery', 'modelDelete']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.modelQuery({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    toEdit({ data }) {
+      this.$router.push({ path: '/lesson/detail', query: { id: data.id } });
+    },
+    async toDelete({ data }) {
+      const res = await this.modelDelete(data.id);
+      this.$checkRes(res, '删除成功', '删除失败');
+      this.search();
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 4 - 1
src/views/location/detail.vue

@@ -21,7 +21,10 @@ export default {
   },
   data: () => ({
     info: {},
-    fields: [{ label: '地点', required: true, model: 'name' }],
+    fields: [
+      { label: '地点', required: true, model: 'name' },
+      { label: '蓝牙id', required: true, model: 'ibeacon' },
+    ],
     rules: {
       name: [{ required: true, message: '请输入地点' }],
     },

+ 2 - 2
src/views/location/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div id="index">
-    <list-frame title="地点管理" @query="search" :total="total" :needFilter="false" @add="$router.push({ path: '/location/detail' })">
+    <list-frame title="班级管理" @query="search" :total="total" :needFilter="false" @add="$router.push({ path: '/location/detail' })">
       <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete"></data-table>
     </list-frame>
   </div>
@@ -12,7 +12,7 @@ import dataTable from '@frame/components/data-table';
 import { createNamespacedHelpers } from 'vuex';
 const { mapActions } = createNamespacedHelpers('location');
 export default {
-  metaInfo: { title: '地点管理' },
+  metaInfo: { title: '班级管理' },
   name: 'index',
   props: {},
   components: {

+ 507 - 0
src/views/new-plan/arrange/arrange.vue

@@ -0,0 +1,507 @@
+<template>
+  <div id="arrange">
+    <!-- <detail-frame :title="pageTitle" :returns="returns"> -->
+    <el-row type="flex" justify="center" v-if="view == 'plan'">
+      <!-- <el-col :span="12">
+          <el-card header="全年计划信息">
+            <el-form :model="info" :rules="rules" :isNew="isNew" label-width="60px" size="small" @submit.native.prevent>
+              <el-form-item label="年份" required>
+                {{ info.year }}
+              </el-form-item>
+              <el-form-item label="标题" prop="title" required>
+                {{ info.title }}
+              </el-form-item>
+
+              <el-collapse v-model="collapse" accordion>
+                <el-collapse-item title="计划简表" name="1">
+                  <data-table :fields="fields" :data="selectList" :opera="opera" @edit="toEdit" @delete="toDelete" :height="heights"></data-table>
+                </el-collapse-item>
+              </el-collapse>
+              <el-form-item>
+                <el-row type="flex" align="middle" justify="space-around" style="margin-top:20px">
+                  <el-col :span="6">
+                    <el-button type="primary" @click="savePlan">保存全年计划</el-button>
+                  </el-col>
+                </el-row>
+              </el-form-item>
+            </el-form>
+          </el-card>
+        </el-col> -->
+      <el-col :span="24" :style="`overflow:auto`">
+        <el-card ref="card" v-if="info.year" height="800px">
+          <calendar
+            :year="`${info.year || '2020'}`"
+            :selfBtn="selfBtn"
+            @draft="selectDate"
+            @eventClick="eventClick"
+            :vacation="vacation"
+            :events="events"
+          ></calendar>
+        </el-card>
+      </el-col>
+    </el-row>
+    <!-- <template v-else-if="view == 'school'">
+      <sch-arr :events="events" :year="info.year" :template="template" @toSave="toSave" @toDirector="toDirector"></sch-arr>
+    </template>
+    <template v-else>
+      <dir-arr :events="events"></dir-arr>
+    </template> -->
+    <!-- </detail-frame> -->
+    <el-drawer :visible.sync="drawer" direction="rtl" title="安排计划" @close="toClose">
+      <event
+        :data="form"
+        :year="info.year"
+        :vacation="vacation"
+        :isNew="formIsNew"
+        :predefineColors="template.color"
+        @save="setEvent"
+        @delete="toDelete"
+      ></event>
+    </el-drawer>
+    <el-dialog :visible.sync="dialog" title="模板计划" width="30%" :close-on-click-modal="false">
+      <el-form>
+        <el-form-item label="请输入您要生成的期数">
+          <el-input v-model="input.term"></el-input>
+        </el-form-item>
+        <el-form-item label="请选择开始日期">
+          <el-date-picker
+            :picker-options="pickerOptions"
+            v-model="input.start"
+            type="date"
+            placeholder="请选择开始日期"
+            format="yyyy-MM-dd"
+            value-format="yyyy-MM-dd"
+          >
+          </el-date-picker>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-row :gutter="20" type="flex" justify="center" align="middle">
+          <el-col :span="4">
+            <el-button @click="dialog = false">取消</el-button>
+          </el-col>
+          <el-col :span="4">
+            <el-button type="primary" @click="setDefaultPlan">确定</el-button>
+          </el-col>
+        </el-row>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+var moment = require('moment');
+import detailFrame from '@frame/layout/admin/detail-frame';
+import calendar from '@frame/components/calendar';
+import dataTable from '@frame/components/data-table';
+import event from '../parts/event';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('trainplan');
+const { mapActions: trainTemplate } = createNamespacedHelpers('trainTemplate');
+const { mapActions: schPlan } = createNamespacedHelpers('schPlan');
+const { mapActions: util } = createNamespacedHelpers('util');
+export default {
+  name: 'arrange',
+  props: {},
+  components: {
+    // detailFrame,
+    calendar,
+    // dataTable,
+    event,
+  },
+  data: function() {
+    var that = this;
+    return {
+      view: 'plan',
+      template: {},
+      info: {
+        year: '2020',
+      },
+      form: {},
+      selectList: [],
+      events: [],
+      vacation: [],
+      fields: [
+        { label: '开始时间', prop: 'start' },
+        { label: '结束时间', prop: 'end' },
+        { label: '期数', prop: 'term' },
+        { label: '班级类型', prop: 'type', format: item => (item === '0' ? '正常班级' : '特殊班级') },
+      ],
+      opera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+          confirm: true,
+        },
+      ],
+      rules: {
+        title: [{ required: true, message: '请输入标题' }],
+      },
+      formRules: {
+        start: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
+        end: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
+        term: [{ required: true, message: '请输入期数' }],
+        number: [{ required: true, message: '请输入每班人数' }],
+        type: [{ required: true, message: '请选择班级类型' }],
+      },
+      dialog: false,
+      selfBtn: {
+        term: {
+          text: '生成模板计划',
+          click: () => (that.dialog = true),
+          position: 'left',
+        },
+        plan: {
+          text: '保存培训计划',
+          click: () => that.savePlan(),
+          position: 'right',
+        },
+      },
+      input: {
+        term: 0,
+      },
+      pickerOptions: {
+        disabledDate: time => that.checkDate(time),
+      },
+      heights: 250,
+      collapse: '',
+      drawer: false,
+      formIsNew: true,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...mapActions(['fetch', 'update']),
+    ...trainTemplate({ trainTemplate: 'query' }),
+    ...schPlan({ setSchPlan: 'schArrange' }),
+    ...util({ modelFetch: 'fetch' }),
+    async search() {
+      let planid = _.get(this.defaultOption, 'planid');
+      if (!planid) return;
+      const res = await this.fetch(planid);
+      if (this.$checkRes(res)) {
+        let fest = _.get(res.data, 'festivals', []);
+        let vac = fest.map(i => {
+          let object = {};
+          object.id = i._id;
+          object.start = i.begindate;
+          object.end = i.finishdate;
+          object.title = i.name;
+          object.rendering = 'background';
+          object.color = 'red';
+          object.editable = false;
+          return object;
+        });
+        this.$set(this, `vacation`, vac);
+        this.$set(this, `info`, res.data);
+        let midArr = JSON.parse(JSON.stringify(res.data));
+        let events = [];
+        events = _.flatten(
+          midArr.termnum.map(item => {
+            item.batchnum.map((i, index) => {
+              i.termid = item._id;
+              i.term = item.term;
+              i.id = i._id;
+              i.start = JSON.parse(JSON.stringify(i.startdate));
+              i.end = JSON.parse(JSON.stringify(i.enddate));
+              i.title = JSON.parse(JSON.stringify(i.name));
+              delete i.startdate, delete i.enddate;
+              return i;
+            });
+            return item.batchnum;
+          })
+        );
+        events = events.map((i, index) => {
+          i.index = index;
+          return i;
+        });
+        this.$set(this, `events`, events);
+        this.$set(this, `selectList`, events);
+      }
+      this.searchTemplate();
+    },
+    //模板事件开始
+    //生成默认模板
+    setDefaultPlan() {
+      this.$set(this, `events`, []);
+      this.$set(this, `selectList`, []);
+      this.dialog = false;
+      let { term, start } = this.input;
+      let { day, batchnum, classnum } = this.template;
+      let event = []; //处理成功事件的存储
+      //第一次正常安排
+      //qb:剩余没有满足之前的批数 qt:剩余没有满足之前的期数
+      let { list, qb, start: rstart } = this.arrange(term, start);
+      event = list;
+      start = rstart;
+      event = this.otherArrange(qb, start, event);
+      //最后赋值回去
+      event.map((i, index) => {
+        i.index = index;
+        return i;
+      });
+      this.$set(this, `events`, event);
+      this.$set(this, `selectList`, event);
+    },
+    //默认安排事件
+    arrange(t, start, part) {
+      let qt = 0;
+      let qb = 0;
+      let { day, batchnum, classnum, stunum } = this.template;
+      if (!part) part = batchnum;
+      let list = [];
+      for (let it = 1; it <= t; it++) {
+        for (let ib = 1; ib <= part; ib++) {
+          let end = this.$plusDay(start, day - 1);
+          let res = this.$checkDate(start, end, this.vacation);
+          if (res == true) {
+            let batch = {
+              term: it - qt,
+              batch: ib,
+              class: classnum,
+              start,
+              end,
+              type: '0',
+              number: stunum,
+              title: `第${it - qt}期第${ib}批次`,
+              color: this.getColor(it, ib),
+            };
+            start = this.$plusDay(start);
+            list.push(batch);
+          } else {
+            if (ib == 1) qt += 1;
+            qb += batchnum - ib + 1;
+            start = this.$plusDay(res.end);
+            break;
+          }
+        }
+      }
+      return { list, qb, start };
+    },
+    //默认处理未安排的事件
+    otherArrange(qb, start, event) {
+      let { day, batchnum, classnum } = this.template;
+      //将剩余的批数转换成局部变量,然后赋0重置qb,为了在arrange中重新计算是否有接触假期
+      let tta = Math.ceil(qb / batchnum);
+      let part = qb % batchnum;
+      qb = 0;
+      let { list, qb: rqb, start: rstart } = this.arrange(tta, start, part);
+      start = rstart;
+      let last = _.last(event);
+      let arr = [];
+      if (last) arr = this.sortOtherData(last, list);
+      else arr = list;
+      event = event.concat(arr);
+      //判断是否还有剩余的批次,期;有的话还需要处理
+      if (rqb > 0) {
+        return this.otherArrange(rqb, start, event);
+      } else {
+        return event;
+      }
+    },
+    //默认事件排序
+    sortOtherData(last, data) {
+      let arr = _.chunk(data, 3).map((i, index) => {
+        i.map(ii => {
+          ii.term = last.term + index + 1;
+          ii.title = `第${ii.term}期第${ii.batch}批次`;
+          return ii;
+        });
+        return i;
+      });
+      return _.flatten(arr);
+    },
+    //模板事件结束
+
+    //手动操作事件开始
+    setEvent({ data, isNew }) {
+      data = JSON.parse(JSON.stringify(data));
+      if (data.type == 0) {
+        data.title = `第${data.term}期第${data.batch}批次`;
+      } else {
+        let { name } = data;
+        data = { ...data, title: name };
+      }
+      this.$set(this.events, data.index, data);
+      this.$set(this.selectList, data.index, data);
+      this.toClose();
+    },
+
+    //列表编辑事件
+    toEdit({ data, index }) {
+      this.$set(this, `form`, JSON.parse(JSON.stringify(data)));
+      this.formIsNew = false;
+      this.drawer = true;
+    },
+    //列表删除事件
+    toDelete({ data, index }) {
+      console.log(data, index);
+      this.$set(
+        this,
+        `events`,
+        this.events.filter(f => f.index !== data.index)
+      );
+      this.selectList.splice(index, 1);
+      this.toClose();
+    },
+    //计划保存
+    savePlan() {
+      let data = JSON.parse(JSON.stringify(this.info));
+      let plan = JSON.parse(JSON.stringify(this.selectList));
+      let termnum = [];
+      termnum = _.uniqBy(
+        plan.map(item => {
+          let obj = { term: item.term };
+          if (item.termid) obj._id = item.termid;
+          return obj;
+        }),
+        'term'
+      ).map(i => {
+        let object = _.cloneDeep(i);
+        object.batchnum = plan
+          .filter(fil => fil.term === i.term)
+          .map(b => {
+            b = _.pickBy(b, (val, key) => key !== 'term');
+            b.startdate = JSON.parse(JSON.stringify(b.start));
+            b.enddate = JSON.parse(JSON.stringify(b.end));
+            b.name ? b.name : (b.name = JSON.parse(JSON.stringify(b.title)));
+            delete b.start, delete b.end;
+            if (_.startsWith(b.id, 'eve') || _.startsWith(b.id, 'vac')) delete b.id;
+            return b;
+          });
+        object.classnum = object.batchnum.reduce((pre, cur) => {
+          if (cur.type === '0') return pre + parseInt(cur.class);
+          else return pre + 1;
+        }, 0);
+        return object;
+      });
+      data.termnum = termnum;
+      let res;
+      let msg;
+      res = this.update(data);
+      msg = `计划保存成功`;
+      this.$checkRes(res, msg);
+    },
+    //选择时间的事件
+    selectDate(object) {
+      let start = JSON.parse(JSON.stringify(object.startStr));
+      let end = JSON.parse(JSON.stringify(object.endStr));
+      this.$set(this.form, `start`, start);
+      this.$set(this.form, `end`, end);
+      this.$set(this.form, `index`, this.events.length);
+      this.drawer = true;
+      this.formIsNew = true;
+    },
+    //
+    eventClick({ event }) {
+      let obj = _.get(event, `extendedProps`);
+      if (!obj) {
+        console.warn(`无对应事件`);
+        return;
+      }
+      let e = this.events.find(f => f.index == obj.index);
+      if (e) this.$set(this, `form`, e);
+      this.drawer = true;
+      this.formIsNew = false;
+    },
+    //
+    async searchTemplate() {
+      let planid = _.get(this.defaultOption, 'planid');
+      let planyearid = _.get(this.defaultOption, 'planyearid');
+      let res = await this.modelFetch({ model: 'trainmodel', planyearid, planid });
+      if (this.$checkRes(res)) {
+        if (res.data !== null) {
+          this.$set(this, `template`, res.data);
+        }
+      }
+    },
+    //其他事件(无关紧要)
+    //关闭抽屉函数
+    toClose() {
+      this.drawer = false;
+      this.formIsNew = true;
+      this.setHeight();
+    },
+    setHeight() {
+      let heights = this.$refs.card.$el.clientHeight * 0.63;
+      this.$set(this, `heights`, heights);
+    },
+    //设置事件颜色
+    getColor(it, ib) {
+      let { color, batchnum } = this.template;
+      if (color.length > 0) {
+        // let num = ((it - 1) * batchnum + ib) % color.length;
+        let num = (it - 1) % color.length;
+        return color[num];
+      } else return '#004499';
+    },
+
+    checkDate(date) {
+      let year = JSON.parse(JSON.stringify(this.info.year));
+      let res = moment(date).isBetween(`${year}-01-01`, `${year}-12-31`, null, '[]');
+      return !res;
+    },
+    // returns() {
+    //   if (this.view == 'plan') this.$router.push({ path: './index' });
+    //   else if (this.view == 'school') this.view = 'plan';
+    //   else this.view = 'school';
+    // },
+    // changeView() {
+    //   this.savePlan();
+    //   this.view = 'school';
+    // },
+    async toSave(schPlan) {
+      const res = await this.setSchPlan(schPlan);
+      if (this.$checkRes(res)) {
+        this.savePlan();
+      }
+    },
+    toDirector() {
+      this.view = 'director';
+    },
+  },
+  watch: {
+    defaultOption: {
+      handler(val) {
+        this.search();
+      },
+      deep: true,
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    id() {
+      return this.$route.query.id;
+    },
+    isNew() {
+      return this.$route.query.id ? false : true; //false : true;
+    },
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    widths() {
+      let width = (document.body.clientWidth - 200) * 0.65;
+      return width > 400 ? width : 400;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.el-card__body {
+  overflow: auto;
+  padding: 10px;
+}
+</style>

+ 212 - 0
src/views/new-plan/arrange/director-arrange.vue

@@ -0,0 +1,212 @@
+<template>
+  <div id="director-arrange">
+    <detail-frame :title="pageTitle">
+      <el-row type="flex" align="middle" justify="end" style="padding-bottom:10px" v-if="!loading">
+        <el-col :span="2">
+          <el-button type="success" size="mini" @click="toSave">保存班主任计划</el-button>
+        </el-col>
+        <el-col :span="2">
+          <el-button type="primary" size="mini" @click="toArrange">一键分配</el-button>
+        </el-col>
+      </el-row>
+      <!-- <data-table v-loading="loading" :fields="fields" :data="classList" :opera="opera" @edit="toEdit" :toFormat="toFormat"></data-table> -->
+      <el-table :data="classList" border stripe v-loading="loading">
+        <el-table-column align="center" type="expand">
+          <template v-slot="{ row }">
+            <el-table :data="row.data" border stripe>
+              <el-table-column align="center" type="expand">
+                <template v-slot="{ row: batRow }">
+                  <el-table :data="batRow.data" border stripe>
+                    <el-table-column align="center" label="班级" prop="name"></el-table-column>
+                    <el-table-column align="center" label="班主任" prop="headteacherid" :formatter="toFormat"></el-table-column>
+                    <el-table-column align="center" label="操作">
+                      <template v-slot="{ row: claRow }">
+                        <el-button type="text" size="mini" icon="el-icon-edit" @click="toEdit(claRow)"></el-button>
+                      </template>
+                    </el-table-column>
+                  </el-table>
+                </template>
+              </el-table-column>
+              <el-table-column align="center" label="批" prop="batch"></el-table-column>
+            </el-table>
+          </template>
+        </el-table-column>
+        <el-table-column align="center" label="期" prop="term"></el-table-column>
+      </el-table>
+    </detail-frame>
+    <el-dialog title="选择班主任" :visible.sync="dialog" width="20%">
+      <data-form :data="form" :fields="fields" :rules="{}" @save="handleSave" :reset="false">
+        <template #options="{item,form}">
+          <template v-if="item.model == 'headteacherid'">
+            <el-option-group v-for="(dept, index) in htList" :label="dept.name" :key="index">
+              <el-option v-for="(i, tIndex) in dept.list" :key="`${index}-${tIndex}`" :label="i.name" :value="i._id"></el-option>
+            </el-option-group>
+          </template>
+        </template>
+      </data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataTable from '@frame/components/data-table';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('teaPlan');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: mapDept } = createNamespacedHelpers('dept');
+const { mapActions: director } = createNamespacedHelpers('director');
+export default {
+  name: 'director-arrange',
+  props: {
+    // events: { type: Array, default: () => [] },
+  },
+  components: { dataForm, detailFrame }, //dataTable
+  data: function() {
+    return {
+      loading: true,
+      dialog: false,
+      form: {},
+      classList: [],
+      htList: [],
+      dirList: [],
+      deptList: [],
+      fields: [
+        { label: '期', prop: 'term', model: 'term', type: 'text' },
+        { label: '批', prop: 'batch', model: 'batch', type: 'text' },
+        { label: '班级', prop: 'name', model: 'name', type: 'text' },
+        { label: '班主任', prop: 'headteacherid', model: 'headteacherid', type: 'select', format: true }, //
+      ],
+      opera: [
+        {
+          label: '选择班主任',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+      ],
+    };
+  },
+  async created() {
+    await this.getOtherList();
+    this.search();
+  },
+  methods: {
+    ...director({ getDirList: 'query' }),
+    ...mapDept({ getDept: 'query' }),
+    ...mapActions(['divide', 'findTeacher']),
+    ...classes(['query', 'upHeadTea']),
+    async search() {
+      let planid = _.get(this.defaultOption, 'planid');
+      if (!planid) return;
+      let res = await this.query({ planid });
+      if (this.$checkRes(res)) {
+        let arr = this.resetList(res.data);
+        this.$set(this, `classList`, arr);
+        this.loading = false;
+      }
+    },
+    async toArrange() {
+      let msg = this.$message({ message: '正在分配班主任,请稍后', duration: 0 });
+      this.loading = true;
+      let duplicate = this.returnList();
+      let planid = _.get(this.defaultOption, `planid`);
+      let res = await this.divide({ trainplanid: planid });
+      if (this.$checkRes(res)) {
+        let arr = duplicate.map(i => {
+          let r = res.data.find(f => f.classid == i._id);
+          if (r) i.headteacherid = r.headteacherid;
+          return i;
+        });
+        arr = this.resetList(arr);
+        this.$set(this, `classList`, arr);
+      }
+      this.loading = false;
+      msg.close();
+      this.$checkRes(res, '分配完成', res.errmsg);
+    },
+    async toEdit(data) {
+      let res = await this.findTeacher({ planid: data.planid, termid: data.termid, batchid: data.batchid });
+      if (this.$checkRes(res)) {
+        let group = _.groupBy(res.data, 'department');
+        let keys = Object.keys(group);
+        let arr = keys.map(key => {
+          let r = this.deptList.find(f => f.id == key);
+          let obj = {};
+          if (r) {
+            obj.name = r.name;
+            obj.list = group[key];
+          }
+          return obj;
+        });
+        this.$set(this, `htList`, arr);
+      }
+      this.dialog = true;
+      this.$set(this, `form`, data);
+    },
+    handleSave({ data }) {
+      // let { index, ...info } = data;
+      // this.$set(this.classList, index, info);
+      this.dialog = false;
+    },
+    async toSave() {
+      let data = JSON.parse(JSON.stringify(this.returnList()));
+      let res = await this.upHeadTea(data);
+      this.$checkRes(res, '保存成功', res.errmsg || '保存失败');
+    },
+    async getOtherList() {
+      let res = await this.getDept();
+      if (this.$checkRes(res)) this.$set(this, `deptList`, res.data);
+      res = await this.getDirList();
+      if (this.$checkRes(res)) this.$set(this, `dirList`, res.data);
+    },
+    toFormat(row, column, cellValue) {
+      let model = _.get(column, 'property');
+      if (model == 'headteacherid') {
+        let r = this.dirList.find(f => f._id == cellValue);
+        if (r) return r.name;
+      }
+    },
+    resetList(data, prop = 'term') {
+      let duplicate = _.groupBy(_.cloneDeep(data), prop);
+      let keys = Object.keys(duplicate);
+      let arr = keys.map(key => {
+        let obj = {};
+        obj[prop] = key;
+        if (prop == 'term') obj.data = this.resetList(duplicate[key], 'batch');
+        else obj.data = duplicate[key];
+        return obj;
+      });
+      return arr;
+    },
+    returnList() {
+      let duplicate = _.cloneDeep(this.classList);
+      let arr = _.flattenDeep(duplicate.map(i => i.data.map(i => i.data)));
+      return arr;
+    },
+  },
+  watch: {
+    defaultOption: {
+      handler(val) {
+        this.search();
+      },
+      deep: true,
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 684 - 0
src/views/new-plan/arrange/school-arrange.vue

@@ -0,0 +1,684 @@
+<template>
+  <div id="school-arrange">
+    <detail-frame :title="pageTitle">
+      <el-row type="flex" align="middle" justify="end" style="padding-bottom:10px" v-if="already">
+        <el-col :span="2">
+          <el-button type="success" size="mini" @click="toSave">保存计划</el-button>
+        </el-col>
+        <el-col :span="2">
+          <el-button type="primary" size="mini" @click="toArrange">一键分配</el-button>
+        </el-col>
+        <el-col :span="2">
+          <el-button type="primary" size="mini" plain @click="dialog = true">查看汇总</el-button>
+        </el-col>
+      </el-row>
+      <!-- 大表 -->
+      <el-card v-loading="!already" style="min-height:500px">
+        <el-table
+          :data="list"
+          border
+          stripe
+          size="mini"
+          height="650px"
+          @cell-click="cellClick"
+          :cell-style="cellStyle"
+          :cell-class-name="cellClass"
+          v-if="already"
+        >
+          <el-table-column label="学校" fixed align="center" prop="name" width="180">
+            <template v-slot="{ row }">
+              <el-row>
+                <el-col :span="24">
+                  {{ row.name }}
+                </el-col>
+                <el-col :span="24" v-if="row.number"> 名额:{{ row.number }} </el-col>
+              </el-row>
+            </template>
+          </el-table-column>
+          <el-table-column align="center" v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`">
+            <el-table-column align="center" width="95" :prop="`term${index + 1}`">
+              <template #header>
+                <el-row>
+                  <el-col :span="24">{{ i.start }}</el-col>
+                  <el-col :span="24">至</el-col>
+                  <el-col :span="24">{{ i.end }}</el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table-column>
+        </el-table>
+      </el-card>
+    </detail-frame>
+
+    <el-drawer :visible.sync="drawer" direction="rtl" title="名额分配" @close="toClose">
+      <el-row type="flex" align="middle" justify="center" style="padding:20px" :gutter="20">
+        <el-col :span="12">
+          <el-card>
+            <el-form size="mini">
+              <el-form-item label="学校">{{ form | getProp('sch.name') }}</el-form-item>
+              <el-form-item label="学校层次">{{ form | getProp('sch.level') }}</el-form-item>
+              <el-form-item label="需要派车">{{ form | getProp('sch.hascar') }}</el-form-item>
+              <el-form-item label="总名额">{{ form | getProp('sch.number') }}</el-form-item>
+              <el-form-item label="剩余名额">{{ form | getProp('sch.remaining') }}</el-form-item>
+            </el-form>
+          </el-card>
+        </el-col>
+        <el-col :span="12">
+          <el-card>
+            <el-form size="mini">
+              <el-form-item label="期数">{{ form | getProp('term.term') }}</el-form-item>
+              <el-form-item label="开始时间">{{ form | getProp('term.start') }}</el-form-item>
+              <el-form-item label="结束时间">{{ form | getProp('term.end') }}</el-form-item>
+              <el-form-item label="总名额">{{ form | getProp('term.tpt') }}</el-form-item>
+              <el-form-item label="剩余名额">{{ form | getProp('term.remaining') }}</el-form-item>
+            </el-form>
+          </el-card>
+        </el-col>
+      </el-row>
+      <el-card style="padding:20px">
+        <el-row type="flex" align="middle" justify="center" :gutter="20">
+          <el-col :span="24">
+            <el-form size="mini" label-width="150px">
+              <el-form-item label="分配给该期的名额">
+                <el-input-number
+                  v-model="form.num"
+                  type="number"
+                  placeholder="请输入分配给该期的名额"
+                  :max="dGetMax(form)"
+                  :min="0"
+                  style="width:250px"
+                  @change="dComputed"
+                ></el-input-number>
+              </el-form-item>
+              <el-form-item>
+                <el-row type="flex" align="middle" justify="start" :gutter="20">
+                  <el-col :span="4">
+                    <el-button type="primary" @click="toSend">分配</el-button>
+                  </el-col>
+                  <el-col :span="4">
+                    <el-button @click="toClose">取消</el-button>
+                  </el-col>
+                </el-row>
+              </el-form-item>
+            </el-form>
+          </el-col>
+        </el-row>
+      </el-card>
+    </el-drawer>
+
+    <!-- 数据汇总 -->
+    <el-dialog :visible.sync="dialog" title="数据汇总">
+      <el-collapse v-model="activeName" accordion>
+        <el-collapse-item :title="`各期情况 (${isOk() ? '已完成' : '未完成'})`" name="1">
+          <el-table :data="totalList" border stripe size="mini">
+            <el-table-column prop="type"></el-table-column>
+            <el-table-column v-for="(i, index) in termList" align="center" :key="index" :label="`第${i.term}期`" :prop="`term${i.term}`"> </el-table-column>
+          </el-table>
+        </el-collapse-item>
+        <el-collapse-item :title="`车辆统计 共计(${getAllCarTotal()})`" name="2">
+          <el-table :data="carList" border stripe size="mini" height="500px">
+            <el-table-column type="expand">
+              <template v-slot="{ row }">
+                <el-row>
+                  <el-col :span="24" v-for="(i, index) in row.carTerm" :key="index">
+                    <span v-if="i.num > 0">第 {{ i.term }} 期需要 {{ i.num }} 辆车</span>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+            <el-table-column label="学校" align="center" prop="name" width="180"></el-table-column>
+            <el-table-column label="车辆需求总数" align="center">
+              <template v-slot="{ row }">
+                {{ getTableCarTotal(row.carTerm) }}
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-collapse-item>
+      </el-collapse>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+var moment = require('moment');
+import detailFrame from '@frame/layout/admin/detail-frame';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainPlan } = createNamespacedHelpers('trainplan');
+const { mapActions: util } = createNamespacedHelpers('util');
+const { mapActions: school } = createNamespacedHelpers('school');
+const { mapActions: schPlan } = createNamespacedHelpers('schPlan');
+export default {
+  name: 'school-arrange',
+  props: {},
+  components: { detailFrame },
+  data: () => {
+    return {
+      list: [],
+      plan: {},
+      termList: [],
+      totalList: [],
+      carList: [],
+      template: {},
+      dialog: false,
+      drawer: false,
+      form: {},
+      activeName: '1',
+      already: false,
+      options: undefined,
+    };
+  },
+  async created() {
+    await this.toGetTrainPlan();
+    await this.toGetTrainTemplate();
+    await this.getSchool();
+  },
+  methods: {
+    ...util({ modelFetch: 'fetch' }),
+    ...trainPlan({ getTrainPlan: 'fetch' }),
+    ...school(['query']),
+    ...schPlan({ schPlanQuery: 'query', createSchPlan: 'create', updateSchPlan: 'update', setSchPlan: 'schArrange' }),
+    //请求,处理学校列表
+    async getSchool() {
+      const res = await this.query();
+      if (this.$checkRes(res)) {
+        let nd = res.data.map(i => (i = _.omit(i, ['meta', 'id', '_id', 'logourl', 'number'])));
+        nd = this.setSchoolNumber(nd);
+        let school = nd.map(i => {
+          if (i.number) i.remaining = JSON.parse(JSON.stringify(i.number));
+          else i.remaining = 0;
+          return i;
+        });
+        this.$set(this, `list`, school);
+        await this.getSchoolPlan();
+        await this.getTotal();
+      }
+    },
+    //自动安排
+    toArrange() {
+      this.toReset();
+      let school = this.list.filter(f => f.daterange);
+      //整理之后的学校列表
+      school = _.reverse(_.sortBy(school, ['level', 'hascar']));
+      //整理每个学校的daterange:[{start,end}]的形式
+      school = this.changeRange(school);
+      let termList = JSON.parse(JSON.stringify(this.termList));
+      for (const sch of school) {
+        for (const t of termList) {
+          if (t.remaining && t.remaining <= 0) continue;
+          let { result } = this.$tqInRange(t.start, t.end, sch.daterange);
+          //result为true:说明该期复合要求
+          if (result) {
+            if (sch.remaining > t.remaining) {
+              //学校名额>本期剩余名额:
+              //学校剩余名额 = 学校剩余名额(原) - 本期剩余名额
+              //学校在该期的名额数量 = 本期剩余名额
+              //本期剩余名额=0;
+              sch.remaining = sch.remaining - t.remaining;
+              sch[`term${t.term}`] = t.remaining;
+              sch[`id_term${t.term}`] = t.termid;
+              t.remaining = 0;
+              continue;
+            } else if (sch.remaining <= t.remaining) {
+              //学校名额<=本期剩余名额:
+              //本期剩余名额 = 本期剩余名额(原) - 学校剩余名额;
+              //学校在该期的名额数量 = 学校剩余名额
+              //学校剩余名额 = 0
+              t.remaining = t.remaining - sch.remaining;
+              sch[`term${t.term}`] = sch.remaining;
+              sch[`id_term${t.term}`] = t.termid;
+              sch.remaining = 0;
+              break;
+            }
+          }
+        }
+        let index = _.findIndex(this.list, f => f.code == sch.code);
+        if (index >= 0) this.$set(this.list, index, sch);
+      }
+      this.listClear();
+      this.$set(this, `termList`, termList);
+      this.getTotal();
+      this.getCarTotal();
+    },
+    //保存整体计划
+    async toSave() {
+      //修改学校上报计划,整理数据
+      let schPlan = this.list.map(i => {
+        let plan = _.cloneDeep(i.plan);
+        let keys = Object.keys(i).filter(f => _.startsWith(f, 'term'));
+        for (const key of keys) {
+          let object = {};
+          object.termnum = key.match(/\d+(.\d+)?/g)[0];
+          object._id = i[`_id_term${object.termnum}`];
+          object.number = i[`term${object.termnum}`];
+          object.termid = i[`id_term${object.termnum}`];
+          if (i.carTerm) {
+            let car = i.carTerm.find(f => f.term == object.termnum);
+            if (car) object.carnum = car.num;
+          }
+          plan.term.push(object);
+        }
+        if (plan) return plan;
+      });
+      schPlan = _.compact(schPlan);
+      schPlan = schPlan.map(i => {
+        i.term = _.uniqBy(this.checkTerm(i.term), '_id');
+        return i;
+      });
+      const res = await this.setSchPlan(schPlan);
+      this.$checkRes(res, '保存成功', res.errmsg);
+    },
+    //手动更改
+    cellClick(row, column) {
+      //获取指定期
+      let term = this.termList.find(f => f.term == column.property.match(/\d+(.\d+)?/g)[0]);
+      //学校上报时间转换
+      row = this.changeRange(row);
+      let { result } = this.$tqInRange(term.start, term.end, row.daterange);
+      if (result) {
+        this.form.term = JSON.parse(JSON.stringify(term));
+        this.form.sch = JSON.parse(JSON.stringify(row));
+        this.$set(this.form, `num`, row[`term${term.term}`] * 1 || 0);
+        this.drawer = true;
+        //可以输入,整理显示数据
+      } else this.$message.error(`第 ${term.term} 期(${term.start} 至 ${term.end})不在 ${row.name} 上报的时间内!`);
+    },
+    toSend() {
+      let num = _.get(this.form, 'num');
+      let term = _.get(this.form, 'term');
+      let sch = _.get(this.form, 'sch');
+      let tn = term.term;
+      sch[`term${tn}`] = num;
+      sch[`id_term${tn}`] = term.termid;
+      // sch[`remaining`] = sch.remaining;
+      // term.remaining = term.remaining;
+      let si = _.findIndex(this.list, f => f.code == sch.code);
+      let ti = _.findIndex(this.termList, f => f.term == term.term);
+      this.$set(this.list, si, sch);
+      this.$set(this.termList, ti, term);
+      this.toClose();
+      this.getTotal();
+      this.getCarTotal();
+    },
+    //将学校上报的月份数组[String]=>[{start,end}]方法
+    changeRange(data) {
+      let duplicate = JSON.parse(JSON.stringify(data));
+      if (_.isArray(duplicate)) {
+        duplicate = duplicate.map(i => {
+          let daterange = _.get(i, `daterange`, []); //['4','5','6']形式
+          i.daterange = this.groupTime(daterange);
+          return i;
+        });
+      } else if (_.isObject(duplicate)) {
+        duplicate.daterange = this.groupTime(duplicate.daterange);
+      }
+
+      return duplicate;
+    },
+    groupTime(data) {
+      let res;
+      let len = _.get(data, 'length');
+      if (len && len == 1) {
+        let dr = data.map(mon => {
+          let r = this.stringDateRange(mon, mon);
+          return r;
+        });
+        res = dr;
+      } else if (len && len > 1) {
+        //TODO 长度大于1也就是说明最少上报了2个月份
+        //先排序,然后按照连续性分组
+        let afterSort = data.sort((a, b) => a * 1 - b * 1);
+        let newRange = this.getConRange(afterSort);
+        let r = newRange.map(arr => this.stringDateRange(_.head(arr), _.last(arr)));
+        res = r;
+      } else res = data;
+      return res;
+    },
+    stringDateRange(data, num) {
+      if (_.isObject(data)) return data;
+      let start = moment()
+        .year(this.plan.year)
+        .month(data * 1 - 1)
+        .date('1')
+        .format('YYYY-MM-DD');
+      let end = moment()
+        .year(this.plan.year)
+        .month(num * 1)
+        .date('1')
+        .subtract(1, 'days')
+        .format('YYYY-MM-DD');
+      return { start, end };
+    },
+    getConRange(array) {
+      let res = [];
+      let limit = _.get(array, 'length') - 1;
+      for (let i = 0; i <= limit; i++) {
+        if (i == 0) res.push([array[i]]);
+        else {
+          let t_num = array[i];
+          let l_num = _.last(_.last(res));
+          let r = _.subtract(t_num * 1, l_num * 1);
+          if (r == 1) {
+            //当前和上一期是连续的
+            let last = _.last(res);
+            last.push(t_num);
+            res[res.length - 1] = last;
+          } else res.push([array[i]]);
+        }
+      }
+      return res;
+    },
+    //学校匹配学校计划
+    async getSchoolPlan() {
+      let planid = _.get(this.defaultOption, 'planid');
+      const res = await this.schPlanQuery({ planid });
+      if (this.$checkRes(res)) {
+        let nl = this.list.map(i => {
+          let r = res.data.find(f => f.schid == i.code);
+          if (r) {
+            i.daterange = r.daterange;
+            i = this.changeRange(i);
+            let term = _.get(r, `term`);
+            term = this.checkTerm(term);
+            if (term) {
+              i.carTerm = [];
+              for (const t of term) {
+                i[`term${t.termnum}`] = t.number;
+                i[`id_term${t.termnum}`] = t.termid;
+                i[`_id_term${t.termnum}`] = t._id;
+                i.carTerm.push({ term: t.termnum, num: t.carnum });
+              }
+              i.remaining = i.remaining - term.reduce((prev, next) => prev + (next.number * 1 || 0), 0);
+            }
+            i.plan = r;
+          }
+          return i;
+        });
+        this.$set(this, `list`, nl);
+      }
+      this.$set(this, `already`, true);
+    },
+    //检查已分配的数据是否与现在的期对应
+    checkTerm(term) {
+      let res = term.map(i => {
+        let r = this.termList.find(f => f.termid == i.termid);
+        if (r) return i;
+      });
+      res = _.compact(res);
+      return res;
+    },
+    //整理期(事件)列表
+    getTermList() {
+      let events = this.resetEvents();
+      let arr = _.toPairs(_.groupBy(events, 'termid'));
+      let res = arr.map(i => {
+        i = _.flatten(_.remove(i, r => _.isArray(r)));
+        let tpt = i.reduce((prev, next) => {
+          let num = next.number ? next.number * 1 * next.class : next.class * _.get(this.template, 'stunum', 0);
+          return prev + num;
+        }, 0);
+        let { start, term, termid } = _.head(i);
+        let { end } = _.last(i);
+        return { start, end, term, tpt, remaining: tpt, termid }; //tpt:该期总人数 ,remaining:剩余人数
+      });
+      this.$set(this, `termList`, res);
+    },
+    //整理计划=>和日历的 events 一样
+    resetEvents() {
+      let events = _.get(this.plan, 'termnum', []);
+      events = events.map(i => {
+        let tobj = { term: i.term, termid: i._id };
+        return i.batchnum.map(bat => {
+          let { startdate: start, enddate: end, ...info } = bat;
+          let bobj = { ...info, ...tobj, start, end };
+          return bobj;
+        });
+      });
+      events = _.flatten(events);
+      return events;
+    },
+    //名额统计
+    getTotal() {
+      let res = this.termList.map((i, index) => {
+        i.total = this.list.reduce((prev, next) => prev + (next[`term${i.term}`] * 1 || 0), 0);
+        return i;
+      });
+      let aObject = { type: '已分配' };
+      let rObject = { type: '未分配' };
+      res.map(i => {
+        aObject[`term${i.term}`] = i.total || 0;
+        i.remaining = i.tpt - i.total || 0;
+        rObject[`term${i.term}`] = i.remaining || 0;
+      });
+      this.$set(this, `totalList`, [aObject, rObject]);
+    },
+    //车辆统计
+    getCarTotal() {
+      let school = this.list.filter(f => f.hascar == '1');
+      let carpnum = this.template.carpnum;
+      school.map(sch => {
+        let keys = Object.keys(sch).filter(f => _.startsWith(f, 'term'));
+        let pt = 0; //人数总数
+        sch.carTerm = [];
+        for (const key of keys) {
+          let term = key.match(/\d+(.\d+)?/g)[0];
+          let num = Math.ceil(sch[key] / carpnum);
+          sch.carTerm.push({ term, num });
+        }
+      });
+      school = school.filter(f => _.get(f, 'carTerm.length', 0) > 0);
+      this.$set(this, `carList`, school);
+    },
+    //计算车辆
+    getTableCarTotal(data) {
+      return data.reduce((prev, next) => {
+        return prev + (next.num || 0);
+      }, 0);
+    },
+    //所有车辆和计算
+    getAllCarTotal() {
+      return this.carList.reduce((prev, next) => {
+        let num = next.carTerm.reduce((p, n) => p + (n.num || 0), 0);
+        return prev + num;
+      }, 0);
+    },
+    //是否分配完毕,按期来说
+    isOk() {
+      let res = true;
+      let obj = this.totalList[1];
+      if (obj) {
+        let keys = Object.keys(obj).filter(f => f != 'type');
+        for (const key of keys) {
+          if (obj[key] > 0) {
+            res = false;
+            break;
+          }
+        }
+      } else res = false;
+
+      return res;
+    },
+    //分配重置
+    async toReset() {
+      this.getTermList();
+      let list = this.list.map(i => {
+        let object = _.pick(i, ['address', 'code', 'daterange', 'hascar', 'level', 'name', 'shortname', 'number', 'plan']);
+        object.remaining = object.number;
+        return object;
+      });
+      this.$set(this, `list`, list);
+    },
+    //将分配为0的数据清除
+    listClear() {
+      let list = this.list.map(i => {
+        let keys = Object.keys(i).filter(f => _.startsWith(f, 'term'));
+        for (const key of keys) {
+          if (i[key] == 0) {
+            delete i[key];
+            delete i[`id_${key}`];
+          }
+        }
+        return i;
+      });
+      this.$set(this, `list`, list);
+    },
+    //抽屉名额响应式计算
+    dComputed(cv, ov) {
+      let sch = this.form.sch;
+      let term = this.form.term;
+      let keys = Object.keys(sch)
+        .filter(f => _.startsWith(f, 'term'))
+        .filter(f => f != `term${term.term}`);
+      let sr = keys.reduce((prev, next) => prev - sch[next], sch.number) - cv;
+      let tr = this.list.reduce((prev, next) => {
+        if (next.code != sch.code) return prev - (next[`term${term.term}`] * 1 || 0);
+        else return prev;
+      }, term.tpt);
+      tr = tr - cv;
+      this.$set(this.form.sch, `remaining`, sr);
+      this.$set(this.form.term, `remaining`, tr);
+    },
+    //计算抽屉最大值
+    dGetMax(data) {
+      if (!this.drawer) return 0;
+      let res = 0;
+      let t = this.termList.find(f => f.termid == this.form.term.termid);
+      let sch = this.list.find(f => f.code == this.form.sch.code);
+      let s = _.get(data, 'sch');
+      let tr = _.get(data, 'term.remaining', 0) * 1;
+      let sr = _.get(data, 'sch.remaining', 0) * 1;
+      let num = this.form.num;
+      if (s.remaining == 0) return num;
+      if (tr > sr) {
+        res = sch.number * 1 || 0;
+      } else if (tr == sr) {
+        let keys = Object.keys(s)
+          .filter(f => _.startsWith(f, 'term'))
+          .filter(f => f != `term${t.term}`);
+        let nsr = keys.reduce((prev, next) => prev - s[next], s.number);
+        res = nsr;
+      } else {
+        res = t.tpt * 1 - t.total * 1 || 0;
+      }
+      return res * 1;
+    },
+    //关闭抽屉
+    toClose() {
+      this.drawer = false;
+      this.form = {};
+    },
+    //单元格样式
+    cellStyle({ row, column, rowIndex, columnIndex }) {
+      let disabled = { background: '#F56C6C' }; //{ background: '#F56C6C' }  ' disabled'
+      if (column.property == 'name') return '';
+      if (!_.get(row, 'daterange')) return disabled;
+      let term = this.termList.find(f => f.term == column.property.match(/\d+(.\d+)?/g)[0]);
+      //学校上报时间转换
+      row = this.changeRange(row);
+      let { result } = this.$tqInRange(term.start, term.end, row.daterange);
+      if (!result) {
+        return disabled;
+      } else return '';
+    },
+    cellClass({ row, column, rowIndex, columnIndex }) {
+      let disabled = ' disabled_point';
+      if (column.property == 'name') return '';
+      if (!_.get(row, 'daterange')) return disabled;
+      let term = this.termList.find(f => f.term == column.property.match(/\d+(.\d+)?/g)[0]);
+      //学校上报时间转换
+      row = this.changeRange(row);
+      let { result } = this.$tqInRange(term.start, term.end, row.daterange);
+      if (!result) return disabled;
+      else return '';
+    },
+    //班主任计划
+    toDirector() {
+      this.toSave();
+      this.$emit(`toDirector`);
+    },
+    //获取计划
+    async toGetTrainPlan() {
+      let planid = _.get(this.defaultOption, 'planid');
+      let res = await this.getTrainPlan(planid);
+      if (this.$checkRes(res)) {
+        this.$set(this, `plan`, res.data);
+        this.getTermList();
+      }
+    },
+    //获取计划模板
+    async toGetTrainTemplate() {
+      let planyearid = _.get(this.defaultOption, 'planyearid');
+      let planid = _.get(this.defaultOption, 'planid');
+      let res = await this.modelFetch({ model: 'trainmodel', planyearid, planid });
+      if (this.$checkRes(res)) {
+        this.$set(this, `template`, res.data ? res.data : {});
+      }
+    },
+    //本计划各个学校分配人数匹配
+    setSchoolNumber(data) {
+      let planSchool = _.get(this.plan, `school`, []);
+      if (planSchool.length <= 0) return data;
+      let nl = data.map(i => {
+        let r = planSchool.find(f => f.code == i.code);
+        if (r) i.number = r.num;
+        return i;
+      });
+      nl = nl.sort((a, b) => a.code * 1 - b.code * 1);
+      return nl;
+    },
+    //默认值改变查询
+    async reSearch() {
+      this.already = false;
+      await this.toGetTrainPlan();
+      await this.toGetTrainTemplate();
+      await this.getSchool();
+    },
+  },
+  filters: {
+    getProp(data, prop) {
+      if (prop.includes('hascar')) {
+        let res = _.get(data, prop);
+        return res == '1' ? '需要' : '不需要';
+      } else return _.get(data, prop);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  watch: {
+    defaultOption: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (!_.get(this, 'options')) {
+          this.$set(this, `options`, _.cloneDeep(val));
+        } else {
+          let nplanid = _.get(val, 'planid');
+          let oplanid = _.get(this.options, 'planid');
+          if (nplanid && !_.isEqual(nplanid, oplanid)) {
+            this.$set(this, `options`, _.cloneDeep(val));
+            this.reSearch();
+          }
+        }
+      },
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.el-table {
+  overflow: visible !important;
+}
+</style>
+<style>
+.disabled_point {
+  cursor: not-allowed;
+}
+</style>

+ 129 - 0
src/views/new-plan/arrange/school-num.vue

@@ -0,0 +1,129 @@
+<template>
+  <div id="school-num">
+    <detail-frame :title="pageTitle">
+      <data-table ref="table" :fields="fields" :data="list" :opera="opera" :total="total" @edit="toEdit" @query="search"></data-table>
+    </detail-frame>
+    <el-dialog title="分配人数" :visible.sync="dialog" @close="toClose">
+      <data-form :data="form" :fields="formFields" :rules="{}" @save="handleSave"> </data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataForm from '@frame/components/form';
+import dataTable from '@frame/components/filter-page-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: plan } = createNamespacedHelpers('trainplan');
+const { mapActions: school } = createNamespacedHelpers('school');
+export default {
+  name: 'school-num',
+  props: {},
+  components: { detailFrame, dataTable, dataForm },
+  data: function() {
+    return {
+      dialog: false,
+      list: [],
+      form: {},
+      plan: {},
+      opera: [
+        {
+          label: '修改',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+      ],
+      fields: [
+        { label: '学校', prop: 'name' },
+        { label: '人数', prop: 'num' },
+      ],
+      formFields: [
+        { label: '学校', model: 'name', type: 'text' },
+        { label: '人数', model: 'num' },
+      ],
+      total: 0,
+    };
+  },
+  async created() {
+    await this.searchPlan();
+    await this.search();
+  },
+  methods: {
+    ...plan({ getPlan: 'fetch', updatePlan: 'update' }),
+    ...school(['query']),
+    async searchPlan() {
+      let planid = _.get(this.defaultOption, 'planid');
+      if (!planid) return;
+      let res = await this.getPlan(planid);
+      if (this.$checkRes(res)) {
+        // res.data.school = _.uniqBy(res.data.school, 'code');
+        this.$set(this, 'plan', res.data);
+      }
+    },
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        //TODO 查出这页的学校分配的人数,放到学校里面,表格输出
+        this.$set(this, `list`, res.data);
+        this.getNum();
+        this.$set(this, `total`, res.total);
+      }
+    },
+    async toEdit({ data }) {
+      let { code, name, num, num_id } = data;
+      this.$set(this, `form`, { code, name, num, num_id });
+      this.dialog = true;
+    },
+    async handleSave({ data }) {
+      let { num_id: _id, name, ...info } = data;
+      if (_id) {
+        let i = this.plan.school.findIndex(f => f._id == _id);
+        this.$set(this.plan.school, i, { _id, ...info });
+      } else this.plan.school.push({ _id, ...info });
+      let duplicate = _.cloneDeep(this.plan);
+      let res = await this.updatePlan(duplicate);
+      if (this.$checkRes(res)) {
+        this.toClose();
+        this.getNum();
+      }
+    },
+    getNum() {
+      let spl = _.get(this.plan, `school`);
+      let list = this.list.map(i => {
+        let r = spl.find(f => f.code == i.code);
+        if (r) {
+          i.num_id = r._id;
+          i.num = r.num;
+        }
+        return i;
+      });
+      this.$set(this, `list`, list);
+    },
+    toClose() {
+      this.dialog = false;
+      this.form = {};
+    },
+  },
+  watch: {
+    defaultOption: {
+      async handler(val) {
+        await this.searchPlan();
+        this.$refs.table.changePage();
+      },
+      deep: true,
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 176 - 0
src/views/new-plan/arrange/school-time.vue

@@ -0,0 +1,176 @@
+<template>
+  <div id="index">
+    <list-frame :title="pageTitle" :needFilter="false" :needAdd="false" :needPag="false">
+      <data-table :fields="fields" :data="list" :total="total" @query="search" :opera="opera" @edit="toEdit" :toFormat="toFormat">
+        <template #custom="{item, row}">
+          <template v-if="item.prop == 'daterange'">
+            <span v-for="(i, index) in row.daterange" :key="index">{{ i }};</span>
+          </template>
+        </template>
+      </data-table>
+    </list-frame>
+    <el-dialog title="上报可行日期" width="30%" :visible.sync="dialog" center :destroy-on-close="true" @close="handleClose">
+      <data-form
+        ref="tables"
+        :data="info"
+        :fields="Ffields"
+        :rules="rules"
+        @save="handleSave"
+        :isNew="!info.id"
+        :styles="{ padding: 0 }"
+        labelWidth="80px"
+        :reset="false"
+      >
+        <template #custom="{item,form}">
+          <template v-if="item.model == 'schid'">
+            {{ toFormat({ model: item.model, value: form[item.model] }) }}
+          </template>
+          <template v-if="item.model == 'daterange'">
+            <el-checkbox-group v-model="form[item.model]">
+              <el-checkbox v-for="i in 12" :key="i" :label="`${i}`">{{ i }}月</el-checkbox>
+            </el-checkbox-group>
+          </template>
+        </template>
+      </data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+var moment = require('moment');
+import _ from 'lodash';
+import Vue from 'vue';
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/filter-page-table.vue';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
+const { mapActions: school } = createNamespacedHelpers('school');
+const { mapActions: schPlan } = createNamespacedHelpers('schPlan');
+export default {
+  name: 'index',
+  props: {},
+  components: { listFrame, dataTable, dataForm },
+  data: () => {
+    return {
+      dialog: false,
+      schoolList: [],
+      opera: [
+        {
+          label: '上报可行日期',
+          icon: 'el-icon-date',
+          method: 'edit',
+        },
+      ],
+      fields: [
+        { label: '学校', prop: 'schid', format: true },
+        { label: '上报时间', prop: 'daterange', custom: true },
+      ],
+      info: { daterange: [] },
+      form: {},
+      Ffields: [
+        // { label: '年度', model: 'year', type: 'text' },
+        { label: '学校', model: 'schid', custom: true },
+        { label: '请假日期', model: 'daterange', custom: true },
+      ],
+      rules: {},
+      list: [],
+      total: 0,
+      options: undefined,
+    };
+  },
+  async created() {
+    await this.otherList();
+    this.search();
+  },
+  methods: {
+    ...school({ getSchool: 'query' }),
+    ...trainplan(['query', 'create', 'delete', 'update']),
+    ...schPlan({ schPlanQuery: 'query', createSchPlan: 'create', updateSchPlan: 'update' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      let planid = _.get(this.defaultOption, 'planid');
+      if (!planid) return;
+      const res = await this.schPlanQuery({ skip, limit, ...info, planid });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        // this.setSchool();
+        this.$set(this, `total`, res.total);
+      }
+    },
+    async toEdit({ data }) {
+      this.$set(this, `info`, data);
+      this.dialog = true;
+    },
+    async handleSave({ data, isNew }) {
+      let res;
+      let msg;
+      let duplicate = JSON.parse(JSON.stringify(data));
+      if (isNew) {
+        //create
+        res = await this.createSchPlan(duplicate);
+        msg = '添加成功';
+      } else {
+        //update
+        res = await this.updateSchPlan(duplicate);
+        msg = '修改成功';
+      }
+      if (this.$checkRes(res, msg, res.errmsg || '操作失败')) {
+        this.handleClose();
+      }
+    },
+    handleClose() {
+      this.dialog = false;
+      this.info = { daterange: [] };
+    },
+    toSort() {
+      this.info.daterange.sort((a, b) => a - b);
+    },
+    toArrange({ data }) {
+      this.$router.push({ path: './detail', query: { id: data.id } });
+    },
+    toFormat({ model, value }) {
+      let res;
+      if (model == 'schid') {
+        let r = this.schoolList.find(f => f.code == value);
+        if (r) res = r.name;
+      }
+      return res;
+    },
+    async otherList() {
+      let res = await this.getSchool();
+      if (this.$checkRes(res)) this.$set(this, `schoolList`, res.data);
+    },
+  },
+  watch: {
+    defaultOption: {
+      handler(val, oval) {
+        //1,进入页面,val有,oval没有=>都查
+        if (!_.get(this, 'options')) {
+          this.$set(this, `options`, _.cloneDeep(val));
+          // this.search();
+        } else {
+          let nplanid = _.get(val, 'planid');
+          let oplanid = _.get(this.options, 'planid');
+          if (nplanid && !_.isEqual(nplanid, oplanid)) {
+            this.$set(this, `options`, _.cloneDeep(val));
+            this.search();
+          }
+        }
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 86 - 0
src/views/new-plan/class/bedroom.vue

@@ -0,0 +1,86 @@
+<template>
+  <div id="bedroom">
+    <list-frame v-if="view === 'list'" returns="./index" :title="pageTitle" :needFilter="false" :needAdd="false" :needPag="false">
+      <el-alert type="warning" title="请确认好学生已经报道后再进行分寝" center :closable="false"></el-alert>
+      <el-form :inline="true">
+        <el-form-item>
+          <el-select v-model="prerequisite.termid" size="small" @change="toGetBatch" placeholder="请选择要对哪一期进行分寝">
+            <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <data-table ref="table" :fields="fields" :data="list" :opera="opera" @bedroom="toBedroom"></data-table>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainPlan } = createNamespacedHelpers('trainplan');
+const { mapActions: bedroom } = createNamespacedHelpers('bedroom'); //分寝
+export default {
+  name: 'bedroom',
+  props: {},
+  components: { listFrame, dataTable },
+  data: () => {
+    return {
+      view: 'list',
+      termList: [],
+      batchList: [],
+      list: [],
+      prerequisite: {},
+      classInfo: {},
+      opera: [
+        {
+          label: '一键分寝',
+          icon: 'el-icon-s-home',
+          method: 'bedroom',
+        },
+      ],
+      fields: [
+        { label: '批次', prop: 'name' },
+        // { label: '批数', prop: 'batch' },
+      ],
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...trainPlan({ getTrainPlan: 'fetch', sendNotice: 'notice' }),
+    ...bedroom({ bedroomApart: 'apart' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.getTrainPlan(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `termList`, _.get(res.data, 'termnum', []));
+      }
+    },
+    toGetBatch(id) {
+      let res = this.termList.find(f => f._id == id);
+      this.$set(this, `list`, res.batchnum || []);
+    },
+    async toBedroom({ data }) {
+      let object = { termid: this.prerequisite.termid, trainplanid: this.id, batchid: data._id };
+      let res = await this.bedroomApart(object);
+      this.$checkRes(res, '分寝成功', res.errmsg);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 243 - 0
src/views/new-plan/class/classes.vue

@@ -0,0 +1,243 @@
+<template>
+  <div id="plan">
+    <!-- 根据计划,获取所有期数及期数下批次,然后提供选择期数;查询出该期数下学校上报的学生,选择学生手动分班  :filter="filterFields"-->
+    <list-frame :title="mainTitle" @query="stuSearch" :total="total" :needAdd="false" :needFilter="false" returns="./index">
+      <template #options="{item}">
+        <template v-if="item.model === 'termid'">
+          <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+        </template>
+      </template>
+      <el-card style="padding:10px">
+        <el-row>
+          <el-form :inline="true" size="mini">
+            <el-form-item label="期">
+              <el-select v-model="selectInfo.termid" placeholder="请选择期数" @change="stuSearch">
+                <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="toAutoSetClass">一键分班</el-button>
+            </el-form-item>
+          </el-form>
+          <el-form :inline="true" size="mini">
+            <el-form-item label="批次">
+              <el-select v-model="selectInfo.batchid" placeholder="请先选择期数" @change="getClasses">
+                <el-option v-for="(i, index) in batchList" :key="index" :label="i.name" :value="i._id"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="班级">
+              <el-select v-model="selectInfo.classid" placeholder="请先选择批次" @change="getLimit">
+                <el-option v-for="(i, index) in classList" :key="index" :label="i.name" :value="i._id"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="toSetClass">确认分班</el-button>
+            </el-form-item>
+          </el-form>
+        </el-row>
+        <el-row type="flex" justify="space-around" :gutter="10" align="middle" style="padding:10px 0;">
+          <el-col :span="7">班级需求:{{ selectInfo.personReq }}人</el-col>
+          <el-col :span="7">已选择学生:{{ selected.length }}人</el-col>
+          <el-col :span="7">男性:{{ selectInfo.male }}人</el-col>
+          <el-col :span="7">女性:{{ selectInfo.female }}人</el-col>
+        </el-row>
+
+        <data-table
+          ref="table"
+          :fields="fields"
+          :data="list"
+          :opera="opera"
+          :select="true"
+          :selected="selected"
+          @edit="toEdit"
+          @delete="toDelete"
+          @handleSelect="toSelect"
+        ></data-table>
+      </el-card>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import _ from 'lodash';
+import { mapStatem, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainPlan } = createNamespacedHelpers('trainplan');
+const { mapActions: schPlan } = createNamespacedHelpers('schPlan');
+const { mapActions: student } = createNamespacedHelpers('student');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: director } = createNamespacedHelpers('director');
+const { mapActions: dept } = createNamespacedHelpers('dept');
+export default {
+  metaInfo: { title: '安排班级' },
+  name: 'plan',
+  props: {},
+  components: { listFrame, dataTable },
+  data: () => ({
+    opera: [],
+    fields: [
+      { label: '学生姓名', prop: 'name' },
+      { label: '学校', prop: 'school_name' },
+      { label: '性别', prop: 'gender' },
+    ],
+    filterFields: [], //{ label: '期数', model: 'termid', type: 'select' }
+    list: [],
+    selected: [],
+    selectedTest: [],
+    total: 0,
+    selectInfo: {
+      male: 0,
+      female: 0,
+      personReq: 0,
+    },
+    termList: [],
+    batchList: [],
+    classList: [],
+    directorList: [],
+    deptList: [],
+  }),
+  created() {
+    this.search();
+  },
+  methods: {
+    ...trainPlan(['fetch']),
+    ...schPlan({ schQuery: 'query' }),
+    ...student({ getStudentList: 'noClass' }), //noClass
+    ...classes({ getClassesList: 'query', createClass: 'divide', addStudent: 'addStudent' }),
+    ...director({ getDirectorList: 'query' }),
+    ...dept({ getDeptList: 'query' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        let { termnum } = res.data;
+        this.$set(this, `termList`, termnum);
+      }
+    },
+    //查询选择期上报的学生,提供批次选择
+    async stuSearch({ skip = 0, limit = 10, ...info } = {}) {
+      // let { termid } = info;
+      // this.selectInfo.termid = termid;
+      this.getBatch(this.selectInfo.termid);
+      const res = await this.getStudentList({ termid: this.selectInfo.termid, skip, limit });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+      if (skip !== 0) return;
+    },
+    getBatch(termid) {
+      let batchs = this.termList.filter(f => f._id === termid);
+      if (batchs.length > 0) {
+        let { batchnum } = batchs[0];
+        this.$set(this, `batchList`, batchnum);
+      }
+    },
+    //根据批次id,查询下面的班级
+    async getClasses(data) {
+      const res = await this.getClassesList({ batchid: data });
+      if (this.$checkRes(res)) {
+        this.$set(this, `classList`, res.data);
+      }
+      // //查询班级
+      // if (!data) return;
+      // const res = await this.getClassesList({ batchid: data });
+      // // gc1 因为已经限制了分班人数,所以不允许出现班级人数不够的情况,查出来的班级就不应该生成,可以使用name过滤掉选项
+      // let classList = [];
+      // if (this.$checkRes(res)) {
+      //   if (res.data.length > 0) classList = res.data;
+      // }
+      // //整理班级数据,根据批次中的class数量,人数,创建班级
+      // let batch = this.batchList.find(f => f._id === data);
+      // let { name, class: classnum = 1, type, number } = batch;
+      // this.selectInfo.type = type;
+      // this.$set(this.selectInfo, `personReq`, number);
+      // let arr = [];
+      // for (let i = 1; i <= classnum; i++) {
+      //   let object = { name: `${name}${type === '0' ? `${i}班` : ''}` };
+      //   arr.push(object);
+      // }
+      // // gc1 过滤掉
+      // arr = arr.filter(f => !classList.find(c => c.name == f.name));
+      // this.$set(this, `classList`, arr);
+    },
+    toEdit({ data }) {
+      this.$router.push({ path: '/dept/detail', query: { id: data.id } });
+    },
+    async toDelete({ data }) {
+      const res = await this.delete(data.id);
+      this.$checkRes(res, '删除成功', '删除失败');
+      this.search();
+    },
+    toSelect(selecteds) {
+      this.$set(this, `selected`, selecteds);
+      let male = 0,
+        female = 0;
+      for (const i of selecteds) {
+        if (i.gender === '1' || i.gender === '男') male++;
+        else female++;
+      }
+      this.$set(this.selectInfo, `male`, male);
+      this.$set(this.selectInfo, `female`, female);
+    },
+    async toAutoSetClass() {
+      // 整理数据生成班级;将学生列表重新查询=>为了将已经有班级的学生剔除,以便继续分班(重新查询)
+      let info = JSON.parse(JSON.stringify(_.omit(this.selectInfo, ['male', 'female', 'personReq', 'students'])));
+      info.planid = this.id;
+      //手动添加学生使用这部分,接口换了,之后转移到新加的手动
+      // let stuList = JSON.parse(JSON.stringify(this.selected));
+      // // if (this.isOutRange(stuList)) return;
+      // info.students = stuList;
+      let res = await this.createClass(info);
+      if (this.$checkRes(res, '分班成功', '分班失败')) this.$router.push({ path: './index' });
+      //重置信息
+      // this.selectInfo = {
+      //   male: 0,
+      //   female: 0,
+      // };
+      // this.selected = [];
+      // this.$refs.table.selectReset();
+    },
+    async toSetClass() {
+      let data = {};
+      data.id = _.pick(this.selectInfo, ['classid']).classid;
+      let stuList = JSON.parse(JSON.stringify(this.selected));
+      data.ids = stuList.map(i => i._id);
+      let res = await this.addStudent(data);
+      if (this.$checkRes(res, '分配成功', res.errmsg || '分配失败')) {
+        this.$router.push({ path: './index' });
+      }
+    },
+    isOutRange(selected) {
+      let res = true;
+      if (selected.length <= 0) this.$message.warning('请选择学生');
+      else if (_.inRange(selected.length, 1, this.selectInfo.personReq * 1)) this.$message.error('选择人数不足');
+      else if (selected.length > this.selectInfo.personReq * 1) this.$message('超出班级规定人数');
+      else res = false;
+      return res;
+    },
+    getLimit(selected) {
+      let res = this.classList.find(f => f._id == selected);
+      this.$set(this.selectInfo, `personReq`, res.number);
+    },
+  },
+  computed: {
+    mainTitle() {
+      let meta = this.$route.meta;
+      let main = meta.title || '';
+      let sub = meta.sub || '';
+      return `${main}${sub}`;
+    },
+    keyWord() {
+      let meta = this.$route.meta;
+      let main = meta.title || '';
+      return main;
+    },
+    id() {
+      return this.$route.query.id;
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 151 - 0
src/views/new-plan/class/index-class.vue

@@ -0,0 +1,151 @@
+<template>
+  <div id="index">
+    <list-frame :title="pageTitle" @query="search" :total="total" :needFilter="false" :needAdd="false">
+      <data-table
+        :fields="fields"
+        :data="list"
+        :opera="opera"
+        @classes="toClasses"
+        @lesson="lesson"
+        @setting="toSetting"
+        @msg="sendMsg"
+        @bedroom="toBedroom"
+        @classlist="toClasslist"
+        @quest="toQuest"
+      ></data-table>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+var moment = require('moment');
+import _ from 'lodash';
+import Vue from 'vue';
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+    dataTable,
+  },
+  data: () => {
+    return {
+      dialog: false,
+      drawer: false,
+      opera: [
+        {
+          label: '班级设置',
+          icon: 'el-icon-setting',
+          method: 'setting',
+        },
+        {
+          label: '排课',
+          icon: 'el-icon-date',
+          method: 'lesson',
+        },
+        {
+          label: '排班',
+          icon: 'el-icon-school',
+          method: 'classes',
+        },
+        {
+          label: '班级人员管理',
+          icon: 'el-icon-s-custom',
+          method: 'classlist',
+        },
+        {
+          label: '分寝',
+          icon: 'el-icon-s-home',
+          method: 'bedroom',
+        },
+        {
+          label: '发送确认通知',
+          icon: 'el-icon-message-solid',
+          method: 'msg',
+        },
+        {
+          label: '非常用问卷管理',
+          icon: 'el-icon-question',
+          method: 'quest',
+        },
+      ],
+      fields: [
+        { label: '年度', prop: 'year' },
+        { label: '标题', prop: 'title' },
+        { label: '状态', prop: 'status', format: i => (i === '0' ? '筹备中' : i === '1' ? '进行中' : '已结束') },
+      ],
+      info: {},
+      form: {},
+      Ffields: [
+        { label: '年度', model: 'year' },
+        { label: '标题', model: 'title' },
+        { label: '假期', model: 'festivals', custom: true },
+        { label: '状态', model: 'status', type: 'radio' },
+      ],
+      rules: {},
+      list: [],
+      holiday: [],
+      total: 0,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...trainplan(['query', 'create', 'delete', 'update', 'notice']),
+    ...classes({ getClasses: 'query' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    toClasses({ data }) {
+      this.$router.push({ path: './detail', query: { id: data.id } });
+    },
+    lesson({ data }) {
+      this.$router.push({ path: './lesson', query: { id: data.id } });
+    },
+    toSetting({ data }) {
+      this.$router.push({ path: './setting', query: { id: data.id } });
+    },
+    toBedroom({ data }) {
+      this.$router.push({ path: './bedroom', query: { id: data.id } });
+    },
+    toClasslist({ data }) {
+      this.$router.push({ path: './namelist', query: { id: data.id } });
+    },
+    toQuest({ data }) {
+      this.$router.push({ path: './quest', query: { id: data.id } });
+    },
+    async sendMsg({ data }) {
+      //TODO need test
+      let res = await this.getClasses({ planid: data._id });
+      if (this.$checkRes(res)) {
+        let arr = res.data.map(i => i._id);
+        if (arr.length > 0) {
+          const resNotice = await this.sendNotice({ classids: arr });
+          this.$checkRes(resNotice, '发送成功', '发送失败');
+        }
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 129 - 0
src/views/new-plan/class/lesson.vue

@@ -0,0 +1,129 @@
+<template>
+  <div id="index">
+    <list-frame v-if="view === 'list'" returns="./index" :title="pageTitle" :needFilter="false" :needAdd="false" :needPag="false">
+      <el-form :inline="true">
+        <el-form-item>
+          <el-select v-model="prerequisite.term" size="small" @change="toGetClass">
+            <el-option-group v-for="(term, index) in termList" :key="index" :label="`第${term.term}期`">
+              <el-option
+                v-for="(batch, bindex) in term.batchnum"
+                :key="`${index}-${bindex}`"
+                :label="batch.type == '0' ? `第${batch.batch}批` : `特殊批`"
+                :value="batch._id"
+              ></el-option>
+            </el-option-group>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <el-row type="flex" align="middle" justify="end" style="padding:10px">
+        <el-col :span="2">
+          <el-button type="primary" size="mini" plain @click="toArrange">按模板排课</el-button>
+        </el-col>
+      </el-row>
+      <data-table ref="table" :fields="fields" :data="list" :opera="opera" @date="toDate" @msg="toMsg" @bedroom="toBedroom"></data-table>
+    </list-frame>
+    <detail-frame v-else title="返回" :returns="deReturn">
+      <lesson-table :classInfo="classInfo"></lesson-table>
+    </detail-frame>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import listFrame from '@frame/layout/admin/list-frame';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataTable from '@frame/components/data-table';
+import lessonTable from './lesson/lesson-table.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainPlan } = createNamespacedHelpers('trainplan');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: lesson } = createNamespacedHelpers('lesson');
+export default {
+  name: 'index',
+  props: {},
+  components: { listFrame, detailFrame, dataTable, lessonTable },
+  data: () => {
+    return {
+      view: 'list',
+      termList: [],
+      batchList: [],
+      list: [],
+      prerequisite: {},
+      classInfo: {},
+      opera: [
+        {
+          label: '排课',
+          icon: 'el-icon-date',
+          method: 'date',
+        },
+      ],
+      fields: [
+        { label: '班级', prop: 'name' },
+        // { label: '批数', prop: 'batch' },
+      ],
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...trainPlan({ getTrainPlan: 'fetch', sendNotice: 'notice' }),
+    ...classes({ getClass: 'query', updateClass: 'update' }),
+    ...lesson({ autoArrange: 'arrange' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.getTrainPlan(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `termList`, _.get(res.data, 'termnum', []));
+      }
+    },
+    async toDate({ data }) {
+      this.view = 'class';
+      this.$set(this, `classInfo`, JSON.parse(JSON.stringify(data)));
+    },
+    async toMsg({ data }) {
+      console.log(`in toMsg`);
+    },
+    async toBedroom({ data }) {
+      console.log(`in toBedroom`);
+    },
+    async toGetClass(data) {
+      let res = await this.getClass({ batchid: data });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+    deReturn() {
+      this.view = 'list';
+    },
+    async toArrange() {
+      this.$confirm('此操作将会把本期下所有的班级课表重置,若您已经修改过某班的信息,请谨慎使用', '提示', {
+        confirmButtonText: '按模板排课',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          console.log('in function:');
+          let res = await this.autoArrange(this.id);
+          this.$checkRes(res, '排课成功', res.errmsg || '排课失败');
+        })
+        .catch(async () => {
+          console.log('已取消');
+        });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 353 - 0
src/views/new-plan/class/lesson/lesson-table.vue

@@ -0,0 +1,353 @@
+<template>
+  <div id="lesson-table">
+    <el-card>
+      <template #header>
+        <el-row type="flex" align="middle" justify="space-between">
+          <el-col :span="4">课程安排</el-col>
+          <el-col :span="2">
+            <el-button type="primary" size="mini" @click="toSave">保存课表</el-button>
+          </el-col>
+        </el-row>
+      </template>
+      <el-table :data="lessonList" border stripe @cell-click="cellClick">
+        <el-table-column align="center" label="时间" prop="time"></el-table-column>
+        <el-table-column align="center" v-for="(i, index) in dateList" :key="index" :label="i" :prop="`subname_day${index + 1}`">
+          <template v-slot="{ row, $index }">
+            <el-row>
+              <el-col :span="24">{{ getProp(row, `subname_day${index + 1}`) }}</el-col>
+              <el-col class="teaname" :span="24" v-if="getProp(row, `teaname_day${index + 1}`)">{{ getProp(row, `teaname_day${index + 1}`) }}</el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-drawer :visible.sync="drawer" direction="rtl" title="课程安排" @close="toClose">
+      <data-form :data="form" :fields="fields" :rules="{}" @save="handleSave" :reset="false">
+        <template #radios="{item, form}">
+          <template v-if="item.model == 'type'">
+            <el-radio @change="radioClearForm" v-for="(i, index) in dayType" :key="index" :label="i.label">{{ i.label }}</el-radio>
+          </template>
+        </template>
+        <template #options="{item, form}">
+          <template v-if="item.model == 'subname'">
+            <el-option v-for="(i, index) in actList" :key="index" :label="i.label" :value="i.label"></el-option>
+          </template>
+          <template v-if="item.model == 'subid'">
+            <el-option v-for="(i, index) in subjectList" :key="index" :label="i.name" :value="i.id"></el-option>
+          </template>
+        </template>
+
+        <template #custom="{item, form}">
+          <template v-if="item.model == 'teaname'">
+            <el-input v-model="form.teaname" :readonly="true" placeholder="点击选择教师" @click.native="toChooseTeacher"></el-input>
+          </template>
+        </template>
+      </data-form>
+    </el-drawer>
+
+    <el-dialog title="选择教师" :visible.sync="dialog" :destroy-on-close="true">
+      <teacher-select :schoolList="schoolList" :subjectList="subjectList" :subjectid="form.subid" @selTea="selTea"> </teacher-select>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+var moment = require('moment');
+import _ from 'lodash';
+import dataForm from '@frame/components/form';
+import teacherSelect from './teacher.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: lesson } = createNamespacedHelpers('lesson');
+const { mapActions: mapUtil } = createNamespacedHelpers('util');
+const { mapActions: subject } = createNamespacedHelpers('subject');
+const { mapActions: teacher } = createNamespacedHelpers('teacher');
+const { mapActions: school } = createNamespacedHelpers('school'); //给选老师组件使用.这个页面请求完就不销毁了
+
+// 本页的组合数据,变量用x表示: _id_day[x];subname_day[x];subid_day[x];teaid_day[x];teaname_day:[x]
+export default {
+  name: 'lesson-table',
+  props: {
+    classInfo: { type: Object, default: () => {} },
+  },
+  components: { dataForm, teacherSelect },
+  data: function() {
+    var that = this;
+    return {
+      lessonInfo: {},
+      lessonList: [],
+      dateList: [],
+      timeList: [],
+      subjectList: [],
+      teacherList: [],
+      schoolList: [], //给选老师组件用
+      drawer: false,
+      dialog: false,
+      form: {},
+      fields: [
+        { label: '日期', model: 'date', type: 'text' },
+        { label: '时间', model: 'time', type: 'text' },
+        { label: '类型', model: 'type', type: 'radio' },
+        { label: '课程安排', model: 'subid', type: 'select', display: (fields, form) => that.fieldDisplay(fields, form) },
+        { label: '活动安排', model: 'subname', type: 'select', display: (fields, form) => that.fieldDisplay(fields, form) },
+        { label: '教师', model: 'teaname', custom: true, display: (fields, form) => that.fieldDisplay(fields, form) },
+      ],
+      dayType: [{ label: '活动' }, { label: '课程' }],
+      actList: [
+        { label: '--' },
+        { label: '报道+开班仪式' },
+        { label: '午餐+休息' },
+        { label: '晚餐' },
+        { label: '团队组建' },
+        { label: '拓展交流' },
+        { label: '课程作业小组展示' },
+        { label: '课程作业' },
+        { label: '礼仪课小组面试' },
+        { label: '结业仪式' },
+      ],
+    };
+  },
+  created() {
+    this.getOtherList();
+  },
+  methods: {
+    ...mapUtil(['fetch']),
+    ...lesson(['query', 'create', 'update']),
+    ...subject({ getSubject: 'query' }),
+    ...teacher({ getTeacher: 'query' }),
+    ...school({ getSchool: 'query' }),
+    async search() {
+      let res = await this.fetch({ model: 'lesson', classid: _.get(this.classInfo, '_id') });
+      if (this.$checkRes(res)) {
+        if (res.data.lessons.length <= 0) this.$message.warning('请先将本期的课程按模板进行初始化');
+        this.$set(this, `lessonInfo`, _.omit(res.data, ['lessons']));
+        let arr = _.get(res.data, `lessons`, []);
+        let x = this.getX(JSON.parse(JSON.stringify(arr)));
+        this.getY(JSON.parse(JSON.stringify(arr)));
+        this.$set(this, `dateList`, x);
+        arr = this.aData(arr);
+        this.$set(this, `lessonList`, arr);
+      }
+    },
+    async toSave() {
+      //整理成原数据形式,提交
+      let data = JSON.parse(JSON.stringify(this.lessonList));
+      data = this.returnData(data);
+      let lesson = JSON.parse(JSON.stringify(this.lessonInfo));
+      lesson.lessons = data;
+      let res = await this.update(lesson);
+      if (this.$checkRes(res, '课程表保存成功', res.errmsg || '课程表保存失败')) {
+        this.$router.push({ path: './index' });
+      }
+    },
+    //点击单元格事件
+    cellClick(row, column) {
+      let date = _.get(column, 'label');
+      let time = _.get(row, 'time');
+      let num = _.get(column, 'property').match(/\d+(.\d+)?/g)[0];
+      let obj = this.getOrderDate(row, num);
+      obj.type = obj.subid ? '课程' : '活动';
+      this.$set(this, `form`, { date, time, ...obj });
+      this.drawer = true;
+    },
+    //抽屉保存
+    handleSave({ data }) {
+      let num = _.get(data, 'index');
+      let type = _.get(data, `type`);
+      let time = _.get(data, `time`);
+      let yIndex = this.lessonList.findIndex(f => f.time == time);
+      let obj = {};
+      if (type == '课程') {
+        obj = _.pick(data, ['subid', 'teaid', 'teaname', '_id']);
+        let r = this.subjectList.find(f => f.id == obj.subid);
+        if (r) obj.subname = r.name;
+      } else {
+        obj = _.pick(data, ['subname', '_id']);
+      }
+      obj = this.resetData(obj, num);
+      this.$set(this.lessonList, yIndex, { ...this.lessonList[yIndex], ...obj });
+      this.drawer = false;
+    },
+    //提交整理数据
+    returnData(data) {
+      let returnArr = [];
+      data.map(i => {
+        let keys = Object.keys(i);
+        let time = _.get(i, `time`);
+        let arr = _.compact(_.uniq(_.flatten(keys.map(i => i.match(/\d+(.\d+)?/g)))));
+        arr.map(index => {
+          let obj = this.getOrderDate(i, index, true);
+          obj.time = time;
+          obj.day = '0';
+          returnArr.push(obj);
+        });
+      });
+      let r = returnArr.filter(f => f.date == _.last(this.dateList));
+      let allday = '0';
+      let res = r.find(f => {
+        //TODO 根据开始时间不超过12点判断是 整天还是半天
+        if (f.subname != '--') {
+          let ts = f.time.split('-');
+          let time = moment(`${f.date} ${ts[0]}`).format('X');
+          let twl = moment(`${f.date} 12:00`).format('X');
+          return twl <= time;
+        }
+      });
+      if (res) allday = '1';
+      returnArr = returnArr.map(i => {
+        if (i.date == _.last(this.dateList)) i.allday = allday;
+        else i.allday = '0';
+        return i;
+      });
+      return returnArr;
+    },
+    //field的显示
+    fieldDisplay(f, form) {
+      if (f.model == 'teaname' || f.model == 'subid') {
+        return form.type == '课程';
+      } else return form.type == '活动';
+    },
+    //请求后整理数据方法
+    aData(data) {
+      let duplicate = JSON.parse(JSON.stringify(data));
+      //按时间分组
+      duplicate = _.flatten(_.toPairs(_.groupBy(data, 'time'))).filter(f => _.isArray(f));
+      let r = duplicate.map(i => {
+        //按日期排序
+        let aa = i.sort((a, b) => moment(a.date).format('X') - moment(b.date).format('X'));
+        //组合数据:{time,day1,id_day1,subid_day1}
+        let object = { time: _.get(i[0], 'time') };
+        aa.map(a => {
+          let index = this.dateList.findIndex(f => f == a.date);
+          if (index >= 0) {
+            index = index + 1;
+          }
+          let obj = this.resetData(a, index);
+          object = { ...object, ...obj };
+        });
+        return object;
+      });
+      r = this.getOrderForTime(r);
+      return r;
+    },
+    //获取指定数据
+    getOrderDate(data, index, needDate = false) {
+      let obj = { index: index };
+      if (_.get(data, `_id_day${index}`)) obj[`_id`] = _.get(data, `_id_day${index}`);
+      obj[`subname`] = _.get(data, `subname_day${index}`, `--`);
+      if (_.get(data, `subid_day${index}`)) obj[`subid`] = _.get(data, `subid_day${index}`);
+      if (_.get(data, `teaid_day${index}`)) obj[`teaid`] = _.get(data, `teaid_day${index}`);
+      if (_.get(data, `teaname_day${index}`)) obj[`teaname`] = _.get(data, `teaname_day${index}`);
+      if (needDate) {
+        //所有的数据都还原了,没必要遥index了
+        delete obj.index;
+        obj.date = this.dateList[index - 1];
+      }
+      return obj;
+    },
+    //整理,匹配数据是哪天,该显示在哪
+    resetData(data, index) {
+      let obj = {};
+      if (_.get(data, '_id')) obj[`_id_day${index}`] = _.get(data, '_id');
+      obj[`subname_day${index}`] = _.get(data, 'subname', '--');
+      if (_.get(data, 'subid')) obj[`subid_day${index}`] = _.get(data, 'subid');
+      if (_.get(data, 'teaid')) obj[`teaid_day${index}`] = _.get(data, 'teaid');
+      if (_.get(data, 'teaname')) obj[`teaname_day${index}`] = _.get(data, 'teaname');
+      return obj;
+    },
+    //根据时间排序
+    getOrderForTime(data) {
+      let duplicate = JSON.parse(JSON.stringify(data));
+      duplicate = duplicate.sort((a, b) => {
+        let a_arr = a.time.split('-');
+        let b_arr = b.time.split('-');
+        let at = moment(`${moment().format('YYYY-MM-DD')} ${a_arr[0]}`).format('X');
+        let bt = moment(`${moment().format('YYYY-MM-DD')} ${b_arr[0]}`).format('X');
+        return at - bt;
+      });
+      return duplicate;
+    },
+    //整理出标头,根据日期排序
+    getX(data) {
+      let r = _.uniqBy(data, 'date').map(i => i.date);
+      r = r.sort((a, b) => moment(a).format('X') - moment(b).format('X'));
+      return r;
+    },
+    //获得时间列表
+    getY(data) {
+      let duplicate = JSON.parse(JSON.stringify(data));
+      let arr = _.uniqBy(
+        duplicate.map(i => _.pick(i, ['time'])),
+        'time'
+      );
+      arr = this.getOrderForTime(arr);
+      this.$set(
+        this,
+        `timeList`,
+        arr.map(i => i.time)
+      );
+    },
+    //教师列表,课程列表
+    async getOtherList() {
+      let res = await this.getSubject();
+      if (this.$checkRes(res)) this.$set(this, `subjectList`, res.data);
+      res = await this.getTeacher({ status: '4' });
+      if (this.$checkRes(res)) this.$set(this, `teacherList`, res.data);
+    },
+    //关闭抽屉
+    toClose() {
+      this.drawer = false;
+      this.form = {};
+    },
+    //修改类型清除数据
+    radioClearForm(data) {
+      if (data == '活动') {
+        delete this.form.subid;
+        this.form.subname = '--';
+      }
+    },
+    //打开选择教师的dialog
+    async toChooseTeacher() {
+      this.dialog = true;
+      if (this.schoolList.length <= 0) {
+        let res = await this.getSchool();
+        if (this.$checkRes(res)) this.$set(this, `schoolList`, res.data);
+      }
+    },
+    //选择教师
+    selTea(data) {
+      this.dialog = false;
+      this.$set(this, `form`, { ...this.form, ...data });
+    },
+    //显示
+    getProp(data, prop) {
+      return _.get(data, prop);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  watch: {
+    classInfo: {
+      handler(val) {
+        let id = _.get(val, '_id');
+        if (id) this.search();
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.teaname {
+  border-top: 1px dashed #cccccc;
+}
+</style>

+ 97 - 0
src/views/new-plan/class/lesson/teacher.vue

@@ -0,0 +1,97 @@
+<template>
+  <div id="teacher">
+    <el-tabs v-model="teaTab">
+      <el-tab-pane style="padding:10px" label="申请授课教师" name="apply">
+        <filter-table :data="applyList" :fields="teaFields" :opera="opera" @select="selectTeacher" :total="applyTotal" @query="toGetApplyList"></filter-table>
+      </el-tab-pane>
+      <el-tab-pane style="padding:10px" label="可授课教师" name="list">
+        <filter-table
+          :data="teacherList"
+          :fields="teaFields"
+          :opera="opera"
+          @select="selectTeacher"
+          :total="teacherTotal"
+          @query="toGetTeacherList"
+        ></filter-table>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import filterTable from '@frame/components/filter-page-table.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: teacher } = createNamespacedHelpers('teacher'); //教师
+const { mapActions: teaPlan } = createNamespacedHelpers('teaPlan'); //教师申请
+export default {
+  name: 'teacher',
+  props: {
+    schoolList: { type: Array, default: () => [] },
+    subjectList: { type: Array, default: () => [] },
+    subjectid: { type: String, default: () => '' },
+  },
+  components: { filterTable },
+  data: function() {
+    return {
+      teaTab: 'apply',
+      teaFields: [
+        { label: '姓名', prop: 'name', filter: 'input' },
+        { label: '学校', prop: 'schname' },
+        { label: '资料评分', prop: 'zlscore' },
+        { label: '面试评分', prop: 'msscore' },
+      ],
+      opera: [
+        {
+          label: '选择教师',
+          icon: 'el-icon-check',
+          method: 'select',
+        },
+      ],
+      applyList: [],
+      applyTotal: 0,
+      teacherList: [],
+      teacherTotal: 0,
+    };
+  },
+  created() {},
+  methods: {
+    ...teacher({ getTeacherList: 'query' }),
+    ...teaPlan({ getApplyTeacherList: 'applyQuery' }),
+    async toGetTeacherList({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.getTeacherList({ skip, limit, ...info, subid: this.subjectid, status: '4' });
+      this.$set(this, `teacherTotal`, res.total);
+      this.$set(this, `teacherList`, res.data);
+    },
+    async toGetApplyList({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.getApplyTeacherList({ skip, limit, ...info, subid: this.subjectid, status: '4' });
+      this.$set(this, `applyTotal`, res.total);
+      this.$set(this, `applyList`, res.data);
+    },
+    selectTeacher({ data }) {
+      this.$emit('selTea', { teaname: data.name, teaid: data._id });
+    },
+  },
+  watch: {
+    subjectid: {
+      handler(val, oval) {
+        if (val && val != oval) {
+          this.toGetApplyList();
+          this.toGetTeacherList();
+        }
+      },
+      immediate: true,
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 247 - 0
src/views/new-plan/class/name-list.vue

@@ -0,0 +1,247 @@
+<template>
+  <div id="name-list">
+    <list-frame :title="pageTitle" @query="getStudent" :total="total" :needAdd="false" :needFilter="false" returns="./index">
+      <el-card style="padding:10px">
+        <el-row>
+          <el-col :span="20">
+            <el-form :inline="true" size="mini">
+              <el-form-item label="期">
+                <el-select v-model="selectInfo.termid" placeholder="请选择期数" @change="getBatch">
+                  <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+                </el-select>
+              </el-form-item>
+              <el-form-item label="批次">
+                <el-select v-model="selectInfo.batchid" placeholder="请先选择期数" @change="getClasses">
+                  <el-option v-for="(i, index) in batchList" :key="index" :label="i.name" :value="i._id"></el-option>
+                </el-select>
+              </el-form-item>
+              <el-form-item label="班级">
+                <el-select v-model="selectInfo.classid" placeholder="请先选择批次" @change="getStudent()">
+                  <el-option v-for="(i, index) in classList" :key="index" :label="i.name" :value="i._id"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-form>
+          </el-col>
+          <el-col :span="2">
+            <el-button type="danger" plain @click="toDelete" size="mini" v-if="list.length > 0" :disabled="selected.length <= 0">
+              {{ selected.length > 0 ? `将选中的 ${selected.length}名学生 从班级移除` : '请选择要从班级移除的学生' }}
+            </el-button>
+          </el-col>
+        </el-row>
+        <data-table
+          :fields="fields"
+          :select="true"
+          :selected="selected"
+          @handleSelect="toSelect"
+          :data="list"
+          :opera="opera"
+          @turn="toTurn"
+          @turnBedroom="turnBed"
+        ></data-table>
+      </el-card>
+    </list-frame>
+    <el-dialog :visible.sync="dialog" :title="dtitle == 0 ? '转班' : '转寝'" @close="toClose" width="30%">
+      <data-form :data="form" :fields="dtitle == 0 ? turnFields : turnBedRoomFields" :rules="{}" @save="turnSave">
+        <template #options="{item,form}">
+          <template v-if="item.model == 'classid'">
+            <el-option v-for="(i, index) in allTermClass" :key="index" :label="`第${i.batch}批-${i.name}`" :value="i._id"></el-option>
+          </template>
+          <template v-if="item.model == 'bedroomid'">
+            <el-option v-for="(i, index) in bedroomList" :key="index" :label="`${i.floor}楼-${i.code}`" :value="i._id"></el-option>
+          </template>
+        </template>
+      </data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainPlan } = createNamespacedHelpers('trainplan');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: student } = createNamespacedHelpers('student');
+const { mapActions: bedroom } = createNamespacedHelpers('bedroom');
+export default {
+  name: 'name-list',
+  props: {},
+  components: { listFrame, dataTable, dataForm },
+  data: function() {
+    return {
+      dialog: false,
+      dtitle: 0,
+      form: {},
+      termList: [],
+      batchList: [],
+      classList: [],
+      allTermClass: [],
+      bedroomList: [],
+      list: [],
+      opera: [
+        {
+          label: '转班',
+          icon: 'el-icon-refresh',
+          method: 'turn',
+          display: i => !i.openid,
+        },
+        {
+          label: '转寝',
+          icon: 'el-icon-s-home',
+          method: 'turnBedroom',
+        },
+      ],
+      fields: [
+        { label: '姓名', prop: 'name' },
+        { label: '性别', prop: 'gender' },
+        { label: '民族', prop: 'nation' },
+        { label: '身份证号', prop: 'id_number' },
+        { label: '学校', prop: 'school_name' },
+        { label: '院系', prop: 'faculty' },
+        { label: '专业', prop: 'major' },
+        { label: '手机号', prop: 'phone' },
+        { label: '邮箱', prop: 'email' },
+      ],
+      turnFields: [
+        { label: '姓名', model: 'name', type: 'text' },
+        { label: '学校', model: 'school_name', type: 'text' },
+        { label: '班级', model: 'classid', type: 'select' },
+      ],
+      turnBedRoomFields: [
+        { label: '姓名', model: 'name', type: 'text' },
+        { label: '学校', model: 'school_name', type: 'text' },
+        { label: '寝室', model: 'bedroomid', type: 'select' },
+        { label: '原寝室', model: 'bedroom', type: 'text' },
+      ],
+      selectInfo: {},
+      total: 0,
+      selected: [],
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...bedroom({ getBedroom: 'query' }),
+    ...trainPlan(['fetch']),
+    ...classes({ getClassesList: 'query' }),
+    ...student({ getStudentList: 'query', updateStudent: 'update', removeClass: 'removeClass' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        let { termnum } = res.data;
+        this.$set(this, `termList`, termnum);
+      }
+    },
+    getBatch(termid) {
+      let batchs = this.termList.filter(f => f._id === termid);
+      if (batchs.length > 0) {
+        let { batchnum } = batchs[0];
+        this.$set(this, `batchList`, batchnum);
+      }
+    },
+    //根据批次id,查询下面的班级
+    async getClasses(data) {
+      const res = await this.getClassesList({ batchid: data });
+      if (this.$checkRes(res)) {
+        this.$set(this, `classList`, res.data);
+      }
+    },
+    //查询学生列表
+    async getStudent({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.getStudentList({ classid: this.selectInfo.classid, skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    //转班按钮
+    toTurn({ data }) {
+      let duplicate = _.cloneDeep(data);
+      let batch = this.batchList.find(f => f._id == data.batchid);
+      if (batch) duplicate.batch = batch.batch;
+      this.$set(this, `form`, duplicate);
+      this.dtitle = 0;
+      this.dialog = true;
+      this.getAllTermClass();
+    },
+    //转寝
+    async turnBed({ data }) {
+      if (this.bedroomList.length <= 0) {
+        let br = await this.getBedroom();
+        if (this.$checkRes(br)) this.$set(this, `bedroomList`, br.data);
+      }
+      let duplicate = _.cloneDeep(data);
+      this.$set(this, `form`, duplicate);
+      this.dtitle = 1;
+      this.dialog = true;
+    },
+    //转班/寝保存
+    async turnSave({ data }) {
+      if (this.dtitle == 1) {
+        let br = this.bedroomList.find(f => f.bedroomid == data.bedroomid);
+        if (br) data.bedroom = br.code;
+      }
+      let msg = this.dtitle == 0 ? '转班' : '转寝';
+      let res = await this.updateStudent(data);
+      if (this.$checkRes(res, `${msg}成功`, res.errmsg || `${msg}失败`)) {
+        this.getStudent();
+        this.toClose();
+      }
+    },
+    async toDelete() {
+      if (this.selected.length <= 0) {
+        this.$message.warning('您没有选择要移出该班的学生');
+        return;
+      }
+      this.$confirm('您确定要从该班移除这些学生吗?', '移出班级', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      }).then(async () => {
+        let ids = this.selected.map(i => i._id);
+        let res = await this.removeClass(ids);
+        if (this.$checkRes(res, '移除成功', res.errmsg || '移除失败')) {
+          this.selected = [];
+          this.getStudent();
+        }
+      });
+    },
+    async getAllTermClass() {
+      let res = await this.getClassesList({ termid: this.selectInfo.termid });
+      if (this.$checkRes(res)) {
+        let duplicate = _.cloneDeep(res.data);
+        duplicate = duplicate.map(i => {
+          let r = this.batchList.find(bf => bf._id == i.batchid);
+          if (r) i.batch = r.batch;
+          return i;
+        });
+        this.$set(this, `allTermClass`, duplicate);
+      }
+    },
+    toClose() {
+      this.dialog = false;
+      this.form = {};
+    },
+    toSelect(selecteds) {
+      this.$set(this, `selected`, selecteds);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 173 - 0
src/views/new-plan/class/setting.vue

@@ -0,0 +1,173 @@
+<template>
+  <div id="setting">
+    <list-frame v-if="view === 'list'" returns="./index" :title="pageTitle" :needFilter="false" :needAdd="false" :needPag="false">
+      <el-form :inline="true">
+        <el-form-item>
+          <el-select v-model="prerequisite.term" size="small" @change="toGetClass">
+            <el-option-group v-for="(term, index) in termList" :key="index" :label="`第${term.term}期`">
+              <el-option
+                v-for="(batch, bindex) in term.batchnum"
+                :key="`${index}-${bindex}`"
+                :label="batch.type == '0' ? `第${batch.batch}批` : `特殊批`"
+                :value="batch._id"
+              ></el-option>
+            </el-option-group>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <data-table ref="table" :fields="fields" :data="list" :opera="opera" @setting="toSetting"></data-table>
+    </list-frame>
+    <detail-frame v-else title="返回" :returns="deReturn">
+      <setting-detail
+        @save="toSave"
+        :classInfo="classInfo"
+        :locationList="locationList"
+        :lyTeacherList="lyTeacherList"
+        :headTeacherList="headTeacherList"
+      ></setting-detail>
+    </detail-frame>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import listFrame from '@frame/layout/admin/list-frame';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataTable from '@frame/components/data-table';
+import settingDetail from './setting/detail.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainPlan } = createNamespacedHelpers('trainplan');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: lesson } = createNamespacedHelpers('lesson');
+//setting-detail
+const { mapActions: location } = createNamespacedHelpers('location'); //地点
+const { mapActions: teacher } = createNamespacedHelpers('teacher'); //教师
+const { mapActions: dept } = createNamespacedHelpers('dept'); //配合教师表使用的部门表
+const { mapActions: dirPlan } = createNamespacedHelpers('dirPlan'); //班主任不能上课的列表
+const { mapActions: teaplan } = createNamespacedHelpers('teaPlan');
+const { mapActions: mapDept } = createNamespacedHelpers('dept');
+
+export default {
+  name: 'setting',
+  props: {},
+  components: { listFrame, detailFrame, dataTable, settingDetail },
+  data: () => {
+    return {
+      view: 'list',
+      termList: [],
+      batchList: [],
+      list: [],
+      prerequisite: {},
+      classInfo: {},
+      opera: [
+        {
+          label: '设置',
+          icon: 'el-icon-setting',
+          method: 'setting',
+        },
+      ],
+      fields: [
+        { label: '班级', prop: 'name' },
+        // { label: '批数', prop: 'batch' },
+      ],
+      //setting-detail list:
+      locationList: [],
+      lyTeacherList: [],
+      headTeacherList: [],
+      deptList: [],
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...trainPlan({ getTrainPlan: 'fetch', sendNotice: 'notice' }),
+    ...classes({ getClass: 'query', updateClass: 'update' }),
+    ...lesson({ autoArrange: 'arrange' }),
+    //setting-detail
+    ...location({ getLocationList: 'query' }),
+    ...teacher({ getTeacherList: 'query' }),
+    ...dirPlan({ dirQuery: 'getDirTeacher' }),
+    ...mapDept({ getDept: 'query' }),
+    ...teaplan(['findTeacher']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.getTrainPlan(this.id);
+      if (this.$checkRes(res)) {
+        this.$set(this, `termList`, _.get(res.data, 'termnum', []));
+      }
+    },
+    async toSetting({ data }) {
+      this.view = 'class';
+      this.getSettingLists(data);
+      this.$set(this, `classInfo`, JSON.parse(JSON.stringify(data)));
+    },
+    async toGetClass(data) {
+      let res = await this.getClass({ batchid: data });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+    deReturn() {
+      this.view = 'list';
+    },
+    async getSettingLists(data) {
+      let res;
+      if (this.locationList.length <= 0) {
+        res = await this.getLocationList();
+        if (this.$checkRes(res)) this.$set(this, `locationList`, res.data);
+      }
+      if (this.lyTeacherList.length <= 0) {
+        res = await this.getTeacherList({ islyteacher: '1', status: '4' });
+        if (this.$checkRes(res)) this.$set(this, `lyTeacherList`, res.data);
+      }
+      if (this.headTeacherList.length <= 0) {
+        res = await this.findTeacher({ planid: data.planid, termid: data.termid, batchid: data.batchid });
+        let duplicate = _.cloneDeep(res.data);
+        if (this.$checkRes(res)) {
+          if (this.deptList.length <= 0) {
+            let dept = await this.getDept();
+            if (this.$checkRes(res)) this.$set(this, `deptList`, dept.data);
+          }
+          //班主任按部门分组
+          let group = _.groupBy(res.data, 'department');
+          let keys = Object.keys(group);
+          let arr = keys.map(key => {
+            let r = this.deptList.find(f => f.id == key);
+            let obj = {};
+            if (r) {
+              obj.name = r.name;
+              obj.list = group[key];
+            }
+            return obj;
+          });
+          this.$set(this, `headTeacherList`, arr);
+          //班主任筛选可以当礼仪老师列表,和 礼仪教师列表合并
+          duplicate = duplicate.filter(f => f.islyteacher == '1');
+          this.$set(this, `lyTeacherList`, [...this.lyTeacherList, ...duplicate]);
+        }
+      }
+    },
+    async toSave(data) {
+      console.log(data);
+      let res = await this.updateClass(data);
+      if (this.$checkRes(res, '保存成功', res.errmsg || '保存失败')) {
+        this.$router.push({ path: './index' });
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 78 - 0
src/views/new-plan/class/setting/detail.vue

@@ -0,0 +1,78 @@
+<template>
+  <div id="detail">
+    <data-form :data="form" :fields="fields" :rules="{}" @save="handleSave" :reset="false">
+      <template #options="{item, form}">
+        <template v-if="item.model == 'headteacherid'">
+          <el-option-group v-for="(dept, index) in headTeacherList" :label="dept.name" :key="index">
+            <el-option v-for="(i, tIndex) in dept.list" :key="`${index}-${tIndex}`" :label="i.name" :value="i._id"></el-option>
+          </el-option-group>
+        </template>
+        <template v-if="item.model == 'lyteacherid'">
+          <el-option v-for="(tea, index) in lyTeacherList" :key="`${item.model}${index}`" :label="tea.name" :value="tea.id"></el-option>
+        </template>
+        <template v-if="item.model == 'jslocationid'">
+          <el-option v-for="(place, index) in locationList" :key="`${item.model}${index}`" :label="place.name" :value="place.id"></el-option>
+        </template>
+        <template v-if="item.model == 'kbyslocationid'">
+          <el-option v-for="(place, index) in locationList" :key="`${item.model}${index}`" :label="place.name" :value="place.id"></el-option>
+        </template>
+        <template v-if="item.model == 'kzjhlocationid'">
+          <el-option v-for="(place, index) in locationList" :key="`${item.model}${index}`" :label="place.name" :value="place.id"></el-option>
+        </template>
+        <template v-if="item.model == 'yclocationid'">
+          <el-option v-for="(place, index) in locationList" :key="`${item.model}${index}`" :label="place.name" :value="place.id"></el-option>
+        </template>
+      </template>
+    </data-form>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: classes } = createNamespacedHelpers('classes');
+export default {
+  name: 'detail',
+  props: {
+    classInfo: { type: Object, default: () => {} },
+    locationList: { type: Array, default: () => [] },
+    lyTeacherList: { type: Array, default: () => [] },
+    headTeacherList: { type: Array, default: () => [] },
+  },
+  components: { dataForm },
+  data: function() {
+    var that = this;
+    return {
+      form: _.cloneDeep(that.classInfo),
+      fields: [
+        { label: '', model: 'name', type: 'text' },
+        { label: '人数', model: 'number', type: 'text' },
+        { label: '班主任', model: 'headteacherid', type: 'select' },
+        { label: '礼仪课教师', model: 'lyteacherid', type: 'select' },
+        { label: '教室地点', model: 'jslocationid', type: 'select' },
+        { label: '开班地点', model: 'kbyslocationid', type: 'select' },
+        { label: '拓展训练地点', model: 'kzjhlocationid', type: 'select' },
+        { label: '用餐地点', model: 'yclocationid', type: 'select' },
+      ],
+    };
+  },
+  created() {},
+  methods: {
+    handleSave({ data }) {
+      this.$emit('save', data);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 226 - 0
src/views/new-plan/index.vue

@@ -0,0 +1,226 @@
+<template>
+  <div id="index">
+    <list-frame :title="pageTitle" @query="search" :total="total" :needFilter="false" @add="toAdd" :returns="returns">
+      <data-table :fields="fields" :data="list" :opera="opera" @set="toSet" @edit="toEdit" @delete="toDelete" @date="date"></data-table>
+    </list-frame>
+    <el-dialog title="年度信息" width="30%" :visible.sync="dialog" center :destroy-on-close="true" @close="handleClose" :reset="false">
+      <data-form :data="info" :fields="Ffields" :rules="rules" @save="handleSave" :isNew="!info.id" :styles="{ padding: 0 }" labelWidth="60px">
+        <template #custom="{item,form}">
+          <template v-if="item.model == 'festivals'">
+            <data-table :fields="festFields" :data="form[item.model]" :opera="formOpera" @edit="toFestEdit" @delete="toFestDelete"></data-table>
+          </template>
+        </template>
+        <template #radios="{item,form}">
+          <el-radio label="0">筹备中</el-radio>
+          <el-radio label="1">发布</el-radio>
+          <el-radio label="2">结束</el-radio>
+        </template>
+      </data-form>
+    </el-dialog>
+    <el-drawer :title="form.name" :visible.sync="drawer" direction="rtl" @close="handleFestClose">
+      <data-form :data="form" :fields="festFields" :rules="{}" @save="handleFestSave" :isNew="!info.id" :styles="{ padding: 0 }"> </data-form>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+var moment = require('moment');
+import _ from 'lodash';
+import Vue from 'vue';
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
+const { mapActions: other } = createNamespacedHelpers('other');
+const { mapActions: setting } = createNamespacedHelpers('setting');
+export default {
+  name: 'index',
+  props: {},
+  components: { listFrame, dataTable, dataForm },
+  data: () => {
+    return {
+      dialog: false,
+      drawer: false,
+      opera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '计划安排',
+          icon: 'el-icon-date',
+          display: i => i.status == 1,
+          method: 'date',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+        },
+        {
+          label: '设置默认年度计划',
+          icon: 'el-icon-setting',
+          methodZh: '您确认设置该年度计划为默认查看?',
+          confirm: true,
+          method: 'set',
+        },
+      ],
+      formOpera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+        },
+      ],
+      fields: [
+        { label: '年度', prop: 'year' },
+        { label: '标题', prop: 'title' },
+        { label: '状态', prop: 'status', format: i => (i === '0' ? '筹备中' : i === '1' ? '进行中' : '已结束') },
+      ],
+      festFields: [
+        { label: '节假日', prop: 'name', model: 'name', options: { readonly: true } },
+        { label: '开始时间', prop: 'begindate', model: 'begindate', type: 'date' },
+        { label: '结束时间', prop: 'finishdate', model: 'finishdate', type: 'date' },
+      ],
+      info: {},
+      form: {},
+      Ffields: [
+        { label: '年度', model: 'year' },
+        { label: '标题', model: 'title' },
+        { label: '假期', model: 'festivals', custom: true },
+        { label: '状态', model: 'status', type: 'radio' },
+      ],
+      rules: {},
+      list: [],
+      holiday: [],
+      total: 0,
+    };
+  },
+  created() {
+    this.getOtherList();
+    this.search();
+  },
+  methods: {
+    ...other(['calendar']),
+    ...trainplan(['query', 'create', 'delete', 'update']),
+    ...setting({ sFetch: 'fetch', sUpdate: 'update' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, ...info, planyearid: this.id });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    toAdd() {
+      this.$set(this.info, `festivals`, JSON.parse(JSON.stringify(this.holiday)));
+      this.dialog = true;
+    },
+    toEdit({ data }) {
+      this.$set(this, `info`, data);
+      this.dialog = true;
+    },
+    async toDelete({ data }) {
+      const res = await this.delete(data.id);
+      this.$checkRes(res, '删除成功', '删除失败');
+      this.search();
+    },
+    async handleSave({ data, isNew }) {
+      let res;
+      let msg;
+      if (isNew) {
+        //create
+        data.planyearid = this.id;
+        res = await this.create(data);
+        msg = '添加成功';
+      } else {
+        //update
+        res = await this.update(data);
+        msg = '修改成功';
+      }
+      if (this.$checkRes(res, msg, res.errmsg || '操作失败')) {
+        this.search();
+        this.handleClose();
+      }
+    },
+    handleClose() {
+      this.dialog = false;
+      this.info = {};
+    },
+    handleFestClose() {
+      this.drawer = false;
+      this.form = {};
+    },
+    toFestEdit({ data, index }) {
+      let nd = JSON.parse(JSON.stringify(data));
+      nd.index = index;
+      this.$set(this, 'form', nd);
+      this.drawer = true;
+    },
+    toFestDelete({ data, index }) {
+      this.info.festivals.splice(index, 1);
+    },
+    handleFestSave({ data }) {
+      let index = data.index;
+      let obj = _.omit(data, ['index']);
+      this.$set(this.info.festivals, index, obj);
+      this.drawer = false;
+    },
+    async getOtherList() {
+      let holiday = sessionStorage.getItem('holiday');
+      if (!holiday) {
+        let year = new Date().getFullYear();
+        let key = Vue.config.jhAppKey;
+        let res = await this.calendar({ key, year });
+        if (res) {
+          let plan = res.holiday_list;
+          plan.map(i => {
+            i.begindate = moment(i.startday).format('YYYY-MM-DD');
+            delete i.startday;
+            return i;
+          });
+          this.$set(this, `holiday`, plan);
+        }
+        sessionStorage.setItem('holiday', JSON.stringify(res.holiday_list));
+      } else this.$set(this, 'holiday', JSON.parse(holiday));
+    },
+    date({ data }) {
+      this.$router.push({ path: './arrange', query: { id: data.id } });
+    },
+    async toSet({ data }) {
+      let planid = _.get(data, 'id');
+      let duplicate = _.cloneDeep(this.defaultOption);
+      duplicate.planyearid = this.id;
+      duplicate.planid = planid;
+      let res = await this.sUpdate(duplicate);
+      if (this.$checkRes(res, '设置成功', res.errmsg)) {
+        this.$router.push({ path: '/' });
+        // this.search();
+      }
+    },
+    returns() {
+      window.history.go(-1);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 130 - 0
src/views/new-plan/parts/event.vue

@@ -0,0 +1,130 @@
+<template>
+  <div id="event">
+    <el-form :model="form" ref="form" :rules="formRules" label-width="80px" size="small" @submit.native.prevent style="padding: 15px;">
+      <el-form-item label="开始时间" prop="start" required>
+        <el-date-picker readonly v-model="form.start" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd"> </el-date-picker>
+      </el-form-item>
+      <el-form-item label="结束时间" prop="end" required>
+        <el-date-picker readonly v-model="form.end" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd"> </el-date-picker>
+      </el-form-item>
+      <el-form-item label="期数" prop="term" required> <el-input v-model="form.term"></el-input> </el-form-item>
+      <el-form-item label="班级类型" prop="type" required>
+        <el-radio-group v-model="form.type">
+          <el-radio label="0">正常班级</el-radio>
+          <el-radio label="1">特殊班级</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="批次" prop="batch" v-if="form.type === '0'"> <el-input v-model="form.batch"></el-input> </el-form-item>
+      <el-form-item label="班级数量" prop="class" v-if="form.type === '0'"> <el-input v-model="form.class"></el-input> </el-form-item>
+      <el-form-item label="每班人数" prop="number" required> <el-input v-model="form.number"></el-input> </el-form-item>
+      <el-form-item label="班级名称" prop="name" v-if="form.type === '1'"> <el-input v-model="form.name"></el-input> </el-form-item>
+      <el-form-item label="颜色" prop="color">
+        <el-color-picker v-model="form.color" :predefine="predefineColors" size="mini"></el-color-picker>
+      </el-form-item>
+      <el-form-item>
+        <el-row type="flex" align="middle" justify="space-around">
+          <el-col :span="6">
+            <el-button type="primary" @click="saveForm">保存</el-button>
+          </el-col>
+          <el-col :span="6">
+            <el-button @click="resetForm">重置</el-button>
+          </el-col>
+          <el-col :span="6">
+            <el-button @click="toDelete" type="danger">删除</el-button>
+          </el-col>
+        </el-row>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+export default {
+  name: 'event',
+  props: {
+    data: { type: Object, default: () => {} }, //数据
+    isNew: { type: Boolean, default: true }, //是不是修改
+    predefineColors: { type: Array, default: () => [] }, //颜色列表
+    year: { type: null, default: new Date().getFullYear() },
+    vacation: { type: Array, default: () => [] },
+  },
+  components: {},
+  data() {
+    return {
+      form: { color: '#409EFF' },
+      formRules: {
+        start: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
+        end: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
+        term: [{ required: true, message: '请输入期数' }],
+        number: [{ required: true, message: '请输入每班人数' }],
+        type: [{ required: true, message: '请选择班级类型' }],
+      },
+      pickerOptions: {
+        disabledDate: time => this.setDisabledDate(time),
+      },
+    };
+  },
+  created() {},
+  methods: {
+    //保存表单函数
+    saveForm() {
+      this.$refs['form'].validate(valid => {
+        if (valid) {
+          let data = JSON.parse(JSON.stringify(this.form));
+          this.resetForm();
+          if (data.type == '1') data.class = `1`;
+          this.$emit('save', { isNew: this.isNew, data });
+        } else {
+          console.warn('form validate error!!!');
+        }
+      });
+    },
+    //重置表单函数
+    resetForm() {
+      this.$refs.form.resetFields();
+    },
+    //删除
+    toDelete() {
+      this.$emit('delete', { index: this.form.index, data: this.form });
+    },
+    //禁用时间
+    setDisabledDate(time) {
+      let thisTime = time.getTime();
+      let start = new Date(`${this.year}-01-01`).getTime();
+      let end = new Date(`${this.year}-12-31`).getTime();
+      if (thisTime < start) return true;
+      else if (thisTime > end) return true;
+      else {
+        //循环假期列表,判断这个时间是不是在假期时间外:
+        // 此刻=假期开始时间||此刻=假期结束时间 return true(不能选)
+        // 此刻<假期开始时间 => return false(继续判断下个假期)
+        // 此刻>假期开始时间 => 此刻<假期结束时间 ? 在假期中,return true(不允许选择): return false(继续判断下个假期)
+        let res = false;
+        for (const vac of this.vacation) {
+          let vacS = new Date(vac.start).setDate(new Date(vac.start).getDate() - 1); //减一天匹配日历
+          let vacE = new Date(vac.end).setDate(new Date(vac.end).getDate() - 1);
+          if (thisTime > vacS) {
+            if (thisTime < vacE) {
+              res = true;
+              break;
+            }
+          }
+        }
+        return res;
+      }
+    },
+  },
+  watch: {
+    data: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (val) this.$set(this, `form`, _.cloneDeep(this.data));
+      },
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 116 - 0
src/views/new-plan/template.vue

@@ -0,0 +1,116 @@
+<template>
+  <div id="templates">
+    <detail-frame :title="pageTitle">
+      <data-form :data="info" :fields="fields" :rules="rules" @save="handleSave" :isNew="isNew" labelWidth="165px" :reset="false">
+        <template #custom="{item}">
+          <template v-if="item.model == 'color'">
+            <el-row type="flex" align="middle">
+              <el-col :span="1">
+                <el-color-picker v-model="color" :predefine="info.color" @change="toChange" color-format="hex"></el-color-picker>
+                <!-- <el-button type="primary" @click="toClear" size="mini">重置默认颜色</el-button> -->
+              </el-col>
+            </el-row>
+          </template>
+        </template>
+      </data-form>
+    </detail-frame>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainTemplate } = createNamespacedHelpers('trainTemplate');
+const { mapActions: util } = createNamespacedHelpers('util');
+export default {
+  name: 'templates',
+  props: {},
+  components: { detailFrame, dataForm },
+  data: () => {
+    return {
+      info: { color: [] },
+      color: '',
+      isNew: true,
+      fields: [
+        { label: '总人数', required: true, model: 'total', type: 'number' },
+        { label: '每批所需天数', required: true, model: 'day', type: 'number' },
+        { label: '默认每期中的批次数', required: true, model: 'batchnum', type: 'number' },
+        { label: '默认每批次中的班级数', required: true, model: 'classnum', type: 'number' },
+        { label: '默认每班中的人数', required: true, model: 'stunum', type: 'number' },
+        { label: '默认每车人数', required: true, model: 'carpnum', type: 'number' },
+        { label: '默认颜色', model: 'color', custom: true },
+      ],
+      rules: {
+        total: [{ required: true, message: '请输入总人数' }],
+        day: [{ required: true, message: '请输入每批所需天数' }],
+        batchnum: [{ required: true, message: '请输入默认每期中的批次数' }],
+        classnum: [{ required: true, message: '请输入默认每批次中的班级数' }],
+        stunum: [{ required: true, message: '请输入默认每班中的人数' }],
+        carpnum: [{ required: true, message: '请输入默认每车人数' }],
+      },
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...util({ modelFetch: 'fetch' }),
+    ...trainTemplate(['query', 'create', 'update']),
+    async search() {
+      let planyearid = _.get(this.defaultOption, 'planyearid');
+      let planid = _.get(this.defaultOption, 'planid');
+      let res = await this.modelFetch({ model: 'trainmodel', planyearid, planid });
+      // let res = await this.query();
+      if (this.$checkRes(res)) {
+        this.$set(this, `info`, res.data ? res.data : {});
+        this.$set(this, `isNew`, res.data == null);
+      }
+    },
+    async handleSave({ isNew, data }) {
+      let res;
+      if (isNew) {
+        let planyearid = _.get(this.defaultOption, 'planyearid');
+        let planid = _.get(this.defaultOption, 'planid');
+        res = await this.create({ ...data, planyearid, planid });
+      } else {
+        res = await this.update(data);
+      }
+      this.$checkRes(res, '保存成功', res.errmsg || '保存失败');
+    },
+    toChange(data) {
+      if (!data) {
+        this.toClear();
+        return;
+      }
+      let colors = _.get(this.info, 'color', []);
+      if (colors && colors.length >= 8) {
+        this.$message.error('只能选择8个预存颜色');
+        return;
+      }
+      let res = colors.find(f => f == data);
+      if (res) {
+        this.$message.error('已选择当前颜色');
+        return;
+      }
+      colors.push(data);
+      this.$set(this.info, `color`, colors);
+    },
+    toClear() {
+      this.info.color = [];
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 2 - 0
src/views/plan/detail.vue

@@ -213,6 +213,7 @@ export default {
         object.batchnum = this.selectList
           .filter(fil => fil.term === i)
           .map(b => {
+            console.log(b);
             b = _.pickBy(b, (val, key) => key !== 'term');
             b.startdate = JSON.parse(JSON.stringify(b.start));
             b.enddate = JSON.parse(JSON.stringify(b.end));
@@ -235,6 +236,7 @@ export default {
         return object;
       });
       data.festivals = vacation;
+      console.log(data);
       let res;
       let msg;
       if (this.isNew) {

+ 0 - 2
src/views/plan/lesson-plan.vue

@@ -396,9 +396,7 @@ export default {
       this.$emit(`save`, tcc);
     },
     toPreview(id) {
-      console.log(id);
       let tcc = this.classList.find(f => f.classid === id);
-      console.log(this.classList);
       let numArr = tcc.name.match(/\d+/g);
       let term = numArr[0];
       let classes = numArr[2];

+ 18 - 1
src/views/questionnaire/detail.vue

@@ -8,10 +8,24 @@
         <el-form-item label="标题" required prop="name">
           <el-input v-model="info.name"></el-input>
         </el-form-item>
+        <el-form-item label="问卷类型" required prop="type">
+          <el-radio-group v-model="info.type">
+            <el-radio label="0">常用问卷</el-radio>
+            <el-radio label="1">非常用问卷</el-radio>
+            <el-radio label="2">教师问卷</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="状态" required prop="type">
+          <el-radio-group v-model="info.status">
+            <el-radio label="0" v-if="isNew == true">草稿</el-radio>
+            <el-radio label="1">发布</el-radio>
+            <el-radio label="2">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
         <el-form-item label="题目" prop="question">
           <el-row>
             <el-col :span="24" style="text-align:right;padding-bottom:10px">
-              <el-button type="primary" size="mini" @click="addQuestion()">添加题目</el-button>
+              <el-button type="primary" size="mini" @click="addQuestion()" v-if="isNew == true">添加题目</el-button>
             </el-col>
             <el-col :span="24">
               <el-table :data="info.question" border stripe size="mini">
@@ -93,6 +107,7 @@ export default {
     rules: {
       num: [{ required: true, message: '请输入序号' }],
       name: [{ required: true, message: '请输入标题' }],
+      type: [{ required: true, message: '请选择问卷类型' }],
       // question: [{ required: true, message: '请选择题目' }],
     },
     qList: [], //题库
@@ -141,6 +156,8 @@ export default {
     async search({ skip = 0, limit = 10, ...info } = {}) {
       const res = await this.fetch(this.id);
       if (this.$checkRes(res)) {
+        console.log(res.data);
+
         this.$set(this, `info`, res.data);
         //需要将code中的id转换为题目重新放进列表中
         //需要过滤出已选择的题目,每次选择题目时也需要过滤

+ 2 - 0
src/views/questionnaire/index.vue

@@ -35,6 +35,7 @@ export default {
     fields: [
       { label: '问卷序号', prop: 'num' },
       { label: '问卷标题', prop: 'name' },
+      { label: '问卷类型', prop: 'type', format: i => (i == '0' ? '常用问卷' : i == '1' ? '非常用问题' : '教师问卷') },
     ],
     filFields: [
       { label: '问卷名', model: 'name' },
@@ -52,6 +53,7 @@ export default {
     ...questionnaire(['query', 'delete']),
     async search({ skip = 0, limit = 10, ...info } = {}) {
       const res = await this.query({ skip, limit, ...info });
+      console.log(res.data);
       if (this.$checkRes(res)) {
         this.$set(this, `list`, res.data);
         this.$set(this, `total`, res.total);

+ 1 - 1
src/views/register/login.vue

@@ -1,6 +1,6 @@
 <template>
   <div id="login">
-    <login-detail></login-detail>
+    <login-detail title="培训会-中心端登录"></login-detail>
   </div>
 </template>
 

+ 101 - 0
src/views/school/detail.vue

@@ -0,0 +1,101 @@
+<template>
+  <div id="detail">
+    <detail-frame :title="pageTitle" returns="./index">
+      <data-form :data="info" :fields="fields" :rules="rules" @save="handleSave" :isNew="isNew">
+        <template #radios="{item}">
+          <template v-if="item.model === 'level'">
+            <el-radio label="本科">本科</el-radio>
+            <el-radio label="专科">专科</el-radio>
+          </template>
+          <template v-if="item.model === 'hascar'">
+            <el-radio label="0">否</el-radio>
+            <el-radio label="1">是</el-radio>
+          </template>
+        </template>
+      </data-form>
+    </detail-frame>
+  </div>
+</template>
+
+<script>
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: school } = createNamespacedHelpers('school');
+export default {
+  name: 'detail',
+  props: {},
+  components: { detailFrame, dataForm },
+  data: () => {
+    return {
+      info: {},
+      fields: [
+        { label: '学校名称', required: true, model: 'name' },
+        { label: '学校代码', required: true, model: 'code' },
+        { label: '学校地点', required: true, model: 'address' },
+        { label: '学校简称', required: true, model: 'shortname' },
+        { label: '每期人数', required: true, model: 'number', type: 'number' },
+        { label: '高校层次', required: true, model: 'level', type: 'radio' },
+        { label: '是否派车', required: true, model: 'hascar', type: 'radio' },
+      ],
+      rules: {
+        name: [{ required: true, message: '请输入学校名称' }],
+        code: [{ required: true, message: '请输入学校代码' }],
+        address: [{ required: true, message: '请选择学校地点' }],
+        shortname: [{ required: true, message: '请输入学校简称' }],
+        number: [{ required: true, message: '请输入每期人数' }],
+        level: [{ required: true, message: '请选择高校层次' }],
+        hascar: [{ required: true, message: '请选择是否派车' }],
+      },
+    };
+  },
+  created() {},
+  methods: {
+    ...school(['fetch', 'update']),
+    async search() {
+      const res = await this.fetch(this.id);
+      if (this.$checkRes(res)) this.$set(this, `info`, res.data);
+      this.loading = false;
+    },
+    async handleSave({ isNew, data }) {
+      let res;
+      let msg;
+      if (!data.hascar) data.hascar = '0';
+      if (isNew) {
+        res = await this.create(data);
+        msg = `添加成功`;
+      } else {
+        res = await this.update(data);
+        msg = `修改成功`;
+      }
+      if (this.$checkRes(res, msg)) this.$router.push({ path: './index' });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    id() {
+      return this.$route.query.id;
+    },
+    isNew() {
+      return this.$route.query.id ? false : true;
+    },
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  watch: {
+    isNew: {
+      immediate: true,
+      handler(val) {
+        if (val) this.loading = false;
+        else this.search();
+      },
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 79 - 0
src/views/school/index.vue

@@ -0,0 +1,79 @@
+<template>
+  <div id="index">
+    <list-frame :title="pageTitle" @query="search" :total="total" :needFilter="false" @add="$router.push({ path: './detail' })">
+      <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete"></data-table>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: school } = createNamespacedHelpers('school');
+export default {
+  name: 'index',
+  props: {},
+  components: { listFrame, dataTable },
+  data: () => {
+    return {
+      opera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+          confirm: true,
+        },
+      ],
+      fields: [
+        { label: '学校名称', prop: 'name' },
+        { label: '学校代码', prop: 'code' },
+        { label: '学校地点', prop: 'address' },
+        { label: '学校简称', prop: 'shortname' },
+        { label: '每期人数', prop: 'number' },
+      ],
+      list: [],
+      total: 0,
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...school(['query', 'delete']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+
+    toEdit({ data }) {
+      this.$router.push({ path: './detail', query: { id: data.id } });
+    },
+
+    async toDelete({ data }) {
+      const res = await this.delete(data.id);
+      this.$checkRes(res, '删除成功', '删除失败');
+      this.search();
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 70 - 0
src/views/setting/system-setting.vue

@@ -0,0 +1,70 @@
+<template>
+  <div id="system-setting">
+    <detail-frame :title="pageTitle">
+      <data-form :data="form" :fields="fields" :rules="rules" @save="handleSave" labelWidth="150px" :reset="false"> </data-form>
+    </detail-frame>
+  </div>
+</template>
+
+<script>
+import detailFrame from '@frame/layout/admin/detail-frame';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: setting } = createNamespacedHelpers('setting');
+
+export default {
+  name: 'system-setting',
+  props: {},
+  components: { dataForm, detailFrame },
+  data: function() {
+    return {
+      form: {},
+      fields: [
+        { label: '服务器邮箱', required: true, model: 'user_email' },
+        { label: '服务器邮箱授权码', required: true, model: 'auth_code' },
+        { label: '上午考勤开始时间', required: true, model: 'am_start', type: 'time' },
+        { label: '上午考勤结束时间', required: true, model: 'am_end', type: 'time' },
+        { label: '下午开始时间', required: true, model: 'pm_start', type: 'time' },
+        { label: '下午结束时间', required: true, model: 'pm_end', type: 'time' },
+        { label: '寝室开始时间', required: true, model: 'bd_start', type: 'time' },
+        { label: '寝室结束时间', required: true, model: 'bd_end', type: 'time' },
+      ],
+      rules: {
+        email: [{ required: true, message: '请输入系统邮箱' }],
+        emailpw: [{ required: true, message: '请输入系统邮箱密码' }],
+        am_start: [{ required: true, message: '请选择上午考勤开始时间' }],
+        am_end: [{ required: true, message: '请选择上午考勤结束时间' }],
+        pm_start: [{ required: true, message: '请选择下午开始时间' }],
+        pm_end: [{ required: true, message: '请选择下午结束时间' }],
+        bd_start: [{ required: true, message: '请选择寝室开始时间' }],
+        bd_end: [{ required: true, message: '请选择寝室结束时间' }],
+      },
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...setting(['fetch', 'update']),
+    async search() {
+      let res = await this.fetch();
+      if (this.$checkRes(res)) this.$set(this, `form`, res.data);
+    },
+    async handleSave({ data }) {
+      let res = await this.update(data);
+      this.$checkRes(res, `修改成功`, res.errmsg);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 103 - 0
src/views/statistics/charts/line-bar.vue

@@ -0,0 +1,103 @@
+<template>
+  <div id="line-bar">
+    <div id="chartLinebar" class="" style="height:350px;"></div>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/line';
+import 'echarts/lib/chart/bar';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'line-bar',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      mychart: null,
+    };
+  },
+  created() {
+    this.$nextTick(() => {
+      this.init();
+    });
+  },
+  methods: {
+    init() {
+      let data = [];
+      for (let index = 0; index < 4; index++) {
+        data.push(_.round(_.random(50, true)));
+      }
+      let data2 = [];
+      for (let index = 0; index < 4; index++) {
+        data2.push(_.round(_.random(50, true)));
+      }
+      const option = {
+        title: { text: '满意度调查' },
+        tooltip: { trigger: 'axis' },
+        legend: {
+          data: ['满意度调查'],
+        },
+        xAxis: {
+          boundaryGap: true,
+          nameLocation: 'center',
+          data: ['满意', '一般', '强差人意', '不满意'],
+        },
+        yAxis: {},
+        series: [
+          {
+            name: '满意度',
+            step: true,
+            label: {
+              show: true,
+              position: 'top',
+            },
+            type: 'line',
+            data: data,
+            color: ['#333ceb'],
+          },
+          {
+            name: '满意度',
+            label: {
+              show: true,
+              position: 'top',
+            },
+            type: 'line',
+            data: data2,
+            color: ['#acaecce'],
+          },
+        ],
+        toolbox: {
+          show: true,
+          feature: {
+            dataView: { readOnly: false },
+            saveAsImage: {},
+            restore: {},
+            magicType: { type: ['bar'] },
+          },
+        },
+      };
+      this.mychart = echarts.init(document.getElementById('chartLinebar'));
+      this.mychart.setOption(option);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 97 - 0
src/views/statistics/charts/pie.vue

@@ -0,0 +1,97 @@
+<template>
+  <div id="pie">
+    <el-row>
+      <el-col :span="24" style="padding:10px">
+        <el-radio-group v-model="myPieRadio" @change="init('reload')">
+          <el-radio label="year">按年统计</el-radio>
+          <el-radio label="term">按期统计</el-radio>
+          <el-radio label="class">按班统计</el-radio>
+        </el-radio-group>
+      </el-col>
+      <el-col :span="24">
+        <div id="chartPie" class="" style="height:350px;"></div>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'pie',
+  props: {},
+  components: {},
+  data: function() {
+    return {
+      myPie: null,
+      myPieRadio: 'year',
+    };
+  },
+  created() {
+    this.$nextTick(() => {
+      this.init();
+    });
+  },
+  methods: {
+    init(type) {
+      if (type) this.myPie.dispose();
+      let data = [];
+      for (let index = 0; index < 4; index++) {
+        data.push(_.round(_.random(50, true)));
+      }
+      this.myPie = echarts.init(document.getElementById('chartPie'));
+      const option = {
+        title: { text: '满意度调查' },
+        tooltip: { trigger: 'item', formatter: '{a} <br/>{b} : {c} ({d}%)' },
+        legend: {
+          data: ['满意', '一般', '强差人意', '不满意'],
+        },
+        series: [
+          {
+            name: '统计',
+            label: {
+              show: true,
+              position: 'top',
+            },
+            type: 'pie',
+            data: [
+              { name: '满意', value: data[0] || 0, itemStyle: { color: '#7cb5ec' } },
+              { name: '一般', value: data[1] || 0, itemStyle: { color: '#ffa94b' } },
+              { name: '强差人意', value: data[2] || 0, itemStyle: { color: '#346da4' } },
+              { name: '不满意', value: data[3] || 0, itemStyle: { color: '#abcdef' } },
+            ],
+            animationType: 'scale',
+          },
+        ],
+        toolbox: {
+          show: true,
+          feature: {
+            dataView: { readOnly: false },
+            saveAsImage: {},
+          },
+        },
+      };
+      this.myPie.setOption(option);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 57 - 0
src/views/statistics/detail.vue

@@ -0,0 +1,57 @@
+<template>
+  <div id="detail">
+    <detail-frame :title="pageTitle" returns="./index">
+      <el-card>
+        <el-row type="flex" align="middle" justify="center" :gutter="20">
+          <el-col :span="10">
+            <pie></pie>
+          </el-col>
+          <el-col :span="10">
+            <line-bar></line-bar>
+          </el-col>
+        </el-row>
+      </el-card>
+    </detail-frame>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import pie from './charts/pie.vue';
+import lineBar from './charts/line-bar.vue';
+import echarts from 'echarts/lib/echarts';
+import 'echarts/lib/chart/pie';
+import 'echarts/lib/component/title';
+import 'echarts/lib/component/legend';
+import 'echarts/lib/component/toolbox';
+import 'echarts/lib/component/markPoint';
+import 'echarts/lib/component/tooltip';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'detail',
+  props: {},
+  components: { detailFrame, pie, lineBar },
+  data: function() {
+    return {
+      mylb: null,
+    };
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    id() {
+      return this.$route.query.id;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 68 - 0
src/views/statistics/index.vue

@@ -0,0 +1,68 @@
+<template>
+  <div id="index">
+    <list-frame title="问卷列表页" @query="search" :total="total" :filter="filFields" :needAdd="false">
+      <data-table :fields="fields" :data="list" :opera="opera" @data="toData"></data-table>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { createNamespacedHelpers } from 'vuex';
+const { mapActions: questionnaire } = createNamespacedHelpers('questionnaire');
+
+export default {
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+    dataTable,
+  },
+  data: () => ({
+    opera: [
+      {
+        label: '查看结果',
+        icon: 'el-icon-s-data',
+        method: 'data',
+      },
+    ],
+    fields: [
+      { label: '问卷序号', prop: 'num' },
+      { label: '问卷标题', prop: 'name' },
+      { label: '问卷类型', prop: 'type', format: i => (i == '0' ? '常用问卷' : i == '1' ? '非常用问题' : '教师问卷') },
+    ],
+    filFields: [
+      { label: '问卷名', model: 'name' },
+      { label: '问卷序号', model: 'id' },
+    ],
+    list: [],
+    total: 0,
+  }),
+
+  created() {
+    this.search();
+  },
+  computed: {},
+  methods: {
+    ...questionnaire(['query', 'delete']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    toData({ data }) {
+      this.$router.push({ path: './detail', query: { id: data.id } });
+    },
+    async toDelete({ data }) {
+      const res = await this.delete(data.id);
+      this.$checkRes(res, '删除成功', '删除失败');
+      this.search();
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 110 - 36
src/views/student/index.vue

@@ -1,16 +1,48 @@
 <template>
   <div id="index">
     <list-frame title="学生管理" @query="search" :total="total" :needFilter="false" @add="$router.push({ path: '/student/detail' })">
-      <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" @delete="toDelete"></data-table>
+      <el-form :inline="true" size="mini">
+        <el-form-item label="期">
+          <el-select v-model="form.termid" placeholder="请选择期数" @change="getBatch">
+            <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="批次">
+          <el-select v-model="form.batchid" placeholder="请先选择期数" @change="getClasses">
+            <el-option v-for="(i, index) in batchList" :key="index" :label="i.name" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="班级">
+          <el-select v-model="form.classid" placeholder="请先选择批次">
+            <el-option v-for="(i, index) in classList" :key="index" :label="i.name" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="search">查询</el-button>
+        </el-form-item>
+      </el-form>
+      <data-table :fields="fields" :data="list" :opera="opera" @turnClass="toTurnClass" @edit="toEdit" @delete="toDelete"></data-table>
     </list-frame>
+    <el-dialog :visible.sync="dialog" title="转班" @close="toClose" width="30%">
+      <data-form :data="studInfo" :fields="turnFields" :rules="{}" @save="turnSave">
+        <template #options="{item,form}">
+          <template v-if="item.model == 'classid'">
+            <el-option v-for="(i, index) in selectClassList" :key="index" :label="`第${i.batch}批-${i.name}`" :value="i._id"></el-option>
+          </template>
+        </template>
+      </data-form>
+    </el-dialog>
   </div>
 </template>
 
 <script>
 import listFrame from '@frame/layout/admin/list-frame';
+import dataForm from '@frame/components/form';
 import dataTable from '@frame/components/data-table';
-import { createNamespacedHelpers } from 'vuex';
+import { mapState, createNamespacedHelpers } from 'vuex';
 const { mapActions } = createNamespacedHelpers('student');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
 
 export default {
   metaInfo: { title: '学生管理' },
@@ -19,14 +51,21 @@ export default {
   components: {
     listFrame,
     dataTable,
+    dataForm,
   },
   data: () => ({
+    dialog: false,
     opera: [
       {
         label: '编辑',
         icon: 'el-icon-edit',
         method: 'edit',
       },
+      {
+        label: '转班',
+        icon: 'el-icon-refresh',
+        method: 'turnClass',
+      },
       {
         label: '删除',
         icon: 'el-icon-delete',
@@ -39,53 +78,39 @@ export default {
       { label: '性别', prop: 'gender' },
       { label: '民族', prop: 'nation' },
       { label: '身份证号', prop: 'id_number' },
+      { label: '期', prop: 'termname' },
+      { label: '批次', prop: 'batchname' },
+      { label: '班级', prop: 'classname' },
       { label: '学校', prop: 'school_name' },
       { label: '院系', prop: 'faculty' },
       { label: '专业', prop: 'major' },
-      { label: '入学年份', prop: 'entry_year' },
-      { label: '毕业年份', prop: 'finish_year' },
-      { label: '在校曾担任何种职务', prop: 'school_job' },
       { label: '手机号', prop: 'phone' },
-      { label: 'QQ号', prop: 'qq' },
       { label: '邮箱', prop: 'email' },
-      { label: '家庭所在地', prop: 'family_place' },
-      {
-        label: '家庭是否困难',
-        prop: 'family_is_hard',
-        format: item => {
-          return item === '1' ? '是' : '否';
-        },
-      },
-      {
-        label: '是否获得过助学金',
-        prop: 'have_grant',
-        format: item => {
-          return item === '1' ? '是' : '否';
-        },
-      },
-      { label: '职务', prop: 'job' },
-      { label: '期', prop: 'term' },
-      { label: '批次', prop: 'batch' },
-      { label: '班级', prop: 'class' },
-      {
-        label: '是否优秀',
-        prop: 'is_fine',
-        format: item => {
-          return item === '1' ? '是' : '否';
-        },
-      },
     ],
+    turnFields: [
+      { label: '姓名', model: 'name', type: 'text' },
+      { label: '学校', model: 'school_name', type: 'text' },
+      { label: '班级', model: 'classid', type: 'select' },
+    ],
+    form: {},
+    studInfo: {},
     list: [],
+    classList: [],
+    batchList: [],
+    termList: [],
     total: 0,
+    selectClassList: [],
   }),
   created() {
-    this.search();
+    this.getPlan();
   },
-  computed: {},
+  computed: { ...mapState(['user', 'defaultOption']) },
   methods: {
-    ...mapActions(['query', 'delete']),
+    ...trainplan({ planfetch: 'fetch' }),
+    ...mapActions(['query', 'delete', 'update']),
+    ...classes({ classesquery: 'query' }),
     async search({ skip = 0, limit = 10, ...info } = {}) {
-      const res = await this.query({ skip, limit, ...info });
+      const res = await this.query({ skip, limit, termid: this.form.termid, classid: this.form.classid });
       if (this.$checkRes(res)) {
         this.$set(this, `list`, res.data);
         this.$set(this, `total`, res.total);
@@ -99,6 +124,55 @@ export default {
       this.$checkRes(res, '删除成功', '删除失败');
       this.search();
     },
+    async getPlan() {
+      const res = await this.planfetch(this.defaultOption.planid);
+      let terms = res.data.termnum;
+      this.$set(this, `termList`, terms);
+      if (this.defaultOption.termid) {
+        this.form.termid = this.defaultOption.termid;
+        this.getBatch(this.form.termid);
+      }
+    },
+    getBatch(termid) {
+      let batchs = this.termList.filter(f => f._id === termid);
+      if (batchs.length > 0) {
+        let { batchnum } = batchs[0];
+        this.$set(this, `batchList`, batchnum);
+      }
+    },
+    async getClasses(batchid) {
+      const res = await this.classesquery({ batchid });
+      this.$set(this, `classList`, res.data);
+    },
+    async toTurnClass({ data }) {
+      let { termid } = data;
+      this.$set(this, `studInfo`, data);
+      let res = await this.classesquery({ termid });
+      if (this.$checkRes(res)) this.$set(this, `selectClassList`, res.data);
+      this.dialog = true;
+    },
+    //转班保存
+    async turnSave({ data }) {
+      let msg = '转班';
+      let res = await this.update(data);
+      if (this.$checkRes(res, `${msg}成功`, res.errmsg || `${msg}失败`)) {
+        this.search();
+        this.toClose();
+      }
+    },
+    toClose() {
+      this.dialog = false;
+      this.form = {};
+    },
+  },
+  watch: {
+    defaultOption: {
+      handler(val) {
+        this.form.termid = this.defaultOption.termid;
+        this.getBatch(this.form.termid);
+      },
+      deep: true,
+    },
   },
 };
 </script>

+ 17 - 1
src/views/subject/detail.vue

@@ -1,7 +1,16 @@
 <template>
   <div id="detail">
     <detail-frame :title="mainTitle" returns="/subject/index">
-      <data-form :data="info" :fields="fields" :rules="rules" @save="handleSave" :isNew="isNew"> </data-form>
+      <data-form :data="info" :fields="fields" :rules="rules" @save="handleSave" :isNew="isNew">
+        <template #radios="{item}">
+          <template v-if="item.model === 'type'">
+            <el-radio label="0">
+              普通班
+            </el-radio>
+            <el-radio label="1">特殊班</el-radio>
+          </template>
+        </template>
+      </data-form>
     </detail-frame>
   </div>
 </template>
@@ -24,10 +33,12 @@ export default {
     fields: [
       { label: '科目名称', required: true, model: 'name' },
       { label: '名称代码', required: true, model: 'code' },
+      { label: '班级类型', required: true, model: 'type', type: `radio` },
     ],
     rules: {
       name: [{ required: true, message: '请输入科目代码' }],
       code: [{ required: true, message: '请输入科目名称' }],
+      type: [{ required: true, message: '请选择班级类型' }],
     },
   }),
   created() {},
@@ -71,9 +82,14 @@ export default {
       let msg;
       if (isNew) {
         res = await this.create(data);
+        console.log(res);
+
         msg = `${this.keyWord}添加成功`;
       } else {
+        console.log(data);
+
         res = await this.update(data);
+
         msg = `${this.keyWord}修改成功`;
       }
       if (this.$checkRes(res, msg)) this.$router.push({ path: '/subject/index' });

+ 1 - 0
src/views/subject/index.vue

@@ -35,6 +35,7 @@ export default {
     fields: [
       { label: '科目名称', prop: 'name' },
       { label: '科目代码', prop: 'code' },
+      { label: '类型', prop: 'type', format: i => (i === '0' ? '普通班' : '特殊班') },
     ],
     filFields: [
       { label: '科目名称', model: 'name' },

+ 0 - 1
src/views/teacher/detail.vue

@@ -36,7 +36,6 @@
 <script>
 import detailFrame from '@frame/layout/admin/detail-frame';
 import dataForm from '@frame/components/form';
-import upload from '@frame/components/upload';
 import { createNamespacedHelpers } from 'vuex';
 const { mapActions: teacher } = createNamespacedHelpers('teacher');
 const { mapActions: subject } = createNamespacedHelpers('subject');

+ 49 - 50
src/views/test/detail.vue

@@ -1,67 +1,66 @@
 <template>
-  <div id="index">
-    <el-row style="padding:10px;height:100vh">
-      <el-col :span="24" class="icon">
-        <el-image :src="question"></el-image>
-      </el-col>
-      <el-col :span="24" style="height:10vh;text-align:center">
-        您确定将您的微信号与本平台账号绑定吗?
-      </el-col>
-      <el-col :span="24" style="height:4rem">
-        <van-button type="info" style="width:100%" round @click="toUpdate()">确定</van-button>
-      </el-col>
-      <el-col :span="24">
-        <van-button plain style="width:100%" round @click="cancel()">取消</van-button>
-      </el-col>
-    </el-row>
+  <div id="detail">
+    <div ref="cal"></div>
   </div>
 </template>
 
 <script>
-import { createNamespacedHelpers } from 'vuex';
-const { mapActions } = createNamespacedHelpers('doctor');
+import { Calendar } from '@fullcalendar/core';
+import '@fullcalendar/core/main.css';
+import '@fullcalendar/daygrid/main.css';
+import interactionPlugin from '@fullcalendar/interaction';
+import dayGridPlugin from '@fullcalendar/daygrid';
+import { mapState, createNamespacedHelpers } from 'vuex';
 export default {
-  metaInfo: { title: '用户绑定' },
-  name: 'index',
+  name: 'detail',
   props: {},
   components: {},
-  data: () => ({
-    question: '',
-  }),
+  data: function() {
+    return {};
+  },
   created() {},
-  computed: {
-    openid() {
-      return this.$route.query.openid;
-    },
-    doctorid() {
-      return this.$route.query.doctorid;
-    },
+  mounted() {
+    this.init();
   },
   methods: {
-    // ...mapActions(['bind']),
-    async toUpdate() {
-      const res = await this.bind({ id: this.doctorid, openid: this.openid });
-      if (res.errcode === 0) {
-        this.$router.push({ path: '/', query: { openid: this.openid } });
-        this.$toast({ type: 'success', message: '绑定成功' });
-      } else {
-        this.$toast({ type: 'fail', message: res.errmsg });
-      }
+    init() {
+      let cal = this.$refs.cal;
+      var calendar = new Calendar(cal, {
+        plugins: [dayGridPlugin, interactionPlugin],
+        locale: 'zh-cn',
+        height: 700,
+        contentHeight: 600,
+        headers: {
+          left: 'prev',
+          center: 'title',
+          right: 'dayGridMonth,dayGridWeek, today ,next',
+        },
+        buttonText: {
+          today: '今天',
+          dayGridMonth: '月视图',
+          dayGridWeek: '周视图',
+          listMonth: '月列表',
+        },
+        selectable: true,
+        displayEventEnd: true,
+        displayEventTime: true,
+        editable: false,
+        droppable: false,
+        weekNumberCalculation: 'ISO',
+      });
+      calendar.render();
     },
-    cancel() {
-      window.history.go(-1);
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
     },
   },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
 };
 </script>
 
-<style lang="less" scoped>
-.icon {
-  height: 30vh;
-  text-align: center;
-}
-.icon .el-image {
-  width: 100px;
-  margin: 40px 0;
-}
-</style>
+<style lang="less" scoped></style>

+ 124 - 0
src/views/train-batch/index.vue

@@ -0,0 +1,124 @@
+<template>
+  <div id="index">
+    <listFrame :title="pageTitle">
+      <el-card v-if="view == 'list'">
+        <el-row type="flex" align="middle" justify="end">
+          <el-col :span="2">
+            <el-button type="primary" size="mini" @click="toAdd" icon="el-icon-plus">添加</el-button>
+          </el-col>
+        </el-row>
+        <data-table :fields="fields" :data="list" :opera="opera" :total="total" @edit="toEdit" @query="search" @delete="toDelete" @plan="toPlan"></data-table>
+      </el-card>
+      <el-card v-else>
+        <template #header>
+          <el-button icon="el-icon-arrow-left" type="text" @click="toBack">返回</el-button>
+        </template>
+        <data-form :data="form" :fields="formFields" :rules="rules" @save="handleSave"> </data-form>
+      </el-card>
+    </listFrame>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import listFrame from '@frame/layout/admin/detail-frame';
+import dataForm from '@frame/components/form';
+import dataTable from '@frame/components/filter-page-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainBatch } = createNamespacedHelpers('trainBatch');
+export default {
+  name: 'index',
+  props: {},
+  components: { dataTable, listFrame, dataForm },
+  data: function() {
+    return {
+      view: 'list',
+      list: [],
+      opera: [
+        {
+          label: '修改',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+        {
+          label: '删除',
+          icon: 'el-icon-delete',
+          method: 'delete',
+          confirm: true,
+        },
+        {
+          label: '年度计划管理',
+          icon: 'el-icon-date',
+          method: 'plan',
+        },
+      ],
+      fields: [
+        { label: '标题', prop: 'title' },
+        { label: '年份', prop: 'year' },
+      ],
+      formFields: [
+        { label: '标题', model: 'title', required: true },
+        { label: '年份', model: 'year', required: true },
+        { label: '备注', model: 'remark', required: true, type: 'textarea' },
+      ],
+      rules: {},
+      total: 0,
+      form: {},
+    };
+  },
+  created() {
+    this.search();
+  },
+  methods: {
+    ...trainBatch(['query', 'create', 'fetch', 'update', 'delete']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.query({ skip, limit, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+    },
+    toAdd() {
+      this.view = 'detail';
+    },
+    toEdit({ data }) {
+      this.$set(this, `form`, _.cloneDeep(data));
+      this.toAdd();
+    },
+    toBack() {
+      this.view = 'list';
+      this.form = {};
+    },
+    async handleSave({ data, isNew }) {
+      let duplicate = _.cloneDeep(data);
+      let res, msg;
+      if (!duplicate.id) (res = await this.create(data)), (msg = '添加');
+      else (res = await this.update(data)), (msg = '修改');
+      if (this.$checkRes(res, `${msg}成功`, res.errmsg)) this.toBack();
+      this.search();
+    },
+    async toDelete({ data }) {
+      let res = await this.delete(data.id);
+      if (this.$checkRes(res, `删除成功`, res.errmsg)) this.search();
+    },
+    toPlan({ data }) {
+      this.$router.push({ path: '/plan/index', query: { id: data.id } });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.el-row {
+  margin: 15px 0;
+}
+</style>

+ 131 - 0
src/views/train-plan/attendance.vue

@@ -0,0 +1,131 @@
+<template>
+  <div id="index">
+    <list-frame title="考勤管理" @query="onsearch" :total="total" :needFilter="false" :needAdd="false">
+      <el-form :inline="true" size="mini">
+        <el-form-item label="期">
+          <el-select v-model="form.termid" placeholder="请选择期数" @change="getBatch">
+            <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="批次">
+          <el-select v-model="form.batchid" placeholder="请先选择期数" @change="getClasses">
+            <el-option v-for="(i, index) in batchList" :key="index" :label="i.name" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="班级">
+          <el-select v-model="form.classid" placeholder="请先选择批次">
+            <el-option v-for="(i, index) in classList" :key="index" :label="i.name" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="">
+          <el-button type="primary" size="mini" @click="onsearch()"> 查询</el-button>
+        </el-form-item>
+      </el-form>
+      <data-table :fields="fields" :data="list"> </data-table>
+    </list-frame>
+  </div>
+</template>
+<script>
+import _ from 'lodash';
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: attendance } = createNamespacedHelpers('attendance');
+const { mapActions: student } = createNamespacedHelpers('student');
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+
+export default {
+  metaInfo: { title: '考勤管理' },
+  name: 'attendance',
+  props: {},
+  components: {
+    listFrame,
+    dataTable,
+  },
+  data: () => ({
+    classList: [],
+    batchList: [],
+    termList: [],
+    fields: [
+      { label: '学生姓名', prop: 'stuname' },
+      { label: '日期', prop: 'date' },
+      { label: '时间', prop: 'time' },
+
+      {
+        label: '类型',
+        prop: 'type',
+        format: item => {
+          return item === '0' ? '上课考勤' : item === '1' ? '上课考勤' : '';
+        },
+      },
+      {
+        label: '状态',
+        prop: 'status',
+        format: item => {
+          return item === '0' ? '未签到' : item === '1' ? '签到' : '迟到';
+        },
+      },
+    ],
+    form: {},
+    list: [],
+    total: 0,
+  }),
+  created() {
+    this.searchinfo();
+  },
+  computed: { ...mapState(['user', 'defaultOption']) },
+  methods: {
+    ...attendance(['query', 'delete']),
+    ...student({ stuquery: 'query' }),
+    ...trainplan({ planfetch: 'fetch' }),
+    ...classes({ classesquery: 'query' }),
+    async searchinfo() {
+      const res = await this.planfetch(this.defaultOption.planid);
+      let terms = res.data.termnum;
+      this.$set(this, `termList`, terms);
+      if (this.defaultOption.termid) {
+        this.form.termid = this.defaultOption.termid;
+        this.getBatch(this.form.termid);
+      }
+    },
+    getBatch(termid) {
+      let batchs = this.termList.filter(f => f._id === termid);
+      if (batchs.length > 0) {
+        let { batchnum } = batchs[0];
+        this.$set(this, `batchList`, batchnum);
+      }
+    },
+    async getClasses(batchid) {
+      const res = await this.classesquery({ batchid });
+      this.$set(this, `classList`, res.data);
+    },
+    async onsearch({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.query({ termid: this.form.termid, classid: this.form.classid, ...info });
+      const newdatas = [];
+      for (const data of res.data) {
+        for (const attend of data.attend) {
+          let newdata = { stuname: data.stuname, ...attend };
+          newdatas.push(newdata);
+        }
+      }
+      if (this.$checkRes(res)) {
+        this.$set(this, `total`, newdatas.length);
+        let _newdatas = newdatas.splice(skip, skip + limit);
+        this.$set(this, `list`, _newdatas);
+      }
+    },
+  },
+  watch: {
+    defaultOption: {
+      handler(val) {
+        this.form.termid = this.defaultOption.termid;
+        this.getBatch(this.form.termid);
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 189 - 0
src/views/train-plan/bedroom.vue

@@ -0,0 +1,189 @@
+<template>
+  <div id="bedroom">
+    <detail-frame :title="pageTitle" v-show="view == 'list'">
+      <el-alert type="warning" title="请确认好学生已经报道后再进行分寝" center :closable="false" class="btn_bar"></el-alert>
+      <el-row type="flex" align="middle" justify="end" class="btn_bar">
+        <el-col :span="2">
+          <el-button type="primary" size="mini" @click="toApart">一键分寝</el-button>
+        </el-col>
+      </el-row>
+      <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" :usePage="false"></data-table>
+    </detail-frame>
+    <detail-frame :title="classInfo.name" :returns="toReturns" v-if="view != 'list'">
+      <el-table :data="stuBedroom" size="mini" border stripe>
+        <el-table-column align="center" label="寝室号" prop="bedroom"></el-table-column>
+        <el-table-column align="center" label="学生">
+          <template v-slot="{ row }">
+            <el-row>
+              <el-col v-for="(i, index) in row.list" :key="index" :span="6">
+                <el-link @click="turnBedroom(i)">{{ i.name }}</el-link>
+              </el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+    </detail-frame>
+    <el-dialog :visible.sync="dialog" title="修改寝室" @close="toClose" width="30%">
+      <data-form :data="form" :fields="turnBedRoomFields" :rules="{}" @save="turnSave">
+        <template #options="{item,form}">
+          <template v-if="item.model == 'bedroom'">
+            <template v-for="(i, index) in bedroomList">
+              <el-option :key="index" :label="`${i.code}`" :value="i.code"></el-option>
+            </template>
+          </template>
+        </template>
+      </data-form>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import axios from 'axios';
+import dataForm from '@frame/components/form';
+import dataTable from '@frame/components/filter-page-table';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: student } = createNamespacedHelpers('student');
+const { mapActions: bedroom } = createNamespacedHelpers('bedroom');
+export default {
+  name: 'bedroom',
+  props: {},
+  components: { detailFrame, dataTable, dataForm },
+  data: function() {
+    return {
+      view: 'list',
+      dialog: false,
+      form: {},
+      list: [],
+      stuBedroom: [],
+      bedroomList: [],
+      classInfo: {},
+      opera: [
+        {
+          label: '查看寝室',
+          icon: 'el-icon-view',
+          method: 'edit',
+        },
+      ],
+      fields: [
+        { label: '期', prop: 'term' },
+        { label: '批', prop: 'batch' },
+        { label: '班级', prop: 'name' },
+      ],
+      turnBedRoomFields: [
+        { label: '姓名', model: 'name', type: 'text' },
+        { label: '学校', model: 'school_name', type: 'text' },
+        { label: '寝室', model: 'bedroom', type: 'select' },
+      ],
+      options: undefined,
+    };
+  },
+  created() {},
+  methods: {
+    ...classes(['query']),
+    ...student({ getStudentList: 'query', updateStudent: 'update' }),
+    ...bedroom({ bedroomApart: 'apart', getBedroomList: 'query' }),
+    async search() {
+      let termid = _.get(this.defaultOption, 'termid');
+      if (!termid) return;
+      let res = await this.query({ termid });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+    toEdit({ data }) {
+      this.$set(this, `classInfo`, data);
+      this.view = 'class';
+      this.getSL();
+    },
+    async getSL() {
+      let res = await this.getStudentList({ classid: this.classInfo._id });
+      if (this.$checkRes(res)) {
+        let duplicate = _.cloneDeep(res.data);
+        let mid = _.groupBy(duplicate, 'bedroom');
+        let keys = Object.keys(mid);
+        let arr = [];
+        for (const key of keys) {
+          let o = {};
+          o['bedroom'] = !key || key == 'undefined' ? '未分寝' : key;
+          o['list'] = mid[key];
+          arr.push(o);
+        }
+        this.$set(this, `stuBedroom`, arr);
+      }
+    },
+    async toApart() {
+      let { planid: trainplanid, termid } = this.options;
+      let batchList = _.uniq(this.list.map(i => i.batchid));
+      let axiosArr = [];
+      batchList.map(batchid => {
+        axiosArr.push(this.bedroomApart({ trainplanid, termid, batchid }));
+      });
+      axios.all(axiosArr).then(
+        axios.spread((...res) => {
+          let r = res.every(e => e && e.errcode == '0');
+          this.$message({
+            type: r ? 'success' : 'error',
+            message: r ? '分寝成功' : '分寝失败',
+          });
+        })
+      );
+    },
+    toReturns() {
+      this.view = 'list';
+      this.$set(this, `classInfo`, {});
+      this.$set(this, `stuBedroom`, []);
+    },
+    async turnBedroom(data) {
+      this.$set(this, `form`, data);
+      let res = await this.getBedroomList({ status: '0', batch: this.classInfo.batch });
+      if (this.$checkRes(res)) this.$set(this, `bedroomList`, res.data);
+      this.dialog = true;
+    },
+    //转寝保存
+    async turnSave({ data }) {
+      let res = await this.updateStudent(data);
+      if (this.$checkRes(res, `转寝成功`, res.errmsg || `转寝失败`)) {
+        this.getSL();
+        this.toClose();
+      }
+    },
+    toClose() {
+      this.dialog = false;
+      this.form = {};
+    },
+  },
+  watch: {
+    defaultOption: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (!_.get(this, 'options')) {
+          this.$set(this, `options`, _.cloneDeep(val));
+          this.search();
+        } else {
+          let ntermid = _.get(val, 'termid');
+          let otermid = _.get(this.options, 'termid');
+          if (ntermid && !_.isEqual(ntermid, otermid)) {
+            this.$set(this, `options`, _.cloneDeep(val));
+            this.search();
+          }
+        }
+      },
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 249 - 0
src/views/train-plan/classes.vue

@@ -0,0 +1,249 @@
+<template>
+  <div id="plan">
+    <!-- 根据计划,获取所有期数及期数下批次,然后提供选择期数;查询出该期数下学校上报的学生,选择学生手动分班  :filter="filterFields"-->
+    <list-frame :title="pageTitle" @query="stuSearch" :total="total" :needAdd="false" :needFilter="false" returns="./index">
+      <el-card style="padding:10px">
+        <el-row>
+          <el-form :inline="true" size="mini">
+            <el-form-item>
+              <el-button type="primary" @click="toAutoSetClass">一键分班</el-button>
+            </el-form-item>
+          </el-form>
+          <el-form :inline="true" size="mini">
+            <el-form-item label="批次">
+              <el-select v-model="selectInfo.batchid" placeholder="请先选择期数" @change="getClasses">
+                <el-option v-for="(i, index) in batchList" :key="index" :label="i.name" :value="i._id"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="班级">
+              <el-select v-model="selectInfo.classid" placeholder="请先选择批次" @change="getLimit">
+                <el-option v-for="(i, index) in classList" :key="index" :label="i.name" :value="i._id"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="toSetClass">确认分班</el-button>
+            </el-form-item>
+          </el-form>
+        </el-row>
+        <el-row type="flex" justify="space-around" :gutter="10" align="middle" style="padding:10px 0;">
+          <el-col :span="7">班级需求:{{ selectInfo.personReq }}人</el-col>
+          <el-col :span="7">已选择学生:{{ selected.length }}人</el-col>
+          <el-col :span="7">男性:{{ selectInfo.male }}人</el-col>
+          <el-col :span="7">女性:{{ selectInfo.female }}人</el-col>
+        </el-row>
+
+        <data-table
+          ref="table"
+          :fields="fields"
+          :data="list"
+          :opera="opera"
+          :select="true"
+          :selected="selected"
+          @edit="toEdit"
+          @delete="toDelete"
+          @handleSelect="toSelect"
+        ></data-table>
+      </el-card>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import _ from 'lodash';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainPlan } = createNamespacedHelpers('trainplan');
+const { mapActions: schPlan } = createNamespacedHelpers('schPlan');
+const { mapActions: student } = createNamespacedHelpers('student');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: director } = createNamespacedHelpers('director');
+const { mapActions: dept } = createNamespacedHelpers('dept');
+export default {
+  metaInfo: { title: '安排班级' },
+  name: 'plan',
+  props: {},
+  components: { listFrame, dataTable },
+  data: () => ({
+    opera: [],
+    fields: [
+      { label: '学生姓名', prop: 'name' },
+      { label: '学校', prop: 'school_name' },
+      { label: '性别', prop: 'gender' },
+    ],
+    filterFields: [], //{ label: '期数', model: 'termid', type: 'select' }
+    list: [],
+    selected: [],
+    selectedTest: [],
+    total: 0,
+    selectInfo: {
+      male: 0,
+      female: 0,
+      personReq: 0,
+    },
+    termList: [],
+    batchList: [],
+    classList: [],
+    directorList: [],
+    deptList: [],
+    options: undefined,
+  }),
+  created() {},
+  methods: {
+    ...trainPlan(['fetch']),
+    ...schPlan({ schQuery: 'query' }),
+    ...student({ getStudentList: 'noClass' }), //noClass
+    ...classes({ getClassesList: 'query', createClass: 'divide', addStudent: 'addStudent' }),
+    ...director({ getDirectorList: 'query' }),
+    ...dept({ getDeptList: 'query' }),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.fetch(this.id);
+      if (this.$checkRes(res)) {
+        let { termnum } = res.data;
+        this.$set(this, `termList`, termnum);
+        let termid = _.get(this.options, `termid`);
+        if (termid) {
+          this.getBatch(termid);
+          this.stuSearch();
+        }
+      }
+    },
+    //查询选择期上报的学生,提供批次选择
+    async stuSearch({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.getStudentList({ termid: this.options.termid, skip, limit });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+        this.$set(this, `total`, res.total);
+      }
+      if (skip !== 0) return;
+    },
+    getBatch() {
+      let termid = _.get(this.options, 'termid');
+      if (!termid) return;
+      let batchs = this.termList.find(f => f._id === termid);
+      if (batchs) {
+        let { batchnum } = batchs;
+        this.$set(this, `batchList`, batchnum);
+      }
+    },
+    //根据批次id,查询下面的班级
+    async getClasses(data) {
+      const res = await this.getClassesList({ batchid: data });
+      if (this.$checkRes(res)) {
+        this.$set(this, `classList`, res.data);
+      }
+    },
+    toEdit({ data }) {
+      this.$router.push({ path: '/dept/detail', query: { id: data.id } });
+    },
+    async toDelete({ data }) {
+      const res = await this.delete(data.id);
+      this.$checkRes(res, '删除成功', '删除失败');
+      this.search();
+    },
+    toSelect(selecteds) {
+      this.$set(this, `selected`, selecteds);
+      let male = 0,
+        female = 0;
+      for (const i of selecteds) {
+        if (i.gender === '1' || i.gender === '男') male++;
+        else female++;
+      }
+      this.$set(this.selectInfo, `male`, male);
+      this.$set(this.selectInfo, `female`, female);
+    },
+    async toAutoSetClass() {
+      // 整理数据生成班级;将学生列表重新查询=>为了将已经有班级的学生剔除,以便继续分班(重新查询)
+      let info = {};
+      info.termid = _.get(this.defaultOption, `termid`);
+      info.planid = this.id;
+      //手动添加学生使用这部分,接口换了,之后转移到新加的手动
+      // let stuList = JSON.parse(JSON.stringify(this.selected));
+      // // if (this.isOutRange(stuList)) return;
+      // info.students = stuList;
+      let res = await this.createClass(info);
+      if (this.$checkRes(res, '分班成功', '分班失败')) this.resetData();
+      //重置信息
+      // this.selectInfo = {
+      //   male: 0,
+      //   female: 0,
+      // };
+      // this.selected = [];
+      // this.$refs.table.selectReset();
+    },
+    async toSetClass() {
+      let data = {};
+      data.id = _.pick(this.selectInfo, ['classid']).classid;
+      let stuList = JSON.parse(JSON.stringify(this.selected));
+      data.ids = stuList.map(i => i._id);
+      let res = await this.addStudent(data);
+      if (this.$checkRes(res, '分配成功', res.errmsg || '分配失败')) {
+        this.resetData();
+        this.search();
+      }
+    },
+    isOutRange(selected) {
+      let res = true;
+      if (selected.length <= 0) this.$message.warning('请选择学生');
+      else if (_.inRange(selected.length, 1, this.selectInfo.personReq * 1)) this.$message.error('选择人数不足');
+      else if (selected.length > this.selectInfo.personReq * 1) this.$message('超出班级规定人数');
+      else res = false;
+      return res;
+    },
+    getLimit(selected) {
+      let res = this.classList.find(f => f._id == selected);
+      this.$set(this.selectInfo, `personReq`, res.number);
+    },
+    resetData() {
+      this.list = [];
+      this.selected = [];
+      this.batchList = [];
+      this.classList = [];
+      this.selectInfo = {
+        male: 0,
+        female: 0,
+        personReq: 0,
+      };
+    },
+  },
+  watch: {
+    defaultOption: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (!_.get(this, 'options')) {
+          this.$set(this, `options`, _.cloneDeep(val));
+          this.search();
+        } else {
+          let nplanid = _.get(val, 'planid');
+          let oplanid = _.get(this.options, 'planid');
+          if (nplanid && !_.isEqual(nplanid, oplanid)) {
+            this.termList = [];
+            this.resetData();
+            this.search();
+          }
+          let ntermid = _.get(val, 'termid');
+          let otermid = _.get(this.options, 'termid');
+          if (ntermid && !_.isEqual(ntermid, otermid)) {
+            this.$set(this, `options`, _.cloneDeep(val));
+            this.resetData();
+            this.getBatch();
+            this.stuSearch();
+          }
+        }
+      },
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    id() {
+      return this.defaultOption.planid;
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 88 - 0
src/views/train-plan/leave.vue

@@ -0,0 +1,88 @@
+<template>
+  <div id="index">
+    <list-frame :title="mainTitle" @query="search" :total="total" :needAdd="false" :needFilter="false">
+      <data-table :fields="fields" :data="list" :opera="opera"></data-table>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: leave } = createNamespacedHelpers('leave');
+const { mapActions: student } = createNamespacedHelpers('student');
+export default {
+  metaInfo: { title: '请假和退出管理' },
+  name: 'index',
+  props: {},
+  components: {
+    listFrame,
+    dataTable,
+  },
+  data: () => ({
+    opera: [],
+    fields: [
+      { label: '学生名称', prop: 'stuname' },
+      { label: '开始时间', prop: 'starttime' },
+      { label: '结束时间', prop: 'endtime' },
+      { label: '请假理由', prop: 'reason' },
+
+      {
+        label: '状态',
+        prop: 'status',
+        format: item => {
+          return item === '0' ? '审核中' : item === '1' ? '通过' : '未通过';
+        },
+      },
+      { label: '拒绝原因', prop: 'refcause' },
+      {
+        label: '类型',
+        prop: 'type',
+        format: item => {
+          return item === '0' ? '请假' : '退出';
+        },
+      },
+    ],
+    searchInfo: {},
+    list: [],
+    total: 0,
+  }),
+  created() {
+    this.search();
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    mainTitle() {
+      let meta = this.$route.meta;
+      let main = meta.title || '';
+      let sub = meta.sub || '';
+      return `${main}${sub}`;
+    },
+    keyWord() {
+      let meta = this.$route.meta;
+      let main = meta.title || '';
+      return main;
+    },
+  },
+  methods: {
+    ...leave(['query']),
+    async search({ skip = 0, limit = 10, ...info } = {}) {
+      info = { planid: this.defaultOption.planid, termid: this.defaultOption.termid };
+      const res = await this.query({ skip, limit, ...info });
+      this.$set(this, `list`, res.data);
+      this.$set(this, `total`, res.total);
+    },
+  },
+  watch: {
+    defaultOption: {
+      handler(val) {
+        this.search();
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 205 - 0
src/views/train-plan/lesson.vue

@@ -0,0 +1,205 @@
+<template>
+  <div id="lesson">
+    <detail-frame :title="pageTitle" v-show="view == 'list'">
+      <el-row type="flex" align="middle" justify="end" class="btn_bar">
+        <el-col :span="2">
+          <el-button type="primary" size="mini" plain @click="toArrange">按模板排课</el-button>
+        </el-col>
+        <el-col :span="2">
+          <el-button type="primary" size="mini" @click="$router.push({ path: '/train/plan/term/lesson' })">查看本期课表</el-button>
+        </el-col>
+      </el-row>
+      <data-table :fields="fields" :data="list" :opera="opera" @edit="toEdit" :usePage="false"></data-table>
+    </detail-frame>
+    <detail-frame v-if="view == 'classView'" title="班级课表" :returns="() => (view = 'list')">
+      <class-table ref="lesson" :classInfo="classInfo" @saveResult="getRes"></class-table>
+      <el-divider></el-divider>
+      <info-class
+        ref="classes"
+        :classInfo="classInfo"
+        :locationList="locationList"
+        :lyTeacherList="lyTeacherList"
+        :headTeacherList="headTeacherList"
+        @saveResult="getRes"
+      ></info-class>
+      <el-row type="flex" align="middle" justify="center" class="btn_bar">
+        <el-col :span="2">
+          <el-button type="primary" @click="classSave">保存</el-button>
+        </el-col>
+      </el-row>
+    </detail-frame>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import dataTable from '@frame/components/filter-page-table';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import classTable from './parts/class-table';
+import infoClass from './parts/class-info';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: lesson } = createNamespacedHelpers('lesson');
+//info-class
+const { mapActions: location } = createNamespacedHelpers('location'); //地点
+const { mapActions: teacher } = createNamespacedHelpers('teacher'); //教师
+const { mapActions: dept } = createNamespacedHelpers('dept'); //配合教师表使用的部门表
+const { mapActions: dirPlan } = createNamespacedHelpers('dirPlan'); //班主任不能上课的列表
+const { mapActions: teaplan } = createNamespacedHelpers('teaPlan');
+const { mapActions: mapDept } = createNamespacedHelpers('dept');
+export default {
+  name: 'lesson',
+  props: {},
+  components: { detailFrame, dataTable, classTable, infoClass },
+  data: function() {
+    return {
+      view: 'list',
+      list: [],
+      opera: [
+        {
+          label: '查看本班课表',
+          icon: 'el-icon-view',
+          method: 'edit',
+        },
+      ],
+      fields: [
+        { label: '期', prop: 'term' },
+        { label: '批', prop: 'batch' },
+        { label: '班级', prop: 'name' },
+      ],
+      options: {},
+      classInfo: {},
+      //info-class
+      lyTeacherList: [],
+      locationList: [],
+      headTeacherList: [],
+      deptList: [],
+      result: [],
+    };
+  },
+  created() {},
+  methods: {
+    ...classes(['query']),
+    ...lesson({ autoArrange: 'arrange' }),
+    //info-class
+    ...location({ getLocationList: 'query' }),
+    ...teacher({ getTeacherList: 'query' }),
+    ...dirPlan({ dirQuery: 'getDirTeacher' }),
+    ...mapDept({ getDept: 'query' }),
+    ...teaplan(['findTeacher']),
+    async search() {
+      let termid = _.get(this.defaultOption, 'termid');
+      if (!termid) return;
+      let res = await this.query({ termid });
+      if (this.$checkRes(res)) {
+        this.$set(this, `list`, res.data);
+      }
+    },
+    toEdit({ data }) {
+      this.getSettingLists(data);
+      this.$set(this, `classInfo`, data);
+      this.view = 'classView';
+    },
+    async toArrange() {
+      this.$confirm('此操作将会将默认 年度计划 下所有的班级课表重置,若您已经修改过某班的信息,请谨慎使用', '提示', {
+        confirmButtonText: '按模板排课',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(async () => {
+          let planid = _.get(this.defaultOption, 'planid');
+          let res = await this.autoArrange(planid);
+          this.$checkRes(res, '排课成功', res.errmsg || '排课失败');
+        })
+        .catch(async () => {
+          console.log('已取消');
+        });
+    },
+    async getSettingLists(data) {
+      let res;
+      if (this.locationList.length <= 0) {
+        res = await this.getLocationList();
+        if (this.$checkRes(res)) this.$set(this, `locationList`, res.data);
+      }
+      if (this.lyTeacherList.length <= 0) {
+        res = await this.getTeacherList({ islyteacher: '1', status: '4' });
+        if (this.$checkRes(res)) this.$set(this, `lyTeacherList`, res.data);
+      }
+      if (this.headTeacherList.length <= 0) {
+        res = await this.findTeacher({ planid: data.planid, termid: data.termid, batchid: data.batchid });
+        let duplicate = _.cloneDeep(res.data);
+        if (this.$checkRes(res)) {
+          if (this.deptList.length <= 0) {
+            let dept = await this.getDept();
+            if (this.$checkRes(res)) this.$set(this, `deptList`, dept.data);
+          }
+          //班主任按部门分组
+          let group = _.groupBy(res.data, 'department');
+          let keys = Object.keys(group);
+          let arr = keys.map(key => {
+            let r = this.deptList.find(f => f.id == key);
+            let obj = {};
+            if (r) {
+              obj.name = r.name;
+              obj.list = group[key];
+            }
+            return obj;
+          });
+          this.$set(this, `headTeacherList`, arr);
+          //班主任筛选可以当礼仪老师列表,和 礼仪教师列表合并
+          duplicate = duplicate.filter(f => f.islyteacher == '1');
+          this.$set(this, `lyTeacherList`, [...this.lyTeacherList, ...duplicate]);
+        }
+      }
+    },
+    classSave() {
+      this.$refs.lesson.toSave();
+      this.$refs.classes.toSave();
+    },
+    getRes({ from, result }) {
+      let r = this.result.find(f => f.from == from);
+      if (r) {
+        console.log(`已有${from}的保存结果,未清除`);
+        return;
+      } else {
+        this.result.push({ from, result });
+      }
+      if (this.result.length == 2) {
+        let resR = this.result.every(e => e.result == true);
+        if (resR) this.$message.success('保存成功');
+        this.$set(this, `result`, []);
+      }
+    },
+  },
+  watch: {
+    defaultOption: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (!_.get(this, 'options')) {
+          this.$set(this, `options`, _.cloneDeep(val));
+          this.search();
+        } else {
+          let ntermid = _.get(val, 'termid');
+          let otermid = _.get(this.options, 'termid');
+          if (ntermid && !_.isEqual(ntermid, otermid)) {
+            this.$set(this, `options`, _.cloneDeep(val));
+            this.search();
+          }
+        }
+      },
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 85 - 0
src/views/train-plan/parts/class-info.vue

@@ -0,0 +1,85 @@
+<template>
+  <div id="class-info">
+    <data-form ref="form" :inline="true" :needSave="false" :data="form" :fields="fields" :rules="{}" @save="handleSave" :reset="false">
+      <template #options="{item, form}">
+        <template v-if="item.model == 'headteacherid'">
+          <el-option-group v-for="(dept, index) in headTeacherList" :label="dept.name" :key="index">
+            <el-option v-for="(i, tIndex) in dept.list" :key="`${index}-${tIndex}`" :label="i.name" :value="i._id"></el-option>
+          </el-option-group>
+        </template>
+        <template v-if="item.model == 'lyteacherid'">
+          <el-option v-for="(tea, index) in lyTeacherList" :key="`${item.model}${index}`" :label="tea.name" :value="tea.id"></el-option>
+        </template>
+        <template v-if="item.model == 'jslocationid'">
+          <el-option v-for="(place, index) in locationList" :key="`${item.model}${index}`" :label="place.name" :value="place.id"></el-option>
+        </template>
+        <template v-if="item.model == 'kbyslocationid'">
+          <el-option v-for="(place, index) in locationList" :key="`${item.model}${index}`" :label="place.name" :value="place.id"></el-option>
+        </template>
+        <template v-if="item.model == 'kzjhlocationid'">
+          <el-option v-for="(place, index) in locationList" :key="`${item.model}${index}`" :label="place.name" :value="place.id"></el-option>
+        </template>
+        <template v-if="item.model == 'yclocationid'">
+          <el-option v-for="(place, index) in locationList" :key="`${item.model}${index}`" :label="place.name" :value="place.id"></el-option>
+        </template>
+      </template>
+    </data-form>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: classes } = createNamespacedHelpers('classes');
+export default {
+  name: 'class-info',
+  props: {
+    classInfo: { type: Object, default: () => {} },
+    locationList: { type: Array, default: () => [] },
+    lyTeacherList: { type: Array, default: () => [] },
+    headTeacherList: { type: Array, default: () => [] },
+  },
+  components: { dataForm },
+  data: function() {
+    var that = this;
+    return {
+      form: _.cloneDeep(that.classInfo),
+      fields: [
+        // { label: '', model: 'name', type: 'text' },
+        // { label: '人数', model: 'number', type: 'text' },
+        { label: '班主任', model: 'headteacherid', type: 'select' },
+        { label: '礼仪课教师', model: 'lyteacherid', type: 'select' },
+        { label: '教室地点', model: 'jslocationid', type: 'select' },
+        { label: '开班地点', model: 'kbyslocationid', type: 'select' },
+        { label: '拓展训练地点', model: 'kzjhlocationid', type: 'select' },
+        { label: '用餐地点', model: 'yclocationid', type: 'select' },
+      ],
+    };
+  },
+  created() {},
+  methods: {
+    ...classes(['update']),
+    toSave() {
+      this.$refs.form.save();
+    },
+    async handleSave({ data }) {
+      let res = await this.update(data);
+      if (this.$checkRes(res, null, res.errmsg || '保存失败')) {
+        this.$emit('saveResult', { from: 'info', result: true });
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 369 - 0
src/views/train-plan/parts/class-table.vue

@@ -0,0 +1,369 @@
+<template>
+  <div id="class-table">
+    <el-card>
+      <template #header>
+        <el-row type="flex" align="middle" justify="space-between">
+          <el-col :span="4">课程安排</el-col>
+          <!-- <el-col :span="2">
+            <el-button type="primary" size="mini" @click="toSave">保存课表</el-button>
+          </el-col> -->
+        </el-row>
+      </template>
+      <el-table :data="lessonList" border stripe @cell-click="cellClick">
+        <el-table-column align="center" label="时间" prop="time"></el-table-column>
+        <el-table-column align="center" v-for="(i, index) in dateList" :key="index" :label="i" :prop="`subname_day${index + 1}`">
+          <template v-slot="{ row, $index }">
+            <el-row>
+              <el-col :span="24">{{ getProp(row, `subname_day${index + 1}`) }}</el-col>
+              <el-col :span="24" v-if="getProp(row, `teaname_day${index + 1}`)">{{ getProp(row, `teaname_day${index + 1}`) }}</el-col>
+            </el-row>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-drawer :visible.sync="drawer" direction="rtl" title="课程安排" @close="toClose">
+      <data-form :data="form" :fields="fields" :rules="{}" @save="handleSave" :reset="false">
+        <template #radios="{item, form}">
+          <template v-if="item.model == 'type'">
+            <el-radio @change="radioClearForm" v-for="(i, index) in dayType" :key="index" :label="i.label">{{ i.label }}</el-radio>
+          </template>
+        </template>
+        <template #options="{item, form}">
+          <template v-if="item.model == 'subname'">
+            <el-option v-for="(i, index) in actList" :key="index" :label="i.label" :value="i.label"></el-option>
+          </template>
+          <template v-if="item.model == 'subid'">
+            <el-option v-for="(i, index) in subjectList" :key="index" :label="i.name" :value="i.id"></el-option>
+          </template>
+        </template>
+
+        <template #custom="{item, form}">
+          <template v-if="item.model == 'teaname'">
+            <el-input v-model="form.teaname" :readonly="true" placeholder="点击选择教师" @click.native="toChooseTeacher"></el-input>
+          </template>
+        </template>
+      </data-form>
+    </el-drawer>
+
+    <el-dialog title="选择教师" :visible.sync="dialog" :destroy-on-close="true">
+      <teacher-select :schoolList="schoolList" :subjectList="subjectList" :subjectid="form.subid" @selTea="selTea"> </teacher-select>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+var moment = require('moment');
+import _ from 'lodash';
+import dataForm from '@frame/components/form';
+import teacherSelect from './teacher.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: lesson } = createNamespacedHelpers('lesson');
+const { mapActions: mapUtil } = createNamespacedHelpers('util');
+const { mapActions: subject } = createNamespacedHelpers('subject');
+const { mapActions: teacher } = createNamespacedHelpers('teacher');
+const { mapActions: school } = createNamespacedHelpers('school'); //给选老师组件使用.这个页面请求完就不销毁了
+
+// 本页的组合数据,变量用x表示: _id_day[x];subname_day[x];subid_day[x];teaid_day[x];teaname_day:[x]
+export default {
+  name: 'class-table',
+  props: {
+    classInfo: { type: Object, default: () => {} },
+  },
+  components: { dataForm, teacherSelect },
+  data: function() {
+    var that = this;
+    return {
+      lessonInfo: {},
+      lessonList: [],
+      dateList: [],
+      timeList: [],
+      subjectList: [],
+      teacherList: [],
+      schoolList: [], //给选老师组件用
+      drawer: false,
+      dialog: false,
+      form: {},
+      fields: [
+        { label: '日期', model: 'date', type: 'text' },
+        { label: '时间', model: 'time', type: 'text' },
+        { label: '类型', model: 'type', type: 'radio' },
+        { label: '课程安排', model: 'subid', type: 'select', display: (fields, form) => that.fieldDisplay(fields, form) },
+        { label: '活动安排', model: 'subname', type: 'select', display: (fields, form) => that.fieldDisplay(fields, form) },
+        { label: '教师', model: 'teaname', custom: true, display: (fields, form) => that.fieldDisplay(fields, form) },
+      ],
+      dayType: [{ label: '活动' }, { label: '课程' }],
+      actList: [
+        { label: '--' },
+        { label: '报道+开班仪式' },
+        { label: '午餐+休息' },
+        { label: '晚餐' },
+        { label: '团队组建' },
+        { label: '拓展交流' },
+        { label: '课程作业小组展示' },
+        { label: '课程作业' },
+        { label: '礼仪课小组面试' },
+        { label: '结业仪式' },
+      ],
+    };
+  },
+  created() {
+    this.getOtherList();
+  },
+  methods: {
+    ...mapUtil(['fetch']),
+    ...lesson(['query', 'create', 'update']),
+    ...subject({ getSubject: 'query' }),
+    ...teacher({ getTeacher: 'query' }),
+    ...school({ getSchool: 'query' }),
+    async search() {
+      let res = await this.fetch({ model: 'lesson', classid: _.get(this.classInfo, '_id') });
+      if (this.$checkRes(res)) {
+        if (!_.get(res.data, 'lessons') || res.data.lessons.length <= 0) {
+          this.$message.warning('请先将本期的课程按模板进行初始化');
+          return;
+        }
+        this.$set(this, `lessonInfo`, _.omit(res.data, ['lessons']));
+        let arr = _.get(res.data, `lessons`, []);
+        let x = this.getX(JSON.parse(JSON.stringify(arr)));
+        this.getY(JSON.parse(JSON.stringify(arr)));
+        this.$set(this, `dateList`, x);
+        arr = this.aData(arr);
+        this.$set(this, `lessonList`, arr);
+      }
+    },
+    async toSave() {
+      //整理成原数据形式,提交
+      let data = JSON.parse(JSON.stringify(this.lessonList));
+      data = this.returnData(data);
+      let lesson = JSON.parse(JSON.stringify(this.lessonInfo));
+      lesson.lessons = data;
+      let res = await this.update(lesson);
+      if (this.$checkRes(res, null, res.errmsg || '课程表保存失败')) {
+        this.$emit('saveResult', { from: 'lesson', result: true });
+      }
+    },
+    //点击单元格事件
+    cellClick(row, column) {
+      let date = _.get(column, 'label');
+      let time = _.get(row, 'time');
+      let num = _.get(column, 'property').match(/\d+(.\d+)?/g)[0];
+      let obj = this.getOrderDate(row, num);
+      obj.type = obj.subid ? '课程' : '活动';
+      this.$set(this, `form`, { date, time, ...obj });
+      this.drawer = true;
+    },
+    //抽屉保存
+    handleSave({ data }) {
+      let num = _.get(data, 'index');
+      let type = _.get(data, `type`);
+      let time = _.get(data, `time`);
+      let yIndex = this.lessonList.findIndex(f => f.time == time);
+      let obj = {};
+      if (type == '课程') {
+        obj = _.pick(data, ['subid', 'teaid', 'teaname', '_id']);
+        let r = this.subjectList.find(f => f.id == obj.subid);
+        if (r) obj.subname = r.name;
+      } else {
+        obj = _.pick(data, ['subname', '_id']);
+      }
+      obj = this.resetData(obj, num);
+      this.$set(this.lessonList, yIndex, { ...this.lessonList[yIndex], ...obj });
+      this.setSubTea();
+      this.drawer = false;
+    },
+    //提交整理数据
+    returnData(data) {
+      let returnArr = [];
+      data.map(i => {
+        let keys = Object.keys(i);
+        let time = _.get(i, `time`);
+        let arr = _.compact(_.uniq(_.flatten(keys.map(i => i.match(/\d+(.\d+)?/g)))));
+        arr.map(index => {
+          let obj = this.getOrderDate(i, index, true);
+          obj.time = time;
+          obj.day = '0';
+          returnArr.push(obj);
+        });
+      });
+      let r = returnArr.filter(f => f.date == _.last(this.dateList));
+      let allday = '0';
+      let res = r.find(f => {
+        //TODO 根据开始时间不超过12点判断是 整天还是半天
+        if (f.subname != '--') {
+          let ts = f.time.split('-');
+          let time = moment(`${f.date} ${ts[0]}`).format('X');
+          let twl = moment(`${f.date} 12:00`).format('X');
+          return twl <= time;
+        }
+      });
+      if (res) allday = '1';
+      returnArr = returnArr.map(i => {
+        if (i.date == _.last(this.dateList)) i.allday = allday;
+        else i.allday = '0';
+        return i;
+      });
+      return returnArr;
+    },
+    //field的显示
+    fieldDisplay(f, form) {
+      if (f.model == 'teaname' || f.model == 'subid') {
+        return form.type == '课程';
+      } else return form.type == '活动';
+    },
+    //请求后整理数据方法
+    aData(data) {
+      let duplicate = JSON.parse(JSON.stringify(data));
+      //按时间分组
+      duplicate = _.flatten(_.toPairs(_.groupBy(data, 'time'))).filter(f => _.isArray(f));
+      let r = duplicate.map(i => {
+        //按日期排序
+        let aa = i.sort((a, b) => moment(a.date).format('X') - moment(b.date).format('X'));
+        //组合数据:{time,day1,id_day1,subid_day1}
+        let object = { time: _.get(i[0], 'time') };
+        aa.map(a => {
+          let index = this.dateList.findIndex(f => f == a.date);
+          if (index >= 0) {
+            index = index + 1;
+          }
+          let obj = this.resetData(a, index);
+          object = { ...object, ...obj };
+        });
+        return object;
+      });
+      r = this.getOrderForTime(r);
+      return r;
+    },
+    //获取指定数据
+    getOrderDate(data, index, needDate = false) {
+      let obj = { index: index };
+      if (_.get(data, `_id_day${index}`)) obj[`_id`] = _.get(data, `_id_day${index}`);
+      obj[`subname`] = _.get(data, `subname_day${index}`, `--`);
+      if (_.get(data, `subid_day${index}`)) obj[`subid`] = _.get(data, `subid_day${index}`);
+      if (_.get(data, `teaid_day${index}`)) obj[`teaid`] = _.get(data, `teaid_day${index}`);
+      if (_.get(data, `teaname_day${index}`)) obj[`teaname`] = _.get(data, `teaname_day${index}`);
+      if (needDate) {
+        //所有的数据都还原了,没必要遥index了
+        delete obj.index;
+        obj.date = this.dateList[index - 1];
+      }
+      return obj;
+    },
+    //整理,匹配数据是哪天,该显示在哪
+    resetData(data, index) {
+      let obj = {};
+      if (_.get(data, '_id')) obj[`_id_day${index}`] = _.get(data, '_id');
+      obj[`subname_day${index}`] = _.get(data, 'subname', '--');
+      if (_.get(data, 'subid')) obj[`subid_day${index}`] = _.get(data, 'subid');
+      if (_.get(data, 'teaid')) obj[`teaid_day${index}`] = _.get(data, 'teaid');
+      if (_.get(data, 'teaname')) obj[`teaname_day${index}`] = _.get(data, 'teaname');
+      return obj;
+    },
+    //修改:选择科目/教师后,将次科目的所有数据统一
+    setSubTea() {
+      let duplicate = _.cloneDeep(this.lessonList);
+      let arr = this.returnData(duplicate);
+      let teaList = arr.filter(f => f.teaid);
+      let res = arr.map(i => {
+        let r = teaList.find(f => f.subid == i.subid);
+        if (r) {
+          i.teaid = r.teaid;
+          i.teaname = r.teaname;
+        }
+        return i;
+      });
+      res = this.aData(res);
+      this.$set(this, 'lessonList', res);
+    },
+    //根据时间排序
+    getOrderForTime(data) {
+      let duplicate = JSON.parse(JSON.stringify(data));
+      duplicate = duplicate.sort((a, b) => {
+        let a_arr = a.time.split('-');
+        let b_arr = b.time.split('-');
+        let at = moment(`${moment().format('YYYY-MM-DD')} ${a_arr[0]}`).format('X');
+        let bt = moment(`${moment().format('YYYY-MM-DD')} ${b_arr[0]}`).format('X');
+        return at - bt;
+      });
+      return duplicate;
+    },
+    //整理出标头,根据日期排序
+    getX(data) {
+      let r = _.uniqBy(data, 'date').map(i => i.date);
+      r = r.sort((a, b) => moment(a).format('X') - moment(b).format('X'));
+      return r;
+    },
+    //获得时间列表
+    getY(data) {
+      let duplicate = JSON.parse(JSON.stringify(data));
+      let arr = _.uniqBy(
+        duplicate.map(i => _.pick(i, ['time'])),
+        'time'
+      );
+      arr = this.getOrderForTime(arr);
+      this.$set(
+        this,
+        `timeList`,
+        arr.map(i => i.time)
+      );
+    },
+    //教师列表,课程列表
+    async getOtherList() {
+      let res = await this.getSubject();
+      if (this.$checkRes(res)) this.$set(this, `subjectList`, res.data);
+      res = await this.getTeacher({ status: '4' });
+      if (this.$checkRes(res)) this.$set(this, `teacherList`, res.data);
+    },
+    //关闭抽屉
+    toClose() {
+      this.drawer = false;
+      this.form = {};
+    },
+    //修改类型清除数据
+    radioClearForm(data) {
+      if (data == '活动') {
+        delete this.form.subid;
+        this.form.subname = '--';
+      }
+    },
+    //打开选择教师的dialog
+    async toChooseTeacher() {
+      this.dialog = true;
+      if (this.schoolList.length <= 0) {
+        let res = await this.getSchool();
+        if (this.$checkRes(res)) this.$set(this, `schoolList`, res.data);
+      }
+    },
+    //选择教师
+    selTea(data) {
+      this.dialog = false;
+      this.$set(this, `form`, { ...this.form, ...data });
+    },
+    //显示
+    getProp(data, prop) {
+      return _.get(data, prop);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  watch: {
+    classInfo: {
+      handler(val) {
+        let id = _.get(val, '_id');
+        if (id) this.search();
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 142 - 0
src/views/train-plan/parts/lesson-table.vue

@@ -0,0 +1,142 @@
+<template>
+  <div id="lesson-table">
+    <el-form :model="form" size="mini" style="padding:20px" label-width="100px">
+      <el-form-item label="日期">{{ form.date }}</el-form-item>
+      <el-form-item label="类型">
+        <el-radio-group v-model="form.type">
+          <el-radio @change="radioClearForm" v-for="(i, index) in dayType" :key="index" :label="i.label">{{ i.label }}</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="活动安排" v-if="form.type == '活动'">
+        <el-select v-model="form.subname" placeholder="请选择活动">
+          <el-option v-for="(i, index) in actList" :key="index" :label="i.label" :value="i.label"></el-option>
+        </el-select>
+      </el-form-item>
+      <template v-if="form.type == '课程'">
+        <el-form-item label="课程安排">
+          <el-select v-model="form.subid" placeholder="请选择课程">
+            <el-option v-for="(i, index) in subjectList" :key="index" :label="i.name" :value="i.id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="教师安排">
+          <el-form-item v-for="(i, index) in cList" :key="index" :label="`${i.class_num}班`" label-width="50px">
+            <el-input v-model="i.name" :readonly="true" placeholder="点击选择教师" @click.native="toChooseTeacher(i.classid)"></el-input>
+          </el-form-item>
+        </el-form-item>
+      </template>
+      <el-form-item label="是否全天" v-if="form.is_last">
+        <el-radio-group v-model="form.allday">
+          <el-radio label="全天">全天</el-radio>
+          <el-radio label="半天">半天</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item>
+        <el-row type="flex" align="middle" justify="center">
+          <el-col :span="2">
+            <el-button type="primary" @click="handleSave">保存</el-button>
+          </el-col>
+        </el-row>
+      </el-form-item>
+    </el-form>
+    <el-dialog title="选择教师" :visible.sync="dialog" :destroy-on-close="true" :append-to-body="true">
+      <teacher-select :schoolList="schoolList" :subjectList="subjectList" :subjectid="form.subid" @selTea="selTea"> </teacher-select>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import teacherSelect from './teacher.vue';
+import dataForm from '@frame/components/form';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'lesson-table',
+  props: {
+    data: { type: Object, default: () => {} },
+    subjectList: { type: Array, default: () => [] },
+    schoolList: { type: Array, default: () => [] },
+    classList: { type: Array, default: () => [] },
+  },
+  components: { teacherSelect }, //dataForm
+  data: function() {
+    var that = this;
+    return {
+      form: _.cloneDeep(that.data),
+      cList: _.cloneDeep(that.classList),
+      dialog: false,
+      isChange: undefined,
+      dayType: [{ label: '活动' }, { label: '课程' }],
+      actList: [
+        { label: '--' },
+        { label: '报道+开班仪式' },
+        { label: '午餐+休息' },
+        { label: '晚餐' },
+        { label: '团队组建' },
+        { label: '拓展交流' },
+        { label: '课程作业小组展示' },
+        { label: '课程作业' },
+        { label: '礼仪课小组面试' },
+        { label: '结业仪式' },
+      ],
+    };
+  },
+  created() {},
+  methods: {
+    handleSave() {
+      let dForm = _.cloneDeep(this.form);
+      if (_.get(dForm, 'subid')) {
+        dForm.subname = this.subjectList.find(f => f.id == _.get(dForm, 'subid')).name;
+      }
+      let dList = _.cloneDeep(this.cList);
+      this.$emit('save', { form: dForm, classList: dList });
+    },
+    //选择教师
+    selTea(data) {
+      let { teaname: name, teaid: id } = data;
+      this.dialog = false;
+      let i = this.cList.findIndex(f => f.classid == this.isChange);
+      let o = this.cList.find(f => f.classid == this.isChange);
+      o = { ...o, name, id };
+      this.$set(this.cList, i, o);
+    },
+    //field的显示
+    fieldDisplay(f, form) {
+      if (f.model == 'name' || f.model == 'subid') {
+        return form.type == '课程';
+      } else return form.type == '活动';
+    },
+    //修改类型清除数据
+    radioClearForm(data) {
+      if (data == '活动') {
+        this.clearTeaInfo();
+        this.form.subname = '--';
+      }
+    },
+    clearTeaInfo() {
+      delete this.form.subid;
+      let narr = this.cList.map(i => {
+        i.id = undefined;
+        i.name = undefined;
+        return i;
+      });
+      this.$set(this, `cList`, narr);
+      this.$forceUpdate();
+    },
+    async toChooseTeacher(classid) {
+      this.$set(this, `isChange`, classid);
+      this.dialog = true;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 97 - 0
src/views/train-plan/parts/teacher.vue

@@ -0,0 +1,97 @@
+<template>
+  <div id="teacher">
+    <el-tabs v-model="teaTab">
+      <el-tab-pane style="padding:10px" label="申请授课教师" name="apply">
+        <filter-table :data="applyList" :fields="teaFields" :opera="opera" @select="selectTeacher" :total="applyTotal" @query="toGetApplyList"></filter-table>
+      </el-tab-pane>
+      <el-tab-pane style="padding:10px" label="可授课教师" name="list">
+        <filter-table
+          :data="teacherList"
+          :fields="teaFields"
+          :opera="opera"
+          @select="selectTeacher"
+          :total="teacherTotal"
+          @query="toGetTeacherList"
+        ></filter-table>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import filterTable from '@frame/components/filter-page-table.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: teacher } = createNamespacedHelpers('teacher'); //教师
+const { mapActions: teaPlan } = createNamespacedHelpers('teaPlan'); //教师申请
+export default {
+  name: 'teacher',
+  props: {
+    schoolList: { type: Array, default: () => [] },
+    subjectList: { type: Array, default: () => [] },
+    subjectid: { type: String, default: () => '' },
+  },
+  components: { filterTable },
+  data: function() {
+    return {
+      teaTab: 'apply',
+      teaFields: [
+        { label: '姓名', prop: 'name', filter: 'input' },
+        { label: '学校', prop: 'schname' },
+        { label: '资料评分', prop: 'zlscore' },
+        { label: '面试评分', prop: 'msscore' },
+      ],
+      opera: [
+        {
+          label: '选择教师',
+          icon: 'el-icon-check',
+          method: 'select',
+        },
+      ],
+      applyList: [],
+      applyTotal: 0,
+      teacherList: [],
+      teacherTotal: 0,
+    };
+  },
+  created() {},
+  methods: {
+    ...teacher({ getTeacherList: 'query' }),
+    ...teaPlan({ getApplyTeacherList: 'applyQuery' }),
+    async toGetTeacherList({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.getTeacherList({ skip, limit, ...info, subid: this.subjectid, status: '4' });
+      this.$set(this, `teacherTotal`, res.total);
+      this.$set(this, `teacherList`, res.data);
+    },
+    async toGetApplyList({ skip = 0, limit = 10, ...info } = {}) {
+      let res = await this.getApplyTeacherList({ skip, limit, ...info, subid: this.subjectid, status: '4' });
+      this.$set(this, `applyTotal`, res.total);
+      this.$set(this, `applyList`, res.data);
+    },
+    selectTeacher({ data }) {
+      this.$emit('selTea', { teaname: data.name, teaid: data._id });
+    },
+  },
+  watch: {
+    subjectid: {
+      handler(val, oval) {
+        if (val && val != oval) {
+          this.toGetApplyList();
+          this.toGetTeacherList();
+        }
+      },
+      immediate: true,
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 133 - 0
src/views/train-plan/parts/term-class-from.vue

@@ -0,0 +1,133 @@
+<template>
+  <div id="term-class-from">
+    <el-form :model="form" size="mini" style="padding:20px" label-width="100px">
+      <el-form-item :label="label">
+        <el-form-item v-for="(cl, index) in classList" :key="index" :label="`${cl.class_num}班`" label-width="50px">
+          <template v-if="form.type == 'headteacher'">
+            <el-select v-model="cl.id" :placeholder="`请选择${label}`">
+              <el-option-group v-for="(dept, index) in list" :label="dept.name" :key="index">
+                <el-option v-for="(i, tIndex) in dept.list" :key="`${index}-${tIndex}`" :label="i.name" :value="i._id"></el-option>
+              </el-option-group>
+            </el-select>
+          </template>
+          <el-select v-model="cl.id" :placeholder="`请选择${label}`" v-else>
+            <el-option v-for="(i, index) in list" :key="index" :label="i.name" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+      </el-form-item>
+      <el-form-item>
+        <el-row type="flex" align="middle" justify="center">
+          <el-col :span="2">
+            <el-button type="primary" @click="handleSave">保存</el-button>
+          </el-col>
+        </el-row>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'term-class-from',
+  props: {
+    data: { type: Object, default: () => {} },
+    lyTeacherList: { type: Array, default: () => [] },
+    headTeacherList: { type: Array, default: () => [] },
+    locationList: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function() {
+    return {
+      form: {},
+      classList: [],
+      label: '',
+      list: [],
+    };
+  },
+  created() {},
+  methods: {
+    handleSave() {
+      let dForm = _.cloneDeep(this.form);
+      let dList = _.cloneDeep(this.classList);
+      dList = dList.map(i => {
+        if (i.id) {
+          let r;
+          let dataList = _.cloneDeep(this.list);
+          if (dForm.type == 'headteacher') {
+            dataList = _.flatten(dataList.map(i => i.list));
+            r = dataList.find(f => f._id == i.id);
+          } else r = dataList.find(f => f._id == i.id);
+          if (r) i.name = r.name;
+        }
+        return i;
+      });
+      let r = {};
+      dList.map(c => {
+        let { class_num, ...info } = c;
+        let keys = Object.keys(info);
+        for (const key of keys) {
+          r[`${key}_${class_num}`] = info[key];
+        }
+      });
+      r = { ...dForm, ...r };
+      this.$emit('save', r);
+    },
+    setData(data) {
+      let { index, batch, date, type, ...others } = data;
+      let obj = { index, batch, date, type };
+      this.$set(this, `form`, obj);
+      this.getAttr();
+      let keys = Object.keys(others);
+      let indexs = _.uniq(keys.map(key => key.match(/\d+(.\d+)?/g)[0]));
+      let arr = indexs.map(i => {
+        let gks = keys.filter(f => f.includes(i));
+        gks = gks.map(gk => {
+          let o = [];
+          o.push(gk.split('_')[0]);
+          o.push(others[gk]);
+          return o;
+        });
+        let res = _.fromPairs(gks);
+        res = { ...res, class_num: i };
+        return res;
+      });
+      this.$set(this, `classList`, arr);
+    },
+    getAttr() {
+      let { type } = this.form;
+      if (type == 'headteacher') {
+        this.$set(this, `list`, this.headTeacherList);
+        this.label = '班主任';
+      } else if (type == 'lyteacher') {
+        this.$set(this, `list`, this.lyTeacherList);
+        this.label = '礼仪课教师';
+      } else {
+        this.$set(this, `list`, this.locationList);
+        this.label = '教室地点';
+      }
+    },
+  },
+  watch: {
+    data: {
+      handler(val) {
+        this.setData(val);
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 82 - 0
src/views/train-plan/parts/term-lesson-form.vue

@@ -0,0 +1,82 @@
+<template>
+  <div id="term-lesson-form">
+    <lt :ref="`lt`" :data="form" :classList="classList" @save="toSave" v-bind="$attrs"></lt>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import lt from './lesson-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'term-lesson-form',
+  props: {
+    data: { type: Object, default: () => {} },
+  },
+  components: { lt },
+  data: function() {
+    return {
+      form: {},
+      classList: [],
+    };
+  },
+  created() {},
+  methods: {
+    setData(val) {
+      let { index, batch, date, is_last, subname, subid, type: dataType, allday, ...others } = val;
+      let obj = { index, batch, date, is_last, subname, subid, dataType, allday };
+      obj.type = subid ? '课程' : '活动';
+      this.$set(this, `form`, obj);
+      let keys = Object.keys(others);
+      let indexs = _.uniq(keys.map(key => key.match(/\d+(.\d+)?/g)[0]));
+      let arr = indexs.map(i => {
+        let gks = keys.filter(f => f.includes(i));
+        gks = gks.map(gk => {
+          let o = [];
+          o.push(gk.split('_')[0]);
+          o.push(others[gk]);
+          return o;
+        });
+        let res = _.fromPairs(gks);
+        res = { ...res, class_num: i };
+        return res;
+      });
+      this.$set(this, `classList`, arr);
+    },
+    toSave({ form, classList }) {
+      let { dataType: type, ...f } = form;
+      form = { ...f, type };
+      let r = {};
+      classList.map(c => {
+        let { class_num, ...info } = c;
+        let keys = Object.keys(info);
+        for (const key of keys) {
+          r[`${key}_${class_num}`] = info[key];
+        }
+      });
+      r = { ...form, ...r };
+      this.$emit('save', r);
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  watch: {
+    data: {
+      handler(val) {
+        this.setData(val);
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 104 - 0
src/views/train-plan/parts/term-lesson-table.vue

@@ -0,0 +1,104 @@
+<template>
+  <div id="term-lesson-table">
+    <el-table
+      :data="list"
+      size="mini"
+      @cell-click="cellClick"
+      tooltip-effect="dark"
+      border
+      stripe
+      :cell-style="{ height: '50px' }"
+      :span-method="data => spanTable(data, 't1')"
+    >
+      <el-table-column align="center" label="日期" prop="date" :show-overflow-tooltip="true"></el-table-column>
+      <el-table-column align="center" label="星期" :show-overflow-tooltip="true">
+        <template v-slot="{ row, $index }">
+          <template v-if="$index !== list.length - 1">
+            {{ row.date | getWeekDay }}
+          </template>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="课程内容" prop="subname" :show-overflow-tooltip="true"></el-table-column>
+      <el-table-column align="center" label="1班" prop="name_1" :show-overflow-tooltip="true"></el-table-column>
+      <el-table-column align="center" label="2班" prop="name_2" :show-overflow-tooltip="true"></el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+const moment = require('moment');
+moment.locale('zh-cn');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'term-lesson-table',
+  props: {
+    data: { type: Array, default: () => [] },
+    batch: { type: [String, Array] },
+  },
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {
+    spanTable({ row, column, rowIndex, columnIndex }, arrayName) {
+      let res = { rowspan: 1, colspan: 1 };
+      let l = _.get(this.list, 'length');
+      let b = this.batch * 1;
+      //区别在于改变的行数不一致,错位的
+      //b: 1-3
+      //t1 是要改变 0+6 至 l-1-2 行的内容 => 6 - 8
+      //t2 1+6 至 l-1-1 => 7-9
+      //t3 2+6 至 l-1-0 => 8-10
+      // b为该期的批次 batch
+      // 左侧是:0+6;1+6;2+6 => b-1+6
+      // 右侧是: l-1 -2; l-1  -1; l-1 -0 => l-1 -(3-b)
+      if (_.inRange(rowIndex, b + 5, l - 4 + b) && columnIndex < 1) {
+        res.colspan = 3;
+      }
+      if (_.inRange(rowIndex, b + 5, l - 4 + b) && columnIndex >= 1 && columnIndex <= 2) {
+        res.colspan = 0;
+      }
+      if (rowIndex == l - 1) {
+        res.colspan = 5;
+      }
+      return res;
+    },
+    cellClick(row, column) {
+      let keys = Object.keys(row);
+      if (keys.length <= 0) return;
+      let index = this.list.findIndex(f => _.isEqual(f, row));
+      let prop = _.get(column, 'property');
+      let type = _.get(row, `type`);
+      if (prop && prop.includes('name')) {
+        let obj = { ...row, batch: this.batch, index };
+        if (type == 'lesson') {
+          let arr = this.data;
+          let fr = arr.filter(f => f.type == 'lesson' && Object.keys(f).length > 0);
+          obj.is_last = _.isEqualWith(_.last(fr), row, (ob, ot) => ob.date == ot.date);
+          this.$emit('lesson', obj);
+        } else this.$emit('other', obj);
+      } else {
+        this.$message.warning('此项不允许更改');
+      }
+    },
+  },
+  filters: {
+    getWeekDay(date) {
+      if (date && moment(date).isValid() && moment.isMoment(moment(date))) return moment(date).format('dddd');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    list() {
+      return _.cloneDeep(this.data);
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 123 - 0
src/views/train-plan/print.vue

@@ -0,0 +1,123 @@
+<template>
+  <div id="print">
+    <list-frame title="打印管理" @query="search" :total="total" :needFilter="false" :needAdd="false">
+      <el-form :inline="true" size="mini">
+        <el-form-item label="期">
+          <el-select v-model="form.termid" placeholder="请选择期数" @change="getBatch">
+            <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="批次">
+          <el-select v-model="form.batchid" placeholder="请先选择期数">
+            <el-option v-for="(i, index) in batchList" :key="index" :label="i.name" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="search">查询</el-button>
+          <el-button @click="nameList()">打印学生名签</el-button>
+          <el-button @click="signList()">打印学生签到表</el-button>
+          <el-button @click="certList()">打印学生证书</el-button>
+          <el-button @click="classLesson()">打印班级课表</el-button>
+        </el-form-item>
+      </el-form>
+      <template>
+        <el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55"> </el-table-column>
+          <el-table-column prop="batch" label="批次"> </el-table-column>
+          <el-table-column prop="name" label="班级名称" show-overflow-tooltip> </el-table-column>
+        </el-table>
+        <!-- <div style="margin-top: 20px">
+          <el-button @click="nameList()">打印学生名签</el-button>
+          <el-button @click="signList()">打印学生签到表</el-button>
+          <el-button @click="certList()">打印学生证书</el-button>
+          <el-button @click="classLesson()">打印班级课表</el-button>
+        </div> -->
+      </template>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import listFrame from '@frame/layout/admin/list-frame';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
+export default {
+  name: 'print',
+  props: {},
+  components: { listFrame },
+  data: function() {
+    return {
+      form: {},
+      termList: [],
+      batchList: [],
+      tableData: [],
+      total: 0,
+      classList: [],
+    };
+  },
+  created() {
+    this.searchinfo();
+  },
+  methods: {
+    ...trainplan({ planfetch: 'fetch' }),
+    ...classes({ classesquery: 'query' }),
+    async searchinfo() {
+      const res = await this.planfetch(this.defaultOption.planid);
+      let terms = res.data.termnum;
+      this.$set(this, `termList`, terms);
+      if (this.defaultOption.termid) {
+        this.form.termid = this.defaultOption.termid;
+        this.getBatch(this.form.termid);
+      }
+    },
+    getBatch(termid) {
+      let batchs = this.termList.filter(f => f._id === termid);
+      if (batchs.length > 0) {
+        let { batchnum } = batchs[0];
+        this.$set(this, `batchList`, batchnum);
+      }
+    },
+    async search() {
+      const res = await this.classesquery({ batchid: this.form.batchid, termid: this.form.termid });
+      this.$set(this, `tableData`, res.data);
+      this.$set(this, `total`, res.total);
+    },
+    handleSelectionChange(val) {
+      this.classList = val;
+    },
+    nameList() {
+      console.log(this.classList);
+    },
+    signList() {
+      console.log(this.classList);
+    },
+    certList() {
+      console.log(this.classList);
+    },
+    classLesson() {
+      console.log(this.classList);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    defaultOption: {
+      handler(val) {
+        this.form.termid = this.defaultOption.termid;
+        this.getBatch(this.form.termid);
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 146 - 0
src/views/train-plan/quest.vue

@@ -0,0 +1,146 @@
+<template>
+  <div id="quest">
+    <list-frame title="非常用问卷管理" @query="search" :needPag="false" :needFilter="false" :needAdd="false">
+      <el-card style="padding:10px">
+        <!-- <data-table :fields="listFields" :data="termList" :opera="opera" @edit="toOpen"></data-table> -->
+        <el-row type="flex" align="middle" justify="center" style="margin-bottom:2rem">
+          <el-col :span="8">
+            <el-alert title="请为本期设置非常用问卷" :closable="false" center></el-alert>
+          </el-col>
+        </el-row>
+        <el-form :model="form" size="mini" label-width="80px">
+          <!-- <el-form-item label="期">
+            <el-select v-model="form.termid" placeholder="请选择期数">
+              <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+            </el-select>
+          </el-form-item> -->
+          <el-form-item label="问卷列表">
+            <data-table :fields="fields" :data="questList" :select="true" :selected="selected" @handleSelect="handleSelect"></data-table>
+          </el-form-item>
+          <el-form-item label="">
+            <el-button type="primary" @click="handleSave">保存</el-button>
+          </el-form-item>
+        </el-form>
+      </el-card>
+    </list-frame>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
+const { mapActions: questionnaire } = createNamespacedHelpers('questionnaire');
+const { mapActions: termquest } = createNamespacedHelpers('termquest');
+const { mapActions: util } = createNamespacedHelpers('util');
+export default {
+  name: 'quest',
+  props: {},
+  components: { listFrame, dataTable },
+  data: function() {
+    return {
+      dialog: false,
+      opera: [
+        {
+          label: '编辑',
+          icon: 'el-icon-edit',
+          method: 'edit',
+        },
+      ],
+      listFields: [{ label: '期', prop: 'term' }],
+      fields: [
+        { label: '问卷序号', prop: 'num' },
+        { label: '问卷标题', prop: 'name' },
+      ],
+      questList: [],
+      selectInfo: {},
+      termList: [],
+      list: [],
+      form: {},
+      selected: [],
+      options: undefined,
+    };
+  },
+  created() {},
+  methods: {
+    ...util({ modelFetch: 'fetch' }),
+    ...trainplan(['fetch']),
+    ...termquest({ getList: 'fetch', createList: 'create', updateList: 'update' }),
+    ...questionnaire(['query', 'delete', 'mergeRequest']),
+    async search() {
+      let termid = _.get(this.defaultOption, 'termid');
+      if (!termid) return;
+      let res = await this.modelFetch({ model: 'termquest', termid });
+      let tsl = await this.query({ type: 1 });
+      if (this.$checkRes(tsl)) {
+        this.$set(this, `questList`, tsl.data);
+      }
+      if (this.$checkRes(res)) {
+        let ids = _.get(res.data, `questionnaireid`);
+        this.$set(this.form, `termid`, termid);
+        this.$set(this.form, `id`, _.get(res.data, `_id`));
+        if (ids) {
+          let questList = await this.mergeRequest({ method: 'fetch', data: ids });
+          this.$set(this, `selected`, questList);
+        } else this.$set(this, 'selected', []);
+      }
+    },
+    async handleSave() {
+      let duplicate = _.cloneDeep(this.form);
+      duplicate.questionnaireid = this.selected.map(i => i._id);
+      let res;
+      if (!duplicate.id) {
+        res = await this.createList(duplicate);
+      } else {
+        res = await this.updateList(duplicate);
+      }
+      if (this.$checkRes(res, '保存成功', res.errmsg || '保存失败')) {
+        this.toClose();
+      }
+    },
+    handleSelect(data) {
+      this.$set(this, `selected`, data);
+    },
+    toClose() {
+      this.selected = [];
+      this.form = {};
+      this.dialog = false;
+    },
+    async toDelete({ data }) {
+      console.log(data);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  watch: {
+    defaultOption: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (!_.get(this, 'options')) {
+          this.$set(this, `options`, _.cloneDeep(val));
+          this.search();
+        } else {
+          let ntermid = _.get(val, 'termid');
+          let otermid = _.get(this.options, 'termid');
+          if (ntermid && !_.isEqual(ntermid, otermid)) {
+            this.$set(this, `options`, _.cloneDeep(val));
+            this.search();
+          }
+        }
+      },
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 30 - 0
src/views/train-plan/remind.vue

@@ -0,0 +1,30 @@
+<template>
+  <div id="remind">
+    <p>remind</p>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'remind',
+  props: {},
+  components: {},
+  data: function() {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 138 - 0
src/views/train-plan/score.vue

@@ -0,0 +1,138 @@
+<template>
+  <div id="score">
+    <list-frame title="学生成绩查看" @query="onsearch" :total="total" :needFilter="false" :needAdd="false">
+      <el-form :inline="true" size="mini">
+        <el-form-item label="期">
+          <el-select v-model="form.termid" placeholder="请选择期数" @change="getBatch">
+            <el-option v-for="(i, index) in termList" :key="index" :label="`第${i.term}期`" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="批次">
+          <el-select v-model="form.batchid" placeholder="请先选择期数" @change="getClasses">
+            <el-option v-for="(i, index) in batchList" :key="index" :label="i.name" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="班级">
+          <el-select v-model="form.classid" placeholder="请先选择批次">
+            <el-option v-for="(i, index) in classList" :key="index" :label="i.name" :value="i._id"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="">
+          <el-button type="primary" size="mini" @click="onsearch()"> 查询</el-button>
+        </el-form-item>
+      </el-form>
+      <data-table :fields="fields" :data="list" :opera="opera" @viewtask="viewtask"> </data-table>
+    </list-frame>
+    <el-dialog title="学生作业" :visible.sync="dialog">
+      <data-table :fields="taskfields" :data="tasklist"> </data-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import listFrame from '@frame/layout/admin/list-frame';
+import dataTable from '@frame/components/data-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: student } = createNamespacedHelpers('student');
+const { mapActions: trainplan } = createNamespacedHelpers('trainplan');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+const { mapActions: group } = createNamespacedHelpers('group');
+const { mapActions: uploadtask } = createNamespacedHelpers('uploadtask');
+export default {
+  name: 'score',
+  props: {},
+  components: {
+    listFrame,
+    dataTable,
+  },
+  data: function() {
+    return {
+      classList: [],
+      batchList: [],
+      termList: [],
+      fields: [
+        { label: '学生姓名', prop: 'name' },
+        { label: '个人分', prop: 'selfscore' },
+        { label: '总分', prop: 'score' },
+        { label: '小组分', prop: 'groupscore' },
+      ],
+      opera: [
+        {
+          label: '查看学生作业',
+          icon: 'el-icon-view',
+          method: 'viewtask',
+        },
+      ],
+      form: {},
+      list: [],
+      total: 0,
+      dialog: false,
+      taskfields: [
+        { label: '科目名称', prop: 'lessonname' },
+        { label: '科目成绩', prop: 'score' },
+      ],
+      tasklist: [],
+    };
+  },
+  created() {
+    this.searchinfo();
+  },
+  methods: {
+    ...trainplan({ planfetch: 'fetch' }),
+    ...classes({ classesquery: 'query' }),
+    ...student({ stuquery: 'query', findscore: 'findscore' }),
+    async searchinfo() {
+      const res = await this.planfetch(this.defaultOption.planid);
+      let terms = res.data.termnum;
+      this.$set(this, `termList`, terms);
+      if (this.defaultOption.termid) {
+        this.form.termid = this.defaultOption.termid;
+        this.getBatch(this.form.termid);
+      }
+    },
+    getBatch(termid) {
+      let batchs = this.termList.filter(f => f._id === termid);
+      if (batchs.length > 0) {
+        let { batchnum } = batchs[0];
+        this.$set(this, `batchList`, batchnum);
+      }
+    },
+    async getClasses(batchid) {
+      const res = await this.classesquery({ batchid });
+      this.$set(this, `classList`, res.data);
+    },
+    async onsearch({ skip = 0, limit = 10, ...info } = {}) {
+      const res = await this.findscore({ skip, limit, termid: this.form.termid, classid: this.form.classid, ...info });
+      if (this.$checkRes(res)) {
+        this.$set(this, `total`, res.total);
+        this.$set(this, `list`, res.data);
+      }
+    },
+    async viewtask({ data }) {
+      this.dialog = true;
+      this.$set(this, `tasklist`, data.tasks);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    defaultOption: {
+      handler(val) {
+        this.form.termid = this.defaultOption.termid;
+        this.getBatch(this.form.termid);
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 589 - 0
src/views/train-plan/term-lesson.vue

@@ -0,0 +1,589 @@
+<template>
+  <div id="term-lesson">
+    <detail-frame :title="pageTitle" returns="/train/plan/lesson">
+      <el-row type="flex" align="middle" justify="end" class="btn_bar">
+        <el-col :span="2">
+          <el-button type="primary" size="mini" @click="allSave" v-if="!loading">保存期课表</el-button>
+        </el-col>
+      </el-row>
+      <el-row type="flex" v-loading="loading" style="min-height:500px">
+        <el-col :span="8" v-for="(i, index) in list" :key="index">
+          <lesson-table :data="i" :batch="`${index + 1}`" @lesson="toLesson" @other="toOther"></lesson-table>
+        </el-col>
+      </el-row>
+    </detail-frame>
+    <el-drawer title="课程安排" :visible.sync="lDrawer" :destroy-on-close="true" @close="toClose">
+      <lesson-form v-if="lDrawer" v-loading="dloading" :data="form" :subjectList="subjectList" :schoolList="schoolList" @save="lessonSave"></lesson-form>
+    </el-drawer>
+    <el-drawer title="其他安排" :visible.sync="oDrawer" :destroy-on-close="true" @close="toClose">
+      <class-form
+        v-if="oDrawer"
+        :data="form"
+        :locationList="locationList"
+        :headTeacherList="headTeacherList"
+        :lyTeacherList="lyTeacherList"
+        v-loading="dloading"
+        @save="lessonSave"
+      ></class-form>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import _ from 'lodash';
+import axios from 'axios';
+const moment = require('moment');
+moment.locale('zh-cn');
+import lessonTable from './parts/term-lesson-table';
+import lessonForm from './parts/term-lesson-form';
+import classForm from './parts/term-class-from';
+import detailFrame from '@frame/layout/admin/detail-frame';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions: lesson } = createNamespacedHelpers('lesson');
+const { mapActions: classes } = createNamespacedHelpers('classes');
+
+const { mapActions: location } = createNamespacedHelpers('location'); //地点
+const { mapActions: teacher } = createNamespacedHelpers('teacher'); //教师
+const { mapActions: dept } = createNamespacedHelpers('dept'); //配合教师表使用的部门表
+const { mapActions: dirPlan } = createNamespacedHelpers('dirPlan'); //班主任不能上课的列表
+const { mapActions: teaplan } = createNamespacedHelpers('teaPlan');
+const { mapActions: mapDept } = createNamespacedHelpers('dept');
+const { mapActions: director } = createNamespacedHelpers('director');
+const { mapActions: school } = createNamespacedHelpers('school'); //给选老师组件使用.这个页面请求完就不销毁了
+const { mapActions: subject } = createNamespacedHelpers('subject');
+export default {
+  name: 'term-lesson',
+  props: {},
+  components: { detailFrame, lessonTable, lessonForm, classForm },
+  data: function() {
+    return {
+      loading: true,
+      dloading: false,
+      lDrawer: false,
+      oDrawer: false,
+      form: {},
+      options: {},
+      list: [],
+      classList: [],
+      lessonList: [],
+
+      locationList: [],
+      lyTeacherList: [],
+      deptList: [],
+      headTeacherList: [],
+      schoolList: [],
+      subjectList: [],
+    };
+  },
+  async created() {
+    await this.getSettingLists();
+    // this.$watch(
+    //   'defaultOption',
+    //   val => {
+    //     if (!_.get(this, 'options')) {
+    //       this.$set(this, `options`, _.cloneDeep(val));
+    //       this.search();
+    //     } else {
+    //       let ntermid = _.get(val, 'termid');
+    //       let otermid = _.get(this.options, 'termid');
+    //       if (ntermid && !_.isEqual(ntermid, otermid)) {
+    //         this.$set(this, `options`, _.cloneDeep(val));
+    //         this.search();
+    //       }
+    //     }
+    //   },
+    //   { deep: true, immediate: true }
+    // );
+  },
+  methods: {
+    ...lesson({ getLesson: 'query', plupdate: 'pluralUpdate' }),
+    ...classes({ getClass: 'query', pcupdate: 'pluralUpdate' }),
+
+    ...subject({ getSubject: 'query' }),
+    ...director({ getDirector: 'fetch' }),
+    ...school({ getSchool: 'query' }),
+    ...location({ getLocationList: 'query' }),
+    ...teacher({ getTeacherList: 'query', getTeacher: 'fetch' }),
+    ...dirPlan({ dirQuery: 'getDirTeacher' }),
+    ...mapDept({ getDept: 'query' }),
+    ...teaplan(['findTeacher']),
+    async search() {
+      this.loading = true;
+      let termid = _.get(this.defaultOption, `termid`);
+      if (!termid) return;
+      let lessonList = [];
+      let classList = [];
+      let lessons = await this.getLesson({ termid });
+      if (this.$checkRes(lessons)) {
+        lessonList = lessons.data;
+        this.$set(this, `lessonList`, lessonList);
+      }
+      let classes = await this.getClass({ termid });
+      if (this.$checkRes(classes)) {
+        classList = classes.data;
+        for (let cla of classList) {
+          cla = await this.searchClassPerson(cla);
+        }
+        this.$set(this, `classList`, classList);
+      }
+      let arr = classList.map(i => {
+        let r = lessonList.find(f => f.classid == i._id);
+        if (r) {
+          let { lessons = [] } = r;
+          i.lessons = lessons;
+        }
+        return i;
+      });
+      arr = _.flatten(
+        _.toPairs(
+          _.groupBy(
+            arr.sort((a, b) => a - b),
+            `batch`
+          )
+        )
+      ).filter(f => _.isArray(f));
+      arr = arr.map((i, index) => {
+        // 先转换lessons,此表的情况是:小批次下的班级都是一样的课程,只有教师,教室不一样
+        let a = this.changeLesson(i);
+        let last = this.setAllday(_.last(a));
+        let ht = this.setProp(a, 'headteacher');
+        let lyt = this.setProp(a, 'lyteacher');
+        let js = this.setProp(a, 'jslocation');
+        for (let ind = 0; ind < index; ind++) {
+          a.unshift({});
+        }
+        //找到该班额班主任和礼仪课教师
+        a.push(ht);
+        a.push(lyt);
+        a.push(js);
+        for (let ind = 2; ind > index; ind--) {
+          a.push({});
+        }
+        a.push(last);
+        return a;
+      });
+      this.$set(this, `list`, arr);
+      this.loading = false;
+    },
+    //课程数据=>页面数据形式
+    changeLesson(array) {
+      //{batch,subname(同一批每天课程一样),subid, classid_1,classid_2, teaid_1, teaid_2, lessonid_1,lessonid_2 }
+      //i(map循环)中获取的参数: batch;classid_*;
+      //lesson中获取的参数: subname;subid_*;teaid_*;lessonid_*
+      let duplicate = _.cloneDeep(array);
+      //按日期排序
+      let res = duplicate.map((i, index) => {
+        let les = _.flatten(
+          _.toPairs(
+            _.groupBy(
+              i.lessons.sort((a, b) => moment(a.date).format('X') - moment(b.date).format('X')),
+              'date'
+            )
+          )
+        ).filter(f => _.isArray(f));
+        // console.log(i);
+        // console.log(les);
+        //每天按时间排序
+        les = les.map(lesi => _.orderBy(lesi, 'time', 'asc'));
+        let t = les.map(li => {
+          let r = li.find(f => f.subid);
+          // console.log(r);
+          let obj;
+          //有课程,则显示课程;没有,显示安排的活动,反正都是用subname字段
+          if (r) {
+            let { subname, subid, teaid, teaname, _id: lessonid, date } = r;
+            obj = { subname, subid };
+            obj[`id_${index + 1}`] = teaid;
+            obj[`name_${index + 1}`] = teaname;
+            obj[`lessonid_${index + 1}`] = lessonid;
+            obj[`date`] = date;
+          } else {
+            let { subname, _id: lessonid, date } = _.head(li);
+            obj = { subname };
+            obj[`lessonid_${index + 1}`] = lessonid;
+            obj[`date`] = date;
+          }
+          obj.batch = i.batch;
+          obj[`classid_${index + 1}`] = i._id;
+          obj['type'] = 'lesson';
+          return obj;
+        });
+        return t;
+      });
+      let l = _.get(_.head(res), 'length');
+      let arr = [];
+      for (let i = 0; i < l; i++) {
+        let obj = {};
+        for (const item of res) {
+          obj = { ...obj, ...item[i] };
+        }
+        arr.push(obj);
+      }
+      return arr;
+    },
+    //班主任/礼仪课老师=>页面数据形式
+    setProp(data, type) {
+      let head = _.head(data);
+      let res = { type };
+      res.date = type == 'headteacher' ? '班主任' : type == 'lyteacher' ? '礼仪课教师' : '教室';
+      let keys = Object.keys(head).filter(f => f.includes('classid'));
+      for (const key of keys) {
+        let cla_id = head[key];
+        let i = key.match(/\d+(.\d+)?/g)[0];
+        let r = this.classList.find(f => f._id == cla_id);
+        if (r) {
+          res[`id_${i}`] = _.get(r, `${type}id`);
+          res[`name_${i}`] = _.get(r, `${type}name`);
+          res[key] = cla_id;
+        }
+      }
+      return res;
+    },
+    setAllday(data) {
+      let ckeys = Object.keys(data).filter(f => f.includes('classid'));
+      let lkeys = Object.keys(data).filter(f => f.includes('lessonid'));
+      let arr = [];
+      for (let i = 0; i < ckeys.length; i++) {
+        if (data[`${ckeys[i]}`] && data[`${lkeys[i]}`]) {
+          let cla = this.classList.find(f => f._id == data[ckeys[i]]);
+          if (cla) {
+            let { lessons } = cla;
+            let r = lessons.find(f => f._id == data[lkeys[i]]);
+            if (r) {
+              let o = {};
+              o[ckeys[i]] = data[ckeys[i]];
+              o[`day`] = _.get(r, 'day');
+              arr.push(o);
+            }
+          }
+        }
+      }
+      let obj = { type: 'allday' };
+      let r = arr.every(f => f.day == '0');
+      if (r) obj.date = '全天';
+      else {
+        r = arr.every(f => f.day == '1');
+        if (r) obj.date = '半天';
+        else {
+          let str = '';
+          arr.map((i, index) => {
+            str += i.day == '0' ? '全天' : '半天';
+            if (index != arr.length - 1) str += '/';
+          });
+          obj.date = str;
+        }
+      }
+      return obj;
+    },
+    async getSettingLists() {
+      let res;
+      let arr = [];
+      if (this.locationList.length <= 0) {
+        arr.push(this.getLocationList());
+      }
+      if (this.lyTeacherList.length <= 0) {
+        arr.push(this.getTeacherList({ islyteacher: '1', status: '4' }));
+      }
+      if (this.schoolList.length <= 0) {
+        arr.push(this.getSchool());
+      }
+      if (this.deptList.length <= 0) {
+        arr.push(this.getDept());
+      }
+      if (this.subjectList.length <= 0) {
+        arr.push(this.getSubject());
+      }
+      axios.all(arr).then(
+        axios.spread((r1, r2, r3, r4, r5) => {
+          if (r1) this.$set(this, `locationList`, r1.data);
+          if (r2) this.$set(this, `lyTeacherList`, r2.data);
+          if (r3) this.$set(this, `schoolList`, r3.data);
+          if (r4) this.$set(this, `deptList`, r4.data);
+          if (r5) this.$set(this, `subjectList`, r5.data);
+        })
+      );
+    },
+    //初始化,将每个班级的班主任,礼仪课老师,教室id=>名称
+    async searchClassPerson(obj) {
+      let arr = [];
+      if (_.get(obj, 'jslocationid')) {
+        let r = this.locationList.find(f => f._id == _.get(obj, 'jslocationid'));
+        if (r) obj.jslocationname = _.get(r, `name`);
+      }
+      if (_.get(obj, 'headteacherid')) {
+        let r = await this.getDirector(_.get(obj, 'headteacherid'));
+        if (this.$checkRes(r)) obj.headteachername = _.get(r.data, `name`);
+      }
+      if (_.get(obj, 'lyteacherid')) {
+        let r = await this.getDirector(_.get(obj, 'lyteacherid'));
+        if (this.$checkRes(r) && r.data) {
+          obj.lyteachername = _.get(r.data, `name`);
+        } else {
+          r = await this.getTeacher(_.get(obj, 'lyteacherid'));
+          if (this.$checkRes(r) && r.data) obj.lyteachername = _.get(r.data, `name`);
+        }
+      }
+      // if (_.get(obj, 'jslocationid')) {
+      //   arr.push(this.modelFetch({ model: 'director', id: _.get(obj, 'jslocationid') }));
+      // }
+    },
+    toLesson(data) {
+      let { is_last, batch } = data;
+      if (is_last) {
+        let last = _.last(this.list[batch - 1]);
+        let { date } = last;
+        data.allday = date;
+      }
+      this.$set(this, `form`, data);
+      this.lDrawer = true;
+    },
+    async toOther(data) {
+      this.oDrawer = true;
+      this.dloading = true;
+      let { type } = data;
+      if (type != 'jslocation') await this.getHeadTeacher(data);
+      this.$set(this, `form`, data);
+      this.dloading = false;
+    },
+    lessonSave(data) {
+      let { index, batch, is_last, allday, ...info } = data;
+      if (is_last) {
+        //TODO 改变半天/全天
+        this.$set(this.list[batch - 1], this.list[batch - 1].length - 1, { date: allday, type: 'allday' });
+      }
+      this.$set(this.list[batch - 1], index, { batch, ...info });
+      this.toClose();
+    },
+    toClose() {
+      this.lDrawer = false;
+      this.oDrawer = false;
+      this.form = {};
+    },
+    async getHeadTeacher(data) {
+      let batchid;
+      let { type } = data;
+      let { planid, termid } = this.options;
+      let keys = Object.keys(data).filter(f => f.includes('classid'));
+      if (keys.length <= 0) return;
+      let cla = this.classList.find(f => f._id == data[keys[0]]);
+      if (!cla) return;
+      batchid = cla.batchid;
+      let res = await this.findTeacher({ planid, termid, batchid });
+      if (this.$checkRes(res)) {
+        let duplicate = _.cloneDeep(res.data);
+        if (type == 'headteacher') {
+          let group = _.groupBy(res.data, 'department');
+          let keys = Object.keys(group);
+          let arr = keys.map(key => {
+            let r = this.deptList.find(f => f.id == key);
+            let obj = {};
+            if (r) {
+              obj.name = r.name;
+              obj.list = group[key];
+            }
+            return obj;
+          });
+          this.$set(this, `headTeacherList`, arr);
+        } else if (type == 'lyteacher') {
+          duplicate = duplicate.filter(f => f.islyteacher == '1');
+          this.$set(this, `lyTeacherList`, _.uniqBy([...this.lyTeacherList, ...duplicate], '_id'));
+        }
+      }
+    },
+    //全部保存
+    allSave() {
+      //match(/\d+(.\d+)?/g)[0];
+      let duplicate = _.cloneDeep(this.list);
+      let dClass = _.cloneDeep(this.classList);
+      let dLesson = _.cloneDeep(this.lessonList);
+      let data = duplicate[0];
+      duplicate.map(data => {
+        // console.log(data);
+        let day = _.get(_.last(data), 'date', '全天');
+        day = day == '全天' ? '0' : '1';
+        //先以班级分组数据
+        let batch = [];
+        for (const obj of data) {
+          let keys = Object.keys(obj);
+          let r = _.uniq(_.flatten(_.compact(keys.map(key => key.match(/\d+(.\d+)?/g)))));
+          // console.log(`r`);
+          // console.log(r);
+          let notClassKey = keys.filter(f => {
+            for (const num of r) {
+              if (f.includes(num)) return false;
+            }
+            return true;
+          });
+          //获得这条数据公共属性
+          let common = {};
+          notClassKey.map(key => {
+            let o = {};
+            common[key] = obj[key];
+          });
+          // console.log(`common:`);
+          // console.log(common);
+          //获取各班设置
+          let self = [];
+          //获取需要分组的字段
+          let classKey = keys.filter(f => {
+            for (const num of r) {
+              if (f.includes(num)) return true;
+            }
+            return false;
+          });
+          // console.log(`classKey:和各个班级有关的key`);
+          // console.log(classKey);
+          //将分组字段的词根筛出来
+          let words = _.uniq(classKey.map(i => i.split('_')[0]));
+          // console.log(`words:classKey的词根`);
+          // console.log(words);
+          let wvs = [];
+          for (const num of r) {
+            let res = {};
+            words.map(word => {
+              res[word] = obj[`${word}_${num}`];
+            });
+            wvs.push(res);
+          }
+          // console.log(`wv:整理出以班为一个object的数组`);
+          wvs = wvs.map(i => {
+            return { ...i, ...common };
+          });
+          // console.log(wvs);
+          batch = [...batch, ...wvs];
+          // break;
+          // let indexs = _.uniq(keys.map(key => key.match(/\d+(.\d+)?/g)[0]));
+          // console.log(indexs);
+        }
+        // console.log(batch);
+        //按班分组,并将最后一天课按照全天/半天处理好
+        //先按classid分组 => [object] , object => array = [[string,array],...] => 数组维度-1 => [string,array] => 过滤出 数据类型是数组的数据 => [array]
+        let claArr = _.flatten(_.toPairs(_.groupBy(batch, 'classid'))).filter(f => _.isArray(f));
+        claArr = claArr.map(i => {
+          let shouldDeal = i.filter(f => f.type == 'lesson');
+          shouldDeal = _.orderBy(shouldDeal, 'date', 'asc');
+          let last = _.last(shouldDeal);
+          last = { ...last, day };
+          shouldDeal.splice(shouldDeal.length - 1, 1, last);
+          //处理班级的三个设置
+          let noDeal = i.filter(f => f.type !== 'lesson');
+          //此方法返回值为已经修改(班主任,礼仪教师,教室的id)后的班级列表(顺带也把meta除了),这个dClass可以作为修改班级的数据,剩下课表没出来
+          dClass = this.proClass(dClass, noDeal);
+          return shouldDeal;
+        });
+        //接下来改课表,之前整理过,所以课表除了改过的那些数据外,还有2份,一份在班级列表中(dClass中的lessons);还有一份原始数据在课程列表中(dLessons中的lessons)
+        //dClass和dLesson还有claArr可以通过classid建立联系确定唯一
+        //现在claArr中的结构是:[[{}],[{}],...] 内部第一层数组按班级分类,也就是说里面的{}都是一个班的数据
+        claArr.map(i => {
+          let cla_id = _.get(_.head(i), 'classid');
+          let les = dLesson.find(f => f.classid == cla_id);
+          let lesi = dLesson.findIndex(f => f.classid == cla_id);
+          if (!les) return;
+          //确定该班的原数据组(les) 和 现数据组(i)
+          //此处原数据的数据形式:{key(日期):array,...}
+          les = _.get(les, 'lessons', []);
+          les = _.groupBy(_.orderBy(les, ['date', 'time'], 'asc'), 'date');
+          //循环现数据,然后利用日期(date)缩小范围,然后过滤出 当前现数据所对应的原数据
+          i.map(nowi => {
+            let dateStr = _.get(nowi, 'date');
+            if (!dateStr) return;
+            //缩小范围,获取该天的原数据
+            let darr = les[dateStr];
+            //找到该条数据,进行替换
+            let r = darr.find(f => f._id == nowi.lessonid);
+            let ri = darr.findIndex(f => f._id == nowi.lessonid);
+            let o = {};
+            if (!r) return;
+            //数据还原与合并
+            let { _id, date, time } = r;
+            o = this.lessonDataReturn(nowi);
+            o = { ...o, _id, date, time };
+            //sub替换:此处靠数据关联=>自动排过课表后,有课的那天的array中一定会有2条数据,我们只改变第一条数据,第二条数据随着第一条数据改变
+            //所以此处会出现的问题就是:如果课表模板没有一天安排2条课的数据,那么此处会出现问题,不过这都是操作人干的.和程序无关
+            darr.splice(ri, 1, o);
+            let { subid, subname, teaid, teaname } = o;
+            if (subid) {
+              let srarr = darr.filter(f => f.subid && f._id != _id);
+              for (const sr of srarr) {
+                let sri = darr.findIndex(f => f._id == sr._id);
+                let sro = { ...sr, subid, subname, teaid, teaname };
+                darr.splice(sri, 1, sro);
+              }
+            }
+          });
+          let dres = _.cloneDeep(les);
+          dres = _.flatten(_.flatten(_.toPairs(dres)).filter(f => _.isArray(f)));
+          dLesson[lesi].lessons = dres;
+        });
+      });
+      dClass = dClass.map(i => _.omit(i, ['lessons']));
+      dLesson = dLesson.map(i => _.omit(i, ['meta']));
+      //最后数据 dClass,班级列表; dLesson 课程列表
+      let axiosArr = [];
+      axiosArr.push(this.plupdate(dLesson));
+      axiosArr.push(this.pcupdate(dClass));
+      axios.all(axiosArr).then(
+        axios.spread((rl, rc) => {
+          if (!rl && rl.errcode != '0') {
+            this.$message.error(rl.errmsg);
+            return;
+          }
+          if (!rc && rc.errcode != '0') {
+            this.$message.error(rc.errmsg);
+            return;
+          }
+          this.$message.success('保存成功');
+        })
+      );
+    },
+    lessonDataReturn(data) {
+      let { id: teaid, name: teaname, subid, subname, day = 0 } = data;
+      return { teaid, teaname, subid, subname, day };
+    },
+    proClass(classes, data) {
+      let obj = {};
+      data.map(i => {
+        obj.classid = i.classid;
+        obj[`${i.type}id`] = i.id;
+      });
+      classes.splice(
+        classes.findIndex(f => f._id == obj.classid),
+        1,
+        { ...classes.find(f => f._id == obj.classid), ...obj }
+      );
+      classes = classes.map(i => _.omit(i, ['meta']));
+      return classes;
+    },
+  },
+  watch: {
+    defaultOption: {
+      immediate: true,
+      deep: true,
+      handler(val) {
+        if (!_.get(this, 'options')) {
+          this.$set(this, `options`, _.cloneDeep(val));
+          this.search();
+        } else {
+          let ntermid = _.get(val, 'termid');
+          let otermid = _.get(this.options, 'termid');
+          if (ntermid && !_.isEqual(ntermid, otermid)) {
+            this.$set(this, `options`, _.cloneDeep(val));
+            this.search();
+          }
+        }
+      },
+    },
+  },
+  computed: {
+    ...mapState(['user', 'defaultOption']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 7 - 18
vue.config.js

@@ -43,24 +43,13 @@ module.exports = {
         changeOrigin: true,
         ws: true,
       },
-      '/admin/center': {
-        target: 'http://free.liaoningdoupo.com',
-      },
-      // '/api/train/department': {
-      //   target: 'http://free.liaoningdoupo.com',
-      // },
-
-      '/admin/director': {
-        target: 'http://free.liaoningdoupo.com',
-      },
-      '/admin/teacher': {
-        target: 'http://free.liaoningdoupo.com',
-      },
-      '/admin/student': {
-        target: 'http://free.liaoningdoupo.com',
-      },
-      '/admin/school': {
-        target: 'http://free.liaoningdoupo.com',
+      '/jh': { //聚合科技API
+        target: 'http://v.juhe.cn',
+        changeOrigin: true,
+        ws: true,
+        pathRewrite: {
+          '^/jh': '',
+        },
       },
     },
   },