index.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import ExcelJS from 'exceljs'
  2. import FileSaver from 'file-saver'
  3. // 颜色字符串(如 'CCC')格式标准化为 ARGB
  4. function normalizeColor (val) {
  5. if (!val) return undefined
  6. if (typeof val === 'object') {
  7. if (val.argb) return { argb: formatARGB(val.argb) }
  8. if (val.rgb) return { argb: formatARGB(val.rgb) }
  9. return val
  10. }
  11. return { argb: formatARGB(val) }
  12. }
  13. // 字符串格式化为 ARGB
  14. function formatARGB (colorStr) {
  15. if (!colorStr) return undefined
  16. let hex = colorStr.replace(/^#/, '').toUpperCase()
  17. if (hex.length === 3) hex = hex.split('').map(c => c + c).join('')
  18. if (hex.length === 6) return 'FF' + hex
  19. if (hex.length === 8) return hex
  20. return 'FFCCCCCC'
  21. }
  22. // 填充样式标准化
  23. function normalizeFill (fill) {
  24. if (!fill || !fill.fgColor) return undefined
  25. return {
  26. // 固定为纯色填充,渐变填充 gradient exceljs支持不完整
  27. type: 'pattern',
  28. pattern: fill.pattern || 'solid',
  29. fgColor: normalizeColor(fill.fgColor),
  30. bgColor: fill.bgColor ? normalizeColor(fill.bgColor) : undefined
  31. }
  32. }
  33. // 边框标准化
  34. function normalizeBorder (border, fallback) {
  35. const result = border && Object.keys(border).length ? { ...border } : { ...fallback }
  36. for (const side of ['top', 'bottom', 'left', 'right']) {
  37. if (result[side]) {
  38. result[side] = {
  39. style: result[side].style || 'thin',
  40. color: normalizeColor(result[side].color)
  41. }
  42. }
  43. }
  44. return result
  45. }
  46. // 样式合并
  47. function mergeStyles (base = {}, override = {}, defaultBorder) {
  48. const deepMerge = (a = {}, b = {}) => ({ ...a, ...b })
  49. const merged = {
  50. font: deepMerge(base.font, override.font),
  51. alignment: deepMerge(base.alignment, override.alignment),
  52. border: deepMerge(base.border, override.border),
  53. fill: deepMerge(base.fill, override.fill),
  54. numFmt: override.numFmt || base.numFmt
  55. }
  56. merged.border = normalizeBorder(merged.border, defaultBorder)
  57. return merged
  58. }
  59. export async function exportExcel (config) {
  60. const {
  61. meta = {},
  62. layout = {},
  63. data = [],
  64. defaults = {}
  65. } = config
  66. const {
  67. title = 'Excel导出',
  68. header,
  69. merges = []
  70. } = meta
  71. const {
  72. columns = [],
  73. rowHeights = {},
  74. cellStyles = {},
  75. nameKey = 'prop',
  76. labelKey = 'label'
  77. } = layout
  78. // 默认边框,与Excel基础样式一致
  79. const defaultBorder = normalizeBorder(undefined, defaults.border || {
  80. top: { style: 'thin', color: 'D4D4D4' },
  81. bottom: { style: 'thin', color: 'D4D4D4' },
  82. left: { style: 'thin', color: 'D4D4D4' },
  83. right: { style: 'thin', color: 'D4D4D4' }
  84. })
  85. const workbook = new ExcelJS.Workbook()
  86. const worksheet = workbook.addWorksheet('Sheet1')
  87. let currentRow = 1
  88. // 顶部标题行
  89. if (header && typeof header === 'object' && header.value) {
  90. worksheet.mergeCells(currentRow, 1, currentRow, columns.length)
  91. const cell = worksheet.getRow(currentRow).getCell(1)
  92. cell.value = header.value
  93. const headerStyle = {
  94. font: header.font || { name: '微软雅黑', size: 16, bold: true },
  95. alignment: header.alignment || { horizontal: 'center', vertical: 'middle' },
  96. fill: header.fill
  97. }
  98. if (headerStyle.font) cell.font = { ...headerStyle.font, color: normalizeColor(headerStyle.font.color) }
  99. if (headerStyle.alignment) cell.alignment = headerStyle.alignment
  100. const fill = normalizeFill(headerStyle.fill)
  101. if (fill) cell.fill = fill
  102. if (rowHeights[0]) worksheet.getRow(currentRow).height = rowHeights[0].height
  103. currentRow++
  104. }
  105. // 表头行
  106. const headerRow = worksheet.getRow(currentRow)
  107. columns.forEach((col, idx) => {
  108. const cell = headerRow.getCell(idx + 1)
  109. cell.value = col[labelKey]
  110. const style = mergeStyles(defaults, col, defaultBorder)
  111. if (style.font) cell.font = { ...style.font, color: normalizeColor(style.font.color) }
  112. if (style.alignment) cell.alignment = style.alignment
  113. const fill = normalizeFill(style.fill)
  114. if (fill) cell.fill = fill
  115. if (style.border) cell.border = style.border
  116. })
  117. if (rowHeights[currentRow - 1]) {
  118. worksheet.getRow(currentRow).height = rowHeights[currentRow - 1].height
  119. }
  120. currentRow++
  121. // 设置列宽等
  122. worksheet.columns = columns.map(col => ({
  123. key: col[nameKey],
  124. width: col.width || defaults.width || 15,
  125. hidden: col.hidden
  126. }))
  127. // 数据区域
  128. data.forEach((rowData, i) => {
  129. const row = worksheet.getRow(currentRow + i)
  130. columns.forEach((col, j) => {
  131. const cell = row.getCell(j + 1)
  132. cell.value = rowData[col[nameKey]]
  133. // 设置格式
  134. const format = col.format || defaults.format
  135. if (typeof cell.value === 'number' && format) cell.numFmt = format
  136. if (cell.value instanceof Date && format) cell.numFmt = format
  137. // 合并样式(默认 → 列 → 特殊单元格)
  138. const baseStyle = mergeStyles(defaults, col, defaultBorder)
  139. const overrideStyle = cellStyles[i]?.[j] || {}
  140. const style = mergeStyles(baseStyle, overrideStyle, defaultBorder)
  141. if (style.font) cell.font = { ...style.font, color: normalizeColor(style.font.color) }
  142. if (style.alignment) cell.alignment = style.alignment
  143. const fill = normalizeFill(style.fill)
  144. if (fill) cell.fill = fill
  145. if (style.border) cell.border = style.border
  146. })
  147. // 行高处理
  148. row.height = rowHeights[currentRow + i]?.height || defaults.height
  149. })
  150. // 合并单元格
  151. merges.forEach(m => {
  152. if (Array.isArray(m)) {
  153. worksheet.mergeCells(`${m[0]}:${m[1]}`)
  154. } else {
  155. worksheet.mergeCells(m.s.r + 1, m.s.c + 1, m.e.r + 1, m.e.c + 1)
  156. }
  157. })
  158. // 输出文件
  159. const buffer = await workbook.xlsx.writeBuffer()
  160. FileSaver.saveAs(new Blob([buffer], { type: 'application/octet-stream' }), `${title}.xlsx`)
  161. }
  162. export default exportExcel