《Vue 大量导入导出excel百万行数据》

记录一次纯前端导入导出百万行数据表格数据。
请求后端:page、limit获取处理的数据。
格式:[{},{}...]

用到插件

import XLSX from 'xlsx'  // "xlsx": "^0.14.1"
import { saveAs } from 'file-saver'  //  "file-saver": "2.0.1"

1、获取数据

由于后端返回数据无法接收太大(5M左右json)。所以这里分段请求。以10000条为分段,进行分次获取,然后进行拼接。


根据选项进行目标数据的获取
// 定义一个导出对象
const exportObj = {
  num1: 0,  // 导出起始
  num2: 0,  // 导出结尾
  slice1: 0, // 结果裁剪起始
  slice2: 0, // 结果裁剪结尾
  page1: 0,  // 起始所在页
  page2: 0,  // 结尾所在页
  total: 0,  // 导出记录数
  exprotPage: 0, // 调用接口次数
  exportSize: 10000 // 导出分段量大小,
  exportPercent: 0; // 合并进度比。由于查看合并进度。
  percent: 0  // 显示进度的组件,绑定的值,
  fileType: 1 // 文件类型,1为xlsx(由于自带多种样式,15万以上数据(文件60M左右)会导致浏览器崩溃),
               //2为csv (excel纯文本,数据少,可以百万(100M))  
}
// 导出目标
exportList: []

获取导出参数, 根据数据量,并多次请求接口
此处由于我后端10000条数据大概有5M大小。所以我limit分段量定为10000,可以根据大小适当增减。

exportFile() {
  // 导出区间
  this.exportObj.total= exportObj.num2 - exportObj.num1
  // 导出limit大小,如果导出的数量比分段还小,那就直接以它作为分段大小
  const exportSize = this.exportObj.total > this.exportObj.exportSize ? this.exportObj.exportSize : this.exportObj.total 
  // 获取num1和 num2 所在的page
  page1 = Math.ceil(num1 / exportSize)
  page2 = Math.ceil(num2 / exportSize)
  if (this.exportObj.total < this.exportObj.exportSize && num1 === 1) {
    //如果 比10000还小数量,像1 - xxx条。就直接获取数据
    this.exportObj.exportPage = 1
    // 计算裁切位置
    this.exportObj.slice1 = 0
    this.exportObj.slice2 = num2
    const page = 1
    const limit = num2
    // 接口传入page limit
    this.getList(page , limit)
  } else {
    // 需要多少页数据
    this.exportObj.exportPage = page2 - page1 + 1
    // 计算裁切位置
    // 例如 3 - 6  则 分段量为 3, 获取page 1 limit 3 page 2 limit 3  两次请求,
    // 结果集[1,2,3,4,5,6]   则需要裁切(3-1,3-1+3+1)=> (2, 6) 应该是5但是由于slice第二参数不包含,所以需要+1.
    this.exportObj.slice1 = num1 % exportSize - 1
    this.exportObj.slice2 = this.exportObj.slice1 + this.exportObj.total + 1
    for (let i = page1; i <= page2; i++) {
      setTimeout(() => {
        const page = i
        const limit = exportSize
        this.getList(page , limit)
      }, 300)
    }
  }
}

根据以上 对后端进行 请求数据。
合并数据。

// data.list 为后端返回的list
this.exportObj.exportPage--
this.exportObj.exportPercent++
if (this.exportObj.exportPage) {
  this.exportList = [...this.exportList, ...data.list]
  this.exportObj.percent = Math.ceil((this.exportObj.exportPercent / Math.ceil(this.exportObj.total/ this.exportObj.exportSize)) * 100)
} else {
  // exportPage为0 ,表示最后一次数据返回。
  this.exportObj.percent = 100
  this.exportList = [...this.exportList, ...data.list]
  // 重置参数
  this.exportConfig.exportPercent = 0
  this.excelInfo.percent = 0
  this.exportList = this.exportList.slice(_that.exportObj.slice1, this.exportObj.slice2)
  // 参数2为列属性的中文意思.[ a,b,c,d...] ['一','二','三'...]
  // 中文用于表头
  this.exportAll(this.exportList, znlist)
}

以上是获取数据的一些处理。因为数量过大,所以我这里需要进行分段处理。可以根据自己需求进行一些变动。

数据的导出

exportAll(row, zn) {
  // zn 为 ['你好','拜拜'...]  我由这个确定导出的列。可以在其他方法确定好。适当减少不需要的列。可以减少导出体积和加快速度。
  // 确认c1列
  const en = []
  // 根据中文,获取对应英文属性.
  zn.forEach(name => {
    // this.exportHeader [ {'你好': hello} ]
    al.push(this.exportHeader[name])
  })
  cl = en
  // XLSX 导出时的列宽。cols
  const cols = []
  for (let i = 0; i < cl.length; i++) {
    // 给对应的列 设置宽度。和颜色
    switch (cl[i]) {
      case 'hello':
        cols.push({ wch: 20, color: 'red' })
        break
      case 'bye':
        cols.push({ wch: 12 })
        break
      default:
        cols.push({ wch: 10 })
        break
    }
  }
  // 创建工作对象
  const _that = {
    v: this, // vue示例
    sheetsList: {},
    sheetNames: ['导出示例'], // 表名,可以是多张表。对应sheetlist
    wb: XLSX.utils.book_new(), // 工作薄对象
    wscols: cols // 列相关格式属性
  }
  _that.v.excelInfo.percent = 0
  const temp = _that.v.transferData(row, cl, zn)
  temp.then(res => {
    _that.v.exportObj.percent = 100
    console.log('导出完成,正在下载.')
    // 导出类型分支
    if (_that.v.exportObj.fileType === 1) {
      _that.sheetsList[_that.sheetNames[0]] = XLSX.utils.aoa_to_sheet(res)
      _that.sheetsList[_that.sheetNames[0]]['!cols'] = _that.wscols
      _that.wb['SheetNames'] = _that.sheetNames
      _that.wb['Sheets'] = _that.sheetsList
      XLSX.writeFile(_that.wb, '导出示例.xlsx')
    } else {
      const blob = new Blob([String.fromCharCode(0xfeff), ...res], {
        type: 'text/plain;charset=utf-8'
      })
      saveAs(blob, '导出示例.csv')
    }
    setTimeout(() => {
      // 重置某些参数
    }, 3000)
  })
},

transferData方法。获取XLSX目标数组,和格式化数据。例如123456时间戳 转 2021-01-01
XLSX.utils.aoa_to_sheet(res) 需要一个二维数组 [[列1,列2..],[列1咧2...]]
这里采用异步分流。防止浏览器持续执行导致奔溃。

async transferData(data, cl, zn) {
  try {
    // 分流量
    const size = 1000
    // 分片(需要循环几次)
    const step = Math.ceil(data.length / size)
    // 目标数组
    const res = []
    // 创建表头[一般表头由外部传入]
    // csv 每行为 列1,列2,列三\n 的纯文本文件。所以这里需要拼接一下
    this.exportObj.fileType === 1 ? res.push(zn) : res.push(zn.toString() + '\n')
    // 遍历分片
    const _that = this
    for (let i = 0; i < step; i++) {
      // 获取分片对应的数据
      const datas = data.slice(i * size, (i + 1) * size)
      // 格式化内容
      datas.forEach(item => {
        const arr = []
        cl.forEach(key => {
          switch (key) {
            case 'hello':
              arr.push(item[key] + '小尾巴')
              break
            case 'bye':
              arr.push(item[key] + '拜拜')
              break
            default:
              arr.push(item[key])
              break
          }
        })
        _that.exportObj.fileType === 1 ? res.push(arr) : res.push(arr.toString() + '\n')
      })
      // 适当暂停,避免页面无法执行渲染
      await new Promise((_resolve) => {
        // 统计进度
        if (i > 0) _that.exportObj.percent = Math.ceil((i / step) * 100) - 1
        setTimeout(() => _resolve(true), 100)
      })
    }
    return res
  } catch (error) {
    return Promise.reject(error)
  }
}
xlsx文件大概60M左右,xlsx这个插件就会卡死。

目前测试能导出CSV文件100M 正常导出

所以,我们可以使用 CSV 格式导出百万左右的数据。但是会excel 支持最大行数好像是105万行。

Blob 对象的性能的确很赞,处理大数据时也不会卡死
适当小的划分数据片段,减少单位时间内浏览器的负荷

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容