lrf 9 місяців тому
батько
коміт
2b63a350f5

+ 38 - 512
src/views/new-plan/arrange/arrange.vue

@@ -1,549 +1,75 @@
 <template>
 <template>
   <div id="arrange">
   <div id="arrange">
-    <el-card ref="card" v-if="info.year" height="800px">
-      <table-cal
-        :events="events"
-        :vacation="vacation"
-        :remark="remark"
-        @cellClick="eventClick"
-        @toSave="savePlan"
-        @toSetPlan="() => (dialog = true)"
-      ></table-cal>
-    </el-card>
-    <el-drawer :visible.sync="drawer" direction="rtl" title="安排计划" @close="toClose">
-      <event
-        :data="form"
-        :year="info.year"
-        :vacation="vacation"
-        :isNew="formIsNew"
-        :predefineColors="template.color"
-        :classTypeList="classTypeList"
-        :loading.sync="eventLoading"
-        @save="setEvent"
-        @delete="toDelete"
-      ></event>
-    </el-drawer>
-    <el-dialog :visible.sync="dialog" title="模板计划" width="40%" :close-on-click-modal="false">
-      <el-form>
-        <el-row type="flex">
-          <el-col :span="12">
-            <el-form-item label="自动生成预估数据">
-              <el-row>
-                <el-col :span="24"> 至少 {{ template.term }} 期 </el-col>
-                <el-col :span="24">至少需要 {{ template.leastDay }} 天 <span>(无假期等任何影响安排的因素)</span></el-col>
-                <el-col :span="24">
-                  <span>每期有 {{ template | getBatchNum }} 批</span>
-                  <span>;每批需要 {{ template.day }} 天</span>
-                </el-col>
-                <el-col :span="24" v-for="(b, index) in template.batchnum" :key="index">
-                  第{{ b.batch }}批:<el-col style="padding-left:20px" :span="24" v-for="(c, cindex) in b.classnum" :key="cindex"
-                    >{{ c.class }}班:{{ c.number }}人</el-col
-                  >
-                </el-col>
-              </el-row>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="请输入开始的期数">
-              <el-tooltip content="第X期 此处为填写X" placement="right">
-                <el-input-number v-model="input.termnum"></el-input-number>
-              </el-tooltip>
-            </el-form-item>
-            <el-form-item label="请选择班级类型">
-              <el-select v-model="input.classType">
-                <el-option v-for="(i, index) in classTypeList" :key="index" :label="i.name" :value="i.code"></el-option>
-              </el-select>
-            </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-item label="请选择结束日期">
-              <el-date-picker
-                :picker-options="pickerOptions"
-                v-model="input.end"
-                type="date"
-                placeholder="请选择结束日期"
-                format="yyyy-MM-dd"
-                value-format="yyyy-MM-dd"
-              >
-              </el-date-picker>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </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>
+    <el-card ref="card" height="800px">
+      <el-row type="flex" justify="space-around">
+        <el-col :span="12">
+          <el-steps :active="active" finish-status="success" simple center>
+            <el-step title="计划模板设置"></el-step>
+            <el-step title="计划日历安排"></el-step>
+          </el-steps>
+        </el-col>
+      </el-row>
+      <template v-if="view === 'template'">
+        <plan-template @changeView="changeView"></plan-template>
+      </template>
+      <template v-if="view === 'plan'">
+        <plan-arrange @changeView="changeView"></plan-arrange>
       </template>
       </template>
-    </el-dialog>
+    </el-card>
+
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
 import _ from 'lodash';
 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 tableCal from './arrange/table';
+import planTemplate from '../template.vue'
+import planArrange from './plan-arrange.vue'
 import { mapState, createNamespacedHelpers } from 'vuex';
 import { mapState, createNamespacedHelpers } from 'vuex';
-const { mapActions } = createNamespacedHelpers('trainplan');
-const { mapActions: trainTemplate } = createNamespacedHelpers('trainTemplate');
-const { mapActions: schPlan } = createNamespacedHelpers('schPlan');
 const { mapActions: util } = createNamespacedHelpers('util');
 const { mapActions: util } = createNamespacedHelpers('util');
-const { mapActions: classtype } = createNamespacedHelpers('classtype');
 export default {
 export default {
   name: 'arrange',
   name: 'arrange',
   props: {},
   props: {},
   components: {
   components: {
-    // detailFrame,
-    // dataTable,
-    event,
-    tableCal,
+    planTemplate,
+    planArrange
   },
   },
-  data: function() {
-    var that = this;
+  data: function () {
     return {
     return {
-      view: 'plan',
-      template: {},
-      info: {
-        year: '2020',
-      },
-      form: {},
-      events: [],
-      vacation: undefined,
-      dialog: false,
-      input: {
-        termnum: 1,
-        classType: '0',
-      },
-      pickerOptions: {
-        disabledDate: time => that.checkDate(time),
-      },
-      heights: 250,
-      collapse: '',
-      drawer: false,
-      formIsNew: true,
-      options: {},
-      classTypeList: [],
-      eventLoading: false,
-      remark: [],
+      view: '',
+      active: 0,
     };
     };
   },
   },
   async created() {
   async created() {
-    this.getOtherList();
+    this.searchTemplate()
   },
   },
   methods: {
   methods: {
-    ...mapActions(['fetch', 'update']),
-    ...trainTemplate({ trainTemplate: 'query' }),
-    ...schPlan({ setSchPlan: 'schArrange' }),
     ...util({ modelFetch: 'fetch' }),
     ...util({ modelFetch: 'fetch' }),
-    ...classtype({ getClassType: 'query' }),
-    async search() {
-      let planid = _.get(this.defaultOption, 'planid');
-      if (!planid) return;
-      const res = await this.fetch(planid);
-      if (this.$checkRes(res)) {
-        this.$set(this, `info`, res.data);
-        let data = _.cloneDeep(res.data);
-        this.$set(this, `events`, _.cloneDeep(data.termnum));
-        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.searchTemplate();
-      this.toResetRemark();
-    },
-    //模板事件开始
-    //生成默认模板
-    async setDefaultPlan() {
-      // this.$set(this, `events`, []);
-      this.$message('正在自动生成计划,请稍后...');
-      this.dialog = false;
-      let isOk = false;
-      let { start, end, termnum, classType } = this.input;
-      let { batchnum, term, total } = this.template;
-      let event = []; //处理成功事件的存储
-      let wtotal = 0,
-        wtermnum = 0;
-      let { total: newTotal, termnum: newTerm, events, isInRange } = this.toArrange(start, end, termnum, term, classType, total);
-      if (newTotal == 0) isOk = true;
-      else if (!isInRange) isOk = true;
-      wtotal = newTotal;
-      wtermnum = newTerm;
-      event = events;
-      while (!isOk) {
-        let nt = this.computedTerm(batchnum, wtotal);
-        let res = this.toArrange(start, end, wtermnum, nt, classType, wtotal, event);
-        // console.log(res);
-        // isOk = true;
-        if (res.total == 0) isOk = true;
-        else if (!res.isInRange) isOk = true;
-        wtotal = res.total;
-        wtermnum = res.termnum;
-        event = events;
-      }
-      this.$set(this, `events`, event);
-      this.toResetRemark();
-    },
-    //安排
-    // start:选择的开始时间
-    // end:选择打的结束时间
-    // termnum:手动输入开始的期数
-    // total:剩余人数
-    // events:已有安排,第一次不用传,再安排需要
-    toArrange(start, end, termnum, term, classType, total, events = []) {
-      let { day, batchnum } = this.template;
-      let iirr = true;
-      for (let i = 0; i < term; i++) {
-        if (total == 0) break;
-        if (!iirr) break;
-        let bnum = [];
-        for (const b of batchnum) {
-          if (total == 0) break;
-          let startdate;
-          let enddate;
-          //先查看是否是这期的批次正在排
-          if (bnum.length > 0) {
-            let last = _.last(bnum);
-            startdate = this.dayPlus(last.startdate, 1);
-          } else if (events.length == 0) {
-            //是第一期处理
-            startdate = start;
-            //此处应该处理startdate,如果startdate在假期中,就应该把时间约过假期再做
-            let res = this.getDateNotInVac(startdate, this.dayPlus(startdate, day - 1), day - 1);
-            startdate = res.start;
-          } else {
-            //不是第一期处理
-            //找到最后一期
-            let lastTerm = _.last(events);
-            //找到对应的批次(其实就是第一批次,因为第二批次已经走了if)
-            let fb = _.head(lastTerm.batchnum);
-            startdate = this.dayPlus(fb.enddate, 1);
-            //此处应该处理startdate,如果startdate在假期中,就应该把时间约过假期再做
-            let res = this.getDateNotInVac(startdate, this.dayPlus(startdate, day - 1), day - 1);
-            startdate = res.start;
-          }
-          enddate = this.dayPlus(startdate, day - 1);
-          let r = this.isInVac(startdate, enddate, day - 1);
-          //判断在不在假期
-          if (r) break;
-          let isInRange = this.isInRange(startdate, enddate, start, end);
-          let classes = [];
-          //判断不在手选的时间范围内
-          if (!isInRange) {
-            iirr = false;
-            break;
-          }
-          let bobj = { startdate, enddate, batch: b.batch };
-          for (const c of b.classnum) {
-            let { class: name, number } = c;
-            let type = classType;
-            if (total * 1 - c.number * 1 >= 0) {
-              total = total * 1 - c.number * 1;
-              classes.push({ name, number, type });
-            } else {
-              number = total;
-              total = 0;
-              classes.push({ name, number, type });
-              break;
-            }
-          }
-          bobj.class = classes;
-          bnum.push(bobj);
-        }
-        //最后处理成期的数据
-        if (bnum.length > 0) {
-          let color = this.getColor(termnum);
-          bnum = bnum.map(i => ({ ...i, color }));
-          let classnum = bnum.reduce((p, n) => p + n.class.length, 0);
-          let obj = { term: termnum, batchnum: bnum, classnum: classnum };
-          termnum = termnum * 1 + 1;
-          events.push(obj);
-        }
-      }
-      let obj = { total, termnum, events, isInRange: iirr };
-      return obj;
-    },
-    //默认事件排序
-    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);
-    },
-    //手动操作事件开始
-    async setEvent({ data, isNew }) {
-      data = JSON.parse(JSON.stringify(data));
-      if (isNew) {
-        let { term, ...info } = data;
-        //新添,找是不是在已知 期 中添加的信息
-        let res = this.events.find(f => f.term == term);
-        if (res) {
-          //找:是不是修改已知 批次 中信息
-          let termIndex = this.events.findIndex(f => f.term == term);
-          let batchIndex = res.batchnum.findIndex(f => f.batch == info.batch);
-          //是修改已知批次,就将信息放进去
-          if (batchIndex >= 0) this.$set(this.events[termIndex].batchnum, batchIndex, info);
-          else {
-            //新添批次,取出来=>复制=>赋回去,保证刷新视图
-            let duplicate = _.cloneDeep(this.events[termIndex].batchnum);
-            duplicate.push(info);
-            this.$set(this.events[termIndex], `batchnum`, duplicate);
-          }
-        } else {
-          //需要处理该数据为期[批次[班级的形式]]
-          let { term, batch, class: cl, ...info } = data;
-          let ta = { term, classnum: cl.length };
-          let tb = { batch, ...info, class: cl };
-          ta.batchnum = [tb];
-          console.log(ta);
-          this.events.push(ta);
-        }
-      } else {
-        let { term, ...info } = data;
-        let res = this.events.find(f => f.term == term);
-        if (res) {
-          let termIndex = this.events.findIndex(f => f.term == term);
-          let batchIndex = res.batchnum.findIndex(f => f.batch == info.batch);
-          if (batchIndex >= 0) this.$set(this.events[termIndex].batchnum, batchIndex, info);
-        }
-      }
-      this.toClose();
-      this.toResetRemark();
-    },
-    //列表删除事件,一定是删除批次,班级属于内部信息,期需要自己把批次都删了
-    toDelete(data) {
-      let { term, batch } = data;
-      let res = this.events.find(f => f.term == term);
-      if (res) {
-        let termIndex = this.events.findIndex(f => f.term == term);
-        let batchIndex = res.batchnum.findIndex(f => f.batch == batch);
-        if (batchIndex >= 0) this.events[termIndex].batchnum.splice(batchIndex, 1);
-      }
-      this.toClose();
-    },
-    //计划保存
-    savePlan() {
-      let data = JSON.parse(JSON.stringify(this.info));
-      let plan = JSON.parse(JSON.stringify(this.events));
-      data.termnum = plan;
-      let res;
-      let msg;
-      res = this.update(data);
-      msg = `计划保存成功`;
-      this.$checkRes(res, msg);
-    },
-    //添加/修改事件
-    eventClick(event) {
-      if (_.isObject(event)) {
-        this.formIsNew = false;
-        let res = this.events.find(f => f.term == event.term);
-        if (res) {
-          let { term } = res;
-          res = res.batchnum.find(f => f.batch == event.batch);
-          let obj = { ...res, term };
-          this.$set(this, `form`, obj);
-        }
-      } else {
-        //新添只允许添加一个批次,一个批次一个批次添加
-        this.$set(this, `form`, { startdate: event, class: [] });
-      }
-      this.drawer = true;
-      // this.formIsNew = false;
-    },
-    //
     async searchTemplate() {
     async searchTemplate() {
-      let planid = _.get(this.defaultOption, 'planid');
       let planyearid = _.get(this.defaultOption, 'planyearid');
       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.modelFetch({ model: 'trainmodel', planyearid, planid });
       if (this.$checkRes(res)) {
       if (this.$checkRes(res)) {
-        if (res.data !== null) {
-          let template = _.cloneDeep(res.data);
-          let { total, batchnum, day } = template;
-          template.term = this.computedTerm(batchnum, total);
-          let bn = _.get(batchnum, 'length', 0) - 1 >= 0 ? _.get(batchnum, 'length', 0) - 1 : 0;
-          template.leastDay = template.term * day + bn;
-          this.$set(this, `template`, template);
-        }
-      }
-    },
-    //人数计算无假期的剩余期
-    // batchnum:模板的批次设置
-    // total:剩余人数(可用来重复计算)
-    computedTerm(batchnum, total) {
-      let termTotal = batchnum.reduce((p, n) => p + n.classnum.reduce((np, nn) => np + nn.number * 1, 0), 0);
-      let term = 0;
-      if (termTotal !== 0) term = Math.ceil(total / termTotal);
-      return term;
-    },
-    //其他事件(无关紧要)
-    //关闭抽屉函数
-    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) {
-      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, '[]');
-      // console.log(res);
-      // let dv = _.cloneDeep(this.vacation);
-      // let vres = dv.find(f => moment(date).isBetween(f.start, f.end, null, '[]'));
-      // console.log(vres);&& !vres
-      return !res;
-    },
-    dayPlus(date, num) {
-      return moment(date)
-        .add(num, 'days')
-        .format('YYYY-MM-DD');
-    },
-    //若遇到假期,返回假期后的日期
-    getDateNotInVac(start, end, day) {
-      let res = false;
-      res = this.isInVac(start, end);
-      if (res) {
-        let ns = this.dayPlus(res.end, 1);
-        let ne = this.dayPlus(ns, day);
-        res = this.getDateNotInVac(ns, ne, day);
-      } else res = { start, end };
-
-      return res;
-    },
-    //如果在假期,就返回这个假期;不在假期,就
-    isInVac(start, end) {
-      let res = false;
-      for (const vac of this.vacation) {
-        let { start: vs, end: ve } = vac;
-        let sr = moment(start).isBetween(vs, ve, null, '[]');
-        let er = moment(end).isBetween(vs, ve, null, '[]');
-        let vsr = moment(vs).isBetween(start, end, null, '[]');
-        let ver = moment(ve).isBetween(start, end, null, '[]');
-        if (sr || er || vsr || ver) {
-          //返回这个假期
-          res = vac;
-          break;
+        if (res.data) {
+          this.view = 'plan';
+          this.active = 1
+          return
         }
         }
       }
       }
-      return res;
-    },
-    //查看是否在要求的事件范围内
-    //start:当前开始时间;end:当前结束时间;rs:范围开始时间;re:范围结束时间
-    isInRange(start, end, rs, re) {
-      let sr = moment(start).isBetween(rs, re, null, '[]');
-      let er = moment(end).isBetween(rs, re, null, '[]');
-      return sr && er;
+      this.view = 'template';
+      this.active = 0;
     },
     },
-    //整理remark
-    toResetRemark() {
-      let des = _.cloneDeep(this.events);
-      des = des.map(i => i.batchnum).flat();
-      des = des.map(i => {
-        i.month = moment(i.enddate).month() + 1;
-        return i;
-      });
-      let r = _.groupBy(des, 'month');
-      let remark = [];
-      for (let i = 1; i <= 12; i++) {
-        let mdata = _.get(r, i);
-        if (mdata) {
-          let obj = {};
-          let cn = mdata.reduce((p, n) => p + _.get(n.class, 'length', 0), 0);
-          obj.class = mdata.reduce((p, n) => p + _.get(n.class, 'length', 0), 0);
-          let num = mdata.reduce((p, n) => p + _.get(n, 'class', []).reduce((pc, nc) => pc + nc.number * 1, 0), 0);
-          obj.number = mdata.reduce((p, n) => p + _.get(n, 'class', []).reduce((pc, nc) => pc + nc.number * 1, 0), 0);
-          obj.month = i;
-          remark.push(obj);
-        }
+    changeView(view) {
+      if (view === 'template') {
+        this.view = 'template';
+        this.active = 0;
+      } else {
+        this.view = 'plan';
+        this.active = 1
       }
       }
-      this.$set(this, `remark`, remark);
-    },
-    async getOtherList() {
-      const res = await this.getClassType();
-      if (this.$checkRes(res)) this.$set(this, `classTypeList`, res.data);
-    },
-  },
-  filters: {
-    getBatchNum: template => _.get(template.batchnum, 'length', 0),
-  },
-  watch: {
-    defaultOption: {
-      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.$set(this, `options`, _.cloneDeep(val));
-            this.search();
-          }
-        }
-      },
-      deep: true,
-      immediate: true,
     },
     },
   },
   },
   computed: {
   computed: {
     ...mapState(['user', 'defaultOption']),
     ...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() {
   metaInfo() {
     return { title: this.$route.meta.title };
     return { title: this.$route.meta.title };

+ 449 - 0
src/views/new-plan/arrange/arrange/new-table.vue

@@ -0,0 +1,449 @@
+<template>
+  <div id="new-table" :key="viewKey">
+    <table border="1" class="table" v-for="month in 12" :key="month">
+      <template v-if="toDisplay(month)">
+        <tr>
+          <th :rowspan="getRowNumber(month)" class="mouthTitle" style="width:55px;">{{ month | getWord }}月</th>
+          <th class="th" v-for="(item, index) in makeCalendar(month)" :key="index">
+            <p>{{ item }}</p>
+            <p>{{ getWeekDay(item) }}</p>
+          </th>
+          <th class="th">
+            <p>班级数</p>
+          </th>
+          <th class="th">
+            <p>人数</p>
+          </th>
+        </tr>
+        <!-- 根据批次循环行,不再根据班级:  -->
+        <tr v-for="(batch, bi) in getMonthBatch(month)" :key="`${month}-${batch}`">
+          <template v-for="(item, ci) in makeCalendar(month)">
+            <td v-if="cellDisplay(item, batch, month, bi)" :colspan="cellColSpan(item, batch, month, bi)"
+              :rowspan="cellRowSpan(item, batch, month, bi)" @click="cellClick(item, batch, month, bi)"
+              :key="`${month}-${ci}`" :style="setStyle(item, batch, month, bi)"
+              :title="renderCell(item, batch, month, bi)">
+              {{ renderCell(item, batch, month, bi) }}
+            </td>
+          </template>
+          <template v-if="bi == 0">
+            <td :rowspan="getRowNumber(month)">{{ getRemark(month, 'class') }}</td>
+            <td :rowspan="getRowNumber(month)">{{ getRemark(month, 'number') }}</td>
+          </template>
+        </tr>
+
+      </template>
+    </table>
+  </div>
+</template>
+
+<script>
+// 需要将所需数据全都加载完后,再使用本组件,否则会造成初次渲染错误问题
+/**
+ * 表格与渲染的处理思路:
+ * 是不是跨月:
+ *     不跨月: 
+ *        再查看是不是第一天:
+ *            是:显示内容,计算合并值: 合并值 = 结束时间 - 开始时间
+ *            不是:不显示,等着被合并
+ *     跨月:
+ *        当前月是和安排的开始时间同一个月:
+ *            是不是第一天:
+ *                是:显示内容,计算合并值: 合并值 = 当前月的最后一天 - 开始时间;
+ *                不是:不显示,等着被合并
+ *        当前月是和安排的结束时间同一个月:
+ *            是不是当前月的第一天:
+ *                是:显示内容,计算合并值:合并值 = 结束时间 - 当前日期(本月1号,要不也不是跨月的情况了)
+ *                不是:不显示,等着被合并
+ */
+import { mapState, createNamespacedHelpers } from 'vuex';
+import { format, coreResult, getDays, getMonthLastDay, getMonthFirstDay, outPutCore, toRegularDate, inDateRange, checkIsSame } from './util'
+const _ = require('lodash');
+const moment = require('moment');
+export default {
+  name: 'new-table',
+  props: {
+    year: { type: String },
+    events: { type: Array },
+    vacation: { type: Array },
+    remark: { type: Array, default: () => { } },
+    placeList: { type: Array },
+  },
+  components: {},
+  data: function () {
+    return {
+      /**
+       * 用于渲染每个月的表格行数
+       * {月份: {}批次分组, 有多少批次: 该月表格的行数 }
+       * 开始时间,结束时间 其中一个和该月有交集的,就列入本月中
+       * { 1: 
+       *      {
+       *         1: [
+       *              {批次数据带期数}
+       *            ],
+       *         2: [],  
+       * 
+       *      }
+       * }
+       */
+      batchList: {},
+      viewKey: undefined
+    };
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  created() {
+    this.rerender();
+    this.setBatchList();
+    // console.log('year')
+    // console.log(this.year)
+    // console.log('events')
+    // console.log(this.events)
+    // console.log('vacation')
+    // console.log(this.vacation)
+    // console.log('remark')
+    // console.log(this.remark)
+    console.log(this.batchList)
+  },
+  methods: {
+    rerender() {
+      this.viewKey = new Date().getTime();
+    },
+    // 单元格内容
+    renderCell(date, batch, month, lineIndex) {
+      if (!date) return ''
+      const rd = toRegularDate(this.year, date)
+      let result = '';
+      // 1.需要查看是不是节日
+      if (lineIndex === 0) {
+        const r1 = this.checkVacation(rd)
+        if (r1) {
+          const r = outPutCore(r1.start, r1.end, rd, month)
+          if (r === coreResult.SMFD) {
+            const isFirstDay = checkIsSame(r1.start, rd)
+            if (isFirstDay) result = _.get(r1, 'title');
+          } else if (r === coreResult.STMFD) {
+            const isFirstDay = checkIsSame(r1.start, rd)
+            if (isFirstDay) result = _.get(r1, 'title');
+          } else if (r === coreResult.ENDMFD) {
+            const firstDay = moment(`${moment(r1.end).year()}-${moment(r1.end).month() + 1}-01`).format(format)
+            if (moment(rd).isSame(firstDay)) result = _.get(r1, 'title');
+          }
+          return result;
+        }
+      }
+
+      // 2.查看有没有安排
+      const v2s = _.get(this.batchList, `${month}.${batch}`)
+      if (v2s) {
+        const v2 = v2s.find(f => inDateRange(f.startdate, f.enddate, rd))
+        if (v2) {
+          const { term, batch, class: cla = [], startdate, enddate } = v2;
+          const r = outPutCore(startdate, enddate, rd, month)
+          if (r === coreResult.SMFD) {
+            result = `第${term}期-第${batch}批-共${cla.length}班`;
+          } else if (r === coreResult.STMFD) {
+            result = `第${term}期-第${batch}批-共${cla.length}班`;
+          } else if (r === coreResult.ENDMFD) {
+            result = `第${term}期-第${batch}批-共${cla.length}班`;
+          }
+        }
+      }
+      return result
+    },
+    // 单元格列合并
+    cellColSpan(date, batch, month) {
+      // 没有日期的为站位格子
+      if (!date) return 1
+      const rd = toRegularDate(this.year, date)
+      // 先判断是不是假期, 假期的第一个单元格是合并,其余的全是0
+      const r1 = this.checkVacation(rd)
+      if (r1) {
+        const r = outPutCore(r1.start, r1.end, rd, month)
+        let colspan = 0;
+        // 列合并,不是第一列不要管
+        if (r === coreResult.SMFD) {
+          // 同月第一天,需要知道后面几天,然后合并
+          const diff = getDays(r1.start, r1.end)
+          if (diff <= 0) colspan = 1;
+          colspan = diff + 1;
+        } else if (r === coreResult.STMFD) {
+          // 开始时间同月第一天: 需要计算出开始时间到月末差几天
+          const lastDay = getMonthLastDay(r1.start)
+          const diff = getDays(r1.start, lastDay)
+          if (diff <= 0) colspan = 1;
+          colspan = diff + 1;
+        } else if (r === coreResult.ENDMFD) {
+          const firstDay = getMonthFirstDay(r1.end)
+          const diff = getDays(firstDay, r1.end)
+          if (diff <= 0) colspan = 1;
+          colspan = diff + 1;
+        }
+        return colspan;
+      }
+      // 查看安排
+      const v2s = _.get(this.batchList, `${month}.${batch}`)
+      if (v2s) {
+        let colspan = 0;
+        const v2 = v2s.find(f => inDateRange(f.startdate, f.enddate, rd))
+        if (v2) {
+          const { startdate, enddate } = v2;
+          const r = outPutCore(startdate, enddate, rd, month)
+          if (r === coreResult.SMFD) {
+            // 直接计算多少天作为合并列数
+            const diff = getDays(startdate, enddate)
+            if (diff > 0) colspan = diff + 1;
+            else if (diff === 0) colspan = 1
+          } else if (r === coreResult.STMFD) {
+            const lastDay = getMonthLastDay(startdate)
+            const diff = getDays(startdate, lastDay)
+            if (diff > 0) colspan = diff + 1;
+            else if (diff === 0) colspan = 1
+          } else if (r === coreResult.ENDMFD) {
+            const firstDay = getMonthFirstDay(enddate)
+            const diff = getDays(firstDay, enddate)
+            if (diff > 0) colspan = diff + 1;
+            else if (diff === 0) colspan = 1
+          }
+          return colspan;
+        }
+      }
+      return 1;
+    },
+    // 单元格行合并
+    cellRowSpan(date, batch, month, lineIndex) {
+      // 没有日期的为站位格子
+      if (!date) return 1
+      const rd = toRegularDate(this.year, date)
+      // 先判断是不是假期
+      const r1 = this.checkVacation(rd)
+      if (r1) {
+        let rowspan = 1;
+        // 行合并,不是第一行不要管
+        if (lineIndex !== 0) rowspan = 0;
+        else {
+          // 同月第一天: 获取该月批次列表的长度+2即为总行数
+          const v2s = _.get(this.batchList, `${month}`, {})
+          const keys = Object.keys(v2s)
+          rowspan = keys.length + 1
+        }
+        return rowspan
+      }
+      // 一个批次一行,不需要处理安排的行
+
+      return 1;
+    },
+    // 单元格点击事件
+    cellClick(date, batch, month, lineIndex) {
+      if (!date) return ''
+      const rd = toRegularDate(this.year, date)
+      const v2s = _.get(this.batchList, `${month}.${batch}`)
+      if (v2s) {
+        const v2 = v2s.find(f => inDateRange(f.startdate, f.enddate, rd))
+        if (v2) this.$emit('cellClick', v2)
+        else this.$emit('cellClick', rd)
+      } else this.$emit('cellClick', rd)
+    },
+
+
+    /**
+     * 整理一维的批次列表,将term加进去并按月份整理成object
+     */
+    setBatchList() {
+      let batchList = this.events.map(i => {
+        const { term, batchnum } = i
+        let bl = _.cloneDeep(batchnum);
+        bl = bl.map(i => ({ ...i, term }))
+        return bl;
+      })
+      batchList = _.flattenDeep(batchList)
+      /**
+       * 1.月为key; value为在该月显示的安排
+       * 2.value: 批次为key, v2为按月分完后,同一批次下的所有数据
+       */
+      const monthObject = {};
+      // 为每个月都创建下
+      for (let i = 1; i <= 12; i++) {
+        monthObject[i] = {}
+      }
+      for (const i of batchList) {
+        const { startdate, enddate, batch } = i;
+        // 必须2个时间和批次名都要有
+        if (!startdate || !enddate || !batch) continue;
+        // 开始时间所在月份,和结束时间所在月份
+        const sm = moment(startdate).month() + 1
+        const em = moment(enddate).month() + 1
+        // 如果是同一个月那就放1个,如果不同月,就各方各的
+        if (sm === em) {
+          const v2 = _.get(monthObject, `${sm}.${batch}`, [])
+          v2.push(i);
+          monthObject[sm][batch] = v2;
+        } else {
+          const sv2 = _.get(monthObject, `${sm}.${batch}`, [])
+          sv2.push(i)
+          monthObject[sm][batch] = sv2;
+          const ev2 = _.get(monthObject, `${em}.${batch}`, [])
+          ev2.push(i)
+          monthObject[em][batch] = ev2;
+        }
+      }
+      this.$set(this, 'batchList', monthObject)
+    },
+
+    // #region 时间处理函数
+    // 检查是否在假期中
+    checkVacation(date) {
+      let res = false;
+      if (!this.vacation) return res;
+      for (const vac of this.vacation) {
+        let start = vac.start;
+        let end = _.get(vac, `end`, moment(start).format(format));
+        let r = moment(date).isBetween(start, end, null, '[]');
+        if (r) {
+          res = vac;
+          break;
+        }
+      }
+      return res;
+    },
+    // #endregion
+
+    // #region html部分
+    getRemark(month, type) {
+      let res = this.remark.find(f => f.month == month);
+      if (res) return _.get(res, type);
+    },
+    // 单元格样式
+    setStyle(date, batch, month, bi) {
+      let style = { cursor: 'pointer' };
+      const rd = toRegularDate(this.year, date)
+      // 先判断是不是假期
+      const r1 = this.checkVacation(rd)
+      if (r1) {
+        style = { cursor: 'not-allowed' };
+        style['pointer-events'] = 'none';
+        if (date !== '') style['background'] = 'red';
+      }
+
+      const v2s = _.get(this.batchList, `${month}.${batch}`)
+      if (v2s) {
+        const v2 = v2s.find(f => inDateRange(f.startdate, f.enddate, rd))
+        if (v2) {
+          const place = _.get(v2, 'place')
+          if (place) {
+            const p = this.placeList.find(f => f._id === place)
+            if (p) style.background = _.get(p, 'color', '#ffffff')
+          }
+        }
+      }
+
+      return style;
+    },
+    // 单元格是否显示(需要被合并的不显示)
+    cellDisplay(date, batch, month, bi) {
+      const colspan = this.cellColSpan(date, batch, month, bi)
+      const rowspan = this.cellRowSpan(date, batch, month, bi)
+      if (colspan > 0 && rowspan > 0) return true;
+    },
+
+    // 获取本月批次列表
+    getMonthBatch(month) {
+      const v1 = this.batchList[month];
+      const keys = Object.keys(v1)
+      // 多留出一行
+      keys.push({})
+      return keys
+    },
+    // 获取一个月的天数列表
+    getDayList(month, days) {
+      let dlist = [];
+      for (let index = 0; index < days; index++) {
+        dlist.push(
+          moment(`${this.year}-${month}-01`)
+            .add(index, 'days')
+            .format('M.D')
+        );
+      }
+      return dlist;
+    },
+    // 获取该月有几行
+    getRowNumber(month) {
+      const v1 = this.batchList[month]
+      const keys = Object.keys(v1)
+      // 因为多留出一行,所以+2,否则加1
+      return keys.length + 2;
+    },
+    // 每个月的日历
+    makeCalendar(month = 1) {
+      if (month * 1 < 10) month = `0${month}`;
+      let days = moment(`${this.year}-${month}`).daysInMonth();
+      let dlist = this.getDayList(month, days);
+      while (dlist.length < 31) {
+        dlist.push('');
+      }
+      return dlist;
+    },
+    // 获取星期几
+    getWeekDay(datestr) {
+      if (datestr.includes('.')) {
+        const strs = datestr.split('.');
+        const dayStr = `${this.year}-${strs[0]}-${strs[1]}`;
+        let weekday = moment(dayStr).weekday();
+        if (weekday || weekday == 0) {
+          let arr = ['日', '一', '二', '三', '四', '五', '六'];
+          return `星期${arr[weekday]}`;
+        }
+      }
+
+      return '';
+    },
+    // 判断月份是否是正常月份,显示正常月份
+    toDisplay(month) {
+      return _.inRange(month, 1, 13);
+    },
+    // #endregion
+
+
+  },
+  filters: {
+    getWord(month) {
+      let arr = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
+      return arr[month - 1];
+    },
+  },
+  watch: {
+    // 监听安排的事件,为了重新渲染
+    events: {
+      deep: true,
+      handler(oval, nval) {
+        this.setBatchList();
+      }
+    }
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.th p {
+  width: 55px;
+  font-size: 14px;
+  word-break: break-all;
+}
+
+tr {
+  height: 18.75px;
+}
+
+table {
+  table-layout: fixed;
+  zoom:0.8;
+  td {
+    text-align: center;
+    width: 30px;
+    height: 10px;
+  }
+}
+</style>

+ 10 - 5
src/views/new-plan/arrange/arrange/table.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
   <div id="tableCal">
   <div id="tableCal">
-    <template v-if="already">
+    <template >
       <el-row type="flex" align="middle" style="padding:10px">
       <el-row type="flex" align="middle" style="padding:10px">
         <el-col :span="2">
         <el-col :span="2">
           <el-button type="primary" size="mini" @click="toBack">返回</el-button>
           <el-button type="primary" size="mini" @click="toBack">返回</el-button>
@@ -23,7 +23,7 @@
             <th class="th"><p>班级数</p></th>
             <th class="th"><p>班级数</p></th>
             <th class="th"><p>人数</p></th>
             <th class="th"><p>人数</p></th>
           </tr>
           </tr>
-          <tr v-for="(b, bi) in list[`${month}`]" :key="bi">
+          <tr v-for="(b, bi) in getMonthLine(month)" :key="bi">
             <td
             <td
               v-for="(date, ci) in makeCalendar(month)"
               v-for="(date, ci) in makeCalendar(month)"
               :key="ci"
               :key="ci"
@@ -43,14 +43,14 @@
         </template>
         </template>
       </table>
       </table>
     </template>
     </template>
-    <div
+    <!-- <div
       style="height:800px"
       style="height:800px"
       v-else
       v-else
       v-loading="!already"
       v-loading="!already"
       element-loading-text="加载中,请稍后..."
       element-loading-text="加载中,请稍后..."
       element-loading-spinner="el-icon-loading"
       element-loading-spinner="el-icon-loading"
       element-loading-background="rgba(0, 0, 0, 0.8)"
       element-loading-background="rgba(0, 0, 0, 0.8)"
-    ></div>
+    ></div> -->
   </div>
   </div>
 </template>
 </template>
 
 
@@ -62,7 +62,7 @@ import { mapState, createNamespacedHelpers } from 'vuex';
 export default {
 export default {
   name: 'tableCal',
   name: 'tableCal',
   props: {
   props: {
-    year: { type: Number, default: new Date().getFullYear() },
+    year: { type: String },
     events: { type: Array },
     events: { type: Array },
     vacation: { type: Array },
     vacation: { type: Array },
     remark: { type: Array, default: () => {} },
     remark: { type: Array, default: () => {} },
@@ -535,6 +535,7 @@ export default {
       } else {
       } else {
         r = this.checkEvents(date, month, bi);
         r = this.checkEvents(date, month, bi);
         if (r) {
         if (r) {
+          console.log(r)
           if (r.type == '0') return `第${r.term}期-第${r.batch}批`;
           if (r.type == '0') return `第${r.term}期-第${r.batch}批`;
           else return `第${r.term}期${r.name}`;
           else return `第${r.term}期${r.name}`;
         }
         }
@@ -549,6 +550,10 @@ export default {
     toDisplay(month) {
     toDisplay(month) {
       return _.inRange(month, 1, 13);
       return _.inRange(month, 1, 13);
     },
     },
+    getMonthLine(month) {
+      console.log(this.list)
+      return this.list[`${month}`]
+    },
     //没有事件的初始化
     //没有事件的初始化
     initNoEventTable() {
     initNoEventTable() {
       let obj = {};
       let obj = {};

+ 107 - 0
src/views/new-plan/arrange/arrange/util.js

@@ -0,0 +1,107 @@
+const _ = require('lodash');
+const moment = require('moment');
+export const format = 'YYYY-MM-DD';
+/**
+ * 转换为常规时间格式 YYYY-MM-DD
+ * @param {String} year 年
+ * @param {String} date 月日: x.x格式
+ * @returns
+ */
+export const toRegularDate = (year, date) => {
+  const dateStr = `${year}.${date}`;
+  const formatStr = moment(dateStr).format(format);
+  return formatStr;
+};
+/**
+ * 根据时间参数获取当前月的最后一天
+ * @param {String} date YYYY-MM-DD格式时间
+ */
+export const getMonthLastDay = date => {
+  const lastDay = moment(`${moment(date).year()}-${moment(date).month() + 1}-01`)
+    .add(1, 'M')
+    .subtract(1, 'd')
+    .format(format);
+  return lastDay;
+};
+/**
+ * 根据时间参数获取当前月的第一天
+ * @param {String} date YYYY-MM-DD格式时间
+ */
+export const getMonthFirstDay = date => {
+  const firstDay = moment(`${moment(date).year()}-${moment(date).month() + 1}-01`).format(format);
+  return firstDay;
+};
+/**
+ * 计算两天相差天数
+ * @param {String} start 开始时间
+ * @param {String} end 结束时间
+ * @returns 
+ */
+export const getDays = (start, end) => {
+  return moment(end).diff(start, 'd')
+};
+
+/**
+ * 判断日期是否在开始时间-结束时间范围内
+ * @param {String} start 开始时间
+ * @param {String} end 结束时间
+ * @param {String} date 日期
+ * @returns
+ */
+export const inDateRange = (start, end, date) => {
+  return moment(date).isBetween(start, end, null, '[]');
+};
+/**
+ * 判断两天是否一致
+ * @param {String} start 开始时间
+ * @param {String} date 日期
+ * @returns
+ */
+export const checkIsSame = (start, date) => {
+  const result = moment(date).isSame(start);
+  return result;
+};
+export const coreResult = {
+  /**同月且第一天 */
+  SMFD: 'same_month_first_day',
+  /**同月且非第一天 */
+  SMNFD: 'same_month_not_first_day',
+  /**开始时间同月且第一天 */
+  STMFD: 'start_month_first_day',
+  /**开始时间同月且非第一天 */
+  STMNFD: 'start_month_not_first_day',
+  /**结束时间同月且第一天 */
+  ENDMFD: 'end_month_first_day',
+  /**结束时间同月且非第一天 */
+  ENDMNFD: 'end_month_not_first_day',
+};
+/**
+ * 核心判断逻辑
+ * @param {String} start 开始时间
+ * @param {String} end 结束时间
+ * @param {String} date 当前日期
+ * @param {String} month 当前月
+ * @returns {coreResult}
+ */
+export const outPutCore = (start, end, date, month) => {
+  let result;
+  const sm = moment(start).month() + 1;
+  const em = moment(end).month() + 1;
+  if (sm === em) {
+    // 同月
+    const isFirstDay = checkIsSame(start, date);
+    if (isFirstDay) result = coreResult.SMFD;
+    else result = coreResult.SMNFD;
+  } else if (sm === month) {
+    // 开始时间月
+    const isFirstDay = checkIsSame(start, date);
+    if (isFirstDay) result = coreResult.STMFD;
+    else result = coreResult.STMNFD;
+  } else if (em === month) {
+    // 结束时间月
+    const firstDay = moment(`${moment(end).year()}-${moment(end).month() + 1}-01`).format(format);
+    if (moment(date).isSame(firstDay)) result = coreResult.ENDMFD;
+    else result = coreResult.ENDMNFD;
+  }
+  return result;
+};

+ 51 - 39
src/views/new-plan/parts/event.vue

@@ -1,25 +1,31 @@
 <template>
 <template>
-  <div
-    id="event"
-    v-loading="loading"
-    element-loading-text="加载中,请稍后..."
-    element-loading-spinner="el-icon-loading"
-    element-loading-background="rgba(0, 0, 0, 0.8)"
-  >
-    <el-form :model="form" ref="form" :rules="formRules" label-width="80px" size="small" @submit.native.prevent style="padding: 15px;">
-      <el-form-item label="开始时间" prop="startdate" required>
-        <el-date-picker :readonly="!isNew" v-model="form.startdate" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd"> </el-date-picker>
+  <div id="event" v-loading="loading" element-loading-text="加载中,请稍后..." element-loading-spinner="el-icon-loading"
+    element-loading-background="rgba(0, 0, 0, 0.8)">
+    <el-form :model="form" ref="form" :rules="formRules" label-width="80px" size="small" @submit.native.prevent
+      style="padding: 15px;">
+      <el-form-item label="开始时间" prop="startdate">
+        <el-date-picker :readonly="!isNew" v-model="form.startdate" type="date" format="yyyy-MM-dd"
+          value-format="yyyy-MM-dd" :disabled="true"> </el-date-picker>
       </el-form-item>
       </el-form-item>
-      <el-form-item label="结束时间" prop="enddate" required>
-        <el-date-picker :readonly="!isNew" v-model="form.enddate" type="date" format="yyyy-MM-dd" value-format="yyyy-MM-dd"> </el-date-picker>
+      <el-form-item label="结束时间" prop="enddate">
+        <el-date-picker :readonly="!isNew" v-model="form.enddate" type="date" format="yyyy-MM-dd"
+          value-format="yyyy-MM-dd" :picker-options="pickerOptions" :default-value="getEndTimeDefault()">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="期数" prop="term" required> <el-input v-model="form.term" :readonly="!isNew"></el-input>
       </el-form-item>
       </el-form-item>
-      <el-form-item label="期数" prop="term" required> <el-input v-model="form.term" :readonly="!isNew"></el-input> </el-form-item>
       <!-- <el-form-item label="班级类型(需要请求)" prop="type" required>
       <!-- <el-form-item label="班级类型(需要请求)" prop="type" required>
         <el-select v-model="form.type">
         <el-select v-model="form.type">
           <el-option v-for="(i, index) in classTypeList" :key="index" :label="i.name" :value="i.code"></el-option>
           <el-option v-for="(i, index) in classTypeList" :key="index" :label="i.name" :value="i.code"></el-option>
         </el-select>
         </el-select>
       </el-form-item> -->
       </el-form-item> -->
-      <el-form-item label="批次" prop="batch" required> <el-input v-model="form.batch" :readonly="!isNew"></el-input> </el-form-item>
+      <el-form-item label="批次" prop="batch" required> <el-input v-model="form.batch"
+          :readonly="!isNew"></el-input></el-form-item>
+      <el-form-item label="培训场地" prop="place" required>
+        <el-select v-model="form.place" placeholder="请选择本批次的培训场地">
+          <el-option v-for="i in placeList" :key="i._id" :label="i.name" :value="i._id"></el-option>
+        </el-select>
+      </el-form-item>
       <el-form-item label="班级">
       <el-form-item label="班级">
         <el-row>
         <el-row>
           <el-col :span="24">
           <el-col :span="24">
@@ -40,7 +46,8 @@
             <el-table-column align="center" label="类型">
             <el-table-column align="center" label="类型">
               <template v-slot="{ row }">
               <template v-slot="{ row }">
                 <el-select v-model="row.type">
                 <el-select v-model="row.type">
-                  <el-option v-for="(i, index) in classTypeList" :key="index" :label="i.name" :value="i.code"></el-option>
+                  <el-option v-for="(i, index) in classTypeList" :key="index" :label="i.name"
+                    :value="i.code"></el-option>
                 </el-select>
                 </el-select>
               </template>
               </template>
             </el-table-column>
             </el-table-column>
@@ -55,9 +62,9 @@
       <!-- <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="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="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="name" v-if="form.type === '1'"> <el-input v-model="form.name"></el-input> </el-form-item> -->
-      <el-form-item label="颜色" prop="color">
+      <!-- <el-form-item label="颜色" prop="color">
         <el-color-picker v-model="form.color" :predefine="predefineColors" size="mini"></el-color-picker>
         <el-color-picker v-model="form.color" :predefine="predefineColors" size="mini"></el-color-picker>
-      </el-form-item>
+      </el-form-item> -->
       <el-form-item>
       <el-form-item>
         <el-row type="flex" align="middle" justify="space-around">
         <el-row type="flex" align="middle" justify="space-around">
           <el-col :span="6">
           <el-col :span="6">
@@ -77,34 +84,37 @@
 
 
 <script>
 <script>
 import _ from 'lodash';
 import _ from 'lodash';
+const moment = require('moment');
 export default {
 export default {
   name: 'event',
   name: 'event',
   props: {
   props: {
-    data: { type: Object, default: () => {} }, //数据
+    data: { type: Object, default: () => { } }, //数据
     isNew: { type: Boolean, default: true }, //是不是修改
     isNew: { type: Boolean, default: true }, //是不是修改
     predefineColors: { type: Array, default: () => [] }, //颜色列表
     predefineColors: { type: Array, default: () => [] }, //颜色列表
     year: { type: null, default: new Date().getFullYear() },
     year: { type: null, default: new Date().getFullYear() },
     vacation: { type: Array, default: () => [] },
     vacation: { type: Array, default: () => [] },
     classTypeList: { type: Array, default: () => [] },
     classTypeList: { type: Array, default: () => [] },
     loading: { type: Boolean, default: false },
     loading: { type: Boolean, default: false },
+    placeList: { type: Array, default: () => [] },// 培训场地
   },
   },
   components: {},
   components: {},
   data() {
   data() {
     return {
     return {
       form: { color: '#409EFF', class: [] },
       form: { color: '#409EFF', class: [] },
       formRules: {
       formRules: {
-        startdate: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
-        enddate: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
-        term: [{ required: true, message: '请输入期数' }],
-        batch: [{ required: true, message: '请输入批次' }],
-        number: [{ required: true, message: '请输入每班人数' }],
+        startdate: [{ required: true, message: '请选择开始时间', trigger: 'blur' }],
+        enddate: [{ required: true, message: '请选择结束时间', trigger: 'blur' }],
+        term: [{ required: true, message: '请输入期数', trigger: 'blur' }],
+        batch: [{ required: true, message: '请输入批次', trigger: 'blur' }],
+        number: [{ required: true, message: '请输入每班人数', trigger: 'blur' }],
+        place: [{ required: true, message: '请选择培训场地', trigger: 'blur' }],
       },
       },
       pickerOptions: {
       pickerOptions: {
         disabledDate: time => this.setDisabledDate(time),
         disabledDate: time => this.setDisabledDate(time),
       },
       },
     };
     };
   },
   },
-  created() {},
+  created() { },
   methods: {
   methods: {
     //保存表单函数
     //保存表单函数
     saveForm() {
     saveForm() {
@@ -136,29 +146,31 @@ export default {
     toDelete() {
     toDelete() {
       this.$emit('delete', this.data);
       this.$emit('delete', this.data);
     },
     },
+    // 设置结束时间默认时间
+    getEndTimeDefault() {
+      let result = new Date();
+      const startTime = _.get(this.form, 'startdate')
+      if (!startTime) result = moment().toDate()
+      result = moment(startTime).add(1, 'd').toDate()
+      return result
+    },
     //禁用时间
     //禁用时间
     setDisabledDate(time) {
     setDisabledDate(time) {
       let thisTime = time.getTime();
       let thisTime = time.getTime();
+      let startTime = _.get(this.form, 'startdate')
+      if (startTime) {
+        // 开始时间之前的全都不能选
+        let sb = moment(thisTime).isSameOrBefore(startTime)
+        if(sb) return true;
+      }
+      // 限制在今年范围内
       let start = new Date(`${this.year}-01-01`).getTime();
       let start = new Date(`${this.year}-01-01`).getTime();
       let end = new Date(`${this.year}-12-31`).getTime();
       let end = new Date(`${this.year}-12-31`).getTime();
       if (thisTime < start) return true;
       if (thisTime < start) return true;
       else if (thisTime > end) return true;
       else if (thisTime > end) return true;
       else {
       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;
-            }
-          }
-        }
+        // 假期不能选
+        const res = this.vacation.find(f => moment(thisTime).isBetween(f.start, f.end, null, '[]'))
         return res;
         return res;
       }
       }
     },
     },

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

@@ -0,0 +1,573 @@
+<template>
+  <div id="plan-arrange">
+    <el-row type="flex" align="middle" style="padding:10px">
+      <el-col :span="2">
+        <el-button type="primary" size="mini" @click="toBack">返回</el-button>
+      </el-col>
+      <el-col :span="2">
+        <el-button type="primary" size="mini" @click="toTemplateView()">查看计划模板</el-button>
+      </el-col>
+      <el-col :span="2">
+        <el-button type="primary" size="mini" @click="() => (dialog = true)">生成模板计划</el-button>
+      </el-col>
+      <el-col :span="2">
+        <el-button type="success" size="mini" @click="savePlan">保存计划</el-button>
+      </el-col>
+      <el-col :span="14" style="text-align:right">
+        <el-tooltip placement="bottom" effect="light">
+          <el-button type="warning" size="mini">场地颜色对应</el-button>
+          <template #content>
+            <el-row v-for="i in placeList" :key="i._id" style="padding-bottom:5px;line-height:18px">
+              <el-col :span="24">{{ i.name }}: <el-color-picker v-model="i.color" size="mini" :predefine="colors"></el-color-picker></el-col>
+            </el-row>
+          </template>
+        </el-tooltip>
+      </el-col>
+    </el-row>
+
+
+    <table-cal v-if="dataReady" :year="info.year" :events="events" :vacation="vacation" :remark="remark"
+      :placeList="placeList" @cellClick="eventClick"></table-cal>
+    <div style="height:800px" v-else v-loading="!dataReady" element-loading-text="加载中,请稍后..."
+      element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)"></div>
+    <el-drawer :visible.sync="drawer" direction="rtl" title="安排计划" @close="toClose">
+      <event :data="form" :year="info.year" :vacation="vacation" :isNew="formIsNew" :predefineColors="template.color"
+        :classTypeList="classTypeList" :loading.sync="eventLoading" :placeList="placeList" @save="setEvent"
+        @delete="toDelete"></event>
+    </el-drawer>
+    <el-dialog :visible.sync="dialog" title="模板计划" width="40%" :close-on-click-modal="false">
+      <el-form>
+        <el-row type="flex">
+          <el-col :span="12">
+            <el-form-item label="自动生成预估数据">
+              <el-row>
+                <el-col :span="24"> 至少 {{ template.term }} 期 </el-col>
+                <el-col :span="24">至少需要 {{ template.leastDay }} 天 <span>(无假期等任何影响安排的因素)</span></el-col>
+                <el-col :span="24">
+                  <span>每期有 {{ template | getBatchNum }} 批</span>
+                  <span>;每批需要 {{ template.day }} 天</span>
+                </el-col>
+                <el-col :span="24" v-for="(b, index) in template.batchnum" :key="index">
+                  第{{ b.batch }}批:<el-col style="padding-left:20px" :span="24" v-for="(c, cindex) in b.classnum"
+                    :key="cindex">{{ c.class }}班:{{ c.number }}人</el-col>
+                </el-col>
+              </el-row>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="请输入开始的期数">
+              <el-tooltip content="第X期 此处为填写X" placement="right">
+                <el-input-number v-model="input.termnum"></el-input-number>
+              </el-tooltip>
+            </el-form-item>
+            <el-form-item label="请选择班级类型">
+              <el-select v-model="input.classType">
+                <el-option v-for="(i, index) in classTypeList" :key="index" :label="i.name" :value="i.code"></el-option>
+              </el-select>
+            </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-item label="请选择结束日期">
+              <el-date-picker :picker-options="pickerOptions" v-model="input.end" type="date" placeholder="请选择结束日期"
+                format="yyyy-MM-dd" value-format="yyyy-MM-dd">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </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 event from './parts/event';
+import tableCal from './arrange/new-table';
+import { mapState, createNamespacedHelpers } from 'vuex';
+const { mapActions } = createNamespacedHelpers('trainplan');
+const { mapActions: trainTemplate } = createNamespacedHelpers('trainTemplate');
+const { mapActions: schPlan } = createNamespacedHelpers('schPlan');
+const { mapActions: util } = createNamespacedHelpers('util');
+const { mapActions: classtype } = createNamespacedHelpers('classtype');
+const { mapActions: location } = createNamespacedHelpers('location');
+export default {
+  name: 'plan-arrange',
+  props: {},
+  components: {
+    event,
+    tableCal,
+  },
+  data: function () {
+    var that = this;
+    return {
+      colors: ['#E60000', '#FF7300', '#996B1F', '#FFD700', '#AFA4E0', '#74868A', '#09A343', '#E9F1F4'],
+      dataReady: false,
+      template: {},
+      info: {
+        year: '2020',
+      },
+      form: {},
+      events: [],
+      vacation: undefined,
+      dialog: false,
+      input: {
+        termnum: 1,
+        classType: '0',
+      },
+      pickerOptions: {
+        disabledDate: time => that.checkDate(time),
+      },
+      collapse: '',
+      drawer: false,
+      formIsNew: true,
+      options: {},
+      classTypeList: [],
+      eventLoading: false,
+      remark: [],
+      placeList: [],
+    };
+  },
+
+  async created() {
+    this.getOtherList();
+  },
+  methods: {
+    ...mapActions(['fetch', 'update']),
+    ...trainTemplate({ trainTemplate: 'query' }),
+    ...schPlan({ setSchPlan: 'schArrange' }),
+    ...util({ modelFetch: 'fetch' }),
+    ...classtype({ getClassType: 'query' }),
+    ...location({ getLocation: 'query' }),
+    async search() {
+      let planid = _.get(this.defaultOption, 'planid');
+      if (!planid) return;
+      const res = await this.fetch(planid);
+      if (this.$checkRes(res)) {
+        this.$set(this, `info`, res.data);
+        let data = _.cloneDeep(res.data);
+        this.$set(this, `events`, _.cloneDeep(data.termnum));
+        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);
+      }
+      await this.searchTemplate();
+      await this.toResetRemark();
+      this.dataReady = true;
+    },
+    //模板事件开始
+    //生成默认模板
+    async setDefaultPlan() {
+      // this.$set(this, `events`, []);
+      this.$message('正在自动生成计划,请稍后...');
+      this.dialog = false;
+      let isOk = false;
+      let { start, end, termnum, classType } = this.input;
+      let { batchnum, term, total } = this.template;
+      let event = []; //处理成功事件的存储
+      let wtotal = 0,
+        wtermnum = 0;
+      let { total: newTotal, termnum: newTerm, events, isInRange } = this.toArrange(start, end, termnum, term, classType, total);
+      if (newTotal == 0) isOk = true;
+      else if (!isInRange) isOk = true;
+      wtotal = newTotal;
+      wtermnum = newTerm;
+      event = events;
+      while (!isOk) {
+        let nt = this.computedTerm(batchnum, wtotal);
+        let res = this.toArrange(start, end, wtermnum, nt, classType, wtotal, event);
+        // console.log(res);
+        // isOk = true;
+        if (res.total == 0) isOk = true;
+        else if (!res.isInRange) isOk = true;
+        wtotal = res.total;
+        wtermnum = res.termnum;
+        event = events;
+      }
+      this.$set(this, `events`, event);
+      this.toResetRemark();
+    },
+    //安排
+    // start:选择的开始时间
+    // end:选择打的结束时间
+    // termnum:手动输入开始的期数
+    // total:剩余人数
+    // events:已有安排,第一次不用传,再安排需要
+    toArrange(start, end, termnum, term, classType, total, events = []) {
+      let { day, batchnum } = this.template;
+      let iirr = true;
+      for (let i = 0; i < term; i++) {
+        if (total == 0) break;
+        if (!iirr) break;
+        let bnum = [];
+        for (const b of batchnum) {
+          if (total == 0) break;
+          let startdate;
+          let enddate;
+          //先查看是否是这期的批次正在排
+          if (bnum.length > 0) {
+            let last = _.last(bnum);
+            startdate = this.dayPlus(last.startdate, 1);
+          } else if (events.length == 0) {
+            //是第一期处理
+            startdate = start;
+            //此处应该处理startdate,如果startdate在假期中,就应该把时间约过假期再做
+            let res = this.getDateNotInVac(startdate, this.dayPlus(startdate, day - 1), day - 1);
+            startdate = res.start;
+          } else {
+            //不是第一期处理
+            //找到最后一期
+            let lastTerm = _.last(events);
+            //找到对应的批次(其实就是第一批次,因为第二批次已经走了if)
+            let fb = _.head(lastTerm.batchnum);
+            startdate = this.dayPlus(fb.enddate, 1);
+            //此处应该处理startdate,如果startdate在假期中,就应该把时间约过假期再做
+            let res = this.getDateNotInVac(startdate, this.dayPlus(startdate, day - 1), day - 1);
+            startdate = res.start;
+          }
+          enddate = this.dayPlus(startdate, day - 1);
+          let r = this.isInVac(startdate, enddate, day - 1);
+          //判断在不在假期
+          if (r) break;
+          let isInRange = this.isInRange(startdate, enddate, start, end);
+          let classes = [];
+          //判断不在手选的时间范围内
+          if (!isInRange) {
+            iirr = false;
+            break;
+          }
+          let bobj = { startdate, enddate, batch: b.batch };
+          for (const c of b.classnum) {
+            let { class: name, number } = c;
+            let type = classType;
+            if (total * 1 - c.number * 1 >= 0) {
+              total = total * 1 - c.number * 1;
+              classes.push({ name, number, type });
+            } else {
+              number = total;
+              total = 0;
+              classes.push({ name, number, type });
+              break;
+            }
+          }
+          bobj.class = classes;
+          bnum.push(bobj);
+        }
+        //最后处理成期的数据
+        if (bnum.length > 0) {
+          let color = this.getColor(termnum);
+          bnum = bnum.map(i => ({ ...i, color }));
+          let classnum = bnum.reduce((p, n) => p + n.class.length, 0);
+          let obj = { term: termnum, batchnum: bnum, classnum: classnum };
+          termnum = termnum * 1 + 1;
+          events.push(obj);
+        }
+      }
+      let obj = { total, termnum, events, isInRange: iirr };
+      return obj;
+    },
+    //默认事件排序
+    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);
+    },
+    //手动操作事件开始
+    async setEvent({ data, isNew }) {
+      console.log(data);
+      data = JSON.parse(JSON.stringify(data));
+      if (isNew) {
+        let { term, ...info } = data;
+        //新添,找是不是在已知 期 中添加的信息
+        let res = this.events.find(f => f.term == term);
+        if (res) {
+          //找:是不是修改已知 批次 中信息
+          let termIndex = this.events.findIndex(f => f.term == term);
+          let batchIndex = res.batchnum.findIndex(f => f.batch == info.batch);
+          //是修改已知批次,就将信息放进去
+          if (batchIndex >= 0) this.$set(this.events[termIndex].batchnum, batchIndex, info);
+          else {
+            //新添批次,取出来=>复制=>赋回去,保证刷新视图
+            let duplicate = _.cloneDeep(this.events[termIndex].batchnum);
+            duplicate.push(info);
+            this.$set(this.events[termIndex], `batchnum`, duplicate);
+          }
+        } else {
+          //需要处理该数据为期[批次[班级的形式]]
+          let { term, batch, class: cl, ...info } = data;
+          let ta = { term, classnum: cl.length };
+          let tb = { batch, ...info, class: cl };
+          ta.batchnum = [tb];
+          console.log(ta);
+          this.events.push(ta);
+        }
+      } else {
+        let { term, ...info } = data;
+        let res = this.events.find(f => f.term == term);
+        if (res) {
+          let termIndex = this.events.findIndex(f => f.term == term);
+          let batchIndex = res.batchnum.findIndex(f => f.batch == info.batch);
+          if (batchIndex >= 0) this.$set(this.events[termIndex].batchnum, batchIndex, info);
+        }
+      }
+      this.toClose();
+      this.toResetRemark();
+    },
+    //列表删除事件,一定是删除批次,班级属于内部信息,期需要自己把批次都删了
+    toDelete(data) {
+      let { term, batch } = data;
+      let res = this.events.find(f => f.term == term);
+      if (res) {
+        let termIndex = this.events.findIndex(f => f.term == term);
+        let batchIndex = res.batchnum.findIndex(f => f.batch == batch);
+        if (batchIndex >= 0) this.events[termIndex].batchnum.splice(batchIndex, 1);
+      }
+      this.toClose();
+    },
+    //计划保存
+    savePlan() {
+      let data = JSON.parse(JSON.stringify(this.info));
+      let plan = JSON.parse(JSON.stringify(this.events));
+      data.termnum = plan;
+      let res;
+      let msg;
+      res = this.update(data);
+      msg = `计划保存成功`;
+      this.$checkRes(res, msg);
+    },
+    //添加/修改事件
+    eventClick(event) {
+      if (_.isObject(event)) {
+        this.formIsNew = false;
+        let res = this.events.find(f => f.term == event.term);
+        if (res) {
+          let { term } = res;
+          res = res.batchnum.find(f => f.batch == event.batch);
+          let obj = { ...res, term };
+          this.$set(this, `form`, obj);
+        }
+      } else {
+        //新添只允许添加一个批次,一个批次一个批次添加
+        this.$set(this, `form`, { startdate: event, class: [] });
+      }
+      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) {
+          let template = _.cloneDeep(res.data);
+          let { total, batchnum, day } = template;
+          template.term = this.computedTerm(batchnum, total);
+          let bn = _.get(batchnum, 'length', 0) - 1 >= 0 ? _.get(batchnum, 'length', 0) - 1 : 0;
+          template.leastDay = template.term * day + bn;
+          this.$set(this, `template`, template);
+        }
+      }
+    },
+    //人数计算无假期的剩余期
+    // batchnum:模板的批次设置
+    // total:剩余人数(可用来重复计算)
+    computedTerm(batchnum, total) {
+      let termTotal = batchnum.reduce((p, n) => p + n.classnum.reduce((np, nn) => np + nn.number * 1, 0), 0);
+      let term = 0;
+      if (termTotal !== 0) term = Math.ceil(total / termTotal);
+      return term;
+    },
+    //其他事件(无关紧要)
+    //关闭抽屉函数
+    toClose() {
+      this.drawer = false;
+      this.formIsNew = true;
+    },
+    //设置事件颜色
+    getColor(it) {
+      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, '[]');
+      // console.log(res);
+      // let dv = _.cloneDeep(this.vacation);
+      // let vres = dv.find(f => moment(date).isBetween(f.start, f.end, null, '[]'));
+      // console.log(vres);&& !vres
+      return !res;
+    },
+    dayPlus(date, num) {
+      return moment(date)
+        .add(num, 'days')
+        .format('YYYY-MM-DD');
+    },
+    //若遇到假期,返回假期后的日期
+    getDateNotInVac(start, end, day) {
+      let res = false;
+      res = this.isInVac(start, end);
+      if (res) {
+        let ns = this.dayPlus(res.end, 1);
+        let ne = this.dayPlus(ns, day);
+        res = this.getDateNotInVac(ns, ne, day);
+      } else res = { start, end };
+
+      return res;
+    },
+    //如果在假期,就返回这个假期;不在假期,就
+    isInVac(start, end) {
+      let res = false;
+      for (const vac of this.vacation) {
+        let { start: vs, end: ve } = vac;
+        let sr = moment(start).isBetween(vs, ve, null, '[]');
+        let er = moment(end).isBetween(vs, ve, null, '[]');
+        let vsr = moment(vs).isBetween(start, end, null, '[]');
+        let ver = moment(ve).isBetween(start, end, null, '[]');
+        if (sr || er || vsr || ver) {
+          //返回这个假期
+          res = vac;
+          break;
+        }
+      }
+      return res;
+    },
+    //查看是否在要求的事件范围内
+    //start:当前开始时间;end:当前结束时间;rs:范围开始时间;re:范围结束时间
+    isInRange(start, end, rs, re) {
+      let sr = moment(start).isBetween(rs, re, null, '[]');
+      let er = moment(end).isBetween(rs, re, null, '[]');
+      return sr && er;
+    },
+    //整理remark
+    toResetRemark() {
+      let des = _.cloneDeep(this.events);
+      des = des.map(i => i.batchnum).flat();
+      // 根据结束日期来决定这些班级和人数归到哪个月份
+      des = des.map(i => {
+        i.month = moment(i.enddate).month() + 1;
+        return i;
+      });
+      let r = _.groupBy(des, 'month');
+      let remark = [];
+      for (let i = 1; i <= 12; i++) {
+        let mdata = _.get(r, i);
+        if (mdata) {
+          let obj = {};
+          let cn = mdata.reduce((p, n) => p + _.get(n.class, 'length', 0), 0);
+          obj.class = mdata.reduce((p, n) => p + _.get(n.class, 'length', 0), 0);
+          let num = mdata.reduce((p, n) => p + _.get(n, 'class', []).reduce((pc, nc) => pc + nc.number * 1, 0), 0);
+          obj.number = mdata.reduce((p, n) => p + _.get(n, 'class', []).reduce((pc, nc) => pc + nc.number * 1, 0), 0);
+          obj.month = i;
+          remark.push(obj);
+        }
+      }
+      this.$set(this, `remark`, remark);
+    },
+    async getOtherList() {
+      let res = await this.getClassType();
+      if (this.$checkRes(res)) this.$set(this, `classTypeList`, res.data);
+      res = await this.getLocation({ type: '4' })
+      if (this.$checkRes(res)) {
+        // 为每个地点随机生成颜色
+        let list = res.data;
+        if (res.data && res.data.length > 0) {
+          list = list.map(i => {
+            const color = '#' + (parseInt(Math.random() * 0xffffff)).toString(16)
+            return { ...i, color }
+          })
+          this.$set(this, `placeList`, list);
+        }
+      }
+    },
+    toBack() {
+      this.$router.go(-1);
+    },
+    toTemplateView() {
+      this.$emit('changeView', 'template')
+    }
+  },
+  filters: {
+    getBatchNum: template => _.get(template.batchnum, 'length', 0),
+  },
+  watch: {
+    defaultOption: {
+      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.$set(this, `options`, _.cloneDeep(val));
+            this.search();
+          }
+        }
+      },
+      deep: true,
+      immediate: 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>

+ 100 - 71
src/views/new-plan/template.vue

@@ -1,93 +1,108 @@
 <template>
 <template>
   <div id="templates">
   <div id="templates">
-    <detail-frame :title="pageTitle">
-      <el-card>
-        <el-form :model="info" size="mini" label-position="right" label-suffix=":" label-width="180px">
-          <template v-for="(i, index) in fields">
-            <template v-if="!i.custom">
-              <el-form-item :key="index" :label="i.label" :required="i.required">
-                <template v-if="i.type != 'text'">
-                  <el-input :type="i.type" v-model="info[i.model]" size="mini" style="width:300px"></el-input>
-                </template>
-                <template v-else>
-                  {{ info[i.model] || 0 }}
-                </template>
-              </el-form-item>
-            </template>
-            <template else>
-              <template v-if="i.model == 'batchnum'">
-                <el-form-item :key="index" :label="i.label" :required="i.required">
-                  <el-row style="padding: 5px 0">
-                    <el-col :span="2">
-                      <el-button icon="el-icon-plus" size="mini" type="primary" @click="addBatch">添加批次</el-button>
-                    </el-col>
-                  </el-row>
-                  <el-row :gutter="5">
-                    <el-col :span="6" v-for="(bat, bindex) in info.batchnum" :key="bindex">
-                      <el-card>
-                        <el-form-item label="批次" label-width="50">
-                          <el-row type="flex" align="middle" justify="space-around">
-                            <el-col :span="2">
-                              {{ bat.batch }}
-                            </el-col>
-                            <el-col :span="2">
-                              <el-button type="danger" plain icon="el-icon-delete" @click="deleteBatch(bindex)"></el-button>
-                            </el-col>
-                          </el-row>
-                          <el-row>
-                            <el-col :span="24">
-                              <el-button type="primary" icon="el-icon-plus" @click="addClass(bindex)">添加班级</el-button>
-                            </el-col>
-                            <el-col :span="24">
-                              <el-table size="mini" :data="bat.classnum">
-                                <el-table-column align="center" label="班级" prop="class"></el-table-column>
-                                <el-table-column align="center" label="人数">
-                                  <template v-slot="{ row }">
-                                    <el-input v-model="row.number" type="number"></el-input>
-                                  </template>
-                                </el-table-column>
-                                <el-table-column align="center" label="操作">
-                                  <template v-slot="{ row, $index }">
-                                    <el-button type="text" icon="el-icon-delete" @click="toDelete(bindex, $index)"></el-button>
-                                  </template>
-                                </el-table-column>
-                              </el-table>
-                            </el-col>
-                          </el-row>
-                        </el-form-item>
-                      </el-card>
-                    </el-col>
-                  </el-row>
-                </el-form-item>
+    <el-card>
+      <el-row type="flex" align="middle" style="padding:10px">
+        <el-col :span="2">
+          <el-button type="primary" size="mini" @click="toBack">返回</el-button>
+        </el-col>
+        <el-col :span="2">
+          <el-button type="primary" size="mini" @click="toPlanView()">查看计划日历</el-button>
+        </el-col>
+      </el-row>
+      <el-form :model="info" size="mini" label-position="right" label-suffix=":" label-width="180px">
+        <template v-for="(i, index) in fields">
+          <template v-if="!i.custom">
+            <el-form-item :key="index" :label="i.label" :required="i.required">
+              <template v-if="i.type != 'text'">
+                <el-input :type="i.type" v-model="info[i.model]" size="mini" style="width:300px"></el-input>
               </template>
               </template>
-              <template v-if="i.model == 'color'">
-                <el-form-item :key="index" :label="i.label" :required="i.required">
-                  <el-color-picker v-model="color" :predefine="info.color" @change="toChange" color-format="hex"></el-color-picker>
-                </el-form-item>
+              <template v-else>
+                {{ info[i.model] || 0 }}
               </template>
               </template>
+            </el-form-item>
+          </template>
+          <template else>
+            <template v-if="i.model == 'batchnum'">
+              <el-form-item :key="index" :label="i.label" :required="i.required">
+                <el-row style="padding: 5px 0">
+                  <el-col :span="2">
+                    <el-button icon="el-icon-plus" size="mini" type="primary" @click="addBatch">添加批次</el-button>
+                  </el-col>
+                </el-row>
+                <el-row :gutter="5">
+                  <el-col :span="6" v-for="(bat, bindex) in info.batchnum" :key="bindex">
+                    <el-card>
+                      <el-form-item label="批次" label-width="50">
+                        <el-row type="flex" align="middle" justify="space-around">
+                          <el-col :span="2">
+                            {{ bat.batch }}
+                          </el-col>
+                          <el-col :span="2">
+                            <el-button type="danger" plain icon="el-icon-delete"
+                              @click="deleteBatch(bindex)"></el-button>
+                          </el-col>
+                        </el-row>
+                      </el-form-item>
+                      <el-form-item label="场地" label-width="50">
+                        <el-select v-model="bat.place" placeholder="请选择本批次的培训场地">
+                          <el-option v-for="i in placeList" :key="i._id" :label="i.name" :value="i._id"></el-option>
+                        </el-select>
+                      </el-form-item>
+                      <el-row>
+                        <el-col :span="24">
+                          <el-button type="primary" icon="el-icon-plus" @click="addClass(bindex)">添加班级</el-button>
+                        </el-col>
+                        <el-col :span="24">
+                          <el-table size="mini" :data="bat.classnum">
+                            <el-table-column align="center" label="班级" prop="class"></el-table-column>
+                            <el-table-column align="center" label="人数">
+                              <template v-slot="{ row }">
+                                <el-input v-model="row.number" type="number"></el-input>
+                              </template>
+                            </el-table-column>
+                            <el-table-column align="center" label="操作">
+                              <template v-slot="{ row, $index }">
+                                <el-button type="text" icon="el-icon-delete"
+                                  @click="toDelete(bindex, $index)"></el-button>
+                              </template>
+                            </el-table-column>
+                          </el-table>
+                        </el-col>
+                      </el-row>
+                    </el-card>
+                  </el-col>
+                </el-row>
+              </el-form-item>
             </template>
             </template>
+            <!-- <template v-if="i.model == 'color'">
+              <el-form-item :key="index" :label="i.label" :required="i.required">
+                <el-color-picker v-model="color" :predefine="info.color" @change="toChange"
+                  color-format="hex"></el-color-picker>
+              </el-form-item>
+            </template> -->
           </template>
           </template>
-          <el-form-item label="">
-            <el-button type="primary" @click="handleSave">保存</el-button>
-          </el-form-item>
-        </el-form>
-      </el-card>
-    </detail-frame>
+        </template>
+        <el-form-item label="">
+          <el-button type="primary" @click="handleSave">保存</el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
 import _ from 'lodash';
 import _ from 'lodash';
-import detailFrame from '@frame/layout/admin/detail-frame';
 import { mapState, createNamespacedHelpers } from 'vuex';
 import { mapState, createNamespacedHelpers } from 'vuex';
 const { mapActions: trainTemplate } = createNamespacedHelpers('trainTemplate');
 const { mapActions: trainTemplate } = createNamespacedHelpers('trainTemplate');
 const { mapActions: util } = createNamespacedHelpers('util');
 const { mapActions: util } = createNamespacedHelpers('util');
+const { mapActions: location } = createNamespacedHelpers('location');
 export default {
 export default {
   name: 'templates',
   name: 'templates',
   props: {},
   props: {},
-  components: { detailFrame },
+  components: {},
   data: () => {
   data: () => {
     return {
     return {
+      placeList: [],
       info: {
       info: {
         color: ['#E60000', '#FF7300', '#996B1F', '#FFD700', '#AFA4E0', '#74868A', '#09A343', '#E9F1F4'],
         color: ['#E60000', '#FF7300', '#996B1F', '#FFD700', '#AFA4E0', '#74868A', '#09A343', '#E9F1F4'],
         carpnum: 53,
         carpnum: 53,
@@ -110,11 +125,13 @@ export default {
     };
     };
   },
   },
   created() {
   created() {
+    this.searchOthers();
     this.search();
     this.search();
   },
   },
   methods: {
   methods: {
     ...util({ modelFetch: 'fetch' }),
     ...util({ modelFetch: 'fetch' }),
     ...trainTemplate(['query', 'create', 'update']),
     ...trainTemplate(['query', 'create', 'update']),
+    ...location({ getLocation: 'query' }),
     async search() {
     async search() {
       let planyearid = _.get(this.defaultOption, 'planyearid');
       let planyearid = _.get(this.defaultOption, 'planyearid');
       let planid = _.get(this.defaultOption, 'planid');
       let planid = _.get(this.defaultOption, 'planid');
@@ -124,6 +141,12 @@ export default {
         this.$set(this, `isNew`, res.data == null);
         this.$set(this, `isNew`, res.data == null);
       }
       }
     },
     },
+    async searchOthers() {
+      const res = await this.getLocation({ type: '4' })
+      if (this.$checkRes(res)) {
+        this.$set(this, 'placeList', _.get(res, 'data', []))
+      }
+    },
     async handleSave() {
     async handleSave() {
       let data = _.cloneDeep(this.info);
       let data = _.cloneDeep(this.info);
       let res;
       let res;
@@ -208,6 +231,12 @@ export default {
       }
       }
       this.$set(this.info, `batchnum`, batchnums);
       this.$set(this.info, `batchnum`, batchnums);
     },
     },
+    toBack() {
+      this.$router.go(-1);
+    },
+    toPlanView() {
+      this.$emit('changeView', 'plan')
+    }
   },
   },
   computed: {
   computed: {
     ...mapState(['user', 'defaultOption']),
     ...mapState(['user', 'defaultOption']),