123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- class ExportExcel {
- workBook = null;
- sheet = null;
-
- sheetIndex = 0;
- border = new GC.Spread.Sheets.LineBorder('#000', GC.Spread.Sheets.LineStyle.thin);
- excelIo = new GC.Spread.Excel.IO();
- $info = $('#excel-info');
- // 表格当前画到的行
- curRow = 0;
- curMaxCol = 0;
- billTree = null;
- libID = '';
- // 分部章节单元格数据
- sections = [];
- // 清单ID - 清单精灵映射
- billIDElfMap = {};
- // 叶子清单ID
- leafIDs = [];
- // 叶子清单总数量
- total = 0;
- // 清单ID - 最大列映射
- maxColMap = {};
- // 叶子节点的父项ID
- leafParentIDs = [];
- // 字体
- font = 'normal normal 9pt 宋体';
- blockTitleFont = 'bold normal 9pt 宋体';
- bigSectionFont = 'bold normal 12pt 黑体';
- leafSectionFont = 'bold normal 11pt 黑体';
- constructor(workBook, billTree, libID) {
- this.workBook = workBook;
- this.sheet = this.workBook.getSheet(0);
- this.billTree = billTree;
- this.libID = libID;
- this.leafIDs = billTree.items.filter(node => !node.children.length).map(node => node.data.ID);
- this.total = this.leafIDs.length;
- }
- // 导出
- async export() {
- if (!this.workBook) {
- return;
- }
- const json = this.workBook.toJSON();
- this.excelIo.save(json, function (blob) {
- saveAs(blob, '清单精灵排版.xlsx');
- }, function (e) {
- // process error
- alert(e);
- console.log(e);
- });
- }
- // 将数据画在工作簿上
- async paintOnWorkBook() {
- this.updateProcessInfo();
- // 根据专业区分不同sheet
- for (const root of this.billTree.roots) {
- this.initSheet(this.sheetIndex++, root.data.name);
- const nodes = [root, ...root.getPosterity()];
- await this.paintOnSheet(nodes);
- }
- this.afterPaint();
- }
- /* ========================================================以下为私有方法============================================== */
- // 当前到多少条叶子清单
- get curProcessCount () {
- return this.total - this.leafIDs.length;
- }
- // 将数据画在表格上
- async paintOnSheet(nodes) {
- this.updateProcessInfo();
- let i = 0;
- this.sheet.suspendPaint();
- this.sheet.suspendEvent();
- for (const billNode of nodes) {
- if (this.curRow >= this.sheet.getRowCount()) {
- this.sheet.addRows(this.curRow, 2);
- }
- this.curMaxCol = 0;
- if (billNode.children.length) {
- // 清单分部章节,合并的列数依赖主体表格,所以先存数据画空行,后续再画具体内容
- this.sections.push({ ID: billNode.data.ID, isNotLeaf: true, row: this.curRow, col: 0, text: `${billNode.data.code} ${billNode.data.name}`, rowCount: 1, colCount: 1, font: this.bigSectionFont });
- this.curRow++;
- } else {
- if (!this.leafParentIDs.includes(billNode.data.ParentID)) {
- this.leafParentIDs.push(billNode.data.ParentID);
- }
- if (this.maxColMap[billNode.data.ParentID] === undefined) {
- this.maxColMap[billNode.data.ParentID] = 0;
- }
- // 叶子清单,合并的列数依赖主体表格,所以先存数据画空行,后续再画具体内容
- const billText = `编码:${billNode.data.code} 名称:${billNode.data.name} 单位:${billNode.data.unit}`;
- this.sections.push({ ID: billNode.data.ID, row: this.curRow, col: 0, text: billText, rowCount: 1, colCount: 1, font: this.leafSectionFont });
- this.curRow++;
- // 画清单精灵表格
- const elfTree = await this.getElfTree(billNode.data.ID);
- if (!elfTree) {
- continue;
- }
- this.drawBlock(elfTree);
- if (this.maxColMap[billNode.data.ParentID] < this.curMaxCol) {
- this.maxColMap[billNode.data.ParentID] = this.curMaxCol;
- }
- i++;
-
- }
- /* if (i === 10) {
- break;
- } */
- }
- // 画章节
- this.drawSection(this.sections);
- const range = this.sheet.getRange(0, 0, this.sheet.getRowCount(), this.sheet.getColumnCount());
- range.vAlign(GC.Spread.Sheets.VerticalAlign.center);
- range.wordWrap(true);
- this.sheet.resumeEvent();
- this.sheet.resumePaint();
- }
- // 画完表格后的处理
- afterPaint() {
- $('#excel-dialog').width('800px');
- $('#excel-spread').show();
- this.workBook.refresh();
- $('#export-excel-confirm').show();
- }
- // 超出范围追加行列
- checkRange(blockRange) {
- // 不够行数,追加行数
- const needRows = this.curRow + blockRange.rowCount;
- const curRowCount = this.sheet.getRowCount();
- if (curRowCount < needRows) {
- this.sheet.addRows(this.curRow, needRows - curRowCount + 5);
- }
- // 不够列数,追加列数
- const curColCount = this.sheet.getColumnCount();
- if (curColCount < blockRange.colCount) {
- this.sheet.addColumns(curColCount - 1, blockRange.colCount - curColCount);
- }
- }
- // 画单元格、合并单元格
- drawCell(block, addCurRow) {
- block.forEach(item => {
- const row = addCurRow ? item.row + this.curRow : item.row;
- this.sheet.addSpan(row, item.col, item.rowCount, item.colCount);
- this.sheet.setFormatter(row, item.col, '@');
- const font = item.font || this.font;
- this.sheet.getCell(row, item.col).font(font);
- if (item.isTitle) {
- // 标题水平居中,字体加粗
- const range = this.sheet.getRange(row, item.col, 1, 1);
- range.hAlign(GC.Spread.Sheets.VerticalAlign.center);
- } else if (item.isNotLeaf) {
- const range = this.sheet.getRange(row, item.col, 1, 1);
- range.hAlign(GC.Spread.Sheets.VerticalAlign.center);
- }
- this.sheet.setText(row, item.col, item.text);
- });
- }
- // 画边框
- drawBorder(range) {
- range.setBorder(this.border, { all: true })
- }
- // 更新进度信息
- updateProcessInfo() {
- this.$info.text(`导出排版中: ${this.curProcessCount} / ${this.total}`)
- }
- // 完善章节清单ID - 最大列映射
- setSectionMaxCol() {
- this.leafParentIDs.forEach(ID => {
- const node = this.billTree.findNode(ID);
- if (!node) {
- return;
- }
- node.children.forEach(child => {
- this.maxColMap[child.data.ID] = this.maxColMap[ID];
- });
- let parent = node.parent;
- while (parent && this.maxColMap[parent.data.ID] === undefined) {
- this.maxColMap[parent.data.ID] = this.maxColMap[ID];
- parent = parent.parent;
- }
- });
- }
- // 画章节
- drawSection(sections) {
- this.setSectionMaxCol();
- sections.forEach(section => {
- const maxCol = this.maxColMap[section.ID] || 0;
- section.colCount = maxCol + 1;
- });
- this.drawCell(sections, false);
- }
- // 画主体表格
- async drawBlock(elfTree) {
- const block = this.getBlock(elfTree);
- if (!block.length) {
- return;
- }
- const blockRange = this.getBlockRange(elfTree);
- this.checkRange(blockRange);
- const range = this.sheet.getRange(block[0].row + this.curRow, block[0].col, blockRange.rowCount, blockRange.colCount)
- // 画单元格
- this.drawCell(block, true);
- // 画边框
- this.drawBorder(range);
- this.curRow += blockRange.rowCount;
- }
- // 精灵树数据转换为表格块单元格数据
- getBlock(elfTree) {
- const block = [];
- // 表格正文
- const blockContent = this.getBlockContent(elfTree);
- // 表格标题:获取完正文,才知道标题的合并列数,因此先获取表格正文,再获取表格标题
- const blockTitle = this.getBlockTitle();
- // 表格正文的行号,整体下移
- blockContent.forEach(cellInfo => {
- cellInfo.row += 1;
- });
- block.push(...blockTitle);
- block.push(...blockContent);
- return block;
- }
- // 获取表格块正文单元格数据
- getBlockContent(elfTree) {
- const block = elfTree.items.map(node => {
- // rowCount、colCount标记合并单元格范围
- const rowCount = node.posterityLeafCount() || 1;
- const parentRow = node.parent && node.parent.cellInfo ? node.parent.cellInfo.row : 0;
- // let prevRowCount = node.preSibling && node.preSibling.cellInfo ? node.preSibling.cellInfo.row + node.preSibling.cellInfo.rowCount - parentRow : 0;
- const prev = node.prevNode();
- let prevRowCount = prev && prev.cellInfo ? prev.cellInfo.row + prev.cellInfo.rowCount - parentRow : 0;
- const row = parentRow + prevRowCount;
- const col = node.depth();
- const name = node.data.type === itemType.ration ? node.data.name.split(' ')[0] : node.data.name;
- const text = `${isProcessNode(node) && node.data.required ? '* ' : ''}${name}`
- node.cellInfo = {
- row,
- col,
- rowCount,
- };
- if (this.curMaxCol < col) {
- this.curMaxCol = col;
- }
- return {
- row,
- col,
- rowCount,
- text,
- isRation: node.data.type === itemType.ration,
- colCount: 1,
- font: this.font,
- }
- });
- this.moveRationTextToLastCol(block);
- return block;
- }
- // 获取表格标题单元格数据,计算标题合并列依赖正文表格
- getBlockTitle() {
- return [
- { row: 0, col: 0, rowCount: 1, colCount: 1, text: '工序', isTitle: true, font: this.blockTitleFont },
- { row: 0, col: 1, rowCount: 1, colCount: this.curMaxCol === 0 ? 1 : this.curMaxCol - 1, text: '选项', isTitle: true, font: this.blockTitleFont },
- { row: 0, col: this.curMaxCol === 0 ? 2 : this.curMaxCol, rowCount: 1, colCount: 1, text: '定额', isTitle: true, font: this.blockTitleFont },
- ];
- }
-
- // 将定额文本挪到表格最大列处显示
- moveRationTextToLastCol(block) {
- block.forEach(cellInfo => {
- if (cellInfo.isRation) {
- cellInfo.col = this.curMaxCol;
- }
- });
- }
- // 从表格中获取最大列号
- getMaxCol(block) {
- return Math.max(...block.map(cellInfo => cellInfo.col));
- }
- // 根据精灵树数据,获取表格块range
- getBlockRange(elfTree) {
- const rowCount = elfTree.roots.reduce((prev, node) => prev + (node.posterityLeafCount() || 1), 0) + 1; // +1是因为有一行标题
- const a = Date.now();
- const colCount = Math.max(...elfTree.items.map(node => node.depth())) + 1;
- console.log(Date.now() - a);
- return {
- rowCount,
- colCount: colCount === 1 ? 3 : colCount, // 有些表格没有选项和定额
- }
- }
- // 获取清单的精灵树
- async getElfTree(billID) {
- const items = await this.getElfItems(billID);
- if (!items || !items.length) {
- return null;
- }
- const tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
- tree.loadDatas(items);
- return tree;
- }
- // 获取清单的精灵数据
- async getElfItems(billID) {
- if (this.billIDElfMap[billID]) {
- return this.billIDElfMap[billID];
- }
- if (!this.leafIDs.length) {
- return null;
- }
- const count = 20; // 每次拉取数据条数
- const billIDs = this.leafIDs.splice(0, count)
- await setTimeoutSync(null, 100);
- const items = await ajaxPost('/billsGuidance/api/getItemsByBillIDs', { guidanceLibID: this.libID, billIDs });
- this.updateProcessInfo();
- items.forEach(item => {
- (this.billIDElfMap[item.billsID] || (this.billIDElfMap[item.billsID] = [])).push(item)
- });
- return this.billIDElfMap[billID];
- }
- initSheet(index, name) {
- this.curRow = 0;
- this.sections = [];
- this.leafParentIDs = [];
- if (index !== 0) {
- // 工作簿自身会有一张表
- this.workBook.addSheet(index);
- }
- this.sheet = this.workBook.getSheet(index);
- this.sheet.name(name);
- for (let col = 0; col < this.sheet.getColumnCount(); col++) {
- this.sheet.setColumnWidth(col, 100);
- }
- }
- }
|