123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844 |
- <template>
- <div id="excel-view">
- <el-row>
- <el-col :span="4">
- <el-button size="mini" @click="getExcelData">获取excel数据</el-button>
- </el-col>
- <el-col :span="4">
- <el-button @click="toAddCol" size="mini" type="primary">新增日期安排</el-button>
- </el-col>
- </el-row>
- <div class="sheetContainerbox" ref="sheetContainer" id="x-spreadsheet-demo"></div>
- <el-dialog :visible.sync="dialog" title="计划变更" @close="toClose" :destroy-on-close="true">
- <term-add :key="new Date().getTime()" :data="form" :classTypeList="classTypeList" :placeList="placeList" v-bind="$attrs" v-on="$listeners"></term-add>
- </el-dialog>
- <el-dialog :visible.sync="dialog2" title="培训安排" @close="toClose" :destroy-on-close="true">
- <arrange-edit :key="new Date().getTime()" :data="form" :planData="planData" :schoolData="schoolData"></arrange-edit>
- </el-dialog>
- </div>
- </template>
- <script>
- const _ = require('lodash');
- const moment = require('moment');
- import termAdd from './term-add.vue';
- import arrangeEdit from './arrange-edit.vue';
- import XLSX from 'xlsx';
- //引入依赖包
- import zhCN from 'x-data-spreadsheet/src/locale/zh-cn';
- import Spreadsheet from 'x-data-spreadsheet';
- //设置中文
- Spreadsheet.locale('zh-cn', zhCN);
- import { mapState, createNamespacedHelpers } from 'vuex';
- export default {
- name: 'excel-view',
- props: {
- year: { type: String }, //当前计划年份
- plan: { type: Object }, //计划
- classTypeList: { type: Array }, // 班级类型列表
- schStuList: { type: Array }, // 学校上传学生的结果 [{schid:学校编码, sum:1}]
- schoolList: { type: Array }, //需要设置的学校列表
- placeList: { type: Array }, //场地列表
- schPlan: { type: Array }, // 培训计划安排
- },
- components: { termAdd, arrangeEdit },
- data: function() {
- return {
- dialog: false, // 计划dialog
- dialog2: false, // 培训计划dialog
- form: {},
- planData: {}, // 为修改培训计划整理的学校数据
- schoolData: {}, // 为修改培训计划整理的期批数据
- /**
- * merge:合并单元格,实际上是 [sri,sci,eri,eci]
- * sri:起始行数,在rows中的合并,已经确定了 起始行这个参数
- * sci:起始列数,在rows中的合并,r.cells.${n} n即为起始列数,也可以确定
- * eri:结束行数,需要自己确定
- * eci:结束列数,需要自己确定
- * 由此可知: 在rows中合并单元格, 我们只需要填写 eri和eci这两个参数.
- * 即由当前单元格出发,计算要合并几行几列,[行,列]
- */
- sheetDefaultConfig: {
- // name: '普通班', // 表单名(sheet1)
- // 样式
- styles: {
- valign: 'middle',
- align: 'center',
- },
- //行数据
- rows: {
- 1: {
- cells: {
- 0: {
- text: '序号',
- merge: [3, 0],
- },
- 1: {
- text: '学校名称',
- merge: [3, 0],
- },
- 2: {
- text: '计划数',
- merge: [3, 0],
- },
- 3: {
- text: '地区',
- merge: [3, 0],
- },
- 4: {
- text: '期数',
- },
- },
- },
- 2: {
- cells: {
- 4: {
- text: '场地',
- },
- },
- },
- 3: {
- cells: {
- 4: {
- text: '班级数',
- },
- },
- },
- 4: {
- cells: {
- 4: {
- text: '时间',
- },
- },
- },
- 5: {
- cells: {
- 2: {
- text: '批次人数',
- merge: [0, 2],
- },
- },
- },
- 6: {
- cells: {
- 2: { text: '督导', merge: [1, 1] },
- 4: { text: '天数' },
- },
- },
- 7: {
- cells: {
- 4: { text: '人数' },
- },
- },
- 8: {
- cells: {
- 2: {
- text: '长春高校用车数',
- merge: [0, 2],
- },
- },
- },
- },
- },
- // 表格事件设置 如果在 x,y其中一个在无操作中, 则本次事件不操作
- /**无操作行: 点击事件,行在这里面,则直接当无事发生 */
- ignoreRows: [0],
- /**无操作列: 点击事件,列在这里面,则直接当无视发生 */
- /**计划行: 点击事件,行在这里面,且 列不在无操作列中, 则进入确定具体修改那期,并进入修改*/
- ignoreCols: [0, 1, 2, 3, 4],
- planRows: [1, 2, 3, 4],
- /**学校计划安排起始行 */
- arrangeStartRow: 8,
- };
- },
- computed: {
- ...mapState(['user']),
- },
- mounted() {
- this.init();
- },
- methods: {
- init() {
- this.xs = new Spreadsheet('#x-spreadsheet-demo', {
- mode: 'read',
- view: {
- height: () => this.$refs.sheetContainer?.offsetHeight,
- width: () => this.$refs.sheetContainer?.offsetWidth,
- },
- col: { len: 45 },
- style: { align: 'center' },
- showToolbar: false,
- showGrid: true,
- showContextmenu: false,
- }).loadData([]);
- this.xs
- .on('cell-selected', (cell, ri, ci) => {
- console.log('cell:', cell, ', ri:', ri, ', ci:', ci);
- this.cellClick(ri, ci, cell);
- })
- .on('cell-edited', (text, ri, ci) => {
- console.log('text:', text, ', ri: ', ri, ', ci:', ci);
- });
- // 整理表头数据
- const sheetDatas = this.organizeLine3456();
- // 检查场地,督导的合并,计算督导的人数
- this.organizeLine378(sheetDatas);
- // 输出学校列表
- this.organizeLineFrom9(sheetDatas);
- this.xs.loadData(sheetDatas);
- // 学校安排人数设置
- this.setSchPlan();
- // TODO:计算高校用车数
- },
- // #region 单元格事件
- /**
- * 单元格点击事件
- * @param {Number} ri 点击的行位置
- * @param {Number} ci 点击的列位置
- * @param {Object} cell 单元格
- */
- cellClick(ri, ci, cell) {
- const inIngnoreRow = this.ignoreRows.includes(ri);
- // 在不操作行中,直接返回
- if (inIngnoreRow) return;
- const inIngnoreCol = this.ignoreCols.includes(ci);
- // 在不操作列中,直接返回
- if (inIngnoreCol) return;
- // 查看是否在计划行中
- const inPlanRows = this.planRows.includes(ri);
- if (inPlanRows) {
- // 确定点击位置是计划的哪个位置,然后进行计划修改
- console.log('in plan');
- // 使用列,确定是哪一批次即可
- const batch = this.toConfirmPlan(ri, ci);
- if (batch) {
- // 没有延迟会又选择好几个
- _.delay(() => {
- this.form = batch;
- this.dialog = true;
- }, 100);
- }
- } else if (ri > this.arrangeStartRow) {
- // 学校的安排,打开学校安排界面
- console.log('in arrange');
- // 确定是哪个学校,哪个批次
- let number = _.get(cell, 'text', 0);
- if (number) number = parseInt(number);
- const res = this.toConfirmArrange(ri, ci);
- if (!res) {
- this.$message.error('培训计划安排整理数据发生错误');
- return;
- }
- const { schoolData, planData } = res;
- console.log(planData)
- _.delay(() => {
- this.form = { number };
- this.schoolData = schoolData;
- this.planData = planData;
- this.dialog2 = true;
- }, 100);
- } else {
- console.log('无操作');
- }
- },
- /**
- * 根据坐标确定培训安排取出数据
- * 收集学校信息:学校名(name);层次(level);需不需要派车(hascar);总名额;剩余名额
- * 期批信息;期数;批次;班级类型;开始时间;结束时间;批次总人数;批次剩余名额
- * 然后提供给修改组件取设置数值
- * @param {Number} ri 行位置
- * @param {Number} ci 列位置
- */
- toConfirmArrange(ri, ci) {
- // ri确定学校, ci确定期-批次
- const sheetName = this.getSheetName();
- const sheetDatas = this.xs.getData();
- const sheetData = sheetDatas.find(f => f.name === sheetName);
- if (!sheetName) return;
- const ct = this.classTypeList.find(f => f.name === sheetName);
- // 班级类型
- const classType = ct.name;
- // 先找行: 学校名 => 学校, 获取分配限制
- const schoolName = _.get(sheetData, `rows.${ri}.cells.1.text`);
- const school = this.schoolList.find(f => f.name === schoolName);
- if (!school) return;
- /**
- * 丰富:
- * 学校总名额: 根据当前班级类型,取出分配的总量即可
- * 剩余名额: 计算当前行所有安排的名额作为减数
- */
- const classnum = _.get(school, 'classnum', []);
- const thisTypeClass = classnum.find(f => f.code === ct.code);
- if (!thisTypeClass) return;
- // 总名额
- school.total = _.get(thisTypeClass, 'number');
- // 剩余名额
- const thisRow = _.get(sheetData, `rows.${ri}.cells`);
- if (!thisRow) return;
- const arrColsDataObject = _.omit(thisRow, this.ignoreCols);
- const arrColsDatas = [];
- for (const key in arrColsDataObject) {
- const e = arrColsDataObject[key];
- let number = _.get(e, 'text', 0);
- if (number) number = parseInt(number);
- arrColsDatas.push(number);
- }
- const et = arrColsDatas.reduce((p, n) => p + n, 0);
- school.elseNumber = school.total - et;
- /**
- * 确定批次,因为之后修改了向3,4,5行都注入了 term和batch,方便快速定位期批.
- * 所以固定取时间行(因为时间行完全不涉及合并问题,就是跟着批次走)
- */
- const row5SameColCell = this.xs.cell(4, ci, 0);
- const { term, batch } = row5SameColCell;
- const termnum = this.plan.termnum.find(f => f.term === term);
- if (!termnum) return;
- const batchnum = _.get(termnum, 'batchnum');
- if (!batchnum) return;
- const batchData = batchnum.find(f => f.batch === batch);
- const planData = {
- term,
- termid: _.get(termnum, '_id'),
- batch,
- batchid: _.get(batchData, '_id'),
- startdate: _.get(batchData, 'startdate'),
- enddate: _.get(batchData, 'enddate'),
- type: classType,
- };
- /**
- * 丰富:
- * 总名额: 通过行数据可以直接获取
- * 剩余名额: 需要算列总和
- */
- /**总名额 */
- const total = _.get(sheetData, `rows.5.cells.${ci}.text`);
- planData.total = total;
- /**计算列和 */
- let keys = Object.keys(sheetData.rows).map(i => parseInt(i));
- keys = keys.filter(f => f > this.arrangeStartRow);
- const schoolRowObject = _.pick(sheetData.rows, keys);
- const arr = [];
- for (const key in schoolRowObject) {
- const e = schoolRowObject[key];
- const obj = _.get(e, 'cells', {});
- if (!obj || Object.keys(obj).length < 0) continue;
- const tc = _.get(obj, `${ci}.text`, 0);
- arr.push(parseInt(tc));
- }
- const inNumber = arr.reduce((p, n) => p + n, 0);
- const elseNumber = total - inNumber;
- planData.elseNumber = elseNumber;
- return { schoolData: school, planData };
- },
- /**
- * 根据坐标确定计划期并取出数据
- * @param {Number} ri 行位置
- * @param {Number} ci 列位置
- */
- toConfirmPlan(ri, ci) {
- const excelDatas = this.xs.getData();
- const sheetName = this.getSheetName();
- const allData = excelDatas.find(f => f.name === sheetName);
- if (!allData) return;
- /**返回的结果变量 */
- let result;
- let batchIndex = 0;
- let term;
- // 不需要看行,只看列在哪,因为已经冻结了所有表格,无法修改.固定走第二行拿到期数,然后去计划中换数据进行修改
- const rowDataObj = allData.rows[1].cells;
- const keys = Object.keys(rowDataObj).map(i => parseInt(i));
- /**定位列分几种情况:
- * 1.该列没有合并单元格,那就能在key中直接找到
- * 2.该列有合并单元格,那就不一定能在里面直接找到:只有合并的起始单元格才能被直接找到,需要查看 点击的单元格 是否在 被合并的单元格中.
- */
- const hasKey = keys.includes(ci);
- if (hasKey) {
- // 直接找到的一定都是第一个单元格
- const cellData = rowDataObj[ci];
- term = _.get(cellData, 'text');
- } else {
- // 将所有列展开,检查是否在被合并的单元格中
- for (const key in rowDataObj) {
- // 无操作列不要
- if (this.ignoreCols.includes(parseInt(key))) continue;
- const cols = [parseInt(key)];
- const { merge = [], text } = rowDataObj[key];
- // 没有两行直接返回
- if (merge.length === 2) {
- const last = _.last(merge);
- if (last) {
- for (let i = 1; i <= last; i++) {
- cols.push(parseInt(i) + parseInt(key));
- }
- }
- }
- // 看下点击的列是否在这里面
- const res = cols.find(f => f === ci);
- if (res) {
- batchIndex = cols.findIndex(f => f === ci);
- term = text;
- break;
- }
- }
- }
- if (term && batchIndex >= 0) {
- const termnum = this.plan.termnum.find(f => f.term === term);
- if (termnum) {
- const batch = _.get(termnum, `batchnum.${batchIndex}`, {});
- result = { ...batch, term: _.get(termnum, 'term') };
- }
- }
- return result;
- },
- /**获取当前浏览的表格名 */
- getSheetName() {
- const sheetName = _.get(this.xs, 'sheet.data.name');
- return sheetName;
- },
- // #endregion
- // #region excel初始化数据整理
- /**
- * 设置excel中学校安排的数据
- * 根据arrange的type:班级类型,确定是普通班还是特殊班
- * 然后计算出该数据所在的横纵坐标,使用 this.xs.cellText(ri,ci,text,sheetIndex)
- */
- setSchPlan() {
- const list = this.schPlan.filter(f => f.arrange.length > 0);
- const datas = this.xs.getData();
- for (let si = 0; si < datas.length; si++) {
- const sheetData = datas[si];
- const name = _.get(sheetData, 'name');
- const ct = this.classTypeList.find(f => f.name === name);
- // 这里不能找到不到,如果找不到,那就中断别找了
- if (!ct) continue;
- const termRowObject = sheetData.rows[1].cells;
- const arrangeCols = _.omit(termRowObject, this.ignoreCols);
- const termRowKeys = Object.keys(arrangeCols);
- const termRowValues = Object.values(arrangeCols);
- for (const i of list) {
- const { arrange, schid } = i;
- const schIndex = this.schoolList.findIndex(f => f.code === schid);
- // 小于零就是没有该学校,那就不要处理
- if (schIndex < 0) continue;
- // 查看有没有当前要处理的班级类型的安排
- const thisTypeClassArrange = arrange.filter(f => f.type === ct.code);
- if (thisTypeClassArrange.length <= 0) continue;
- // 计算行位置
- const ri = this.arrangeStartRow + schIndex + 1;
- // 计算列位置: 先确定是普通班还是特殊班,然后
- for (const a of arrange) {
- const { term, batchid, number } = a;
- let ci;
- // term 确定列范围, batchid确定索引, 两个值之和就是列的位置
- const valIndex = termRowValues.findIndex(f => f.text === term);
- if (valIndex < 0) continue;
- const termStartPos = termRowKeys[valIndex];
- // 再确定批次索引
- const termnum = this.plan.termnum.find(f => f.term === term);
- if (!termnum) continue;
- const batchnum = _.get(termnum, 'batchnum');
- if (!batchnum) continue;
- const batchIndex = batchnum.findIndex(f => f._id === batchid);
- if (batchIndex >= 0) ci = batchIndex + parseInt(termStartPos);
- if (ci) {
- this.xs.cellText(ri, ci, number, si);
- }
- }
- }
- }
- this.xs.reRender();
- },
- /**
- * 整理学校数据
- * 从3456或378后开始都行,反正不影响计划
- * @param {Object} sheetData organizeLine3456返回的数据
- */
- organizeLineFrom9(sheetDatas) {
- for (const sheetData of sheetDatas) {
- const name = _.get(sheetData, 'name');
- const ct = this.classTypeList.find(f => f.name === name);
- // 这里不能找到不到,如果找不到,那就中断别找了
- if (!ct) continue;
- let rowKey = this.arrangeStartRow + 1;
- let dataIndex = 0;
- for (let i = 0; i < this.schoolList.length; i++) {
- const sch = this.schoolList[i];
- const clas = _.get(sch, 'classnum', []);
- const typeCla = clas.find(f => f.code === ct.code);
- if (!typeCla) continue;
- const num = _.get(typeCla, 'number', 0);
- if (num <= 0) continue;
- dataIndex = dataIndex + 1;
- const obj = {
- 0: { text: dataIndex },
- 1: { text: _.get(sch, 'name', '') },
- 2: { text: num },
- 3: { text: _.get(sch, 'address', '') },
- };
- sheetData.rows[rowKey] = { cells: obj };
- rowKey = rowKey + 1;
- }
- }
- },
- /**
- * 处理场地的合并及督导天数,人数行合并及内容
- * 场地合并: 相邻且相同场地合并;
- * 督导天数合并: 与场地合并一致;
- * 督导天数内容: 多个批次的 最晚结束时间 - 最早开始时间;
- * 督导人数合并: 和期数合并一致;
- * 督导人数内容: 几个场地几个人;
- * @param {Array<Object>} sheetDatas organizeLine3456返回的数据
- */
- organizeLine378(sheetDatas) {
- // 场地合并
- // 取出期数行
- for (const sheetData of sheetDatas) {
- const name = _.get(sheetData, 'name');
- const ct = this.classTypeList.find(f => f.name === name);
- // 这里不能找到不到,如果找不到,那就中断别找了
- if (!ct) continue;
- const termRowObject = sheetData.rows[1].cells;
- const termColsMappings = [];
- // 整理出期范围
- for (const key in termRowObject) {
- // 无操作列直接跳过
- if (this.ignoreCols.includes(parseInt(key))) continue;
- const termObject = termRowObject[key];
- const cols = [parseInt(key)];
- const { merge = [], text } = termObject;
- // 没有两行直接返回
- if (merge.length === 2) {
- const last = _.last(merge);
- if (last) {
- for (let i = 1; i <= last; i++) {
- cols.push(parseInt(i) + parseInt(key));
- }
- }
- }
- termColsMappings.push({ term: text, cols, merge, col: parseInt(key) });
- }
- // 先把督导人数行处理了
- const ddpersonNumRowObject = {};
- for (const i of termColsMappings) {
- const { term, merge = [], col } = i;
- const obj = { text: 0, merge };
- const r = this.plan.termnum.find(f => f.term === term);
- if (r) {
- const bm = _.get(r, 'batchnum', []);
- let batchnum = this.batchFilterByClassType(bm, ct.code);
- if (batchnum.length <= 0) continue;
- const placeList = _.uniq(_.compact(batchnum.map(i => _.get(i, 'place')).filter(f => f !== '')));
- obj.text = placeList.length;
- }
- ddpersonNumRowObject[col] = obj;
- }
- const old7Cells = _.get(sheetData, `rows.7.cells`, {});
- sheetData.rows[7] = { cells: { ...old7Cells, ...ddpersonNumRowObject } };
- const placeRowObject = sheetData.rows[2].cells;
- // 处理督导天数,再查相邻场地是否相同
- for (const tcm of termColsMappings) {
- const { cols } = tcm;
- const inSameTermDatas = _.pick(placeRowObject, cols);
- const keys = Object.keys(inSameTermDatas);
- for (const key in inSameTermDatas) {
- const cell = inSameTermDatas[key];
- const isMerged = _.get(cell, 'isMerged', false);
- if (isMerged) continue;
- const text = _.get(cell, 'text');
- const keyIndex = keys.findIndex(f => f === key);
- let nextKeyIndex = keyIndex + 1;
- // 需要确定当前场地和下一个场地是否一致.所以需要确定当前列是否是最后一列,如果是最后一列,就不需要处理了,因为没有后面.
- while (nextKeyIndex < keys.length) {
- const nextKey = keys[nextKeyIndex];
- const nextCell = inSameTermDatas[nextKey];
- const nextCellText = _.get(nextCell, 'text');
- if (text === nextCellText) {
- // 一致,主单元格写入合并参数
- const merge = _.get(inSameTermDatas[key], 'merge', [0, 0]);
- const newLast = _.last(merge) + 1;
- merge[merge.length - 1] = newLast;
- inSameTermDatas[key].merge = merge;
- // 当前单元格写入isMerged:true
- inSameTermDatas[nextKey].isMerged = true;
- nextKeyIndex = nextKeyIndex + 1;
- continue;
- }
- // 下一个单元格的场地与当前单元格场地不一致,不处理,直接跳过
- break;
- }
- }
- }
- // 删掉被合并的单元格
- for (const key in placeRowObject) {
- const value = placeRowObject[key];
- if (_.get(value, 'isMerged', false)) delete placeRowObject[key];
- }
- // 督导天数的合并同 场地行一致,内容由 时间行计算而来
- // 时间行数据
- const timeRowObject = sheetData.rows[4].cells;
- // 督导天数行
- let ddDaysRowObject = sheetData.rows[6].cells;
- const timeColsMappings = [];
- // 根据场地范围,再计算天数,整理出督导天数行
- for (const key in placeRowObject) {
- // 无操作列直接跳过
- if (this.ignoreCols.includes(parseInt(key))) continue;
- const object = placeRowObject[key];
- const cols = [parseInt(key)];
- const { merge = [] } = object;
- // 没有两行直接返回
- if (merge.length === 2) {
- const last = _.last(merge);
- if (last) {
- for (let i = 1; i <= last; i++) {
- cols.push(parseInt(i) + parseInt(key));
- }
- }
- }
- timeColsMappings.push({ cols, merge, col: parseInt(key) });
- const timeDataInPlaceRange = _.pick(timeRowObject, cols);
- let days = [];
- for (const key in timeDataInPlaceRange) {
- const cell = timeDataInPlaceRange[key];
- const text = _.get(cell, 'text');
- if (text === '') continue;
- let arr = text.split('-');
- if (arr.length <= 0) continue;
- arr = arr.map(i => this.excelDateToStringDate(i));
- days.push(...arr);
- }
- // 升序排序
- days = days.sort((a, b) => new Date(a).getTime() - new Date(b).getTime());
- const start = _.head(days);
- const last = _.last(days);
- const diff = moment(last).diff(start, 'd');
- const m = _.get(object, 'merge');
- const cell = { text: diff };
- if (m) cell.merge = m;
- ddDaysRowObject[key] = cell;
- }
- }
- },
- /**组织表头,3&4&5&6行的数据:场地,班级,时间,批次人数
- */
- organizeLine3456() {
- /**
- * 这几行主要都是由批次信息得来的
- * 场地(3),班级数(4),时间(5),批次人数(6)都是可以在批次中获取的.
- * 期数可以直接获取,但是合并单元格的长度是根据批次来的
- */
- const sheetDataTemplate = _.cloneDeep(this.sheetDefaultConfig);
- const result = [];
- // 根据班级类型开始生成对应excel的数据
- for (const ct of this.classTypeList) {
- const sheetData = _.cloneDeep(sheetDataTemplate);
- // 先给名字
- sheetData.name = ct.name;
- // 获取本excel的预设数据,之后的操作都在这上面搞
- let rows = sheetData.rows;
- // 对期进行循环
- const termnum = _.get(this.plan, 'termnum', []);
- for (const t of termnum) {
- const { batchnum: bm = [], term } = t;
- /**
- * 因为根据班级来分sheet,所以从最开始就把班级都分开
- * 根据batchnum下的classnum来决定,如果有1个班级符合当前的班级类型.
- * 那就留下这个班,这个批次,这个期
- * 如果1个符合条件的班级都没有,那就直接爆了这期
- */
- let batchnum = this.batchFilterByClassType(bm, ct.code);
- if (batchnum.length <= 0) continue;
- const { pos, merge } = this.termLineDeal(rows, term, batchnum);
- for (const b of batchnum) {
- const { place, class: cla = [], startdate, enddate } = b;
- // 场地行处理
- const p = this.placeList.find(f => f._id === place);
- let pstr = place;
- if (p) pstr = _.get(p, 'name');
- const params = { term, batch: b.batch };
- this.batchLineDeal(rows, 2, pstr, params);
- // 班级数处理
- const clanum = cla.length;
- this.batchLineDeal(rows, 3, clanum, params);
- // 时间处理
- const sStr = moment(startdate).format('M.D');
- const eStr = moment(enddate).format('M.D');
- const timeStr = `${sStr}-${eStr}`;
- this.batchLineDeal(rows, 4, timeStr, params);
- // 批次人数
- const batchPersonNumber = cla.reduce((p, n) => parseInt(p) + parseInt(_.get(n, 'number', 0)), 0);
- this.batchLineDeal(rows, 5, batchPersonNumber, params);
- }
- }
- result.unshift(sheetData);
- }
- return result;
- },
- /**
- * 针对批次的统一处理方式
- * @param {Object} rows excel的所有行数据
- * @param {String} linePos 行位置
- * @param {String} text 显示内容
- * @param {Object} params 额外参数{term,batch}
- */
- batchLineDeal(rows, linePos, text, params = {}) {
- // 获取行数据
- const row = _.get(rows, `${linePos}.cells`, {});
- // 计算单元格的起始位置
- const pos = this.computedPosition(row);
- const cell = { text, ...params };
- // 不需要计算合并
- row[pos] = cell;
- },
- /**
- * 设置期数行
- * @param {Object} rows excel的所有行数据
- * @param {String} term 期数
- * @param {Array} batchnum 批次
- */
- termLineDeal(rows, term, batchnum) {
- // 获取行数据
- const row = _.get(rows, `1.cells`, {});
- // 计算单元格的起始位置
- const pos = this.computedPosition(row);
- // 设置单元格的具体内容(显示内容和单元格合并的设置)
- const cell = { text: term };
- const blen = batchnum.length;
- if (blen > 0) cell.merge = [0, blen - 1];
- row[pos] = cell;
- return { pos, merge: cell.merge };
- },
- /**
- * 计算该单元格所在列的位置.
- * @param {Object} row 当前行
- */
- computedPosition(row) {
- // 获取最后单元格的位置
- const keys = Object.keys(row);
- const lastKey = _.last(keys);
- const last = row[lastKey];
- // 设置单元格的位置
- let pos = parseInt(lastKey) + 1;
- if (!_.isObject(last)) {
- console.error(`${pos}_${lastKey}解析错误-单元格设置不是object类型`);
- return;
- }
- const lastCellMerge = _.get(last, 'merge', []);
- if (lastCellMerge.length > 0) {
- // 说明有合并操作,需要将被合并的单元格留下来后,再继续添加
- // [r,c]: r是行数合并; c:列合并主要看c,需要往后多窜c个位置, pos + c
- const ec = _.last(lastCellMerge);
- pos = parseInt(pos) + parseInt(ec);
- }
- return pos;
- },
- /**
- * 根据班级类型过滤批次
- * @param {Array} batch 批次
- * @param {String} code 班级类型编码
- */
- batchFilterByClassType(batch, code) {
- let batchnum = [];
- for (const bnum of batch) {
- const { class: clas = [], ...others } = bnum;
- const r = clas.find(f => f.type === code);
- if (!r) continue;
- const thisTypeClass = clas.filter(f => f.type === code);
- const obj = { ...others, class: thisTypeClass };
- batchnum.push(obj);
- }
- return batchnum;
- },
- // #endregion
- /**关闭对话框 */
- toClose() {
- this.form = {};
- this.planData = {};
- this.schoolData = {};
- },
- /**打开新增日期安排的对话框 */
- toAddCol() {
- this.dialog = true;
- },
- /**
- * 拼接成正常时间: YYYY-MM-DD
- * @param {String} pointDate 带点的缩短时间 7.1
- */
- excelDateToStringDate(pointDate) {
- return moment(`${this.year}.${pointDate}`).format('YYYY-MM-DD');
- },
- /**获取excel数据 */
- getExcelData() {
- const data = this.xs.getData();
- console.log(data);
- const copy = this.xs.copy();
- console.log(copy);
- /**
- * 由期行 的位置与合并数量,可以确定一期有几批次,批次的场地,班级,
- * 批次还原的原则: 场地,时间; 如果场地和时间都一致还存在2条以上的数据,那就说明分配有问题.在一个场地一个时间只能有个1个批次
- * 所以根据场地和时间判断数据是否新添加的(其实判断是不是新数据并没什么用,要做的是把数据整理出来)
- * 根据时间和场地,与plan中的数据对比:存在-修改数据;不存在:创建数据
- */
- },
- /** 导出excel */
- exportExcel() {
- var new_wb = this.xtos(this.xs.getData());
- /* generate download */
- XLSX.writeFile(new_wb, `培训计划${new Date().getTime()}.xlsx`);
- },
- xtos(sdata) {
- console.log(sdata);
- var out = XLSX.utils.book_new();
- sdata.forEach(function(xws) {
- var aoa = [[]];
- var rowobj = xws.rows;
- for (var ri = 0; ri < rowobj.len; ++ri) {
- var row = rowobj[ri];
- if (!row) continue;
- aoa[ri] = [];
- Object.keys(row.cells).forEach(function(k) {
- var idx = +k;
- if (isNaN(idx)) return;
- aoa[ri][idx] = row.cells[k].text;
- });
- }
- var ws = XLSX.utils.aoa_to_sheet(aoa);
- /** 读取在线中的合并单元格,并写入导出的数据中
- * merges: Array(19)
- 0: "A16:P16"
- 1: "A17:P17"
- 2: "O2:P2"
- 3: "F2:G2"
- */
- ws['!merges'] = [];
- xws.merges.forEach(merge => {
- ws['!merges'].push(XLSX.utils.decode_range(merge));
- });
- XLSX.utils.book_append_sheet(out, ws, xws.name);
- });
- return out;
- },
- },
- metaInfo() {
- return { title: this.$route.meta.title };
- },
- };
- </script>
- <style lang="less" scoped>
- .sheetContainerbox {
- width: 100%;
- height: 80vh;
- }
- </style>
|