From f3893dac8b6c7cbf85a83f66ab6d6e87bcea958c Mon Sep 17 00:00:00 2001 From: toesbieya <1647775459@qq.com> Date: Sat, 20 Jun 2020 18:47:34 +0800 Subject: [PATCH] =?UTF-8?q?excel=E5=AF=BC=E5=87=BA=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=9A=E7=BA=A7=E8=A1=A8=E5=A4=B4=20=E6=94=BE=E5=BC=83?= =?UTF-8?q?=E4=BD=BF=E7=94=A8xlsx=EF=BC=8C=E4=BD=BF=E7=94=A8exceljs?= =?UTF-8?q?=E4=BB=A3=E6=9B=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vue/src/mixins/bizDocumentTableMixin.js | 2 +- vue/src/utils/excel/common.js | 105 +++++++++++++++++- vue/src/utils/excel/exceljs.js | 58 +++++----- vue/src/utils/excel/index.js | 3 + vue/src/utils/excel/xlsx.js | 43 ++++--- vue/src/utils/tree.js | 13 +++ .../components/ExportExcelTest.vue | 8 +- vue/src/views/stock/current/index.vue | 4 +- 8 files changed, 183 insertions(+), 53 deletions(-) create mode 100644 vue/src/utils/excel/index.js diff --git a/vue/src/mixins/bizDocumentTableMixin.js b/vue/src/mixins/bizDocumentTableMixin.js index 64970f2..0ebab71 100644 --- a/vue/src/mixins/bizDocumentTableMixin.js +++ b/vue/src/mixins/bizDocumentTableMixin.js @@ -3,7 +3,7 @@ import LinerProgress from '@/components/LinerProgress' import {isEmpty} from "@/utils" import {elConfirm, elError, elSuccess} from "@/utils/message" import {auth} from "@/utils/auth" -import {exportExcel} from "@/utils/excel/xlsx" +import {exportExcel} from "@/utils/excel" export const commonMethods = { getStatus(status) { diff --git a/vue/src/utils/excel/common.js b/vue/src/utils/excel/common.js index 3c6c4f3..711cfc9 100644 --- a/vue/src/utils/excel/common.js +++ b/vue/src/utils/excel/common.js @@ -2,6 +2,7 @@ import {isEmpty} from "@/utils" import request from "@/config/request" import {elError} from "@/utils/message" import {download} from "@/utils/file" +import {flatTree} from "@/utils/tree" /** * 搜索表格页导出excel @@ -9,7 +10,7 @@ import {download} from "@/utils/file" * * @param url 搜索的请求地址 * @param searchForm 搜索参数 - * @param options 合并选项{column, merge},只有前端导出时才需要 + * @param options 合并选项{columns, merge},只有前端导出时才需要 * @param json2workbook * @param exportExcelByJs */ @@ -25,8 +26,8 @@ export function abstractExportExcel(url, searchForm, options, json2workbook, exp reader.onload = () => { const response = JSON.parse(reader.result) if (response.status !== 200) return elError(response.msg) - const {column, merge} = options - const workbook = json2workbook(column, response.data, merge) + const {columns, merge} = options + const workbook = json2workbook(response.data, columns, merge) exportExcelByJs(workbook, filename) } reader.readAsText(data) @@ -118,6 +119,104 @@ export function mergeExcel(props, data, primaryKey, orderKey, ignoreRows = 1) { return result } +/** + * 生成表头的二维数组以及合并结果 + * 参考element的table-header + * + * @param columns 列配置 + */ +export function generateHeaders(columns) { + const tree = [] + let maxDepth = 1 + + for (let col of columns) { + const header = col.header, node = {depth: 1} + + if (Array.isArray(header)) { + const depth = header.length + if (depth > maxDepth) maxDepth = depth + + let arr = tree + + for (let i = 0, temp = node; i < depth; i++) { + const value = header[i] + + temp.depth = i + 1 + temp.value = value + + if (arr) { + const already = arr.find(i => i.value === value) + if (!already) { + arr.push(temp) + arr = null + } + else { + if (!already.children) already.children = [] + arr = already.children + } + } + + if (i === depth - 1) break + + temp.children = [{}] + temp = temp.children[0] + } + } + else { + node.value = header || '' + tree.push(node) + } + } + + const traverse = column => { + if (column.children) { + let colSpan = 0 + column.children.forEach(subColumn => { + traverse(subColumn) + colSpan += subColumn.colSpan + }) + column.rowSpan = 1 + column.colSpan = colSpan + } + else { + column.rowSpan = maxDepth - column.depth + 1 + column.colSpan = 1 + } + } + + tree.forEach(node => traverse(node)) + + const headers = [] + for (let i = 0; i < maxDepth; i++) headers.push([]) + + const headerMerge = [] + + let lastDepth = 0, colIndex = 0 //当前是第几列,从0开始 + + for (const node of flatTree(tree)) { + //判断是否是下一列,节点深度从1开始 + if (node.depth <= lastDepth) colIndex++ + lastDepth = node.depth + + //填充表头 + headers[node.depth - 1][colIndex] = node.value + + //获取合并结果,要么rowSpan大于1要么colSpan大于1,两者不可能同时大于1 + const startColHeader = number2excelColumnHeader(colIndex) + const startCell = startColHeader + node.depth + if (node.rowSpan > 1) { + const endCell = startColHeader + (node.depth + node.rowSpan - 1) + headerMerge.push(`${startCell}:${endCell}`) + } + if (node.colSpan > 1) { + const endCell = number2excelColumnHeader(colIndex + node.colSpan - 1) + node.depth + headerMerge.push(`${startCell}:${endCell}`) + } + } + + return {headers, headerMerge} +} + /** * 生成二维数组,例如json为[{id:1,name:'x'}],结果为[[1,'x']] * diff --git a/vue/src/utils/excel/exceljs.js b/vue/src/utils/excel/exceljs.js index f2aebf3..036762d 100644 --- a/vue/src/utils/excel/exceljs.js +++ b/vue/src/utils/excel/exceljs.js @@ -1,6 +1,6 @@ import {isEmpty} from "@/utils" import {download} from "@/utils/file" -import {abstractExportExcel, jsonArray2rowArray, mergeExcel, number2excelColumnHeader} from "./common" +import {abstractExportExcel, jsonArray2rowArray, mergeExcel, generateHeaders, number2excelColumnHeader} from "./common" //默认的excel单元格样式 const defaultCellStyle = { @@ -32,52 +32,56 @@ export function exportExcelByJs(workbook, filename) { } /** - * @param column 列配置 * @param data json数组 - * @param mergeOption 合并配置项 - * @param headerRows 表头占的行数,为0时表头不写入表格 + * @param columns 列配置 + * @param merge 合并配置项 * @returns */ -export function json2workbook(column, data, mergeOption, headerRows = 1) { +export function json2workbook(data, columns, merge) { const workbook = new window.ExcelJS.Workbook() const sheet = workbook.addWorksheet('Sheet1') - const colWidth = [] - const header = [] //表格头部 + const columnSettings = [] const propMap = {} //表头和字段名的映射表,propMap[字段名]=对应的表头的数组下标 const needMerge = [] //合并行配置,[i]=字段名 - column.forEach((item, i) => { - if (!isEmpty(item.header)) header[i] = item.header - if (!isEmpty(item.prop)) propMap[item.prop] = i - if (item.merge) needMerge[i] = item.prop - colWidth[i] = {width: item.width || 20, style: defaultCellStyle} + columns.forEach((col, i) => { + if (!isEmpty(col.prop)) propMap[col.prop] = i + if (col.merge) needMerge[i] = col.prop + columnSettings[i] = {width: col.width || 20, style: defaultCellStyle} }) - sheet.columns = colWidth + sheet.columns = columnSettings - let merge = [] + const {headers, headerMerge} = generateHeaders(columns) - if (mergeOption && needMerge.length > 0) { - const {primaryKey, orderKey} = mergeOption - merge = mergeExcel(needMerge, data, primaryKey, orderKey, headerRows) + if (headers.length > 0) { + sheet.addRows(headers) + headers.forEach((rows, rowIndex) => { + rows.forEach((cellValue, colIndex) => { + if (cellValue !== undefined) { + const cell = sheet.getCell(number2excelColumnHeader(colIndex) + (rowIndex + 1)) + Object.keys(defaultHeaderStyle).forEach(key => { + cell[key] = defaultHeaderStyle[key] + }) + } + }) + }) } - const result = jsonArray2rowArray(data, propMap) + let bodyMerge = [] - if (headerRows > 0) { - sheet.addRow(header) - header.forEach((_, index) => { - const cell = sheet.getCell(number2excelColumnHeader(index) + '1') - Object.keys(defaultHeaderStyle).forEach(key => { - cell[key] = defaultHeaderStyle[key] - }) - }) + if (merge && needMerge.length > 0) { + const {primaryKey, orderKey} = merge + bodyMerge = mergeExcel(needMerge, data, primaryKey, orderKey, headers.length) } + const result = jsonArray2rowArray(data, propMap) + sheet.addRows(result) //exceljs的合并行必须在数据添加后进行 - merge.forEach(i => sheet.mergeCells(i)) + headerMerge.forEach(i => sheet.mergeCells(i)) + bodyMerge.forEach(i => sheet.mergeCells(i)) return workbook } diff --git a/vue/src/utils/excel/index.js b/vue/src/utils/excel/index.js new file mode 100644 index 0000000..b44182b --- /dev/null +++ b/vue/src/utils/excel/index.js @@ -0,0 +1,3 @@ +import {exportExcel, exportExcelByJs, json2workbook} from "./exceljs" + +export {exportExcel, exportExcelByJs, json2workbook} diff --git a/vue/src/utils/excel/xlsx.js b/vue/src/utils/excel/xlsx.js index afbbff5..a839cb3 100644 --- a/vue/src/utils/excel/xlsx.js +++ b/vue/src/utils/excel/xlsx.js @@ -1,7 +1,17 @@ +/** + * @deprecated + * xlsx问题多多,准备全面转向excelJS + */ + import {isEmpty} from "@/utils" import {download} from "@/utils/file" import {abstractExportExcel, jsonArray2rowArray, mergeExcel} from "./common" +/** + * @param url + * @param searchForm + * @param options + */ export function exportExcel(url, searchForm, options) { abstractExportExcel(url, searchForm, options, json2workbook, exportExcelByJs) } @@ -17,30 +27,31 @@ export function exportExcelByJs(workbook, filename) { } /** - * @param column 列配置 * @param data json数组 - * @param mergeOption 合并配置项 - * @param headerRows 表头占的行数,为0时表头不写入表格 + * @param columns 列配置 + * @param merge 合并配置项 * @returns */ -export function json2workbook(column, data, mergeOption, headerRows = 1) { +export function json2workbook(data, columns, merge) { const header = [] //表格头部 const propMap = {} //表头和字段名的映射表,propMap[字段名]=对应的表头的数组下标 - const colWidth = [] //列宽配置,默认20 + const columnSettings = [] //列宽配置,默认20 const needMerge = [] //合并行配置,[i]=字段名 - column.forEach((item, i) => { - if (!isEmpty(item.header)) header[i] = item.header - if (!isEmpty(item.prop)) propMap[item.prop] = i - if (item.merge) needMerge[i] = item.prop - colWidth[i] = {wch: item.width || 20} + columns.forEach((col, i) => { + if (!isEmpty(col.header)) header[i] = col.header + if (!isEmpty(col.prop)) propMap[col.prop] = i + if (col.merge) needMerge[i] = col.prop + columnSettings[i] = {wch: col.width || 20} }) - let mergeResult = [] + const headerRows = header.length > 0 ? 1 : 0 + + let bodyMerge = [] - if (mergeOption && needMerge.length > 0) { - const {primaryKey, orderKey} = mergeOption - mergeResult = mergeExcel(needMerge, data, primaryKey, orderKey, headerRows) + if (merge && needMerge.length > 0) { + const {primaryKey, orderKey} = merge + bodyMerge = mergeExcel(needMerge, data, primaryKey, orderKey, headerRows) } const result = jsonArray2rowArray(data, propMap) @@ -48,8 +59,8 @@ export function json2workbook(column, data, mergeOption, headerRows = 1) { headerRows && result.unshift(header) const sheet = aoa_to_sheet(result, headerRows) - sheet['!cols'] = colWidth - sheet['!merges'] = mergeResult.map(i => window.XLSX.utils.decode_range(i)) + sheet['!cols'] = columnSettings + sheet['!merges'] = bodyMerge.map(i => window.XLSX.utils.decode_range(i)) return {SheetNames: ['Sheet1'], Sheets: {Sheet1: sheet}} } diff --git a/vue/src/utils/tree.js b/vue/src/utils/tree.js index 58fa711..0aec211 100644 --- a/vue/src/utils/tree.js +++ b/vue/src/utils/tree.js @@ -148,6 +148,19 @@ export function getNodeId(arr) { return res } +//树转一维数组 +export function flatTree(tree, childrenKey = 'children') { + const result = [] + tree.forEach(node => { + if (node[childrenKey]) { + result.push(node) + result.push.apply(result, flatTree(node[childrenKey], childrenKey)) + } + else result.push(node) + }) + return result +} + export function getNodesByDfs(node) { const nodes = [] const stack = [] diff --git a/vue/src/views/example/developingTest/components/ExportExcelTest.vue b/vue/src/views/example/developingTest/components/ExportExcelTest.vue index a791560..3bef781 100644 --- a/vue/src/views/example/developingTest/components/ExportExcelTest.vue +++ b/vue/src/views/example/developingTest/components/ExportExcelTest.vue @@ -12,7 +12,7 @@