记录一次纯前端导入导出百万行数据表格数据。
请求后端: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)
}
}
目前测试能导出CSV文件100M 正常导出
所以,我们可以使用 CSV 格式导出百万左右的数据。但是会excel 支持最大行数好像是105万行。
Blob 对象的性能的确很赞,处理大数据时也不会卡死
适当小的划分数据片段,减少单位时间内浏览器的负荷