前端实现复杂的Excel导出

1、需要实现的效果

2、模板

需求:前端需要实现个人信息的Excel导出。我的实现方案是使用模板,通过读取模板,使用模板语法去写值。因为其余的库都需要写样式,我得保证样式和模板一致,使用这个库只需要写值进去,以及合并单元格。

3、安装插件

npm i exceljs-xlsx-template

4、导出的工具函数

javascript 复制代码
import { renderXlsxTemplate, placeholderRange } from 'exceljs-xlsx-template'
import { userInfoAllStore } from '@/stores/userInfoAll'

const BASE_URL = import.meta.env.VITE_APP_BASE_API

export async function handleXlsxTemplate() {
  const userInfoAll = userInfoAllStore()
  console.log(userInfoAll.userInfoAll)
  const xlsxFile = '/template.xlsx'
  const data = [
    {
      ...userInfoAll.userInfoAll
    }
  ]

  try {
    const response = await fetch(xlsxFile)
    if (!response.ok) {
      alert('模板文件加载失败,请检查文件是否存在')
      return
    }

    const arrayBuffer = await response.arrayBuffer()

    renderXlsxTemplate(arrayBuffer, data, `${data[0]?.name}--个人基本信息.xlsx`, {
      parseImage: true,
      async beforeSave(workbook) {
        const worksheet = workbook.getWorksheet('sheet1')
        if (!worksheet) return
        formatMergeCell(worksheet, data)

        // 头像
        const officialsealFile = data[0]?.profilePhoto ? BASE_URL + '/' + data[0]?.profilePhoto : ''
        const officialsealRresponse = await fetch(officialsealFile)
        if (!officialsealRresponse.ok) {
          console.error(
            `Failed to download image file, status code: ${officialsealRresponse.status}`
          )
          return
        }
        const officialsealArrayBuffer = await officialsealRresponse.arrayBuffer()
        // 将图片添加到工作簿
        const imageId = workbook.addImage({
          buffer: officialsealArrayBuffer,
          extension: 'png'
        })
        // 获取头像占位符位置信息
        const range = placeholderRange(worksheet, '{{#officialseal}}')
        if (range) {
          // 插入图片到表格中
          worksheet.addImage(imageId, {
            tl: { col: range.start.col - 0.9, row: range.start.row - 0.9 },
            ext: { width: 150, height: 184 }
          })
        }
      }
    })
  } catch (error) {
    console.error('Error processing Excel file:', error)
  }
}

// 工具函数:合并单元格区域
const mergeCellRange = (worksheet, startRow, endRow, startCol = 'A', endCol = 'B') => {
  const range = `${startCol}${startRow}:${endCol}${endRow}`
  worksheet.unMergeCells(range)
  worksheet.mergeCells(range)
  return range
}

const formatMergeCell = (worksheet, data) => {
  // 使用工具函数
  const baseRow = 12
  const familyMembersLength = data[0].familyMembers.length
  const educationExperiencesLength = data[0].educationExperiences.length
  const trainingCoursesLength = data[0].trainingCourses.length
  const workExperiencesLength = data[0].workExperiences.length
  const cadreAppointmentsLength = data[0].cadreAppointments.length
  const hRPositionSettingsLength = data[0].hRPositionSettings.length
  const centerPositionSettingsLength = data[0].centerPositionSettings.length
  const professionalQualificationsLength = data[0].professionalQualifications.length
  const papersLength = data[0].papers.length
  const booksLength = data[0].books.length
  const patentsLength = data[0].patents.length
  const awardInfosLength = data[0].awardInfos.length
  const techResearchsLength = data[0].techResearchs.length
  const annualAssessmentsLength = data[0].annualAssessments.length
  const awardsLength = data[0].awards.length
  const punishmentsLength = data[0].punishments.length

  // 合并家庭成员列
  const familyRange = mergeCellRange(worksheet, baseRow, baseRow + familyMembersLength)
  if (familyMembersLength < 2) {
    const targetRow = baseRow + familyMembersLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['F', 'G'],
      ['H', 'I'],
      ['J', 'K'],
      ['M', 'N']
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并家庭成员区域: ${familyRange}`)

  // 合并学习经历列
  const educationStartRow = baseRow + familyMembersLength + 2
  const educationRange = mergeCellRange(
    worksheet,
    educationStartRow,
    educationStartRow + educationExperiencesLength
  )
  if (educationExperiencesLength < 2) {
    const targetRow = educationStartRow + educationExperiencesLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['D', 'E'],
      ['F', 'G'],
      ['H', 'I'],
      ['J', 'K'],
      ['L', 'M'],
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并学习经历区域: ${educationRange}`)

  // 合并培训进修列
  const trainingStartRow = educationStartRow + educationExperiencesLength + 1
  const trainingRange = mergeCellRange(
    worksheet,
    trainingStartRow,
    trainingStartRow + trainingCoursesLength
  )
  if (trainingCoursesLength < 2) {
    const targetRow = trainingStartRow + trainingCoursesLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['D', 'E'],
      ['H', 'I'],
      ['J', 'K']
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并培训进修区域: ${trainingRange}`)

  // 合并工作经历列
  const workStartRow = trainingStartRow + trainingCoursesLength + 2
  const workRange = mergeCellRange(worksheet, workStartRow, workStartRow + workExperiencesLength)
  // 工作经历列--单行数据无需合并
  console.log(`合并工作经历区域: ${workRange}`)

  // 合并干部任免列
  const postStartRow = workStartRow + workExperiencesLength + 1
  const postRange = mergeCellRange(worksheet, postStartRow, postStartRow + cadreAppointmentsLength)
  if (cadreAppointmentsLength < 2) {
    const targetRow = postStartRow + cadreAppointmentsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'E'],
      ['F', 'H'],
      ['I', 'K'],
      ['M', 'N']
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并干部任免列区域: ${postRange}`)

  // 合并人社局岗位设置列
  const postHrStartRow = postStartRow + cadreAppointmentsLength + 1
  const postHrRange = mergeCellRange(
    worksheet,
    postHrStartRow,
    postHrStartRow + hRPositionSettingsLength
  )
  if (hRPositionSettingsLength < 2) {
    const targetRow = postHrStartRow + hRPositionSettingsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'F'],
      ['G', 'J'],
      ['K', 'N'],
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并人社局岗位设置列区域: ${postHrRange}`)

  // 合并中心岗位设置列
  const postCenterStartRow = postHrStartRow + hRPositionSettingsLength + 1
  const postCenterRange = mergeCellRange(
    worksheet,
    postCenterStartRow,
    postCenterStartRow + centerPositionSettingsLength
  )
  if (centerPositionSettingsLength < 2) {
    const targetRow = postCenterStartRow + centerPositionSettingsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'E'],
      ['F', 'H'],
      ['I', 'K'],
      ['L', 'N'],
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并中心岗位设置列区域: ${postCenterRange}`)

  // 合并职称职业资格列
  const professionalQualificationStartRow = postCenterStartRow + centerPositionSettingsLength + 1
  const professionalQualificationRange = mergeCellRange(
    worksheet,
    professionalQualificationStartRow,
    professionalQualificationStartRow + professionalQualificationsLength
  )
  if (professionalQualificationsLength < 2) {
    const targetRow = professionalQualificationStartRow + professionalQualificationsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['D', 'E'],
      ['F', 'G'],
      ['K', 'L'],
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并职称职业资格列区域: ${professionalQualificationRange}`)

  // 合并论文列
  const paperStartRow = professionalQualificationStartRow + professionalQualificationsLength + 2
  const paperRange = mergeCellRange(worksheet, paperStartRow, paperStartRow + papersLength)
  if (papersLength < 2) {
    const targetRow = paperStartRow + papersLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'D'],
      ['G', 'H'],
      ['M', 'N']
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并论文列区域: ${paperRange}`)

  // 合并专著列
  const bookStartRow = paperStartRow + papersLength + 1
  const bookRange = mergeCellRange(worksheet, bookStartRow, bookStartRow + booksLength)
  if (booksLength < 2) {
    const targetRow = bookStartRow + booksLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'D'],
      ['F', 'G'],
      ['H', 'I'],
      ['J', 'K']
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并专著列区域: ${bookRange}`)

  // 合并专利列
  const patentStartRow = bookStartRow + booksLength + 1
  const patentRange = mergeCellRange(worksheet, patentStartRow, patentStartRow + patentsLength)
  if (patentsLength < 2) {
    const targetRow = patentStartRow + patentsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'D'],
      ['E', 'F'],
      ['G', 'H'],
      ['M', 'N'],
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并专利列区域: ${patentRange}`)

  // 合并业务获奖列
  const businessAwardStartRow = patentStartRow + patentsLength + 1
  const businessAwardRange = mergeCellRange(
    worksheet,
    businessAwardStartRow,
    businessAwardStartRow + awardInfosLength
  )
  if (awardInfosLength < 2) {
    const targetRow = businessAwardStartRow + awardInfosLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['E', 'F']
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并业务获奖列区域: ${businessAwardRange}`)

  // 合并科技研发列
  const techDevelopStartRow = businessAwardStartRow + awardInfosLength + 1
  const techDevelopRange = mergeCellRange(
    worksheet,
    techDevelopStartRow,
    techDevelopStartRow + techResearchsLength
  )
  if (techResearchsLength < 2) {
    const targetRow = techDevelopStartRow + techResearchsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'D'],
      ['F', 'G']
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并科技研发列区域: ${techDevelopRange}`)

  // 合并年度考核列
  const annualAssessmentStartRow = techDevelopStartRow + techResearchsLength + 2
  const annualAssessmentRange = mergeCellRange(
    worksheet,
    annualAssessmentStartRow,
    annualAssessmentStartRow + annualAssessmentsLength
  )
  if (annualAssessmentsLength < 2) {
    const targetRow = annualAssessmentStartRow + annualAssessmentsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'F'],
      ['G', 'J'],
      ['K', 'N'],
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并年度考核列区域: ${annualAssessmentRange}`)

  // 合并奖励列
  const rewardStartRow = annualAssessmentStartRow + annualAssessmentsLength + 1
  const rewardRange = mergeCellRange(
    worksheet, 
    rewardStartRow, 
    rewardStartRow + awardsLength
  )
  if (awardsLength < 2) {
    const targetRow = rewardStartRow + awardsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'D'],
      ['E', 'F'],
      ['G', 'H'],
      ['I', 'J'],
      ['K', 'L'],
      ['M', 'N']
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并奖励列区域: ${rewardRange}`)

  // 合并处罚列
  const penaltyStartRow = rewardStartRow + awardsLength + 1
  const penaltyRange = mergeCellRange(
    worksheet,
    penaltyStartRow,
    penaltyStartRow + punishmentsLength
  )
  if (punishmentsLength < 2) {
    const targetRow = penaltyStartRow + punishmentsLength

    // 定义需要合并的列范围
    const columnRanges = [
      ['C', 'D'],
      ['E', 'F'],
      ['G', 'H'],
      ['I', 'J'],
      ['K', 'L'],
      ['M', 'N'],
    ]

    // 循环合并每个列范围
    columnRanges.forEach(([startCol, endCol]) => {
      mergeCellRange(worksheet, targetRow, targetRow, startCol, endCol)
    })
  }
  console.log(`合并处罚列区域: ${penaltyRange}`)
}

5、使用

直接调用即可,因为我这些数据都存储到了Pinia,直接取就行
javascript 复制代码
<el-button link type="primary" size="large" @click="handleXlsxTemplate">
	<el-icon :style="{ marginRight: '10px' }">
    <Download /></el-icon>导出信息
</el-button>

6、数据结构

以上就是完整的实现Excel复杂导出。作者本人也是研究了半天,有任何问题评论区下讨论,在下知无不言言无不尽。

相关推荐
艾小码7 分钟前
ES6+革命:8大特性让你的JavaScript代码质量翻倍
前端·javascript
两个西柚呀11 分钟前
Vue组件的一些底层细节
前端·javascript·vue.js
IT技术分享社区14 分钟前
前端:浏览器Content Security Policy 安全策略介绍和用法
前端·前端开发
林强1814 小时前
前端文件预览docx、pptx和xlsx
前端
一路向北⁢4 小时前
基于 Apache POI 5.2.5 构建高效 Excel 工具类:从零到生产级实践
java·apache·excel·apache poi·easy-excel·fast-excel
像是套了虚弱散7 小时前
DevEco Studio与Web联合开发:打造鸿蒙混合应用的全景指南
开发语言·前端·华为·harmonyos·鸿蒙
衬衫chenshan7 小时前
【CTF】强网杯2025 Web题目writeup
前端
飞翔的佩奇7 小时前
【完整源码+数据集+部署教程】【天线&水】舰船战舰检测与分类图像分割系统源码&数据集全套:改进yolo11-repvit
前端·python·yolo·计算机视觉·数据集·yolo11·舰船战舰检测与分类图像分割系统
哆啦A梦15888 小时前
点击Top切换数据
前端·javascript·vue.js
程序猿追8 小时前
Vue组件化开发
前端·html