在Web应用开发中,数据导出功能几乎是每个后台管理系统的标配需求。最近在重构公司内部运营系统时,我再次遇到了这个经典场景:业务部门需要将表格数据导出为Excel文件进行二次分析。经过多次技术方案对比,最终选择了Vue3 + Element Plus的组合来实现这个功能。
这个方案最大的优势在于:
实现Excel导出的核心库我们选择了xlsx。这个库的优势非常明显:
安装命令:
bash复制npm install xlsx
# 或
yarn add xlsx
为了更好配合Element Plus使用,我们还需要:
bash复制npm install file-saver @types/file-saver --save-dev
file-saver用于处理文件下载动作,@types包则提供TypeScript类型支持。
假设我们有如下表格数据:
javascript复制const tableData = [
{ id: 1, name: '张三', age: 28, department: '研发部' },
{ id: 2, name: '李四', age: 32, department: '产品部' },
// 更多数据...
]
javascript复制import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
function exportExcel() {
// 创建工作簿
const wb = XLSX.utils.book_new()
// 创建工作表数据
const ws = XLSX.utils.json_to_sheet(tableData)
// 将工作表添加到工作簿
XLSX.utils.book_append_sheet(wb, ws, '员工数据')
// 生成Excel文件并下载
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' })
const data = new Blob([excelBuffer], { type: 'application/octet-stream' })
saveAs(data, '员工数据.xlsx')
}
实际业务中经常需要多级表头:
javascript复制const headers = [
{ label: '基本信息', children: [
{ label: 'ID', prop: 'id' },
{ label: '姓名', prop: 'name' }
]},
{ label: '其他信息', children: [
{ label: '年龄', prop: 'age' },
{ label: '部门', prop: 'department' }
]}
]
function buildComplexHeader(headers) {
const headerRows = []
const maxDepth = getMaxDepth(headers)
// 构建表头数据结构
// ...具体实现逻辑
return headerRows
}
javascript复制function exportMultiSheet() {
const wb = XLSX.utils.book_new()
// Sheet1
const ws1 = XLSX.utils.json_to_sheet(tableData1)
XLSX.utils.book_append_sheet(wb, ws1, '第一季度')
// Sheet2
const ws2 = XLSX.utils.json_to_sheet(tableData2)
XLSX.utils.book_append_sheet(wb, ws2, '第二季度')
// 导出...
}
当数据量超过1万条时:
javascript复制async function exportLargeData(data) {
const CHUNK_SIZE = 5000
const wb = XLSX.utils.book_new()
for(let i = 0; i < data.length; i += CHUNK_SIZE) {
const chunk = data.slice(i, i + CHUNK_SIZE)
const ws = XLSX.utils.json_to_sheet(chunk)
XLSX.utils.book_append_sheet(wb, ws, `数据块${i/CHUNK_SIZE + 1}`)
// 避免UI阻塞
await new Promise(resolve => setTimeout(resolve, 100))
}
// 导出...
}
创建worker.js:
javascript复制self.importScripts('https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js')
self.onmessage = function(e) {
const { data, fileName } = e.data
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.json_to_sheet(data)
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' })
self.postMessage({ excelBuffer, fileName })
}
主线程调用:
javascript复制const worker = new Worker('worker.js')
worker.postMessage({ data: largeData, fileName: '大数据导出.xlsx' })
worker.onmessage = function(e) {
const blob = new Blob([e.data.excelBuffer], {type: 'application/octet-stream'})
saveAs(blob, e.data.fileName)
}
javascript复制function setCellStyle(ws, cellRef, style) {
if(!ws['!styles']) ws['!styles'] = {}
if(!ws['!styles'][cellRef]) ws['!styles'][cellRef] = {}
Object.assign(ws['!styles'][cellRef], style)
}
// 使用示例
const ws = XLSX.utils.json_to_sheet(data)
setCellStyle(ws, 'A1', {
font: { bold: true, color: { rgb: 'FF0000' } },
fill: { fgColor: { rgb: 'FFFF00' } }
})
javascript复制function applyConditionalFormatting(ws, range, conditionFn, style) {
const { s, e } = decodeRange(range)
for(let R = s.r; R <= e.r; ++R) {
for(let C = s.c; C <= e.c; ++C) {
const cellRef = XLSX.utils.encode_cell({r:R, c:C})
if(conditionFn(ws[cellRef])) {
setCellStyle(ws, cellRef, style)
}
}
}
}
解决方案:
javascript复制// 在Blob构造时指定编码
const blob = new Blob(
[new Uint8Array([0xEF, 0xBB, 0xBF]), excelBuffer],
{ type: 'application/vnd.ms-excel;charset=utf-8' }
)
javascript复制function formatDateCell(value) {
if(value instanceof Date) {
const date1900 = new Date(1900, 0, 1)
const diffDays = Math.floor((value - date1900) / (1000 * 60 * 60 * 24)) + 1
return { t: 'n', v: diffDays, z: 'yyyy-mm-dd' }
}
return value
}
优化方案:
vue复制<template>
<el-button @click="handleExport">
<i class="el-icon-download"></i> 导出Excel
</el-button>
</template>
<script>
import { defineComponent } from 'vue'
import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
export default defineComponent({
props: {
tableData: Array,
fileName: {
type: String,
default: 'export-data'
},
headers: Array
},
methods: {
async handleExport() {
try {
this.$emit('before-export')
const wb = XLSX.utils.book_new()
const ws = this.headers
? this.buildSheetWithHeaders()
: XLSX.utils.json_to_sheet(this.tableData)
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
const excelBuffer = XLSX.write(wb, {
bookType: 'xlsx',
type: 'array'
})
const blob = new Blob(
[new Uint8Array([0xEF, 0xBB, 0xBF]), excelBuffer],
{ type: 'application/vnd.ms-excel;charset=utf-8' }
)
saveAs(blob, `${this.fileName}.xlsx`)
this.$emit('export-success')
} catch (error) {
this.$emit('export-error', error)
}
},
buildSheetWithHeaders() {
// 复杂表头构建逻辑
}
}
})
</script>
在最近的项目中,我们通过这套方案成功实现了:
这个方案已经稳定运行了6个月,导出了超过5万份报表,证明了其可靠性和实用性。对于有类似需求的Vue项目,这确实是一个值得推荐的前端导出解决方案。