tableToMultiHeaderExcel.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. //exportTableToMultiExcel
  2. import * as XLSX from 'xlsx';
  3. import { saveAs } from 'file-saver';
  4. type DataType = { [key: string]: string | number | null };
  5. type Column = {
  6. title: string;
  7. dataIndex: string;
  8. key: string;
  9. children?: Column[];
  10. };
  11. type Merge = {
  12. s: { r: number; c: number };
  13. e: { r: number; c: number };
  14. };
  15. function getMaxDepth(columns: Column[]): number {
  16. let maxDepth = 0;
  17. for (const column of columns) {
  18. if (column.children) {
  19. // 如果有子列,递归计算子列的深度
  20. const depth = 1 + getMaxDepth(column.children);
  21. maxDepth = Math.max(maxDepth, depth);
  22. } else {
  23. // 如果没有子列,深度为1
  24. maxDepth = Math.max(maxDepth, 1);
  25. }
  26. }
  27. return maxDepth;
  28. }
  29. function processColumns(columns: Column[], startRow = 0, startCol = 0, maxDepth = 0,colLength=0): { headers: any[][], merges: Merge[] } {
  30. let headers: any[][] = Array.from({ length: maxDepth }, () => Array(colLength).fill(''));
  31. let merges: Merge[] = [];
  32. let colIndex = startCol;
  33. // console.log({columns});
  34. for (const column of columns) {
  35. let endRow = startRow;
  36. let endCol = colIndex;
  37. if (column.children) {
  38. const result = processColumns(column.children, startRow + 1, colIndex, maxDepth,colLength);
  39. headers = headers.map((row, i) => {
  40. if (i === startRow) {
  41. return [...row.slice(0, colIndex), ...result.headers[i], ...row.slice(colIndex + result.headers[i].length)];
  42. } else {
  43. return [...row.slice(0, colIndex), ...result.headers[i].slice(colIndex, result.headers[i].length), ...row.slice(colIndex + result.headers[i].length)];
  44. }
  45. });
  46. merges = [...merges, ...result.merges];
  47. endCol += getAllColumns(column.children).length - 1; // 根据所有子列的总数来更新 endCol
  48. endRow = startRow; // 父列的标题应该在子列标题的上一行
  49. } else {
  50. endRow = maxDepth - 1;
  51. }
  52. for (let i = startRow; i <= endRow; i++) {
  53. headers[i][colIndex] = i === startRow ? column.title : '';
  54. }
  55. merges.push({ s: { r: startRow, c: colIndex }, e: { r: endRow, c: endCol } });
  56. colIndex = endCol + 1;
  57. }
  58. return { headers, merges };
  59. }
  60. function exportTableToMultiExcel(tableData: DataType[], columns: Column[], fileName = 'data', needSummary = false) {
  61. const allColumns = getAllColumns(columns); // 获取所有列(包括子列)
  62. // 计算总结信息
  63. const summary: DataType = {};
  64. if (needSummary) {
  65. allColumns.forEach(column => {
  66. summary[column.dataIndex] = tableData.reduce((total, row) => {
  67. if (typeof row[column.dataIndex] === 'number') {
  68. return total + (row[column.dataIndex] as number);
  69. }
  70. return total;
  71. }, 0);
  72. });
  73. summary[allColumns[0].dataIndex] = '总计'; // 设置第一列的值为'总计'
  74. }
  75. const dataValues = [...tableData.map(row => {
  76. const rowData = [];
  77. for (const column of allColumns) {
  78. rowData.push(row[column.dataIndex]);
  79. }
  80. return rowData;
  81. }), needSummary ? Object.values(summary) : []]; // 如果需要汇总,那么在数据数组的末尾添加总结信息
  82. const maxDepth = getMaxDepth(columns);
  83. const { headers, merges } = processColumns(columns, 0, 0, maxDepth,columns.length);
  84. const wb = XLSX.utils.book_new();
  85. const ws = XLSX.utils.aoa_to_sheet([...headers, ...dataValues]);
  86. // 计算每一列的最大字符长度
  87. const colWidths = allColumns.map((column, i) => {
  88. let maxLength = column.title.length * 2; // 初始值设为列标题的长度的两倍
  89. dataValues.forEach(row => {
  90. const cellValue = row[i];
  91. if (cellValue) {
  92. const cellLength = cellValue.toString().length;
  93. // 对于宽字符,使用较大的系数;对于窄字符,使用较小的系数
  94. const cellWidth = /[\u4e00-\u9fa5]/.test(cellValue.toString()) ? cellLength * 2 : cellLength;
  95. maxLength = Math.max(maxLength, cellWidth);
  96. }
  97. });
  98. return { wch: maxLength };
  99. });
  100. // 设置每一列的宽度
  101. ws['!cols'] = colWidths;
  102. // 设置单元格样式为水平和垂直居中
  103. const range = XLSX.utils.decode_range(ws['!ref'] as string);
  104. for (let R = range.s.r; R <= range.e.r; ++R) {
  105. for (let C = range.s.c; C <= range.e.c; ++C) {
  106. const cell_address = { c: C, r: R };
  107. const cell_ref = XLSX.utils.encode_cell(cell_address);
  108. if (!ws[cell_ref]) ws[cell_ref] = {};
  109. ws[cell_ref].s = {
  110. alignment: {
  111. horizontal: 'center',
  112. vertical: 'center'
  113. }
  114. };
  115. }
  116. }
  117. // 设置表头的背景色
  118. for (let r = 0; r < maxDepth; r++) {
  119. for (let c = 0; c < headers[r].length; c++) {
  120. if (!ws[XLSX.utils.encode_cell({ r, c })]) {
  121. ws[XLSX.utils.encode_cell({ r, c })] = {};
  122. }
  123. ws[XLSX.utils.encode_cell({ r, c })].s = {
  124. fill: {
  125. fgColor: { rgb: "FFFF00" } // 设置为黄色
  126. },
  127. alignment: {
  128. horizontal: 'center',
  129. vertical: 'center'
  130. }
  131. };
  132. }
  133. }
  134. ws['!merges'] = merges;
  135. XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
  136. const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
  137. const blob = new Blob([wbout], { type: 'application/octet-stream' });
  138. saveAs(blob, `${fileName}.xlsx`);
  139. }
  140. function getAllColumns(columns: Column[]): Column[] {
  141. let allColumns: Column[] = [];
  142. for (const column of columns) {
  143. if (column.children) {
  144. allColumns = [...allColumns, ...getAllColumns(column.children)];
  145. } else {
  146. allColumns.push(column);
  147. }
  148. }
  149. return allColumns;
  150. }
  151. export default exportTableToMultiExcel;
  152. const columns = [
  153. {
  154. dataIndex: "unitName",
  155. ellipsis: true,
  156. fixed: "left",
  157. key: "unitName",
  158. title: "核算单元",
  159. width: 140
  160. },
  161. {
  162. dataIndex: "key-23",
  163. ellipsis: true,
  164. key: "key-23",
  165. title: "单元管理绩效",
  166. width: 200
  167. },
  168. {
  169. dataIndex: "key-1679019958216040448",
  170. ellipsis: true,
  171. key: "key-1679019958216040448",
  172. title: "临床诊察积分奖金",
  173. width: 200,
  174. children: [
  175. {
  176. dataIndex: "key-6",
  177. ellipsis: true,
  178. key: "key-6",
  179. title: "临床诊察积分-节假日奖金",
  180. width: 200,
  181. children: [
  182. {
  183. dataIndex: "key-4",
  184. ellipsis: true,
  185. key: "key-4",
  186. title: "临床诊察积分-节假日",
  187. width: 200
  188. },
  189. {
  190. dataIndex: "key-5",
  191. ellipsis: true,
  192. key: "key-5",
  193. title: "临床诊察积分-节假日点值",
  194. width: 200
  195. }
  196. ]
  197. },
  198. {
  199. dataIndex: "key-3",
  200. ellipsis: true,
  201. key: "key-3",
  202. title: "临床诊察积分-工作日奖金",
  203. width: 200,
  204. children: [
  205. {
  206. dataIndex: "key-1",
  207. ellipsis: true,
  208. key: "key-1",
  209. title: "临床诊察积分-工作日",
  210. width: 200
  211. },
  212. {
  213. dataIndex: "key-2",
  214. ellipsis: true,
  215. key: "key-2",
  216. title: "临床诊察积分-工作日点值",
  217. width: 200
  218. }
  219. ]
  220. }
  221. ]
  222. },
  223. {
  224. dataIndex: "totalScore",
  225. ellipsis: true,
  226. fixed: "right",
  227. key: "totalScore",
  228. title: "总奖金",
  229. width: 140
  230. }
  231. ]