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