excel导出支持多级表头

放弃使用xlsx,使用exceljs代替
master
toesbieya 6 years ago
parent 9702024611
commit f3893dac8b
  1. 2
      vue/src/mixins/bizDocumentTableMixin.js
  2. 105
      vue/src/utils/excel/common.js
  3. 58
      vue/src/utils/excel/exceljs.js
  4. 3
      vue/src/utils/excel/index.js
  5. 43
      vue/src/utils/excel/xlsx.js
  6. 13
      vue/src/utils/tree.js
  7. 8
      vue/src/views/example/developingTest/components/ExportExcelTest.vue
  8. 4
      vue/src/views/stock/current/index.vue

@ -3,7 +3,7 @@ import LinerProgress from '@/components/LinerProgress'
import {isEmpty} from "@/utils" import {isEmpty} from "@/utils"
import {elConfirm, elError, elSuccess} from "@/utils/message" import {elConfirm, elError, elSuccess} from "@/utils/message"
import {auth} from "@/utils/auth" import {auth} from "@/utils/auth"
import {exportExcel} from "@/utils/excel/xlsx" import {exportExcel} from "@/utils/excel"
export const commonMethods = { export const commonMethods = {
getStatus(status) { getStatus(status) {

@ -2,6 +2,7 @@ import {isEmpty} from "@/utils"
import request from "@/config/request" import request from "@/config/request"
import {elError} from "@/utils/message" import {elError} from "@/utils/message"
import {download} from "@/utils/file" import {download} from "@/utils/file"
import {flatTree} from "@/utils/tree"
/** /**
* 搜索表格页导出excel * 搜索表格页导出excel
@ -9,7 +10,7 @@ import {download} from "@/utils/file"
* *
* @param url 搜索的请求地址 * @param url 搜索的请求地址
* @param searchForm 搜索参数 * @param searchForm 搜索参数
* @param options 合并选项{column, merge}只有前端导出时才需要 * @param options 合并选项{columns, merge}只有前端导出时才需要
* @param json2workbook * @param json2workbook
* @param exportExcelByJs * @param exportExcelByJs
*/ */
@ -25,8 +26,8 @@ export function abstractExportExcel(url, searchForm, options, json2workbook, exp
reader.onload = () => { reader.onload = () => {
const response = JSON.parse(reader.result) const response = JSON.parse(reader.result)
if (response.status !== 200) return elError(response.msg) if (response.status !== 200) return elError(response.msg)
const {column, merge} = options const {columns, merge} = options
const workbook = json2workbook(column, response.data, merge) const workbook = json2workbook(response.data, columns, merge)
exportExcelByJs(workbook, filename) exportExcelByJs(workbook, filename)
} }
reader.readAsText(data) reader.readAsText(data)
@ -118,6 +119,104 @@ export function mergeExcel(props, data, primaryKey, orderKey, ignoreRows = 1) {
return result 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']] * 生成二维数组例如json为[{id:1,name:'x'}]结果为[[1,'x']]
* *

@ -1,6 +1,6 @@
import {isEmpty} from "@/utils" import {isEmpty} from "@/utils"
import {download} from "@/utils/file" import {download} from "@/utils/file"
import {abstractExportExcel, jsonArray2rowArray, mergeExcel, number2excelColumnHeader} from "./common" import {abstractExportExcel, jsonArray2rowArray, mergeExcel, generateHeaders, number2excelColumnHeader} from "./common"
//默认的excel单元格样式 //默认的excel单元格样式
const defaultCellStyle = { const defaultCellStyle = {
@ -32,52 +32,56 @@ export function exportExcelByJs(workbook, filename) {
} }
/** /**
* @param column 列配置
* @param data json数组 * @param data json数组
* @param mergeOption 合并配置项 * @param columns 列配置
* @param headerRows 表头占的行数为0时表头不写入表格 * @param merge 合并配置项
* @returns * @returns
*/ */
export function json2workbook(column, data, mergeOption, headerRows = 1) { export function json2workbook(data, columns, merge) {
const workbook = new window.ExcelJS.Workbook() const workbook = new window.ExcelJS.Workbook()
const sheet = workbook.addWorksheet('Sheet1') const sheet = workbook.addWorksheet('Sheet1')
const colWidth = [] const columnSettings = []
const header = [] //表格头部
const propMap = {} //表头和字段名的映射表,propMap[字段名]=对应的表头的数组下标 const propMap = {} //表头和字段名的映射表,propMap[字段名]=对应的表头的数组下标
const needMerge = [] //合并行配置,[i]=字段名 const needMerge = [] //合并行配置,[i]=字段名
column.forEach((item, i) => { columns.forEach((col, i) => {
if (!isEmpty(item.header)) header[i] = item.header if (!isEmpty(col.prop)) propMap[col.prop] = i
if (!isEmpty(item.prop)) propMap[item.prop] = i if (col.merge) needMerge[i] = col.prop
if (item.merge) needMerge[i] = item.prop columnSettings[i] = {width: col.width || 20, style: defaultCellStyle}
colWidth[i] = {width: item.width || 20, style: defaultCellStyle}
}) })
sheet.columns = colWidth sheet.columns = columnSettings
let merge = [] const {headers, headerMerge} = generateHeaders(columns)
if (mergeOption && needMerge.length > 0) { if (headers.length > 0) {
const {primaryKey, orderKey} = mergeOption sheet.addRows(headers)
merge = mergeExcel(needMerge, data, primaryKey, orderKey, headerRows) 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) { if (merge && needMerge.length > 0) {
sheet.addRow(header) const {primaryKey, orderKey} = merge
header.forEach((_, index) => { bodyMerge = mergeExcel(needMerge, data, primaryKey, orderKey, headers.length)
const cell = sheet.getCell(number2excelColumnHeader(index) + '1')
Object.keys(defaultHeaderStyle).forEach(key => {
cell[key] = defaultHeaderStyle[key]
})
})
} }
const result = jsonArray2rowArray(data, propMap)
sheet.addRows(result) sheet.addRows(result)
//exceljs的合并行必须在数据添加后进行 //exceljs的合并行必须在数据添加后进行
merge.forEach(i => sheet.mergeCells(i)) headerMerge.forEach(i => sheet.mergeCells(i))
bodyMerge.forEach(i => sheet.mergeCells(i))
return workbook return workbook
} }

@ -0,0 +1,3 @@
import {exportExcel, exportExcelByJs, json2workbook} from "./exceljs"
export {exportExcel, exportExcelByJs, json2workbook}

@ -1,7 +1,17 @@
/**
* @deprecated
* xlsx问题多多准备全面转向excelJS
*/
import {isEmpty} from "@/utils" import {isEmpty} from "@/utils"
import {download} from "@/utils/file" import {download} from "@/utils/file"
import {abstractExportExcel, jsonArray2rowArray, mergeExcel} from "./common" import {abstractExportExcel, jsonArray2rowArray, mergeExcel} from "./common"
/**
* @param url
* @param searchForm
* @param options
*/
export function exportExcel(url, searchForm, options) { export function exportExcel(url, searchForm, options) {
abstractExportExcel(url, searchForm, options, json2workbook, exportExcelByJs) abstractExportExcel(url, searchForm, options, json2workbook, exportExcelByJs)
} }
@ -17,30 +27,31 @@ export function exportExcelByJs(workbook, filename) {
} }
/** /**
* @param column 列配置
* @param data json数组 * @param data json数组
* @param mergeOption 合并配置项 * @param columns 列配置
* @param headerRows 表头占的行数为0时表头不写入表格 * @param merge 合并配置项
* @returns * @returns
*/ */
export function json2workbook(column, data, mergeOption, headerRows = 1) { export function json2workbook(data, columns, merge) {
const header = [] //表格头部 const header = [] //表格头部
const propMap = {} //表头和字段名的映射表,propMap[字段名]=对应的表头的数组下标 const propMap = {} //表头和字段名的映射表,propMap[字段名]=对应的表头的数组下标
const colWidth = [] //列宽配置,默认20 const columnSettings = [] //列宽配置,默认20
const needMerge = [] //合并行配置,[i]=字段名 const needMerge = [] //合并行配置,[i]=字段名
column.forEach((item, i) => { columns.forEach((col, i) => {
if (!isEmpty(item.header)) header[i] = item.header if (!isEmpty(col.header)) header[i] = col.header
if (!isEmpty(item.prop)) propMap[item.prop] = i if (!isEmpty(col.prop)) propMap[col.prop] = i
if (item.merge) needMerge[i] = item.prop if (col.merge) needMerge[i] = col.prop
colWidth[i] = {wch: item.width || 20} columnSettings[i] = {wch: col.width || 20}
}) })
let mergeResult = [] const headerRows = header.length > 0 ? 1 : 0
let bodyMerge = []
if (mergeOption && needMerge.length > 0) { if (merge && needMerge.length > 0) {
const {primaryKey, orderKey} = mergeOption const {primaryKey, orderKey} = merge
mergeResult = mergeExcel(needMerge, data, primaryKey, orderKey, headerRows) bodyMerge = mergeExcel(needMerge, data, primaryKey, orderKey, headerRows)
} }
const result = jsonArray2rowArray(data, propMap) const result = jsonArray2rowArray(data, propMap)
@ -48,8 +59,8 @@ export function json2workbook(column, data, mergeOption, headerRows = 1) {
headerRows && result.unshift(header) headerRows && result.unshift(header)
const sheet = aoa_to_sheet(result, headerRows) const sheet = aoa_to_sheet(result, headerRows)
sheet['!cols'] = colWidth sheet['!cols'] = columnSettings
sheet['!merges'] = mergeResult.map(i => window.XLSX.utils.decode_range(i)) sheet['!merges'] = bodyMerge.map(i => window.XLSX.utils.decode_range(i))
return {SheetNames: ['Sheet1'], Sheets: {Sheet1: sheet}} return {SheetNames: ['Sheet1'], Sheets: {Sheet1: sheet}}
} }

@ -148,6 +148,19 @@ export function getNodeId(arr) {
return res 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) { export function getNodesByDfs(node) {
const nodes = [] const nodes = []
const stack = [] const stack = []

@ -12,7 +12,7 @@
</template> </template>
<script> <script>
import {exportExcelByJs, json2workbook} from "@/utils/excel/exceljs" import {exportExcelByJs, json2workbook} from "@/utils/excel"
export default { export default {
name: "ExportExcelTest", name: "ExportExcelTest",
@ -20,8 +20,8 @@
return { return {
loading: false, loading: false,
column: [ column: [
{header: '序号', prop: 'no', merge: true}, {header: ['合并表头演示', '序号'], prop: 'no', merge: true},
{header: '名称', prop: 'name', merge: true}, {header: ['合并表头演示', '名称'], prop: 'name', merge: true},
{header: '日期', prop: 'date'}, {header: '日期', prop: 'date'},
], ],
row: { row: {
@ -41,7 +41,7 @@
this.loading = true this.loading = true
const arr = new Array(this.rows).fill(this.row) const arr = new Array(this.rows).fill(this.row)
const workbook = json2workbook(this.column, arr, this.merge ? { const workbook = json2workbook(arr, this.column, this.merge ? {
primaryKey: 'name', primaryKey: 'name',
orderKey: 'no' orderKey: 'no'
} : null) } : null)

@ -72,7 +72,7 @@
import {search} from "@/api/stock/current" import {search} from "@/api/stock/current"
import {isEmpty, debounce} from "@/utils" import {isEmpty, debounce} from "@/utils"
import {plus} from "@/utils/math" import {plus} from "@/utils/math"
import {exportExcel} from "@/utils/excel/xlsx" import {exportExcel} from "@/utils/excel"
import {getNodeId} from "@/utils/tree" import {getNodeId} from "@/utils/tree"
import tableMixin from '@/mixins/tablePageMixin' import tableMixin from '@/mixins/tablePageMixin'
@ -97,7 +97,7 @@
}, },
detailDialog: false, detailDialog: false,
excel: { excel: {
column: [ columns: [
{header: '序号', prop: 'id', width: 20, merge: true}, {header: '序号', prop: 'id', width: 20, merge: true},
{header: '分类名称', prop: 'cname', width: 20, merge: true}, {header: '分类名称', prop: 'cname', width: 20, merge: true},
{header: '库存总数', prop: 'total_num', width: 20, merge: true}, {header: '库存总数', prop: 'total_num', width: 20, merge: true},

Loading…
Cancel
Save