最近在做一个项目二次开发,有40万的用户数据需要导出到excel里,研究了几天,参考一些代码终于找到了实现的方法。如果数据有**10万条数据**,如何导出excel表格呢,这么大的数据如果一次导出对于服务器或者网络都是一个及大的考验,不太可能一性导出,要么资源被用完,要么网络超时,因为导出的数据excel文件可能有大几M或上百M,对于后端来说大量数据查询读取,特别是10万数据里如果涉及到其他表二次查询,基本上是卡死超时。
先说说后端PHP实现导出excel方式:无非用PhpOffice\PhpSpreadsheet组件来导出,但是对于大数据来说一次导出也是吃力。资源瓶颈基本是在于数据库查询或IO读取上。或者一次读取到内存里导致内存溢出异常,后端解决方案,无非是分批导出成多个excel,再多个excel打包成zip下载。
excel最大支持行数
在导出前先了解下一下`.xlsx`最多支持多少行及多少列数据。查了一下资料,
在 Excel2003(.xls)文件:最大256(IV,2的8次方)列,最大65536(2的16次方)行;即横向256个单元格,竖向65536个单元格,不包括表头。
Excel2007(.xlsx)文件:最大16384(XFD,2的14次方)列,最大1048576(2的20次方)行(**即104万行**);即横向16384个单元格,竖向1048576个单元格。
纯前端vue导出10万+数据excel实现思路方法:
分批请求数据回浏览器
带分页码循环去后端接口请求分页数据。
分页数据在浏览器合并到变量里
用excel导出组件数据导出excel。
vue需要用的的组件。
file-saver
xlsx
script-loader
组件安装
npm install -S file-saver(生产依赖,则为-s)
npm install -S xlsx
npm install -D script-loader (开发依赖,则为-d)
vue10万大数据分批导出效果:
vue页面调用
import exportExcel from "@/utils/newToExcel.js"; //导出方法 async exports() { let [th, filekey, data, fileName] = [[], [], [], ""]; let formValidate = this.artFrom; let excelData = {}; excelData.page = 1; //分批请求数据接口 for (let i = 0; i < excelData.page + 1; i++) { let lebData = await this.getExcelData(excelData); if (!fileName) fileName = lebData.filename; if (!filekey.length) { filekey = lebData.filekey; } if (!th.length) th = lebData.header; if (lebData.export.length) { data = data.concat(lebData.export);//分批数据合并 excelData.page++; } else { //导出成excel exportExcel(th, filekey, fileName, data); } } }, //请求数据接口 getExcelData(excelData) { return new Promise((resolve, reject) => { //带分页数据接口 storeProductApi(excelData).then((res) => { return resolve(res.data); }); }); },
Export2Excel.js 生成excel
//uihtm.com提供 import { export_json_to_excel } from '../vendor/Export2Excel'; /** * @method exportExcel * @param {Array} header 表头 * @param {Array} filterVal 表头属性字段 * @param {String} filename 文件名称 * @param {Array} tableData 列表数据 **/ export default function exportExcel(header, filterVal, filename, tableData) { var data = formatJson(filterVal, tableData); export_json_to_excel( header, data, filename ); } function formatJson(filterVal, tableData) { return tableData.map(v => { return filterVal.map(j => { return v[j]; }); }); }
Export2Excel.js 导出excel工具方法
require('script-loader!file-saver'); require('script-loader!@/vendor/Blob'); require('script-loader!xlsx/dist/xlsx.core.min'); function generateArray(table) { var out = []; var rows = table.querySelectorAll('tr'); var ranges = []; for (var R = 0; R < rows.length; ++R) { var outRow = []; var row = rows[R]; var columns = row.querySelectorAll('td'); for (var C = 0; C < columns.length; ++C) { var cell = columns[C]; var colspan = cell.getAttribute('colspan'); var rowspan = cell.getAttribute('rowspan'); var cellValue = cell.innerText; if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; //Skip ranges ranges.forEach(function (range) { if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) { for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); } }); //Handle Row Span if (rowspan || colspan) { rowspan = rowspan || 1; colspan = colspan || 1; ranges.push({ s: { r: R, c: outRow.length }, e: { r: R + rowspan - 1, c: outRow.length + colspan - 1 } }); } ; //Handle Value outRow.push(cellValue !== "" ? cellValue : null); //Handle Colspan if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null); } out.push(outRow); } return [out, ranges]; }; function datenum(v, date1904) { if (date1904) v += 1462; var epoch = Date.parse(v); return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); } function sheet_from_array_of_arrays(data, opts) { var ws = {}; var range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } }; for (var R = 0; R != data.length; ++R) { for (var C = 0; C != data[R].length; ++C) { if (range.s.r > R) range.s.r = R; if (range.s.c > C) range.s.c = C; if (range.e.r < R) range.e.r = R; if (range.e.c < C) range.e.c = C; var cell = { v: data[R][C] }; if (cell.v == null) continue; var cell_ref = XLSX.utils.encode_cell({ c: C, r: R }); if (typeof cell.v === 'number') cell.t = 'n'; else if (typeof cell.v === 'boolean') cell.t = 'b'; else if (cell.v instanceof Date) { cell.t = 'n'; cell.z = XLSX.SSF._table[14]; cell.v = datenum(cell.v); } else cell.t = 's'; ws[cell_ref] = cell; } } if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); return ws; } function Workbook() { if (!(this instanceof Workbook)) return new Workbook(); this.SheetNames = []; this.Sheets = {}; } function s2ab(s) { var buf = new ArrayBuffer(s.length); var view = new Uint8Array(buf); for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; return buf; } export function export_table_to_excel(id) { var theTable = document.getElementById(id); var oo = generateArray(theTable); var ranges = oo[1]; /* original data */ var data = oo[0]; var ws_name = "SheetJS"; var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); /* add ranges to worksheet */ // ws['!cols'] = ['apple', 'banan']; ws['!merges'] = ranges; /* add worksheet to workbook */ wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' }); saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), "test.xlsx") } function formatJson(jsonData) { } export function export_json_to_excel(th, jsonData, defaultTitle) { /* original data */ var data = jsonData; data.unshift(th); let merges = []; let mergeItem = {} for (let i = 0; i < data.length; i++) { for (let j = 0; j < data[i].length; j++) { if (data[i][j] !== null || data[i - 1][j] === null) { continue } mergeItem.s = { r: i - 1, c: j } for (let k = i; k < data.length; k++) { if (data[k][j] === null && k !== data.length - 1) { continue } mergeItem.e = { r: data[k][j] !== null ? k - 1 : k, c: j } merges.push({...mergeItem}) break } } } var ws_name = "SheetJS"; var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); ws['!merges'] = merges; /* add worksheet to workbook */ wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' }); var title = defaultTitle || '列表' saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), title + ".xlsx") }
最后总结,在分页导出时自带调整分页大小,本次实战项目上整个10万数据导出基本上在1-2分钟左右,效果还是可以的。