|
|
@@ -2,6 +2,8 @@ class ExportExcel {
|
|
|
workBook = null;
|
|
|
|
|
|
sheet = null;
|
|
|
+
|
|
|
+ sheetIndex = 0;
|
|
|
|
|
|
border = new GC.Spread.Sheets.LineBorder('#000', GC.Spread.Sheets.LineStyle.thin);
|
|
|
|
|
|
@@ -12,14 +14,15 @@ class ExportExcel {
|
|
|
// 表格当前画到的行
|
|
|
curRow = 0;
|
|
|
|
|
|
- billTree = null;
|
|
|
+ curMaxCol = 0;
|
|
|
|
|
|
- curBillNode = null;
|
|
|
+ billTree = null;
|
|
|
|
|
|
libID = '';
|
|
|
|
|
|
- // 表格块,类型为ExcelBlock实例
|
|
|
- blocks = [];
|
|
|
+ // 分部章节单元格数据
|
|
|
+ sections = [];
|
|
|
+
|
|
|
|
|
|
// 清单ID - 清单精灵映射
|
|
|
billIDElfMap = {};
|
|
|
@@ -30,10 +33,15 @@ class ExportExcel {
|
|
|
// 叶子清单总数量
|
|
|
total = 0;
|
|
|
|
|
|
+ // 清单ID - 最大列映射
|
|
|
+ maxColMap = {};
|
|
|
+
|
|
|
+ // 叶子节点的父项ID
|
|
|
+ leafParentIDs = [];
|
|
|
+
|
|
|
constructor(workBook, billTree, libID) {
|
|
|
this.workBook = workBook;
|
|
|
this.sheet = this.workBook.getSheet(0);
|
|
|
- this.initSheet();
|
|
|
this.billTree = billTree;
|
|
|
this.libID = libID;
|
|
|
this.leafIDs = billTree.items.filter(node => !node.children.length).map(node => node.data.ID);
|
|
|
@@ -53,59 +61,18 @@ class ExportExcel {
|
|
|
alert(e);
|
|
|
console.log(e);
|
|
|
});
|
|
|
-
|
|
|
}
|
|
|
|
|
|
- // 将数据画在表格上
|
|
|
- async paintOnSheet() {
|
|
|
+ // 将数据画在工作簿上
|
|
|
+ async paintOnWorkBook() {
|
|
|
this.updateProcessInfo();
|
|
|
- // let i = 0;
|
|
|
- this.sheet.suspendPaint();
|
|
|
- this.sheet.suspendEvent();
|
|
|
- for (const billNode of this.billTree.items) {
|
|
|
- // 画标题
|
|
|
- if (this.curRow >= this.sheet.getRowCount()) {
|
|
|
- this.sheet.addRows(this.curRow, 2);
|
|
|
- }
|
|
|
- this.sheet.setFormatter(this.curRow, 0, '@');
|
|
|
- this.sheet.setFormatter(this.curRow, 1, '@');
|
|
|
- this.sheet.setText(this.curRow, 0, billNode.data.code);
|
|
|
- this.sheet.setText(this.curRow++, 1, billNode.data.name);
|
|
|
- // 画表格
|
|
|
- if (!billNode.children.length) {
|
|
|
- this.curRow++; // 空行
|
|
|
- const elfTree = await this.getElfTree(billNode.data.ID);
|
|
|
- if (!elfTree) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- const block = this.getBlock(elfTree);
|
|
|
- if (!block.length) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- 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)
|
|
|
- // 画单元格
|
|
|
- console.log(billNode.data);
|
|
|
- console.log(block);
|
|
|
- this.drawCell(block);
|
|
|
- // 画边框
|
|
|
- this.drawBorder(range)
|
|
|
- // i++;
|
|
|
- this.curRow += blockRange.rowCount + 1;
|
|
|
- }
|
|
|
- /* if (i === 100) {
|
|
|
- break;
|
|
|
- } */
|
|
|
- }
|
|
|
- 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();
|
|
|
- if (this.total === this.curProcessCount) {
|
|
|
- this.afterPaint();
|
|
|
+ // 根据专业区分不同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();
|
|
|
}
|
|
|
|
|
|
/* ========================================================以下为私有方法============================================== */
|
|
|
@@ -115,6 +82,58 @@ class ExportExcel {
|
|
|
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 });
|
|
|
+ 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 });
|
|
|
+ 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');
|
|
|
@@ -139,11 +158,20 @@ class ExportExcel {
|
|
|
}
|
|
|
|
|
|
// 画单元格、合并单元格
|
|
|
- drawCell(block) {
|
|
|
+ drawCell(block, addCurRow) {
|
|
|
block.forEach(item => {
|
|
|
- const row = item.row + this.curRow;
|
|
|
+ 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, '@');
|
|
|
+ if (item.isTitle) {
|
|
|
+ // 标题水平居中,字体加粗
|
|
|
+ const range = this.sheet.getRange(row, item.col, 1, 1);
|
|
|
+ range.hAlign(GC.Spread.Sheets.VerticalAlign.center);
|
|
|
+ this.sheet.getCell(row, item.col).font('bold normal 15px normal');
|
|
|
+ } 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);
|
|
|
});
|
|
|
}
|
|
|
@@ -158,9 +186,70 @@ class ExportExcel {
|
|
|
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) {
|
|
|
- return elfTree.items.map(node => {
|
|
|
+ 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;
|
|
|
@@ -176,25 +265,54 @@ class ExportExcel {
|
|
|
col,
|
|
|
rowCount,
|
|
|
};
|
|
|
+ if (this.curMaxCol < col) {
|
|
|
+ this.curMaxCol = col;
|
|
|
+ }
|
|
|
return {
|
|
|
row,
|
|
|
col,
|
|
|
rowCount,
|
|
|
text,
|
|
|
+ isRation: node.data.type === itemType.ration,
|
|
|
colCount: 1,
|
|
|
}
|
|
|
- })
|
|
|
+ });
|
|
|
+ this.moveRationTextToLastCol(block);
|
|
|
+ return block;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取表格标题单元格数据,计算标题合并列依赖正文表格
|
|
|
+ getBlockTitle() {
|
|
|
+ return [
|
|
|
+ { row: 0, col: 0, rowCount: 1, colCount: 1, text: '工序', isTitle: true },
|
|
|
+ { row: 0, col: 1, rowCount: 1, colCount: this.curMaxCol === 0 ? 1 : this.curMaxCol - 1, text: '选项', isTitle: true },
|
|
|
+ { row: 0, col: this.curMaxCol === 0 ? 2 : this.curMaxCol, rowCount: 1, colCount: 1, text: '定额', isTitle: true },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将定额文本挪到表格最大列处显示
|
|
|
+ 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);
|
|
|
+ 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: colCount === 1 ? 3 : colCount, // 有些表格没有选项和定额
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -228,8 +346,16 @@ class ExportExcel {
|
|
|
return this.billIDElfMap[billID];
|
|
|
}
|
|
|
|
|
|
- initSheet() {
|
|
|
- this.sheet.name('清单精灵');
|
|
|
+ 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);
|
|
|
}
|