| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- import ExcelJS from 'exceljs'
- import FileSaver from 'file-saver'
- // 颜色字符串(如 'CCC')格式标准化为 ARGB
- function normalizeColor (val) {
- if (!val) return undefined
- if (typeof val === 'object') {
- if (val.argb) return { argb: formatARGB(val.argb) }
- if (val.rgb) return { argb: formatARGB(val.rgb) }
- return val
- }
- return { argb: formatARGB(val) }
- }
- // 字符串格式化为 ARGB
- function formatARGB (colorStr) {
- if (!colorStr) return undefined
- let hex = colorStr.replace(/^#/, '').toUpperCase()
- if (hex.length === 3) hex = hex.split('').map(c => c + c).join('')
- if (hex.length === 6) return 'FF' + hex
- if (hex.length === 8) return hex
- return 'FFCCCCCC'
- }
- // 填充样式标准化
- function normalizeFill (fill) {
- if (!fill || !fill.fgColor) return undefined
- return {
- // 固定为纯色填充,渐变填充 gradient exceljs支持不完整
- type: 'pattern',
- pattern: fill.pattern || 'solid',
- fgColor: normalizeColor(fill.fgColor),
- bgColor: fill.bgColor ? normalizeColor(fill.bgColor) : undefined
- }
- }
- // 边框标准化
- function normalizeBorder (border, fallback) {
- const result = border && Object.keys(border).length ? { ...border } : { ...fallback }
- for (const side of ['top', 'bottom', 'left', 'right']) {
- if (result[side]) {
- result[side] = {
- style: result[side].style || 'thin',
- color: normalizeColor(result[side].color)
- }
- }
- }
- return result
- }
- // 样式合并
- function mergeStyles (base = {}, override = {}, defaultBorder) {
- const deepMerge = (a = {}, b = {}) => ({ ...a, ...b })
- const merged = {
- font: deepMerge(base.font, override.font),
- alignment: deepMerge(base.alignment, override.alignment),
- border: deepMerge(base.border, override.border),
- fill: deepMerge(base.fill, override.fill),
- numFmt: override.numFmt || base.numFmt
- }
- merged.border = normalizeBorder(merged.border, defaultBorder)
- return merged
- }
- export async function exportExcel (config) {
- const {
- meta = {},
- layout = {},
- data = [],
- defaults = {}
- } = config
- const {
- title = 'Excel导出',
- header,
- merges = []
- } = meta
- const {
- columns = [],
- rowHeights = {},
- cellStyles = {},
- nameKey = 'prop',
- labelKey = 'label'
- } = layout
- // 默认边框,与Excel基础样式一致
- const defaultBorder = normalizeBorder(undefined, defaults.border || {
- top: { style: 'thin', color: 'D4D4D4' },
- bottom: { style: 'thin', color: 'D4D4D4' },
- left: { style: 'thin', color: 'D4D4D4' },
- right: { style: 'thin', color: 'D4D4D4' }
- })
- const workbook = new ExcelJS.Workbook()
- const worksheet = workbook.addWorksheet('Sheet1')
- let currentRow = 1
- // 顶部标题行
- if (header && typeof header === 'object' && header.value) {
- worksheet.mergeCells(currentRow, 1, currentRow, columns.length)
- const cell = worksheet.getRow(currentRow).getCell(1)
- cell.value = header.value
- const headerStyle = {
- font: header.font || { name: '微软雅黑', size: 16, bold: true },
- alignment: header.alignment || { horizontal: 'center', vertical: 'middle' },
- fill: header.fill
- }
- if (headerStyle.font) cell.font = { ...headerStyle.font, color: normalizeColor(headerStyle.font.color) }
- if (headerStyle.alignment) cell.alignment = headerStyle.alignment
- const fill = normalizeFill(headerStyle.fill)
- if (fill) cell.fill = fill
- if (rowHeights[0]) worksheet.getRow(currentRow).height = rowHeights[0].height
- currentRow++
- }
- // 表头行
- const headerRow = worksheet.getRow(currentRow)
- columns.forEach((col, idx) => {
- const cell = headerRow.getCell(idx + 1)
- cell.value = col[labelKey]
- const style = mergeStyles(defaults, col, defaultBorder)
- if (style.font) cell.font = { ...style.font, color: normalizeColor(style.font.color) }
- if (style.alignment) cell.alignment = style.alignment
- const fill = normalizeFill(style.fill)
- if (fill) cell.fill = fill
- if (style.border) cell.border = style.border
- })
- if (rowHeights[currentRow - 1]) {
- worksheet.getRow(currentRow).height = rowHeights[currentRow - 1].height
- }
- currentRow++
- // 设置列宽等
- worksheet.columns = columns.map(col => ({
- key: col[nameKey],
- width: col.width || defaults.width || 15,
- hidden: col.hidden
- }))
- // 数据区域
- data.forEach((rowData, i) => {
- const row = worksheet.getRow(currentRow + i)
- columns.forEach((col, j) => {
- const cell = row.getCell(j + 1)
- cell.value = rowData[col[nameKey]]
- // 设置格式
- const format = col.format || defaults.format
- if (typeof cell.value === 'number' && format) cell.numFmt = format
- if (cell.value instanceof Date && format) cell.numFmt = format
- // 合并样式(默认 → 列 → 特殊单元格)
- const baseStyle = mergeStyles(defaults, col, defaultBorder)
- const overrideStyle = cellStyles[i]?.[j] || {}
- const style = mergeStyles(baseStyle, overrideStyle, defaultBorder)
- if (style.font) cell.font = { ...style.font, color: normalizeColor(style.font.color) }
- if (style.alignment) cell.alignment = style.alignment
- const fill = normalizeFill(style.fill)
- if (fill) cell.fill = fill
- if (style.border) cell.border = style.border
- })
- // 行高处理
- row.height = rowHeights[currentRow + i]?.height || defaults.height
- })
- // 合并单元格
- merges.forEach(m => {
- if (Array.isArray(m)) {
- worksheet.mergeCells(`${m[0]}:${m[1]}`)
- } else {
- worksheet.mergeCells(m.s.r + 1, m.s.c + 1, m.e.r + 1, m.e.c + 1)
- }
- })
- // 输出文件
- const buffer = await workbook.xlsx.writeBuffer()
- FileSaver.saveAs(new Blob([buffer], { type: 'application/octet-stream' }), `${title}.xlsx`)
- }
- export default exportExcel
|