excel-export.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. let XLSXInstance: any = null
  2. const getXLSX = async () => {
  3. if (!XLSXInstance) {
  4. XLSXInstance = await import('xlsx')
  5. }
  6. return XLSXInstance
  7. }
  8. /**
  9. * 导出Excel文件
  10. * @param {Array} data - 导出的数据
  11. * @param {String} filename - 文件名
  12. * @param {String} sheetName - 工作表名称
  13. */
  14. export const exportToExcel = async (data:any, filename:string = 'export.xlsx', sheetName:string = 'Sheet1') => {
  15. const XLSX = await getXLSX()
  16. const wb = XLSX.utils.book_new()
  17. const ws = XLSX.utils.json_to_sheet(data)
  18. XLSX.utils.book_append_sheet(wb, ws, sheetName)
  19. XLSX.writeFile(wb, filename)
  20. }
  21. /**
  22. * 导出多工作表Excel文件
  23. * @param {Object} sheetData - 包含多个工作表数据的对象 {sheetName1: data1, sheetName2: data2}
  24. * @param {String} filename - 文件名
  25. */
  26. export const exportMultiSheetExcel = async (sheetData:any, filename:string = 'export.xlsx') => {
  27. const XLSX = await getXLSX()
  28. const wb = XLSX.utils.book_new()
  29. Object.keys(sheetData).forEach(sheetName => {
  30. const ws = XLSX.utils.json_to_sheet(sheetData[sheetName])
  31. XLSX.utils.book_append_sheet(wb, ws, sheetName)
  32. })
  33. XLSX.writeFile(wb, filename)
  34. }
  35. /**
  36. * 自定义表头的Excel导出
  37. * @param {Array} data - 数据
  38. * @param {Array} headers - 表头配置 [{key: 'name', title: '姓名', width: 20}, ...]
  39. * @param {String} filename - 文件名
  40. * @param {String} sheetName - 工作表名称
  41. */
  42. export const exportWithCustomHeaders = async (data:any, headers:any, filename:string = 'export.xlsx', sheetName:string = 'Sheet1') => {
  43. const XLSX = await getXLSX()
  44. const exportData = data.map(item => {
  45. const row = {}
  46. headers.forEach(header => {
  47. row[header.title] = item[header.key]
  48. })
  49. return row
  50. })
  51. const wb = XLSX.utils.book_new()
  52. const ws = XLSX.utils.json_to_sheet(exportData)
  53. // 设置列宽
  54. const colWidths = headers.map(header => ({ wch: header.width || 15 }))
  55. ws['!cols'] = colWidths
  56. XLSX.utils.book_append_sheet(wb, ws, sheetName)
  57. XLSX.writeFile(wb, filename)
  58. }
  59. /**
  60. * 高级对象数组处理 - 支持展开模式
  61. * @param {Array} data - 数据
  62. * @param {Array} headers - 表头配置
  63. * 普通字段: {key: 'name', title: '姓名'}
  64. * 对象数组展开: {
  65. * key: 'projects',
  66. * title: '项目',
  67. * arrayType: 'object',
  68. * expand: true, // 展开模式
  69. * fieldMappings: { // 字段映射
  70. * name: '项目名称',
  71. * role: '项目角色'
  72. * }
  73. * }
  74. * @param {String} filename - 文件名
  75. * @param {String} sheetName - 工作表名称
  76. */
  77. export const exportWithExpandedObjectArrays = async (data:any, headers:any, filename:string = 'export.xlsx', sheetName:string = 'Sheet1') => {
  78. const XLSX = await getXLSX()
  79. let processedData = [...data]
  80. let processedHeaders = [...headers]
  81. const expandFields = headers.filter(header =>
  82. header.arrayType === 'object' && header.expand
  83. )
  84. if (expandFields.length > 0) {
  85. expandFields.forEach(field => {
  86. const maxLength = Math.max(...data.map(item => {
  87. const arr = item[field.key]
  88. return Array.isArray(arr) ? arr.length : 0
  89. }))
  90. processedHeaders = processedHeaders.filter(h => h.key !== field.key)
  91. for (let i = 0; i < maxLength; i++) {
  92. const baseKey = `${field.key}[${i}]`
  93. const baseTitle = `${field.title}[${i + 1}]`
  94. if (field.fieldMappings) {
  95. Object.keys(field.fieldMappings).forEach(sourceField => {
  96. processedHeaders.push({
  97. key: `${baseKey}.${sourceField}`,
  98. title: `${baseTitle} - ${field.fieldMappings[sourceField]}`
  99. })
  100. })
  101. } else {
  102. processedHeaders.push({
  103. key: `${baseKey}`,
  104. title: baseTitle
  105. })
  106. }
  107. }
  108. })
  109. processedData = data.map(item => {
  110. const newItem = { ...item }
  111. expandFields.forEach(field => {
  112. const arr = item[field.key]
  113. if (Array.isArray(arr)) {
  114. for (let i = 0; i < arr.length; i++) {
  115. const obj = arr[i]
  116. if (typeof obj === 'object' && obj !== null) {
  117. if (field.fieldMappings) {
  118. Object.keys(field.fieldMappings).forEach(sourceField => {
  119. newItem[`${field.key}[${i}].${sourceField}`] = obj[sourceField]
  120. })
  121. } else {
  122. Object.keys(obj).forEach(sourceField => {
  123. newItem[`${field.key}[${i}].${sourceField}`] = obj[sourceField]
  124. })
  125. }
  126. }
  127. }
  128. }
  129. })
  130. return newItem
  131. })
  132. }
  133. const exportData = processedData.map(item => {
  134. const row = {}
  135. processedHeaders.forEach(header => {
  136. if (header.arrayType === 'object' && !header.expand) {
  137. const value = item[header.key]
  138. if (Array.isArray(value)) {
  139. if (header.extractFields && header.extractFields.length > 0) {
  140. const extractedValues = value.map(obj => {
  141. if (typeof obj === 'object' && obj !== null) {
  142. return header.extractFields.map(field => obj[field]).join(' ')
  143. }
  144. return obj
  145. })
  146. row[header.title] = extractedValues.join(header.joinWith || ', ')
  147. } else {
  148. const extractedValues = value.map(obj => {
  149. if (typeof obj === 'object' && obj !== null) {
  150. return Object.keys(obj).map(key => `${key}: ${obj[key]}`).join(' ')
  151. }
  152. return obj
  153. })
  154. row[header.title] = extractedValues.join(header.joinWith || ', ')
  155. }
  156. } else {
  157. row[header.title] = value
  158. }
  159. } else if (!header.expand) {
  160. const value = item[header.key]
  161. if (Array.isArray(value)) {
  162. row[header.title] = value.join(header.joinWith || ', ')
  163. } else {
  164. row[header.title] = value
  165. }
  166. } else {
  167. row[header.title] = item[header.key]
  168. }
  169. })
  170. return row
  171. })
  172. const wb = XLSX.utils.book_new()
  173. const ws = XLSX.utils.json_to_sheet(exportData)
  174. XLSX.utils.book_append_sheet(wb, ws, sheetName)
  175. XLSX.writeFile(wb, filename)
  176. }