Pārlūkot izejas kodu

feat: 导出精灵excel排版调整

vian 3 gadi atpakaļ
vecāks
revīzija
1d245264f3

+ 1 - 1
modules/std_billsGuidance_lib/facade/facades.js

@@ -292,7 +292,7 @@ async function getLibWithBills(libID) {
     if (!billsLib) {
         throw '引用的清单规则库不存在!';
     }
-    let bills = await stdBillsModel.find({ billsLibId: billsLib.billsLibId }, '-_id code name ID NextSiblingID ParentID jobs items comment').lean();
+    let bills = await stdBillsModel.find({ billsLibId: billsLib.billsLibId }, '-_id code name ID unit NextSiblingID ParentID jobs items comment').lean();
     const guideItems = await billsGuideItemsModel.find({ libID: guidanceLib[0].ID }, '-_id billsID').lean();
     const billsMap = {};
     for (const item of guideItems) {

+ 3 - 2
web/maintain/billsGuidance_lib/js/billsGuidance.js

@@ -2315,7 +2315,8 @@ const billsGuidance = (function () {
             if (bills.tree) {
                 excelInstance = new ExportExcel(exportExcelWorkBook, bills.tree, libID);
                 try {
-                    await excelInstance.paintOnSheet();
+                    // await excelInstance.paintOnSheet();
+                    await excelInstance.paintOnWorkBook();
                 } catch (error) {
                     console.log(error);
                     alert(error);
@@ -3075,7 +3076,7 @@ const billsGuidance = (function () {
             console.log(classData);
         }
     });
-    return { initViews, initSlideSize };
+    return { initViews, initSlideSize, bills };
 })();
 
 $(document).ready(function () {

+ 188 - 62
web/maintain/billsGuidance_lib/js/exportExcel.js

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