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

相关推荐
Baklib梅梅2 小时前
2025文档管理软件推荐:效率、安全与协作全解析
前端·ruby on rails·前端框架·ruby
卷Java3 小时前
小程序前端功能更新说明
java·前端·spring boot·微信小程序·小程序·uni-app
FogLetter3 小时前
前端性能救星:虚拟列表原理与实现,让你的10万条数据流畅如丝!
前端·性能优化
我是天龙_绍3 小时前
前端驼峰,后端下划线,问:如何统一?
前端
知识分享小能手3 小时前
微信小程序入门学习教程,从入门到精通,微信小程序常用API(下)——知识点详解 + 案例实战(5)
前端·javascript·学习·微信小程序·小程序·vue·前端开发
code_YuJun3 小时前
nginx 配置相关
前端·nginx
对不起初见4 小时前
PlantUML 完整教程:从入门到精通
前端·后端
东方掌管牛马的神4 小时前
oh-my-zsh 配置与使用技巧
前端