//exportTableToMultiExcel import * as XLSX from 'xlsx'; import { saveAs } from 'file-saver'; type DataType = { [key: string]: string | number | null }; type Column = { title: string; dataIndex: string; key: string; children?: Column[]; }; type Merge = { s: { r: number; c: number }; e: { r: number; c: number }; }; function getMaxDepth(columns: Column[]): number { let maxDepth = 0; for (const column of columns) { if (column.children) { // 如果有子列,递归计算子列的深度 const depth = 1 + getMaxDepth(column.children); maxDepth = Math.max(maxDepth, depth); } else { // 如果没有子列,深度为1 maxDepth = Math.max(maxDepth, 1); } } return maxDepth; } function processColumns(columns: Column[], startRow = 0, startCol = 0, maxDepth = 0,colLength=0): { headers: any[][], merges: Merge[] } { let headers: any[][] = Array.from({ length: maxDepth }, () => Array(colLength).fill('')); let merges: Merge[] = []; let colIndex = startCol; // console.log({columns}); for (const column of columns) { let endRow = startRow; let endCol = colIndex; if (column.children) { const result = processColumns(column.children, startRow + 1, colIndex, maxDepth,colLength); headers = headers.map((row, i) => { if (i === startRow) { return [...row.slice(0, colIndex), ...result.headers[i], ...row.slice(colIndex + result.headers[i].length)]; } else { return [...row.slice(0, colIndex), ...result.headers[i].slice(colIndex, result.headers[i].length), ...row.slice(colIndex + result.headers[i].length)]; } }); merges = [...merges, ...result.merges]; endCol += getAllColumns(column.children).length - 1; // 根据所有子列的总数来更新 endCol endRow = startRow; // 父列的标题应该在子列标题的上一行 } else { endRow = maxDepth - 1; } for (let i = startRow; i <= endRow; i++) { headers[i][colIndex] = i === startRow ? column.title : ''; } merges.push({ s: { r: startRow, c: colIndex }, e: { r: endRow, c: endCol } }); colIndex = endCol + 1; } return { headers, merges }; } function exportTableToMultiExcel(tableData: DataType[], columns: Column[], fileName = 'data', needSummary = false) { const allColumns = getAllColumns(columns); // 获取所有列(包括子列) // 计算总结信息 const summary: DataType = {}; if (needSummary) { allColumns.forEach(column => { summary[column.dataIndex] = tableData.reduce((total, row) => { if (typeof row[column.dataIndex] === 'number') { return total + (row[column.dataIndex] as number); } return total; }, 0); }); summary[allColumns[0].dataIndex] = '总计'; // 设置第一列的值为'总计' } const dataValues = [...tableData.map(row => { const rowData = []; for (const column of allColumns) { rowData.push(row[column.dataIndex]); } return rowData; }), needSummary ? Object.values(summary) : []]; // 如果需要汇总,那么在数据数组的末尾添加总结信息 const maxDepth = getMaxDepth(columns); const { headers, merges } = processColumns(columns, 0, 0, maxDepth,columns.length); const wb = XLSX.utils.book_new(); const ws = XLSX.utils.aoa_to_sheet([...headers, ...dataValues]); // 计算每一列的最大字符长度 const colWidths = allColumns.map((column, i) => { let maxLength = column.title.length * 2; // 初始值设为列标题的长度的两倍 dataValues.forEach(row => { const cellValue = row[i]; if (cellValue) { const cellLength = cellValue.toString().length; // 对于宽字符,使用较大的系数;对于窄字符,使用较小的系数 const cellWidth = /[\u4e00-\u9fa5]/.test(cellValue.toString()) ? cellLength * 2 : cellLength; maxLength = Math.max(maxLength, cellWidth); } }); return { wch: maxLength }; }); // 设置每一列的宽度 ws['!cols'] = colWidths; // 设置单元格样式为水平和垂直居中 const range = XLSX.utils.decode_range(ws['!ref'] as string); for (let R = range.s.r; R <= range.e.r; ++R) { for (let C = range.s.c; C <= range.e.c; ++C) { const cell_address = { c: C, r: R }; const cell_ref = XLSX.utils.encode_cell(cell_address); if (!ws[cell_ref]) ws[cell_ref] = {}; ws[cell_ref].s = { alignment: { horizontal: 'center', vertical: 'center' } }; } } // 设置表头的背景色 for (let r = 0; r < maxDepth; r++) { for (let c = 0; c < headers[r].length; c++) { if (!ws[XLSX.utils.encode_cell({ r, c })]) { ws[XLSX.utils.encode_cell({ r, c })] = {}; } ws[XLSX.utils.encode_cell({ r, c })].s = { fill: { fgColor: { rgb: "FFFF00" } // 设置为黄色 }, alignment: { horizontal: 'center', vertical: 'center' } }; } } ws['!merges'] = merges; XLSX.utils.book_append_sheet(wb, ws, 'Sheet1'); const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); const blob = new Blob([wbout], { type: 'application/octet-stream' }); saveAs(blob, `${fileName}.xlsx`); } function getAllColumns(columns: Column[]): Column[] { let allColumns: Column[] = []; for (const column of columns) { if (column.children) { allColumns = [...allColumns, ...getAllColumns(column.children)]; } else { allColumns.push(column); } } return allColumns; } export default exportTableToMultiExcel; const columns = [ { dataIndex: "unitName", ellipsis: true, fixed: "left", key: "unitName", title: "核算单元", width: 140 }, { dataIndex: "key-23", ellipsis: true, key: "key-23", title: "单元管理绩效", width: 200 }, { dataIndex: "key-1679019958216040448", ellipsis: true, key: "key-1679019958216040448", title: "临床诊察积分奖金", width: 200, children: [ { dataIndex: "key-6", ellipsis: true, key: "key-6", title: "临床诊察积分-节假日奖金", width: 200, children: [ { dataIndex: "key-4", ellipsis: true, key: "key-4", title: "临床诊察积分-节假日", width: 200 }, { dataIndex: "key-5", ellipsis: true, key: "key-5", title: "临床诊察积分-节假日点值", width: 200 } ] }, { dataIndex: "key-3", ellipsis: true, key: "key-3", title: "临床诊察积分-工作日奖金", width: 200, children: [ { dataIndex: "key-1", ellipsis: true, key: "key-1", title: "临床诊察积分-工作日", width: 200 }, { dataIndex: "key-2", ellipsis: true, key: "key-2", title: "临床诊察积分-工作日点值", width: 200 } ] } ] }, { dataIndex: "totalScore", ellipsis: true, fixed: "right", key: "totalScore", title: "总奖金", width: 140 } ]