在传统Web开发中,导出Excel通常由后端完成:前端发起请求,后端生成文件并返回下载链接。这种方式存在几个明显痛点:
而现代前端技术已经能够独立处理这类需求。以Vue 3 + Element Plus为例,配合专门的Excel处理库,可以实现:
我曾在一个数据看板项目中,用纯前端方案替代原来的Java导出服务,服务器负载直接降低37%
| 库名称 | 体积 | 功能完整性 | 性能 | 中文文档 |
|---|---|---|---|---|
| SheetJS | 3.2MB | ★★★★★ | ★★★★☆ | × |
| ExcelJS | 1.8MB | ★★★★☆ | ★★★★★ | √ |
| xlsx-populate | 2.1MB | ★★★☆☆ | ★★★☆☆ | × |
最终选择SheetJS的社区版(xlsx),原因在于:
安装核心依赖:
bash复制npm install xlsx @types/xlsx --save
Element Plus需要额外安装按钮组件(如果尚未引入):
bash复制npm install @element-plus/icons-vue
假设我们要导出用户列表,典型的数据结构如下:
typescript复制interface User {
id: number
name: string
age: number
department: string
joinDate: string
}
const tableData: User[] = [
{
id: 1001,
name: '张三',
age: 28,
department: '研发部',
joinDate: '2020-06-15'
},
// ...更多数据
]
typescript复制import * as XLSX from 'xlsx'
import { Download } from '@element-plus/icons-vue'
const exportExcel = () => {
// 1. 创建工作簿
const workbook = XLSX.utils.book_new()
// 2. 转换数据格式
const worksheet = XLSX.utils.json_to_sheet(tableData.value)
// 3. 设置列宽(单位:字符宽度)
worksheet['!cols'] = [
{ wch: 8 }, // ID列
{ wch: 10 }, // 姓名列
{ wch: 6 }, // 年龄列
{ wch: 12 }, // 部门列
{ wch: 12 } // 入职日期
]
// 4. 添加到工作簿
XLSX.utils.book_append_sheet(workbook, worksheet, '用户列表')
// 5. 生成文件并下载
XLSX.writeFile(workbook, `用户数据_${new Date().toLocaleDateString()}.xlsx`)
}
vue复制<template>
<el-button
type="primary"
:icon="Download"
@click="exportExcel">
导出Excel
</el-button>
</template>
typescript复制const exportWithCustomHeader = () => {
const headers = {
id: '员工ID',
name: '姓名',
age: '年龄',
department: '所属部门',
joinDate: '入职日期'
}
// 转换数据时指定header映射
const worksheet = XLSX.utils.json_to_sheet(
tableData.value,
{ header: Object.values(headers) }
)
// ...后续操作相同
}
当数据量超过10万行时,建议采用分片生成策略:
typescript复制const exportLargeData = async (data: any[], chunkSize = 50000) => {
const workbook = XLSX.utils.book_new()
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.slice(i, i + chunkSize)
const worksheet = XLSX.utils.json_to_sheet(chunk)
XLSX.utils.book_append_sheet(
workbook,
worksheet,
`数据分片_${i/chunkSize + 1}`
)
await new Promise(r => setTimeout(r, 0)) // 避免UI阻塞
}
XLSX.writeFile(workbook, '大数据导出.xlsx')
}
通过自定义单元格格式实现:
typescript复制// 创建样式化的工作表
const styledWorksheet = XLSX.utils.aoa_to_sheet([
['姓名', '年龄', '部门'], // 表头行
...tableData.value.map(item => [
item.name,
item.age,
item.department
])
])
// 设置表头样式
styledWorksheet['A1'].s = {
font: { bold: true, color: { rgb: 'FFFFFF' } },
fill: { fgColor: { rgb: '4472C4' } }
}
// 设置斑马线
tableData.value.forEach((_, index) => {
const row = index + 2 // 数据从第二行开始
const cell = `A${row}`
styledWorksheet[cell].s = {
fill: {
fgColor: {
rgb: index % 2 ? 'D9E1F2' : 'FFFFFF'
}
}
}
})
当导出超50万行数据时:
stream模式:typescript复制import { writeFile, utils } from 'xlsx'
const stream = new Blob([
utils.sheet_to_csv(worksheet, { FS: '\t' })
], { type: 'text/plain' })
saveAs(stream, '大数据.csv')
typescript复制const workbook = XLSX.utils.book_new()
workbook.CalcProperties = { fullCalcOnLoad: false }
问题1:导出的中文乱码
typescript复制XLSX.writeFile(workbook, 'data.xlsx', { bookType: 'xlsx', type: 'buffer' })
问题2:日期格式显示异常
typescript复制worksheet['D2'].z = 'yyyy-mm-dd'
问题3:大文件导出卡顿
typescript复制// worker.js
self.importScripts('https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js')
self.onmessage = (e) => {
const workbook = XLSX.utils.book_new()
// ...处理逻辑
postMessage(workbook)
}
vue复制<template>
<el-popover>
<template #reference>
<el-button :icon="Download">导出</el-button>
</template>
<div class="export-options">
<el-radio-group v-model="exportType">
<el-radio label="current">当前页</el-radio>
<el-radio label="all">全部数据</el-radio>
</el-radio-group>
<el-button @click="handleExport">确认导出</el-button>
</div>
</el-popover>
</template>
typescript复制const exportWithLog = async () => {
await logExportAction({
operator: currentUser.value.name,
dataType: 'user',
rowCount: tableData.value.length
})
// ...执行导出
}
在最近参与的供应链系统中,这套方案实现了: