前端实现复杂的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复杂导出。作者本人也是研究了半天,有任何问题评论区下讨论,在下知无不言言无不尽。

相关推荐
崔庆才丨静觅12 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606113 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了13 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅13 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅13 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅14 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment14 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅14 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊14 小时前
jwt介绍
前端
爱敲代码的小鱼14 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax