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); } } }