vue + ant-design + xlsx 实现表格导出进度提示功能

Vue + Ant Design 扩展:实现表格导出进度提示功能

引言

在处理大数据量(10万+条)表格导出时,常面临以下问题:

  1. 浏览器卡顿无响应
  2. 导出进度不可见
  3. 导出失败无反馈

本文将深度扩展以下功能:

  • 分片数据加载机制
  • 实时进度可视化
  • 异常中断处理
  • 性能优化策略

通过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>

五、生产级增强建议

  1. 导出日志记录:记录每次导出的关键指标(耗时、数据量、异常)
  2. 权限校验:在exportData方法前添加Token验证
  3. 格式预设:预先定义单元格样式模板
  4. 多格式支持:扩展CSV/PDF导出选项

总结

通过本文实现的进度提示方案,可获得以下提升:

  • 用户体验:实时反馈增强操作信心
  • 系统稳定性:分片加载降低内存压力
  • 可维护性:Hook封装提高代码复用
  • 错误可追溯:完善的异常处理机制
相关推荐
酷爱码6 分钟前
CSS3实现的账号密码输入框提示效果
前端·javascript·css3
NoneCoder11 分钟前
React Context 与状态管理:用与不用
前端·react.js·面试
不爱吃饭爱吃菜11 分钟前
uniapp小程序开发,判断跳转页面是否需要登录方法封装
开发语言·前端·javascript·vue.js·uni-app
霸王蟹14 分钟前
React 泛型组件:用TS来打造灵活的组件。
前端·学习·react.js·typescript·前端框架
菥菥爱嘻嘻17 分钟前
React---day3
javascript·react.js·ecmascript
Dontla18 分钟前
React声明式编程(手动控制,大型项目,深度定制)与Vue响应式系统(自动优化,中小型项目,快速开发)区别
javascript·vue.js·react.js
阳光开朗大男孩 = ̄ω ̄=37 分钟前
【JavaScript】Ajax 侠客行:axios 轻功穿梭服务器间
前端·javascript·ajax
顽强d石头1 小时前
微信小程序关于截图、录屏拦截
前端
新中地GIS开发老师1 小时前
25年GIS开发暑期实训营,15天Get三维可视化智慧城市开发项目
前端·人工智能·智慧城市·web·gis开发·webgis·地信
m0_376534071 小时前
flutter使用html_editor_enhanced: ^2.6.0后,编辑框无法获取焦点,无法操作
前端·flutter·html