UNPKG

shawn133-utils

Version:

430 lines (424 loc) 13.7 kB
import * as XLSX from "xlsx"; import moment from "moment"; import { ElMessage } from 'element-plus' export function exportExcel(config: any = {}) { console.log('exportExcel', config); let { excelName = '文件名称', headerData = [], exportData = [] } = config // headerData = [ // { // name: "表头测试", // prop: "test", // child: [ // { // name: "表头测试1", // prop: "test1", // }, // { // name: "表头测试2", // prop: "test2", // }, // ], // }, // ]; // exportData = [ // { // test: "测试数据", // test1: "测试数据1", // test2: "测试数据2", // }, // ]; if (exportData.length === 0) { ElMessage({ message: '暂无数据导出', type: 'warning', }) return } exportData.forEach((item: any) => { for (var k in item) { if (item.hasOwnProperty(k)) { if (item[k] !== null || item[k] !== true || item[k] !== false) { if (item[k] === "true") { item[k] = "是"; } else if (item[k] === "false") { item[k] = "否"; } else if (item[k] === null) { item[k] = ""; } else { item[k] = item[k] + ""; } } } } }); let sheetName = `${excelName}${moment().format("YYYYMMDDHHmmss")}`; // excel表头 let excelHeader = buildHeader(headerData); // 头部行数,用来固定表头 let headerRows = excelHeader.length; // 提取数据 let dataList = extractData(exportData, headerData); excelHeader.push(...dataList, []); // 计算合并 let merges = doMerges(excelHeader); // 生成sheet let ws = aoa_to_sheet(excelHeader, headerRows) as any; // 单元格合并 ws["!merges"] = merges; // 头部冻结 ws["!freeze"] = { xSplit: "1", ySplit: "" + headerRows, topLeftCell: "B" + (headerRows + 1), activePane: "bottomRight", state: "frozen", }; // 列宽 //ws["!cols"] = [{ wpx: 165 },{ wpx: 165 }]; ws["!cols"] = []; for (let j in headerData) { console.log('j',j) ws["!cols"].push({ wpx: 165 }); } let workbook = { SheetNames: [sheetName], Sheets: {}, } as any; workbook.Sheets[sheetName] = ws; // excel样式 let wopts = { bookType: "xlsx", bookSST: false, type: "binary", cellStyles: true, } as any; let wbout = XLSX.write(workbook, wopts); let blob = new Blob([s2ab(wbout)], { type: "application/octet-stream", }); openDownloadXLSXDialog(blob, sheetName + ".xlsx"); } function buildHeader(headerData: any) { let excelHeader: any[] = []; // 构建生成excel表头需要的数据结构 getHeader(headerData, excelHeader, 0, 0); // 多行表头长短不一,短的向长的看齐,不够的补上行合并占位符 let max = Math.max(...excelHeader.map((a: any) => a.length)); excelHeader .filter((e: any) => e.length < max) .forEach((e: any) => pushRowSpanPlaceHolder(e, max - e.length)); return excelHeader; } function getHeader(headers: any, excelHeader: any, deep: any, perOffset: any) { let offset = 0; let cur = excelHeader[deep]; if (!cur) { cur = excelHeader[deep] = []; } // 填充行合并占位符 pushRowSpanPlaceHolder(cur, perOffset - cur.length); for (let i = 0; i < headers.length; i++) { let head = headers[i]; cur.push(head.name || head.label); if ( head.hasOwnProperty("child") && Array.isArray(head.child) && head.child.length > 0 ) { let childOffset = getHeader( head.child, excelHeader, deep + 1, cur.length - 1, ); // 填充列合并占位符 pushColSpanPlaceHolder(cur, childOffset - 1); offset += childOffset; } else { offset++; } } return offset; } /** * 根据选中的数据和展示的列,生成结果 * @param exportData * @param headerData */ function extractData(exportData: any, headerData: any) { // 列 let headerList = flat(headerData); // 导出的结果集 let excelRows: any[] = []; // 如果有child集合的话会用到 let dataKeys = new Set(Object.keys(exportData[0])); exportData.some((e: any) => { if (e.child && e.child.length > 0) { let childKeys = Object.keys(e.child[0]); for (let i = 0; i < childKeys.length; i++) { dataKeys.delete(childKeys[i]); } return true; } }); flatData(exportData, (list: any) => { excelRows.push(...buildExcelRow(dataKeys, headerList, list)); }); return excelRows; } /** * * * */ function buildExcelRow(mainKeys: any, headers: any, rawDataList: any) { // 合计行 let sumCols: any[] = []; // 数据行 let rows = []; for (let i = 0; i < rawDataList.length; i++) { let cols = []; let rawData = rawDataList[i]; // 提取数据 for (let j = 0; j < headers.length; j++) { let header = headers[j]; // 父元素键需要行合并 if (rawData["rowSpan"] === 0 && mainKeys.has(header.prop)) { cols.push("!$ROW_SPAN_PLACEHOLDER"); } else { let value; if (typeof header.exeFun === "function") { value = header.exeFun(rawData); } else { value = rawData[header.prop]; } cols.push(value); // 如果该列需要合计,并且是数字类型 if (header["summable"] && typeof value === "number") { sumCols[j] = (sumCols[j] ? sumCols[j] : 0) + value; } } } rows.push(cols); } // 如果有合计行 if (sumCols.length > 0) { rows.push(...sumRowHandle()); } return rows; } function sumRowHandle() { return []; } /** * 合并头部单元格 **/ function doMerges(arr: any) { // 要么横向合并 要么纵向合并 let deep = arr.length; let merges = []; for (let y = 0; y < deep; y++) { // 先处理横向合并 let row = arr[y]; let colSpan = 0; for (let x = 0; x < row.length; x++) { if (row[x] === "!$COL_SPAN_PLACEHOLDER") { row[x] = undefined; if (x + 1 === row.length) { merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x }, }); } colSpan++; } else if (colSpan > 0 && x > colSpan) { merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 }, }); colSpan = 0; } else { colSpan = 0; } } } // 再处理纵向合并 let colLength = arr[0].length; for (let x = 0; x < colLength; x++) { let rowSpan = 0; for (let y = 0; y < deep; y++) { if (arr[y][x] === "!$ROW_SPAN_PLACEHOLDER") { arr[y][x] = undefined; rowSpan++; if (y + 1 === deep) { merges.push({ s: { r: y - rowSpan, c: x }, e: { r: y, c: x } }); } } else if (rowSpan > 0 && y > rowSpan) { merges.push({ s: { r: y - rowSpan - 1, c: x }, e: { r: y - 1, c: x }, }); rowSpan = 0; } else { rowSpan = 0; } } } return merges; } /** * 从github复制过来的 */ function aoa_to_sheet(data: any, headerRows: any) { const ws = {} as any; const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } }; for (let R = 0; R !== data.length; ++R) { for (let C = 0; C !== data[R].length; ++C) { if (range.s.r > R) { range.s.r = R; } if (range.s.c > C) { range.s.c = C; } if (range.e.r < R) { range.e.r = R; } if (range.e.c < C) { range.e.c = C; } /// 这里生成cell的时候,使用上面定义的默认样式 const cell = { v: data[R][C] || "", s: { font: { name: "宋体", sz: 11, color: { auto: 1 } }, alignment: { /// 自动换行 wrapText: 1, // 居中 horizontal: "center", vertical: "center", indent: 0, }, }, } as any; // 头部列表加边框 if (R < headerRows) { cell.s.border = { top: { style: "thin", color: { rgb: "000000" } }, left: { style: "thin", color: { rgb: "000000" } }, bottom: { style: "thin", color: { rgb: "000000" } }, right: { style: "thin", color: { rgb: "000000" } }, }; cell.s.fill = { patternType: "solid", fgColor: { theme: 3, tint: 0.3999755851924192, rgb: "DDD9C4" }, bgColor: { theme: 7, tint: 0.3999755851924192, rgb: "8064A2" }, }; } const cell_ref = XLSX.utils.encode_cell({ c: C, r: R }); if (typeof cell.v === "number") { cell.t = "n"; } else if (typeof cell.v === "boolean") { cell.t = "b"; } else { cell.t = "s"; } ws[cell_ref] = cell; } } if (range.s.c < 10000000) { ws["!ref"] = XLSX.utils.encode_range(range); } return ws; } /** * 填充行合并占位符 * */ function pushRowSpanPlaceHolder(arr: any, count: any) { for (let i = 0; i < count; i++) { arr.push("!$ROW_SPAN_PLACEHOLDER"); } } // 填充列合并占位符 function pushColSpanPlaceHolder(arr: any, count: any) { for (let i = 0; i < count; i++) { arr.push("!$COL_SPAN_PLACEHOLDER"); } } function flatData(list: any, eachDataCallBack: any) { let resultList = []; for (let i = 0; i < list.length; i++) { let data = list[i]; let rawDataList = []; // 每个子元素都父元素合并成一条数据 if (data.child && data.child.length > 0) { for (let j = 0; j < data.child.length; j++) { delete data.child[j].bsm; let copy = Object.assign({}, data, data.child[j]); rawDataList.push(copy); copy["rowSpan"] = j > 0 ? 0 : data.child.length; } } else { data["rowSpan"] = 1; rawDataList.push(data); } resultList.push(...rawDataList); if (typeof eachDataCallBack === "function") { eachDataCallBack(rawDataList); } } return resultList; } // 扁平头部 function flat(headerData: any) { let result: any[] = []; headerData.forEach((e: any) => { if (e.hasOwnProperty("child")) { result.push(...flat(e.child)); } else if (e.hasOwnProperty("exeFun")) { result.push(e); } else if (e.hasOwnProperty("prop")) { result.push(e); } }); return result; } function s2ab(s: any) { let buf = new ArrayBuffer(s.length); let view = new Uint8Array(buf); for (let i = 0; i !== s.length; ++i) { view[i] = s.charCodeAt(i) & 0xff; } return buf; } function openDownloadXLSXDialog(url: any, saveName: any) { if (typeof url == "object" && url instanceof Blob) { url = URL.createObjectURL(url); // 创建blob地址 } var aLink = document.createElement("a"); aLink.href = url; aLink.download = saveName || ""; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效 var event; if (window.MouseEvent) { event = new MouseEvent("click"); } else { event = document.createEvent("MouseEvents"); event.initMouseEvent( "click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, ); } aLink.dispatchEvent(event); }