|
@@ -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>
|