《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 对象的性能的确很赞,处理大数据时也不会卡死
适当小的划分数据片段,减少单位时间内浏览器的负荷

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容