当前位置: 首页 > article >正文

【vue+excel】导出excel(目前是可以导出两个sheet)

项目里经常用到的导出ecxel功能是默认导出一个sheet页
在这里插入图片描述
现在需要导出两个sheet,一个是总计,另一个是明细
效果如下:
在这里插入图片描述

我就在现有的单个导出的功能上改造了一下,只支持导出两个(代码不够灵活,如果需要多个,还需要再优化或直接手动往里面加)

1、导出按钮的点击事件中

 // 导出
  handleExport() {
 // 参数分别是:id数组(没用到,传空),sheet1名称,sheet2名称,sheet1表头,sheet2表头,sheet1数据源,sheet2数据源
  this.double_sheet_exportExcel('', 'xx统计','xx明细', this.columns,this.detailColumns, 
  this.dataSource,this.detailDataSource)
  }

2、安装excel相关的库

npm install --save xlsx file-saver
npm install --save xlsx-style

3、在utils的公共js文件中,如common.js(自己项目定义的放公共方法的文件中)

export function double_sheet_exportExcel(ids, filename1,filename2, excelProps1, excelProps2, excelList1, excelList2, dictOptions = {}) {
  import('@/utils/export2Excel').then(excel => {
    // 处理第一个工作表的表头和数据
    const newExcelProps1 = JSON.parse(JSON.stringify(excelProps1))
    // 处理第一个工作表的表头
    newExcelProps1.forEach((prop, index) => {
      if (prop.visible === false || prop.field === 'operation') {
        newExcelProps1.splice(index, 1)
      }
    })
    const name = 'title'
    const tHeader1 = newExcelProps1.map(item => item[name]) // 第一个工作表的表头

    // 处理第一个工作表的数据
    let fileData1
    if (ids === undefined || ids.length === 0) {
      fileData1 = excelList1
    } else {
      let arr1 = JSON.parse(JSON.stringify(excelList1))
      let arrMap1 = new Map(arr1.map(obj => [obj.id, obj]))
      let resultArr1 = ids.map(id => arrMap1.get(id))
      fileData1 = resultArr1
    }
    fileData1.forEach(item => {
      newExcelProps1.forEach(v => {
        if (v.type === 'dict' && v.scopedSlots.content) {
          let dictField = v.dictField || ['dictValue', 'dictLabel']
          const filterData = dictOptions[v.field].filter(f => item[v.field] == f[dictField[0]])
          item[v.field] = !filterData.length ? '' : filterData[0][dictField[1]]
        }
      })
    })
    const data1 = fileData1.map(v => newExcelProps1.map(({ field }) => v[field]))
   // 处理第二个工作表的表头和数据
    const newExcelProps2 = JSON.parse(JSON.stringify(excelProps2))
    // 处理第二个工作表的表头
    newExcelProps2.forEach((prop, index) => {
      if (prop.visible === false || prop.field === 'operation') {
        newExcelProps2.splice(index, 1)
      }
    })
    const tHeader2 = newExcelProps2.map(item => item[name]) // 第二个工作表的表头
    // 处理第二个工作表的数据
    let fileData2
    if (ids === undefined || ids.length === 0) {
      fileData2 = excelList2
    } else {
      let arr2 = JSON.parse(JSON.stringify(excelList2))
      let arrMap2 = new Map(arr2.map(obj => [obj.id, obj]))
      let resultArr2 = ids.map(id => arrMap2.get(id))
      fileData2 = resultArr2
    }
    fileData2.forEach(item => {
      newExcelProps2.forEach(v => {
        if (v.type === 'dict' && v.scopedSlots.content) {
          let dictField = v.dictField || ['dictValue', 'dictLabel']
          const filterData = dictOptions[v.field].filter(f => item[v.field] == f[dictField[0]])
          item[v.field] = !filterData.length ? '' : filterData[0][dictField[1]]
        }
      })
    })
    const data2 = fileData2.map(v => newExcelProps2.map(({ field }) => v[field]))

    // 创建多 Sheet 的 Excel 文件
    const multiHeader = [[filename1], [filename2]] // 多 Sheet 的标题
    const merges = [
      [`A1:${numToEng(newExcelProps1.length)}1`], // 第一个 Sheet 的合并单元格
      [`A1:${numToEng(newExcelProps2.length)}1`], // 第二个 Sheet 的合并单元格
    ]
const filename = filename1
    excel.double_sheet_export_json_to_excel({
      title: [],
      multiHeader, // 多 Sheet 的标题
      header: [tHeader1, tHeader2], // 每个 Sheet 的表头
      data: [data1, data2], // 每个 Sheet 的数据
      merges, // 合并单元格
      filename, // 文件名
      autoWidth: true, // 自动宽度
      bookType: 'xlsx', // 文件格式
      sheetName: [filename1, filename2], // 每个 Sheet 的名称
    })
  })
}

4、在main.js中全局挂载

import {
 double_sheet_exportExcel,
 }
 Vue.prototype.double_sheet_exportExcel = double_sheet_exportExcel

5、在utils中新建ecport2Excel.js

export function double_sheet_export_json_to_excel({
  title,
  multiHeader = [],
  header,
  data,
  filename,
  departName = [],
  merges = [], // 二维数组,每个子数组对应一个 Sheet 的合并单元格
  autoWidth = true,
  bookType = 'xlsx',
  sheetName = ['Sheet1'], // 每个 Sheet 的名称
  exportAlignleft,
} = {}) {
  const wb = new Workbook()

  // 确保 header 和 data 是数组的数组(多 Sheet 支持)
  if (!Array.isArray(header[0])) {
    header = [header]
  }
  if (!Array.isArray(data[0])) {
    data = [data]
  }
  if (!Array.isArray(merges[0])) {
    merges = [merges]
  }

  header.forEach((sheetHeader, index) => {
    const sheetData = data[index] || []
    const sheetMerges = merges[index] || []
    const ws_name = sheetName[index] || `Sheet${index + 1}`
    const doubleMutiHeader = multiHeader[index]
    // console.log('sheetHeader:', sheetHeader)
    // console.log('sheetData:', sheetData)
    // 构造工作表数据
    const ws_data = [...title, ...departName, doubleMutiHeader, sheetHeader, ...sheetData]

    // 创建工作表
    const ws = double_sheet_from_array_of_arrays(ws_data)

    // 处理合并单元格
    if (sheetMerges && sheetMerges.length > 0) {
      if (!ws['!merges']) ws['!merges'] = []
      sheetMerges.forEach(item => {
        try {
          ws['!merges'].push(XLSX.utils.decode_range(item))
        } catch (e) {
          console.error(`Invalid merge range: ${item}`, e)
        }
      })
    }

    // 设置列宽
    if (autoWidth) {
      const validRows = ws_data.filter(row => Array.isArray(row) && row.length > 0)
      const colWidth = validRows.map(row => {
        return row.map(val => {
          if (val == null || val === '') {
            return { wch: 40 } // 默认宽度
          } else if (val.toString().charCodeAt(0) > 255) {
            return { wch: val.toString().length * 5 } // 中文字符宽度
          } else {
            return { wch: val.toString().length * 1 } // 英文字符宽度
          }
        })
      })
      // let result = colWidth[1] || []原先取的是colWidth[0],打印发现colWidth[0]只有一项,应该是colWidth[1],长度同列表总列数
      let result = colWidth[1] || []
      for (let i = 1; i < colWidth.length; i++) {
        for (let j = 0; j < colWidth[i].length; j++) {
          if (result[j]?.wch < colWidth[i][j].wch) {
            result[j] = colWidth[i][j]
          }
        }
      }
      // ws['!cols'] = result
      //  ws['!cols'],假如导出共有7列,格式:[{wch:25},{wch:45},{wch:25},{wch:25},{wch:25},{wch:25},{wch:25}]

      ws['!cols'] = result.map(col => {
        return { wch: col.wch }
      })
    }
    

    // 设置单元格样式
    const borderAll = {
      top: { style: 'thin' },
      bottom: { style: 'thin' },
      left: { style: 'thin' },
      right: { style: 'thin' },
    }

    const dataInfo = ws
    const row = ['!ref', '!merges', '!cols']
    for (var i in dataInfo) {
      if (row.indexOf(i) === -1) {
        if (exportAlignleft == 'exportAlignleft') {
          dataInfo[i].s = {
            border: borderAll,
            alignment: { horizontal: 'left', vertical: 'center' },
          }
        } else {
          if (dataInfo[i].t == 'n') {
            dataInfo[i].s = {
              border: borderAll,
              alignment: { horizontal: 'right', vertical: 'center' },
            }
          } else {
            dataInfo[i].s = {
              border: borderAll,
              alignment: { horizontal: 'center', vertical: 'center' },
            }
          }
        }
      }
    }

    // 添加工作表到工作簿
    wb.SheetNames.push(ws_name)
    wb.Sheets[ws_name] = ws
    // 根据表头长度生成单元格列数组
    const arr = generateHeaderArr(header[0].length)
    // 表头加上样式
    arr.forEach(v => {
      let v2 = v + '2'
      if (dataInfo.hasOwnProperty(v2)) {
        dataInfo[v2].s = {
          border: borderAll,
          font: {
            color: { rgb: 'FFFFFF' },
            bold: false,
            italic: false,
            underline: false,
          },
          alignment: {
            horizontal: 'center',
            vertical: 'center',
          },
          fill: {
            fgColor: { rgb: '808080' },
          },
        }
      }
    })
    //设置主标题样式
    dataInfo['A1'].s = {
      font: {
        name: '微软雅黑',
        sz: 16,
        color: { rgb: '000000' },
        bold: false,
        italic: false,
        underline: false,
      },
      alignment: {
        horizontal: 'center',
        vertical: 'center',
      },
    }
  })
 
  // 导出 Excel 文件
  const wbout = XLSX.write(wb, {
    bookType: bookType,
    bookSST: false,
    type: 'binary',
  })

  saveAs(
    new Blob([s2ab(wbout)], {
      type: 'application/octet-stream',
    }),
    `${filename}.${bookType}`
  )
}
function double_sheet_from_array_of_arrays(data, opts) {
  var ws = {}
  var range = {
    s: { c: 10000000, r: 10000000 }, // 初始化范围的起始点
    e: { c: 0, r: 0 }, // 初始化范围的结束点
  }

  // 确保 data 是一个数组
  if (!Array.isArray(data)) {
    console.error('Data is not an array:', data)
    return ws
  }

  for (var R = 0; R < data.length; ++R) {
    // 确保 data[R] 是一个数组
    if (!Array.isArray(data[R])) {
      console.warn('Row', R, 'is not an array:', data[R])
      continue // 跳过非数组行
    }

    for (var C = 0; C < data[R].length; ++C) {
      if (range.s.r > R) range.s.r = R
      if (range.s.c > C) range.s.c = C
      if (range.e.r < R) range.e.r = R
      if (range.e.c < C) range.e.c = C

      var cell = { v: data[R][C] }
      if (cell.v == null) continue

      var cell_ref = XLSX.utils.encode_cell({ c: C, r: R })

      if (typeof cell.v === 'number') cell.t = 'n'
      else if (typeof cell.v === 'boolean') cell.t = 'b'
      else if (cell.v instanceof Date) {
        cell.t = 'd'
        cell.z = XLSX.SSF._table[14]
        cell.v = datenum(cell.v)
      } else cell.t = 's'

      ws[cell_ref] = cell
    }
  }

  if (range.s.c < 10000000) {
    ws['!ref'] = XLSX.utils.encode_range(range)
  }

  return ws
}
// 迭代生成单元格表头数组
function generateHeaderArr(length) {
  const result = []
  const generator = generateHeaderString()
  for (let i = 0; i < length; i++) {
    const nextString = generator.next().value
    result.push(nextString)
  }
  return result
}
// 生成器
function* generateHeaderString() {
  let index = 0
  const letterString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  while (true) {
    let str = ''
    let n = index
    while (n >= 0) {
      str = letterString[n % 26] + str
      n = Math.floor(n / 26) - 1
    }
    yield str
    index++
  }
}
function Workbook() {
  if (!(this instanceof Workbook)) return new Workbook()
  this.SheetNames = []
  this.Sheets = {}
}
function datenum(v, date1904) {
  if (date1904) v += 1462
  var epoch = Date.parse(v)
  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}
function s2ab(s) {
  var buf = new ArrayBuffer(s.length)
  var view = new Uint8Array(buf)
  for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
  return buf
}

http://www.kler.cn/a/583943.html

相关文章:

  • 【C#学习笔记04】C语言格式化输出
  • Gartner发布中国CIO和安全团队生成式AI指南:制定人工智能安全治理计划的五个阶段
  • 江科大51单片机笔记【15】直流电机驱动(PWM)
  • 【测试开发面试题】每日 3 题(十一)
  • 星越L_发动机舱开启及油液加注讲解
  • matlab:二维绘图篇——plot绘图命令
  • Java中如何去自定义一个类加载器
  • SQL Server查询优化
  • Axure设计之下拉多选框制作教程C(中继器)
  • 【MySQL 中 `TINYINT` 类型与布尔值的关系】
  • 宇树人形机器人开源模型
  • 【一键让照片动起来】阿里万相2.1图生视频+蓝耘智算零门槛部署指南
  • 正则应用--java算法
  • 【Java项目】基于JSP的咨询交流论坛系统
  • 用 Python 检测两个文本文件的相似性的几种方法
  • 人工智能混合编程实践:Python ONNX进行图像超分重建
  • 【探秘机器人:从当下到未来的科技跃迁】
  • VSCode集成C语言开发环境
  • 八叉树地图的原理与实现
  • 基于GoogleNet深度学习网络和GEI步态能量提取的步态识别算法matlab仿真,数据库采用CASIA库