exportExcel.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. class ExportExcel {
  2. workBook = null;
  3. sheet = null;
  4. border = new GC.Spread.Sheets.LineBorder('#000', GC.Spread.Sheets.LineStyle.thin);
  5. excelIo = new GC.Spread.Excel.IO();
  6. $info = $('#excel-info');
  7. // 表格当前画到的行
  8. curRow = 0;
  9. billTree = null;
  10. curBillNode = null;
  11. libID = '';
  12. // 表格块,类型为ExcelBlock实例
  13. blocks = [];
  14. // 清单ID - 清单精灵映射
  15. billIDElfMap = {};
  16. // 叶子清单ID
  17. leafIDs = [];
  18. // 叶子清单总数量
  19. total = 0;
  20. constructor(workBook, billTree, libID) {
  21. this.workBook = workBook;
  22. this.sheet = this.workBook.getSheet(0);
  23. this.initSheet();
  24. this.billTree = billTree;
  25. this.libID = libID;
  26. this.leafIDs = billTree.items.filter(node => !node.children.length).map(node => node.data.ID);
  27. this.total = this.leafIDs.length;
  28. }
  29. // 导出
  30. async export() {
  31. if (!this.workBook) {
  32. return;
  33. }
  34. const json = this.workBook.toJSON();
  35. this.excelIo.save(json, function (blob) {
  36. saveAs(blob, '清单精灵排版.xlsx');
  37. }, function (e) {
  38. // process error
  39. alert(e);
  40. console.log(e);
  41. });
  42. }
  43. // 将数据画在表格上
  44. async paintOnSheet() {
  45. this.updateProcessInfo();
  46. // let i = 0;
  47. this.sheet.suspendPaint();
  48. this.sheet.suspendEvent();
  49. for (const billNode of this.billTree.items) {
  50. // 画标题
  51. if (this.curRow >= this.sheet.getRowCount()) {
  52. this.sheet.addRows(this.curRow, 2);
  53. }
  54. this.sheet.setFormatter(this.curRow, 0, '@');
  55. this.sheet.setFormatter(this.curRow, 1, '@');
  56. this.sheet.setText(this.curRow, 0, billNode.data.code);
  57. this.sheet.setText(this.curRow++, 1, billNode.data.name);
  58. // 画表格
  59. if (!billNode.children.length) {
  60. this.curRow++; // 空行
  61. const elfTree = await this.getElfTree(billNode.data.ID);
  62. if (!elfTree) {
  63. continue;
  64. }
  65. const block = this.getBlock(elfTree);
  66. if (!block.length) {
  67. continue;
  68. }
  69. const blockRange = this.getBlockRange(elfTree);
  70. this.checkRange(blockRange);
  71. const range = this.sheet.getRange(block[0].row + this.curRow, block[0].col, blockRange.rowCount, blockRange.colCount)
  72. // 画单元格
  73. console.log(billNode.data);
  74. console.log(block);
  75. this.drawCell(block);
  76. // 画边框
  77. this.drawBorder(range)
  78. // i++;
  79. this.curRow += blockRange.rowCount + 1;
  80. }
  81. /* if (i === 100) {
  82. break;
  83. } */
  84. }
  85. const range = this.sheet.getRange(0, 0, this.sheet.getRowCount(), this.sheet.getColumnCount());
  86. range.vAlign(GC.Spread.Sheets.VerticalAlign.center);
  87. range.wordWrap(true);
  88. this.sheet.resumeEvent();
  89. this.sheet.resumePaint();
  90. if (this.total === this.curProcessCount) {
  91. this.afterPaint();
  92. }
  93. }
  94. /* ========================================================以下为私有方法============================================== */
  95. // 当前到多少条叶子清单
  96. get curProcessCount () {
  97. return this.total - this.leafIDs.length;
  98. }
  99. // 画完表格后的处理
  100. afterPaint() {
  101. $('#excel-dialog').width('800px');
  102. $('#excel-spread').show();
  103. this.workBook.refresh();
  104. $('#export-excel-confirm').show();
  105. }
  106. // 超出范围追加行列
  107. checkRange(blockRange) {
  108. // 不够行数,追加行数
  109. const needRows = this.curRow + blockRange.rowCount;
  110. const curRowCount = this.sheet.getRowCount();
  111. if (curRowCount < needRows) {
  112. this.sheet.addRows(this.curRow, needRows - curRowCount + 5);
  113. }
  114. // 不够列数,追加列数
  115. const curColCount = this.sheet.getColumnCount();
  116. if (curColCount < blockRange.colCount) {
  117. this.sheet.addColumns(curColCount - 1, blockRange.colCount - curColCount);
  118. }
  119. }
  120. // 画单元格、合并单元格
  121. drawCell(block) {
  122. block.forEach(item => {
  123. const row = item.row + this.curRow;
  124. this.sheet.addSpan(row, item.col, item.rowCount, item.colCount);
  125. this.sheet.setFormatter(row, item.col, '@');
  126. this.sheet.setText(row, item.col, item.text);
  127. });
  128. }
  129. // 画边框
  130. drawBorder(range) {
  131. range.setBorder(this.border, { all: true })
  132. }
  133. // 更新进度信息
  134. updateProcessInfo() {
  135. this.$info.text(`导出清单中: ${this.curProcessCount} / ${this.total}`)
  136. }
  137. // 精灵树数据转换为表格块单元格数据
  138. getBlock(elfTree) {
  139. return elfTree.items.map(node => {
  140. // rowCount、colCount标记合并单元格范围
  141. const rowCount = node.posterityLeafCount() || 1;
  142. const parentRow = node.parent && node.parent.cellInfo ? node.parent.cellInfo.row : 0;
  143. // let prevRowCount = node.preSibling && node.preSibling.cellInfo ? node.preSibling.cellInfo.row + node.preSibling.cellInfo.rowCount - parentRow : 0;
  144. const prev = node.prevNode();
  145. let prevRowCount = prev && prev.cellInfo ? prev.cellInfo.row + prev.cellInfo.rowCount - parentRow : 0;
  146. const row = parentRow + prevRowCount;
  147. const col = node.depth();
  148. const name = node.data.type === itemType.ration ? node.data.name.split(' ')[0] : node.data.name;
  149. const text = `${isProcessNode(node) && node.data.require ? '* ' : ''}${name}`
  150. node.cellInfo = {
  151. row,
  152. col,
  153. rowCount,
  154. };
  155. return {
  156. row,
  157. col,
  158. rowCount,
  159. text,
  160. colCount: 1,
  161. }
  162. })
  163. }
  164. // 根据精灵树数据,获取表格块range
  165. getBlockRange(elfTree) {
  166. const rowCount = elfTree.roots.reduce((prev, node) => prev + (node.posterityLeafCount() || 1), 0);
  167. const a = Date.now();
  168. const colCount = Math.max(...elfTree.items.map(node => node.depth())) + 1;
  169. console.log(Date.now() - a);
  170. return {
  171. rowCount,
  172. colCount
  173. }
  174. }
  175. // 获取清单的精灵树
  176. async getElfTree(billID) {
  177. const items = await this.getElfItems(billID);
  178. if (!items || !items.length) {
  179. return null;
  180. }
  181. const tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
  182. tree.loadDatas(items);
  183. return tree;
  184. }
  185. // 获取清单的精灵数据
  186. async getElfItems(billID) {
  187. if (this.billIDElfMap[billID]) {
  188. return this.billIDElfMap[billID];
  189. }
  190. if (!this.leafIDs.length) {
  191. return null;
  192. }
  193. const count = 20; // 每次拉取数据条数
  194. const billIDs = this.leafIDs.splice(0, count)
  195. await setTimeoutSync(null, 100);
  196. const items = await ajaxPost('/billsGuidance/api/getItemsByBillIDs', { guidanceLibID: this.libID, billIDs });
  197. this.updateProcessInfo();
  198. items.forEach(item => {
  199. (this.billIDElfMap[item.billsID] || (this.billIDElfMap[item.billsID] = [])).push(item)
  200. });
  201. return this.billIDElfMap[billID];
  202. }
  203. initSheet() {
  204. this.sheet.name('清单精灵');
  205. for (let col = 0; col < this.sheet.getColumnCount(); col++) {
  206. this.sheet.setColumnWidth(col, 100);
  207. }
  208. }
  209. }