Vue3 + Element Plus 表格导出Excel 通用工具文档

该文章已生成可运行项目,

一、工具概述

该工具基于 xlsx 库封装,提供 Vue3 + Element Plus 场景下 el-table 数据导出 Excel 的通用解决方案,支持「数组数据导出」(推荐)和「DOM 表格导出」(兼容分页/筛选场景)两种模式,具备参数可配置、错误处理、格式兼容等特性。

1.1 核心优势

  • 通用化:适配任意 el-table 表格,支持自定义表头、文件名、工作表名等;
  • 易用性:提供默认配置,减少重复代码,支持自动解析表格表头;
  • 健壮性:内置参数校验、错误提示,兼容数字格式(如手机号防科学计数法);
  • 无强依赖:原生实现文件下载,无需额外依赖 file-saver

1.2 依赖安装

# 核心依赖
npm install xlsx --save

# (可选)若使用Element Plus的消息提示,需确保已安装
npm install element-plus --save

二、工具文件结构

src/
└── utils/
    └── exportExcel.js  # 核心封装文件

三、核心 API 文档

3.1 导出数组数据(推荐)exportExcel

功能说明

直接导出前端维护的数组数据(不受分页/筛选影响),通过表头映射控制导出字段和显示名。

函数签名
export const exportExcel = async (options) => {}
参数配置(options)
参数名类型是否必传默认值说明
dataArray-源数据数组(如接口返回的完整列表、el-tabledata 数据)
headerMapObject-表头映射对象,格式:{ 字段名: 显示名 },控制导出字段和表头显示
sheetNameString‘数据表’Excel 工作表名称
fileNameString‘导出数据’导出文件名称(自动拼接时间戳,可通过 withTimestamp 关闭)
withTimestampBooleantrue文件名是否添加时间戳(避免重复)
返回值

Promise<void>,无返回值,内部自动触发文件下载和消息提示。

示例
import { exportExcel } from '@/utils/exportExcel'

// 模拟表格数据
const userList = [
  { name: '张三', age: 20, phone: 13800138000, email: 'zhangsan@test.com' },
  { name: '李四', age: 22, phone: 13900139000, email: 'lisi@test.com' }
]

// 导出用户列表
exportExcel({
  data: userList,
  headerMap: {
    name: '姓名',    // 字段名: Excel显示名
    age: '年龄',
    phone: '手机号' // 仅导出这3个字段,email会被过滤
  },
  fileName: '用户列表',
  sheetName: '2025年用户数据',
  withTimestamp: false // 文件名不添加时间戳
})

3.2 导出DOM表格数据 exportTableDom

功能说明

导出 el-table 当前DOM显示的数据(含分页/筛选后的内容),适用于后端分页、前端仅显示部分数据的场景。

函数签名
export const exportTableDom = async (options) => {}
参数配置(options)
参数名类型是否必传默认值说明
eleString/HTMLElement-表格DOM选择器(如 #export-table)或表格DOM元素(如 tableRef.value.$el
sheetNameString‘当前页数据’Excel 工作表名称
fileNameString‘当前页数据’导出文件名称
withTimestampBooleantrue文件名是否添加时间戳
返回值

Promise<void>,无返回值,内部自动触发文件下载和消息提示。

示例
import { exportTableDom } from '@/utils/exportExcel'
import { ref } from 'vue'

const orderTableRef = ref(null) // 绑定到el-table的ref

// 方式1:通过选择器导出
exportTableDom({
  ele: '#order-table', // el-table需设置id="order-table"
  fileName: '2025-12订单列表',
  sheetName: '12月订单'
})

// 方式2:通过ref导出
exportTableDom({
  ele: orderTableRef.value.$el,
  fileName: '订单数据'
})

3.3 自动解析表格表头 getTableHeaderMap

功能说明

自动解析 el-table 的列配置,生成 headerMap(无需手动编写表头映射),适配不想手动定义 headerMap 的场景。

函数签名
export const getTableHeaderMap = (tableRef) => {}
参数说明
参数名类型是否必传说明
tableRefObjectel-table 的 ref 引用对象
返回值
类型说明
Object解析后的 headerMap,格式:{ prop: label }
示例
import { exportExcel, getTableHeaderMap } from '@/utils/exportExcel'
import { ref } from 'vue'

const userTableRef = ref(null) // 绑定到el-table的ref

// 自动解析表头并导出
const exportAutoHeader = () => {
  const headerMap = getTableHeaderMap(userTableRef.value)
  exportExcel({
    data: userList.value,
    headerMap, // 自动解析的表头映射
    fileName: '用户列表'
  })
}

四、完整工具代码(exportExcel.js)

import XLSX from 'xlsx'
import { ElMessage } from 'element-plus'

/**
 * 通用Excel导出方法(支持数组数据导出)
 * @param {Object} options 导出配置
 * @param {Array} options.data 源数据数组(必传)
 * @param {Object} options.headerMap 表头映射({ 字段名: 显示名 },必传)
 * @param {String} [options.sheetName='数据表'] Excel工作表名称
 * @param {String} [options.fileName='导出数据'] 导出文件名(自动加时间戳)
 * @param {Boolean} [options.withTimestamp=true] 文件名是否带时间戳
 * @returns {Promise<void>}
 */
export const exportExcel = async (options) => {
  const {
    data,
    headerMap,
    sheetName = '数据表',
    fileName = '导出数据',
    withTimestamp = true
  } = options

  // 校验必传参数
  if (!data || !Array.isArray(data)) {
    ElMessage.error('导出失败:源数据必须为数组!')
    return
  }
  if (!headerMap || typeof headerMap !== 'object' || Object.keys(headerMap).length === 0) {
    ElMessage.error('导出失败:表头映射不能为空!')
    return
  }

  try {
    // 处理数据:按headerMap过滤并映射表头,处理空值和数字格式
    const exportData = data.map(item => {
      const row = {}
      Object.entries(headerMap).forEach(([key, label]) => {
        let value = item[key] ?? ''
        // 特殊处理:手机号/身份证等长数字转文本(避免科学计数法)
        if (['phone', 'idCard', 'mobile', 'tel'].includes(key) && typeof value === 'number') {
          value = `\t${value}` // 加制表符强制文本格式
        }
        row[label] = value
      })
      return row
    })

    // 创建工作簿和工作表
    const ws = XLSX.utils.json_to_sheet(exportData)
    const wb = XLSX.utils.book_new()
    XLSX.utils.book_append_sheet(wb, ws, sheetName)

    // 生成文件并下载(原生实现)
    const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' })
    const blob = new Blob([wbout], {
      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    })
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    
    // 处理文件名
    const timestamp = withTimestamp ? `_${new Date().getTime()}` : ''
    a.download = `${fileName}${timestamp}.xlsx`
    
    a.href = url
    a.click()
    URL.revokeObjectURL(url) // 释放URL对象

    ElMessage.success(`${fileName}》导出成功!`)
  } catch (error) {
    console.error('Excel导出失败:', error)
    ElMessage.error('导出失败,请检查数据格式或重试!')
  }
}

/**
 * 通用DOM表格导出方法(导出当前页面显示的表格)
 * @param {Object} options 配置
 * @param {String|HTMLElement} options.ele 表格选择器/DOM元素(必传)
 * @param {String} [options.sheetName='当前页数据'] 工作表名称
 * @param {String} [options.fileName='当前页数据'] 文件名
 * @param {Boolean} [options.withTimestamp=true] 文件名是否带时间戳
 * @returns {Promise<void>}
 */
export const exportTableDom = async (options) => {
  const {
    ele,
    sheetName = '当前页数据',
    fileName = '当前页数据',
    withTimestamp = true
  } = options

  // 获取表格DOM
  let table = null
  if (typeof ele === 'string') {
    table = document.querySelector(ele)
  } else if (ele instanceof HTMLElement) {
    table = ele
  }
  if (!table) {
    ElMessage.error('导出失败:未找到表格DOM!')
    return
  }

  try {
    // DOM转工作表(raw: true 保留原始格式)
    const ws = XLSX.utils.table_to_sheet(table, { raw: true })
    const wb = XLSX.utils.book_new()
    XLSX.utils.book_append_sheet(wb, ws, sheetName)

    // 下载文件
    const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' })
    const blob = new Blob([wbout], {
      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    })
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    
    const timestamp = withTimestamp ? `_${new Date().getTime()}` : ''
    a.download = `${fileName}${timestamp}.xlsx`
    
    a.href = url
    a.click()
    URL.revokeObjectURL(url)

    ElMessage.success(`${fileName}》导出成功!`)
  } catch (error) {
    console.error('DOM表格导出失败:', error)
    ElMessage.error('导出失败,请重试!')
  }
}

/**
 * 解析el-table的表头,自动生成headerMap
 * @param {Object} tableRef el-table的ref引用对象
 * @returns {Object} 表头映射 { prop: label }
 */
export const getTableHeaderMap = (tableRef) => {
  if (!tableRef || !tableRef.columns) {
    ElMessage.warning('解析表头失败:表格ref无效!')
    return {}
  }
  const headerMap = {}
  // 遍历表格列配置,过滤空prop/label
  tableRef.columns.forEach(col => {
    if (col.prop && col.label && !col.hidden) { // 排除隐藏列
      headerMap[col.prop] = col.label
    }
  })
  return headerMap
}

五、组件中使用示例

<template>
  <div class="excel-export-demo">
    <!-- 示例1:数组数据导出 -->
    <el-table
      ref="userTableRef"
      :data="userList"
      border
      style="width: 100%; margin-bottom: 20px"
    >
      <el-table-column prop="name" label="姓名" />
      <el-table-column prop="age" label="年龄" />
      <el-table-column prop="phone" label="手机号" />
      <el-table-column prop="email" label="邮箱" />
    </el-table>
    <el-button type="primary" @click="exportUserExcel">导出用户列表(手动headerMap)</el-button>
    <el-button type="primary" @click="exportUserExcelAuto">导出用户列表(自动解析表头)</el-button>

    <!-- 示例2:DOM表格导出(分页场景) -->
    <el-table
      :data="orderList"
      border
      id="order-table"
      style="width: 100%; margin-bottom: 20px"
    >
      <el-table-column prop="orderNo" label="订单号" />
      <el-table-column prop="amount" label="金额(元)" />
      <el-table-column prop="createTime" label="创建时间" />
    </el-table>
    <el-button type="primary" @click="exportOrderDom">导出当前订单页</el-button>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { exportExcel, exportTableDom, getTableHeaderMap } from '@/utils/exportExcel'

// 模拟用户数据
const userTableRef = ref(null)
const userList = ref([
  { name: '张三', age: 20, phone: 13800138000, email: 'zhangsan@test.com' },
  { name: '李四', age: 22, phone: 13900139000, email: 'lisi@test.com' },
  { name: '王五', age: 25, phone: 13700137000, email: 'wangwu@test.com' }
])

// 模拟订单数据(分页)
const orderList = ref([
  { orderNo: 'OD20251209001', amount: 199, createTime: '2025-12-09 10:00' },
  { orderNo: 'OD20251209002', amount: 299, createTime: '2025-12-09 11:00' }
])

// 1. 手动配置headerMap导出
const exportUserExcel = () => {
  exportExcel({
    data: userList.value,
    headerMap: {
      name: '姓名',
      age: '年龄',
      phone: '手机号' // 仅导出这3个字段
    },
    fileName: '用户列表',
    sheetName: '2025用户数据',
    withTimestamp: true
  })
}

// 2. 自动解析表头导出
const exportUserExcelAuto = () => {
  const headerMap = getTableHeaderMap(userTableRef.value)
  exportExcel({
    data: userList.value,
    headerMap, // 自动解析所有列的prop和label
    fileName: '用户列表_自动解析',
    sheetName: '用户数据'
  })
}

// 3. 导出DOM表格(当前页)
const exportOrderDom = () => {
  exportTableDom({
    ele: '#order-table',
    fileName: '12月订单列表',
    sheetName: '2025-12-09订单'
  })
}
</script>

六、常见问题与解决方案

问题现象原因分析解决方案
手机号/身份证显示为科学计数法数字过长被Excel自动格式化工具已内置处理:给指定字段加 \t 强制文本格式;或手动将值转为字符串
导出后Excel内容为空源数据为空或headerMap配置错误检查 data 是否为非空数组;检查 headerMap 的字段名是否与数据匹配
DOM导出找不到表格选择器错误或DOM未挂载确认选择器正确;或通过 ref 传递DOM元素(tableRef.value.$el
导出文件乱码Blob类型配置错误工具已默认配置正确的 Blob 类型(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
合并单元格导出异常xlsx库对DOM合并单元格支持有限优先使用「数组数据导出」模式,避免依赖DOM解析

七、扩展说明

  1. 支持自定义格式:可在 exportExcel 方法的 exportData 处理阶段,扩展日期、金额等字段的格式化逻辑;
  2. 大文件优化:若导出数据量超1万条,可改用 XLSX.writeFile 替代 Blob 下载,避免内存溢出;
  3. 多工作表导出:可扩展 exportExcel 方法,支持传入多个 data + headerMap,生成多工作表的Excel文件;
  4. 样式定制:xlsx 库基础版本不支持Excel样式,若需定制样式,可升级为 xlsx-styleexceljs 库。
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值