useExcel.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import ExcelJS from 'exceljs'
  2. /* 导出的参数 */
  3. export interface IExcel {
  4. // 文件名称
  5. fileName?: string
  6. // 表头数据
  7. rows: Array<Array<string | number>>
  8. // 表格数据
  9. data: Array<Array<any>>
  10. // 表格列宽
  11. width?: Array<number>
  12. // 需要合并的表格
  13. merge?: Array<any>
  14. }
  15. export const useExcel = () => {
  16. /**
  17. * 导入excel
  18. * @param file 传入文件
  19. * @param fileMaxSize number 最大文件大小 单位 MB
  20. */
  21. const importExcel = (file: File, fileMaxSize: number = 20) => {
  22. return new Promise((resolve, reject) => {
  23. if (
  24. !['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'].includes(
  25. file.type
  26. )
  27. ) {
  28. reject(new Error('只能选择 excel 文件'))
  29. return
  30. }
  31. if (file.size / 1024 / 1024 > fileMaxSize) {
  32. reject(new Error('大小不能超过 20MB'))
  33. return
  34. }
  35. const reader = new FileReader()
  36. reader.onload = e => {
  37. const workbook = new ExcelJS.Workbook()
  38. workbook.xlsx.load(e.target?.result as any).then(() => {
  39. const title: string[] = []
  40. const list: Record<string, any>[] = []
  41. workbook.eachSheet(sheet => {
  42. list.splice(0, list.length)
  43. sheet.eachRow({ includeEmpty: true }, row => {
  44. title.splice(0, title.length)
  45. const item: Record<string, any> = {}
  46. row.eachCell({ includeEmpty: true }, cell => {
  47. const key = cell.address.slice(0, -String(cell.row).length)
  48. title.push(key)
  49. const isMerged = cell.isMerged && cell.address !== cell.master.address
  50. item[key] = {
  51. value: cell.value,
  52. colspan: isMerged ? 0 : 1,
  53. rowspan: isMerged ? 0 : 1
  54. }
  55. })
  56. list.push(item)
  57. })
  58. // 生成表格的跨行跨列
  59. sheet.eachRow({ includeEmpty: true }, row => {
  60. row.eachCell({ includeEmpty: true }, cell => {
  61. const master = cell.master
  62. if (cell.isMerged && cell.address !== master.address) {
  63. const mk = master.address.slice(0, -String(master.row).length)
  64. const mItem = list[Number(master.row) - 1][mk]
  65. const rs = Math.abs(Number(cell.row) - Number(master.row)) + 1
  66. rs > mItem.rowspan && (mItem.rowspan = rs)
  67. const cs = Math.abs(Number(cell.col) - Number(master.col)) + 1
  68. cs > mItem.colspan && (mItem.colspan = cs)
  69. }
  70. })
  71. })
  72. })
  73. resolve({ title, list })
  74. })
  75. }
  76. reader.readAsArrayBuffer(file)
  77. })
  78. }
  79. /**
  80. * 导出excel
  81. */
  82. const exportExcel = (options: IExcel) => {
  83. return new Promise((resolve, reject) => {
  84. options = Object.assign(
  85. {
  86. fileName: options.fileName || Date.now()
  87. },
  88. options
  89. )
  90. const workbook = new ExcelJS.Workbook()
  91. const sheet = workbook.addWorksheet('Sheet1')
  92. if (!(Array.isArray(options.rows) && Array.isArray(options.rows[0]))) {
  93. reject(new Error('表头数据必须为二维数组'))
  94. return
  95. }
  96. if (!Array.isArray(options.data)) {
  97. reject(new Error('表格数据必须为数组'))
  98. return
  99. }
  100. // 设置表格宽度
  101. const widths =
  102. Array.isArray(options.width) && options.width.length
  103. ? options.width
  104. : Array.from(Array(options.rows[0].length).keys()).map(() => 16)
  105. // 设置表头
  106. options.rows.forEach(row => {
  107. sheet.addRow(row)
  108. })
  109. // 设置列宽
  110. options.data.forEach(item => {
  111. sheet.addRow(item)
  112. })
  113. // 设置列宽
  114. widths.forEach((width, index) => {
  115. sheet.getColumn(index + 1).width = width
  116. })
  117. // 设置样式
  118. sheet.eachRow({ includeEmpty: true }, (row, rowIndex) => {
  119. row.height = 20
  120. row.eachCell({ includeEmpty: true }, cell => {
  121. cell.border = {
  122. top: { style: 'thin' },
  123. left: { style: 'thin' },
  124. bottom: { style: 'thin' },
  125. right: { style: 'thin' }
  126. }
  127. cell.alignment = {
  128. vertical: 'middle',
  129. horizontal: 'center'
  130. }
  131. cell.font = { size: 12, bold: rowIndex < options.rows.length }
  132. })
  133. })
  134. // 合并单元格
  135. if (Array.isArray(options.merge)) {
  136. options.merge.forEach(item => {
  137. sheet.mergeCells(item)
  138. })
  139. }
  140. // 下载文件
  141. workbook.xlsx.writeBuffer().then(data => {
  142. download(data, `${options.fileName}.xlsx`)
  143. })
  144. })
  145. }
  146. /* 转为二维数组 */
  147. const toTwoArray = (list: Record<string, any>[]) => {
  148. const arr: Array<Array<string | number | null | undefined>> = []
  149. list.forEach((item: any) => {
  150. const brr = Object.values(item).map((ite: any) => ite.value)
  151. arr.push(brr)
  152. })
  153. return arr
  154. }
  155. /**
  156. * 下载文件
  157. * @param data 二进制数据,如果传入是http地址,则直接下载
  158. * @param name 文件名
  159. * @param type 文件类型
  160. */
  161. const download = (data: Blob | ArrayBuffer | string, name: string, type?: string) => {
  162. let url = ''
  163. if (typeof data === 'string') {
  164. url = data
  165. } else {
  166. const blob = new Blob([data], { type: type || 'application/octet-stream' })
  167. url = window.URL.createObjectURL(blob)
  168. }
  169. const a = document.createElement('a')
  170. a.href = url
  171. a.download = name
  172. a.style.display = 'none'
  173. document.body.appendChild(a)
  174. a.click()
  175. document.body.removeChild(a)
  176. URL.revokeObjectURL(url)
  177. }
  178. return { importExcel, toTwoArray, exportExcel, download }
  179. }