Browse Source

修改培训计划

zs 9 months ago
parent
commit
cb529a3287

+ 2 - 1
package.json

@@ -33,7 +33,8 @@
     "vue-router": "^3.3.4",
     "vuex": "^3.1.2",
     "wangeditor": "^3.1.1",
-    "xlsx": "^0.16.2"
+    "x-data-spreadsheet": "^1.1.9",
+    "xlsx": "^0.16.9"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "^4.1.0",

File diff suppressed because it is too large
+ 2892 - 0
src/views/new-plan/arrange/jsondata.js


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

@@ -0,0 +1,814 @@
+<template>
+  <div id="school-arrange">
+    <detail-frame :title="pageTitle">
+      <!-- 大表 -->
+      <el-card v-loading="!already" style="min-height:500px">
+        <el-row type="flex" align="middle" justify="end" style="padding-bottom:10px">
+          <el-col :span="2">
+            <el-button type="success" size="mini" @click="toExcel">导出学校计划</el-button>
+          </el-col>
+          <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="toConfirm">一键分配</el-button>
+          </el-col>
+        </el-row>
+        <a-table
+          ref="planTabel"
+          :termList="termList"
+          :data="list"
+          @cellClick="cellClick"
+          :getDesignate="getDesignate"
+          :changeRange="changeRange"
+          :proAffix="proAffix"
+          :classTypeList="classTypeList"
+          :schStuList="schStuList"
+        ></a-table>
+      </el-card>
+    </detail-frame>
+
+    <el-drawer :before-close="handleClose" :visible.sync="drawer" direction="rtl" title="名额分配" @close="toClose">
+      <el-row type="flex" align="top" justify="center" style="padding:20px" :gutter="20">
+        <el-col :span="12">
+          <el-card>
+            <el-form size="mini">
+              <el-form-item label="学校">{{ getProp(form, 'sch.name') }}</el-form-item>
+              <el-form-item label="学校层次">{{ getProp(form, 'sch.level') }}</el-form-item>
+              <el-form-item label="需要派车">{{ getProp(form, 'sch.hascar') }}</el-form-item>
+              <el-form-item label="总名额">{{ getProp(form, 'sch.number') }}</el-form-item>
+              <el-form-item label="剩余名额">{{ getProp(form, '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="期数">{{ getProp(form, 'info.term') }}</el-form-item>
+              <el-form-item label="批次">{{ getProp(form, 'info.batch') }}</el-form-item>
+              <el-form-item label="班级类型">{{ getProp(form, 'info.classtype') }}</el-form-item>
+              <el-form-item label="开始时间">{{ getProp(form, 'info.start') }}</el-form-item>
+              <el-form-item label="结束时间">{{ getProp(form, 'info.end') }}</el-form-item>
+              <el-form-item label="总名额">{{ getProp(form, 'info.bpt') }}</el-form-item>
+              <el-form-item label="剩余名额">{{ getProp(form, 'info.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="请输入分配给该期的名额"
+                  :min="0"
+                  style="width:250px"
+                  @change="dComputed"
+                ></el-input-number>
+                <!-- :max="dGetMax(form)" -->
+              </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>
+  </div>
+</template>
+
+<script>
+import aTable from './table/table';
+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');
+const { mapActions: classtype } = createNamespacedHelpers('classtype');
+const { mapActions: student } = createNamespacedHelpers('student');
+// 表中变量:
+// batch-${batchid}:${batch}
+// batch_type-${batchid}:${type} 这期是什么类型的班级
+// batch_total-${batchid}:${number}
+// car_total-${batchid}: ${carnum}
+// term-${termid}:${term}
+// term_batch-${batchid}: ${termid} 关键变量,拿到它,就可以找到配套的其他所有变量
+// arrange_id-${batchid}: ${_id}
+// remark-${batchid}: ${remark}
+export default {
+  name: 'school-arrange',
+  props: {},
+  components: { detailFrame, aTable },
+  data: () => {
+    return {
+      list: [],
+      plan: {},
+      termList: [],
+      totalList: [],
+      carList: [],
+      template: {},
+      dialog: false,
+      drawer: false,
+      form: {},
+      activeName: '1',
+      already: false,
+      options: undefined,
+      classTypeList: [],
+      // 学校学生
+      schStuList: [],
+    };
+  },
+  async created() {
+    await this.getOtherList();
+    await this.toGetTrainPlan();
+    await this.toGetTrainTemplate();
+    await this.getSchool();
+    // this.already = true;
+  },
+  methods: {
+    ...util({ modelFetch: 'fetch' }),
+    ...student({ getSchoolStudent: 'schoolStudent' }),
+    ...trainPlan({ getTrainPlan: 'fetch', exportPlan: 'exportSchoolPlan' }),
+    ...school(['query', 'findSchool']),
+    ...schPlan({ schPlanQuery: 'query', createSchPlan: 'create', updateSchPlan: 'update', setSchPlan: 'schArrange' }),
+    ...classtype({ getClassType: 'query' }),
+    handleClose(done) {
+      this.$confirm('确认关闭?')
+        .then(_ => {
+          done();
+        })
+        .catch(_ => {});
+    },
+    //请求,处理学校列表
+    async getSchool() {
+      const res = await this.findSchool();
+      if (this.$checkRes(res)) {
+        let nd = res.data.map(i => (i = _.omit(i, ['meta', 'id', '_id', 'logourl', 'number'])));
+        nd = this.setSchoolNumber(nd);
+        this.$set(this, `list`, nd);
+        await this.getSchoolPlan();
+      }
+    },
+    //自动安排提示确认
+    toConfirm() {
+      this.$confirm('此操作将会重置之前排好的数据,若之前已经安排好,请注意使用! 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {
+          this.toReset();
+        })
+        .catch(() => {});
+    },
+    //自动安排
+    async toArrange() {
+      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));
+      //批次分配的人数 batch_total-${batchid} = 人数
+      for (let sch of school) {
+        for (let t of termList) {
+          for (let b of t.batchnum) {
+            // 用对应的 b 的 remaining 去判断 这期是否还有剩余
+            if (b.remaining && b.remaining <= 0) continue;
+            // break;
+            // 根据 b 中的classtype 取出对应的 remaining
+            // 用对应的 remaining 去分配,处理完后把结果赋回对应的 remaining
+
+            // 判断时间是否在 学校范围内
+            let { result } = this.$tqInRange(b.start, b.end, sch.daterange);
+            if (result) {
+              let { classtype } = b;
+              // 获取对应班级类型的 剩余人数
+              let schremaining = _.get(sch, this.proAffix(classtype, 'remaining'), 0);
+              if (schremaining <= 0) continue;
+              // 学校名额>本批次剩余名额:
+              if (schremaining > b.remaining) {
+                schremaining = schremaining - b.remaining; //学校剩余名额 = 学校剩余名额(原) - 本批次剩余名额
+                if (b.remaining > 0) {
+                  this.setNumOthers(sch, b, b.remaining);
+                }
+                b.remaining = 0; //本批次剩余名额=0;
+                sch[this.proAffix(classtype, 'remaining')] = schremaining;
+                continue;
+              } else {
+                //学校名额<=本批次剩余名额
+                b.remaining = b.remaining - schremaining; //本批次剩余名额 = 本批次剩余名额(原) - 学校剩余名额;
+                if (schremaining > 0) {
+                  this.setNumOthers(sch, b, schremaining);
+                }
+                schremaining = 0; //学校剩余名额 = 0
+                sch[this.proAffix(classtype, 'remaining')] = schremaining;
+                //让循环自然结束就好了
+                continue;
+              }
+            }
+          }
+        }
+        sch = this.computedSchRemaining(sch);
+        let index = _.findIndex(this.list, f => f.code == sch.code);
+        if (index >= 0) this.$set(this.list, index, sch);
+      }
+      this.$set(this, `termList`, termList);
+      this.getCarTotal();
+      this.$message.success('分配完成');
+    },
+    //保存整体计划
+    async toSave() {
+      //修改学校上报计划,整理数据
+      //备注
+      let remarks = this.$refs.planTabel.remarks;
+      let duplicate = _.cloneDeep(this.list);
+      duplicate = duplicate.filter(f => Object.keys(f).find(key => key.includes('batch_total')));
+      let schPlan = duplicate.map((i, index) => {
+        let plan = _.cloneDeep(_.get(i, 'plan', {}));
+        let arrange = [];
+        //arrange [object] object: _id;term;termid;batch;batchid;number;carnum
+        let arr_term_batch = Object.keys(i).filter(f => f.includes('term_batch')); //key的id为批次id;值为期id
+        arr_term_batch.map(key => {
+          let { value: batchid, affix } = this.proAffix(key);
+          let termid = i[key];
+          let term = i[this.proAffix(termid, 'term')];
+          let batch = i[this.proAffix(batchid, 'batch')];
+          let number = i[this.proAffix(batchid, 'batch_total')] || 0;
+          let carnum = i[this.proAffix(batchid, 'car_total')] || 0;
+          //数据的原_id arrange_id-${batchid}
+          let _id = i[this.proAffix(batchid, 'arrange_id')];
+          //取出有备注的批次备注
+          let remark = remarks[batchid];
+          //去掉值为undefined的key和value
+          arrange.push(_.pickBy({ _id, term, termid, batch, batchid, number, carnum, remark }, _.identity));
+        });
+        plan.arrange = arrange;
+        return plan;
+        // }
+      });
+      // this.$set(this, `schPlan`, schPlan);
+      schPlan = _.compact(schPlan);
+      const res = await this.setSchPlan(schPlan);
+      this.$checkRes(res, '保存成功', res.errmsg);
+    },
+    cellClick(data) {
+      this.drawer = true;
+      let { sch, info } = data;
+      let { classtype } = info;
+      // TODO车辆没改
+      let keys = Object.keys(sch).filter(f => !f.includes('number') || !f.includes('remaining'));
+      let nsch = _.pick(sch, keys);
+      nsch[`remaining`] = sch[this.proAffix(classtype, 'remaining')];
+      nsch[`number`] = sch[this.proAffix(classtype, 'number')];
+      data.sch = nsch;
+      data.osch = sch;
+      this.$set(this, `form`, data);
+    },
+    toSend() {
+      let num = _.get(this.form, 'num');
+      let sch = _.get(this.form, 'sch'); //修改部分
+      let osch = _.get(this.form, 'osch'); //原来所有的学校内容
+      let info = _.get(this.form, 'info');
+      let { classtype } = info; //获取班级类型,用来减去对应的人数
+      sch = this.setNumOthers(sch, info, num);
+      let { remaining } = sch;
+      sch.number = osch.number;
+      sch[this.proAffix(classtype, 'remaining')] = remaining;
+      let keys = Object.keys(sch).filter(f => f.includes('remaining-'));
+      let re = keys.reduce((p, n) => p + sch[n] * 1, 0);
+      sch.remaining = re;
+      let si = _.findIndex(this.list, f => f.code == sch.code);
+      let termIndex = this.termList.findIndex(f => f._id == info.termid);
+      let batchIndex = this.termList.find(f => f._id == info.termid).batchnum.findIndex(f => f._id == info._id);
+      if (termIndex >= 0 && batchIndex >= 0 && si >= 0) {
+        this.$set(this.list, si, sch);
+        this.$set(this.termList[termIndex].batchnum, batchIndex, info);
+      } else this.$message.error('数据处理错误,请反馈该bug:未找到指定学校或指定期或指定批次');
+      this.toClose();
+      this.getCarTotal();
+    },
+    //设置人数的配套信息
+    setNumOthers(sch, b, number) {
+      sch[this.proAffix(b._id, 'term_batch')] = b.termid; //记录这期和批次的对应关系 term_batch-${batchid} = termid
+      sch[this.proAffix(b.termid, 'term')] = b.term; //记录该期 term-${termid} = term
+      sch[this.proAffix(b._id, 'batch')] = b.batch; //记录该批次 batch-${batchid} = batch
+      sch[this.proAffix(b._id, 'batch_total')] = number; //学校哪一批次有多少人,表格上显示的
+      // sch[this.proAffix(b._id, 'batch_type')] = b.classtype; //学校哪一批次有多少人,表格上显示的
+      return sch;
+    },
+    //将学校上报的月份数组[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) {
+        //长度大于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 duplicate = _.cloneDeep(res.data);
+        let list = this.list.map((i, index) => {
+          let plan = duplicate.find(f => f.schid == i.code);
+          if (plan) {
+            let { arrange, daterange } = plan;
+            i.daterange = daterange;
+            //改变上报时间格式
+            i = this.changeRange(i);
+            if (arrange)
+              for (const p of arrange) {
+                let { termid, term, batchid, batch, number, carnum, _id, remark } = p;
+                if (!parseInt(number)) continue;
+                let obj = {};
+                obj[this.proAffix(batchid, 'term_batch')] = termid;
+                obj[this.proAffix(batchid, 'batch')] = batch;
+                obj[this.proAffix(batchid, 'batch_total')] = parseInt(number) || 0;
+                obj[this.proAffix(batchid, 'car_total')] = parseInt(carnum) || 0;
+                obj[this.proAffix(termid, 'term')] = term;
+                obj[this.proAffix(batchid, 'arrange_id')] = _id;
+                obj[this.proAffix(batchid, 'remark')] = remark;
+                i = { ...i, ...obj };
+                i = _.pickBy(i, _.identity);
+              }
+            i.plan = plan;
+          }
+          return i;
+        });
+        this.$set(this, `list`, list);
+        //计算下期列表的情况
+        this.getTotal();
+      }
+      this.$set(this, `already`, true);
+    },
+    //回显计算
+    getTotal() {
+      this.computedTermList();
+      this.computedSchool();
+    },
+    //检查已分配的数据是否与现在的期对应
+    //TODO term=>arrange {termid,batchid都需要检测}
+    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;
+    },
+    //整理批次(事件)列表
+    getBatchList() {
+      let events = this.resetEvents();
+      //计算每批次人数上限
+      let res = events.map(i => {
+        i.batchnum = i.batchnum.map(b => {
+          //因为要分类型去分配,所以remaining要有多种类型,都带着自己的编号在后面
+          b.remaining = b.class.reduce((p, n) => p + (n.number || 0) * 1, 0);
+          b.bpt = _.clone(b.remaining);
+          b.classtype = _.get(_.head(b.class), 'type');
+          return b;
+        });
+        i.batchnum = _.orderBy(i.batchnum, ['start'], ['asc']);
+        if (i.term == '360') console.log(i.batchnum);
+        return i; //bpt:该批次总人数 ,remaining:剩余人数
+      });
+      this.$set(this, `termList`, res);
+    },
+    //整理计划=>和日历的 events 一样
+    resetEvents() {
+      let events = _.cloneDeep(_.get(this.plan, 'termnum', []));
+      events = events.map(i => {
+        let tobj = { term: i.term, termid: i._id };
+        i.batchnum = i.batchnum.map(bat => {
+          let { startdate: start, enddate: end, ...info } = bat;
+          let bobj = { ...info, ...tobj, start, end };
+          return bobj;
+        });
+        return i;
+      });
+      return events;
+    },
+    //车辆统计
+    getCarTotal() {
+      let carpnum = this.template.carpnum * 1 || 53;
+      let list = this.list.map(i => {
+        //先排除学校就不需要车的
+        if (i.hascar == '1') {
+          //过滤出所有已分配批次的人数的 key
+          let keys = Object.keys(i).filter(f => f.includes('batch_total'));
+          keys.map(key => {
+            //将key 拆开 词缀固定不需要, 获得value=>batchid
+            let { value: batchid } = this.proAffix(key);
+            // 从 termList 找到该批次,找到批次中的 classtype 然后去查询classTypeList,看看这个类型的班级需不要派车
+            let ct;
+            for (const term of this.termList) {
+              let r = term.batchnum.find(f => f._id == batchid);
+              if (r) {
+                ct = _.get(r, `classtype`);
+                break;
+              }
+            }
+            if (ct) {
+              let r = this.classTypeList.find(f => f.code == ct);
+              if (r) {
+                let hascar = _.get(r, 'hascar');
+                if (hascar == '1') {
+                  let carnum = Math.ceil(i[key] / carpnum);
+                  i[this.proAffix(batchid, 'car_total')] = carnum;
+                }
+              }
+            }
+          });
+        }
+        return i;
+      });
+      this.$set(this, `list`, list);
+    },
+    //分配重置
+    async toReset() {
+      await this.getBatchList();
+      await this.resetSchool();
+      this.toArrange();
+    },
+    //抽屉名额响应式计算
+    dComputed(cv = 0, ov = 0) {
+      let sr = _.get(this.form.sch, 'remaining');
+      let pr = _.get(this.form.info, 'remaining');
+      let remainder = cv - ov;
+      this.$set(this.form.sch, `remaining`, sr - remainder);
+      this.$set(this.form.info, `remaining`, pr - remainder);
+    },
+    //计算抽屉最大值
+    dGetMax(data) {
+      if (!this.drawer) return 0;
+      let { num = 0, info, sch } = data;
+      let { remaining: pl, bpt, _id: batchid, classtype } = info; //批次限制,批次总限制,批次id
+      let { remaining: sl, number: spt, code } = sch; ////学校限制
+      let res = 0;
+      //如果学校没有名额了,那么数字框内的最大值就是当前的值
+      if (sl == 0) {
+        console.warn('学校名额没了');
+        res = num;
+      }
+      //根据这个表来看,2种算法
+      // 1) 纵向计算(按批次算) 批次总限制 - 这批次  除了这个学校外  所有学校分配的人数之和
+      // 2) 横向计算(按学校算) 学校总限制 - 这个学校 除了当前批次外 所有批次的分配的人数之和
+      //如果批次限制 大于 学校限制:那么最大值 = 横向计算结果
+      if (pl > sl) {
+        //需要过滤出这个在符合当前批次的班级类型的所有学生的key=>所有批次列表
+        let keys = Object.keys(sch)
+          .filter(f => f.includes('batch_total-'))
+          .map(i => {
+            let { value } = this.proAffix(i);
+            return value;
+          });
+        // 接着过滤,过滤出classtype符合条件的批次
+        let r = keys.filter(f => {
+          if (batchid == f) return false;
+          let midr = false;
+          for (const term of this.termList) {
+            //找到该期
+            let batch = term.batchnum.find(bf => bf._id == f);
+            if (batch) {
+              //判断该期的类型与现在要编辑的期的类型是不是一致的
+              if (batch.classtype == classtype) {
+                midr = true;
+                break;
+              }
+            }
+          }
+          return midr;
+        });
+        // 过滤后的批次都是和这次编辑相同班级类型的班级
+        r = r.map(i => this.proAffix(i, 'batch_total'));
+        let obj = _.pick(sch, r);
+        let ot = Object.values(obj).reduce((prev, next) => prev + next * 1, 0);
+        res = spt * 1 - ot || 0;
+      }
+      //如果批次限制 小于等于 学校限制:那么最大值 = 批次总限制 - 纵向计算结果
+      if (pl <= sl) {
+        let bTotal = this.list.reduce((prev, next) => {
+          let num = 0;
+          if (next.code != code) {
+            num = next[this.proAffix(batchid, 'batch_total')] * 1 || 0;
+          }
+
+          return prev + num;
+        }, 0);
+        res = bpt - bTotal;
+      }
+      return res;
+    },
+    //关闭抽屉
+    toClose() {
+      this.drawer = false;
+      this.form = {};
+    },
+
+    //获取计划
+    async toGetTrainPlan() {
+      let planid = _.get(this.defaultOption, 'planid');
+      let res = await this.getTrainPlan(planid);
+      if (this.$checkRes(res)) {
+        let { data } = res;
+        let { termnum } = data;
+        termnum = _.orderBy(termnum, ['term'], ['asc']);
+        data.termnum = termnum;
+        this.$set(this, `plan`, data);
+        this.getBatchList();
+      }
+    },
+    //获取计划模板
+    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) {
+          let obj = {};
+          for (const ct of this.classTypeList) {
+            let number = r.classnum.reduce((p, n) => {
+              let num = 0;
+              if (n.code == ct.code) num = n.number * 1;
+              return p + num;
+            }, 0);
+            obj[this.proAffix(ct.code, 'number')] = number;
+            obj[this.proAffix(ct.code, 'remaining')] = number;
+          }
+          i = { ...i, ...obj };
+        }
+        let keysNum = Object.keys(i).filter(f => f.includes('number-'));
+        let number = keysNum.reduce((p, n) => p + i[n], 0);
+        i.number = number;
+        let keysRem = Object.keys(i).filter(f => f.includes('remaining-'));
+        let remaining = keysRem.reduce((p, n) => p + i[n], 0);
+        i.remaining = remaining;
+        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();
+    },
+    //batch分离与拼接
+    proAffix(value, affix) {
+      if (affix) return `${affix}-${value}`;
+      else {
+        let arr = value.split('-');
+        return { affix: _.head(arr), value: _.last(arr) };
+      }
+    },
+    //取出学校中,指定位置的信息
+    getDesignate(value, object) {
+      let obj = {};
+      let keys = Object.keys(object);
+      let arr = keys.filter(f => f.includes(value));
+      let termid = object[arr.find(f => f.includes('term_batch'))];
+      let term = object[this.proAffix(termid, 'term')];
+      obj.termid = termid;
+      obj.term = term;
+      obj.num = object[arr.find(f => f.includes('batch_total'))];
+      obj.batch = object[arr.find(f => f.includes('batch'))];
+      return obj;
+    },
+    //重新计算期列表
+    computedTermList() {
+      let dt = _.cloneDeep(this.termList);
+      let dl = _.cloneDeep(this.list);
+      let arr = dt.map(t => {
+        t.batchnum.map(b => {
+          let { _id: batchid, remaining, bpt } = b;
+          let num = dl.reduce((prev, next) => {
+            let n = next[this.proAffix(batchid, 'batch_total')] * 1 || 0;
+            return prev + n;
+          }, 0);
+          let remainder = bpt - num;
+          b.remaining = remainder;
+          return b;
+        });
+        return t;
+      });
+      this.$set(this, `termList`, arr);
+    },
+    //根据当前情况重新计算学校剩余名额
+    computedSchool() {
+      let dl = _.cloneDeep(this.list);
+      dl = dl.map(i => {
+        let keys = Object.keys(i).filter(f => f.includes('batch_total'));
+        let term_batchKeys = Object.keys(i).filter(f => f.includes('term_batch')); //key为batchid value为termid
+        let { remaining } = i;
+        //TODO 需要计算每种类型班级的remaining
+        // 找到批次id => 再找到对应的班级类型 => 用对应的remaining - 对应 batch_total => 最后将所有的remaining加一起,用btp - 和
+        let arr = this.termList.map(i => i.batchnum).flat();
+        for (const tbk of term_batchKeys) {
+          let { value } = this.proAffix(tbk);
+          let r = arr.find(f => f._id == value);
+          if (r) {
+            let { classtype } = r;
+            let num = i[this.proAffix(value, 'batch_total')];
+            i[this.proAffix(classtype, 'remaining')] = i[this.proAffix(classtype, 'remaining')] * 1 - num * 1;
+          }
+        }
+        let remainkey = Object.keys(i).filter(f => f.includes('remaining-'));
+        let sum = remainkey.reduce((p, n) => p + i[n] * 1, 0);
+        i['remaining'] = i.number - sum;
+        // let n = keys.reduce((prev, next) => {
+        //   let num = i[next] || 0;
+        //   return prev + num;
+        // }, 0);
+        // i.remaining = remaining - n;
+        return i;
+      });
+      this.$set(this, `list`, dl);
+    },
+    //计算学校的剩余人数
+    computedSchRemaining(sch) {
+      let keys = Object.keys(sch).filter(f => f.includes(`remaining-`));
+      let num = keys.reduce((p, n) => p + sch[n] * 1, 0);
+      sch.remaining = num;
+      return sch;
+    },
+    //重置学校设置
+    async resetSchool() {
+      let 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);
+        //TODO 日期没有放进去 daterange
+        let planid = _.get(this.defaultOption, 'planid');
+        res = await this.schPlanQuery({ planid });
+        if (this.$checkRes(res)) {
+          let duplicate = _.cloneDeep(res.data);
+          let list = nd.map((i, index) => {
+            let plan = duplicate.find(f => f.schid == i.code);
+            if (plan) {
+              let { daterange } = plan;
+              i.daterange = daterange;
+              //改变上报时间格式
+              i = this.changeRange(i);
+            }
+            i.plan = plan;
+            return i;
+          });
+          this.$set(this, `list`, list);
+        }
+      }
+    },
+    async getOtherList() {
+      const res = await this.getClassType();
+      if (this.$checkRes(res)) this.$set(this, `classTypeList`, res.data);
+      const { planid } = this.defaultOption;
+      const schoolStudent = await this.getSchoolStudent({ planid });
+      console.log(schoolStudent);
+      if (this.$checkRes(schoolStudent)) {
+        const { data } = schoolStudent;
+        this.$set(this, `schStuList`, data);
+      }
+    },
+    getProp(data, prop) {
+      if (prop.includes('hascar')) {
+        let res = _.get(data, prop);
+        return res == '1' ? '需要' : '不需要';
+      } else if (prop.includes('classtype')) {
+        let value = _.get(data, prop);
+        let res = this.classTypeList.find(f => f.code == value);
+        if (res) return res.name;
+      } else return _.get(data, prop);
+    },
+    //导出Excel
+    async toExcel() {
+      const res = await this.exportPlan({ trainplanId: this.defaultOption.planid });
+      if (this.$checkRes(res)) window.open(res.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));
+        } 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>

File diff suppressed because it is too large
+ 199 - 760
src/views/new-plan/arrange/school-arrange.vue