Vue + Ant Design 扩展:实现表格导出进度提示功能
引言
在处理大数据量(10万+条)表格导出时,常面临以下问题:
- 浏览器卡顿无响应
- 导出进度不可见
- 导出失败无反馈
本文将深度扩展以下功能:
- 分片数据加载机制
- 实时进度可视化
- 异常中断处理
- 性能优化策略
通过Ant Design Vue的Progress组件与自定义Hook,打造企业级导出体验。
一、进度提示核心原理
1.1 分片加载模型
数据总量 → 分片大小 → 当前分片 → 进度百分比
↑ ↑ ↑
100,000条 5,000条/片 20次迭代
1.2 异步处理流程
是 否 是 否 点击导出 初始化进度条 发起首个分片请求 数据获取成功? 追加数据到工作表 更新进度 是否完成? 生成最终文件 触发错误处理
二、完整实现方案
2.1 进度条组件集成
html
<template>
<a-modal v-model:open="exportVisible" :footer="null">
<a-progress :percent="progress" status="active" />
<div style="margin-top: 16px">
{{ progress }}% 完成 | 当前处理:{{ currentChunk }} / {{ totalChunks }} 分片
</div>
</a-modal>
</template>
<script setup>
import { ref } from 'vue'
const exportVisible = ref(false)
const progress = ref(0)
const currentChunk = ref(0)
const totalChunks = ref(0)
</script>
2.2 核心导出Hook(useExportProgress)
javascript
import { ref } from 'vue'
import * as XLSX from 'xlsx/xlsx.mjs'
import { saveAs } from 'file-saver'
export const useExportProgress = () => {
const isExporting = ref(false)
const progress = ref(0)
let workbook = null
let worksheet = null
const initWorkbook = () => {
workbook = XLSX.utils.book_new()
worksheet = XLSX.utils.aoa_to_sheet([])
XLSX.utils.book_append_sheet(workbook, worksheet, '大数据报表')
}
const exportData = async (config) => {
return new Promise(async (resolve, reject) => {
try {
isExporting.value = true
initWorkbook()
const { chunkSize, total, fetchData } = config
const totalChunks = Math.ceil(total / chunkSize)
for (let i = 0; i < totalChunks; i++) {
const data = await fetchData(i, chunkSize)
appendData(data)
updateProgress(i, totalChunks)
}
const blob = generateFile()
saveAs(blob, '大数据报表.xlsx')
resolve()
} catch (error) {
reject(error)
} finally {
isExporting.value = false
resetState()
}
})
}
const appendData = (data) => {
const startRow = worksheet['!ref']
? worksheet['!ref'].split(':')[1].replace(/[^0-9]/g, '') + 1
: 1
XLSX.utils.sheet_add_json(worksheet, data, {
origin: startRow,
skipHeader: true
})
}
const updateProgress = (current, total) => {
progress.value = Math.round((current / total) * 100)
}
const generateFile = () => {
return new Blob([XLSX.write(workbook, {
bookType: 'xlsx',
type: 'array'
})], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
}
const resetState = () => {
workbook = null
worksheet = null
progress.value = 0
}
return {
isExporting,
progress,
exportData
}
}
2.3 调用示例
html
<script setup>
import { useExportProgress } from './hooks/useExportProgress'
const { isExporting, progress, exportData } = useExportProgress()
// 模拟分片数据获取
const fetchDataChunk = async (page, pageSize) => {
const start = page * pageSize
const end = start + pageSize
return new Promise(resolve => {
setTimeout(() => {
const mockData = Array.from({ length: pageSize }, (_, i) => ({
id: start + i + 1,
value: Math.random().toFixed(4)
}))
resolve(mockData)
}, 50)
})
}
const handleExport = async () => {
try {
await exportData({
chunkSize: 5000,
total: 100000,
fetchData: fetchDataChunk
})
} catch (error) {
console.error('导出失败:', error)
}
}
</script>
三、性能优化策略
3.1 内存管理优化
javascript
// 修改appendData方法
const appendData = (data) => {
// 每次处理后释放内存
const tempData = [...data]
XLSX.utils.sheet_add_json(worksheet, tempData, {
origin: -1,
skipHeader: true
})
tempData.length = 0 // 手动GC
}
3.2 Web Worker方案
javascript
// worker.js
self.onmessage = async (e) => {
const { config, chunkIndex } = e.data
const data = await config.fetchData(chunkIndex, config.chunkSize)
self.postMessage({ chunkIndex, data })
}
// 主线程调用
const worker = new Worker('./worker.js')
worker.postMessage({ config, chunkIndex: i })
3.3 虚拟滚动优化
javascript
// 分片加载时动态创建Worksheet
if (i === 0) {
XLSX.utils.book_append_sheet(workbook, worksheet, '数据分片1')
} else {
const newSheet = XLSX.utils.aoa_to_sheet([])
XLSX.utils.book_append_sheet(workbook, newSheet, `数据分片${i+1}`)
}
四、异常处理方案
4.1 断点续传实现
javascript
// 添加本地存储
const saveProgress = (current) => {
localStorage.setItem('exportProgress', JSON.stringify({
currentChunk: current,
timestamp: Date.now()
}))
}
// 启动时检查
const checkResume = () => {
const saved = localStorage.getItem('exportProgress')
if (saved && Date.now() - saved.timestamp < 300000) {
return JSON.parse(saved).currentChunk
}
return 0
}
4.2 错误边界处理
html
<template>
<ErrorBoundary @error="handleError">
<YourExportComponent />
</ErrorBoundary>
</template>
<script setup>
const handleError = (error) => {
console.error('捕获到导出错误:', error)
// 发送错误日志到服务端
}
</script>
五、生产级增强建议
- 导出日志记录:记录每次导出的关键指标(耗时、数据量、异常)
- 权限校验:在exportData方法前添加Token验证
- 格式预设:预先定义单元格样式模板
- 多格式支持:扩展CSV/PDF导出选项
总结
通过本文实现的进度提示方案,可获得以下提升:
- 用户体验:实时反馈增强操作信心
- 系统稳定性:分片加载降低内存压力
- 可维护性:Hook封装提高代码复用
- 错误可追溯:完善的异常处理机制