效果如图↓
一级导出:↓
二级导出:如下图
三级导出:↓
Export2Excel.js Blob.js 等
安装依赖
npm install -S file-savernpm install --save xlsx@0.10.0npm install -D script-loader npm install xlsx-style --save
其中xlsx-style 有坑↓
Can‘t resolve ‘./cptable‘ in ‘xxx\node_modules_xlsx
Can’t resolve ‘fs’
在vue.config.js中配置↓
const { defineConfig } = require('@vue/cli-service')module.exports = defineConfig({transpileDependencies: true,configureWebpack:{externals: {'./cptable': 'var cptable',},resolve: { fallback: { fs: false } }}})
jszip not a constructor↓ 在xslx-style下的xslx.js中
(node_modules\xlsx-style\xlsx.js )
将if(typeof jszip === 'undefined') jszip = require('./js'+'zip').JSZip;替换成if(typeof jszip === 'undefined') jszip = require('./jszip.js');
项目结构↓
注:
其中Export2ExcelPro.js 是可以导出二级标题,Export2Excel.js是导入一级标题
excelExport.js是封装了调用方法。
Blob.js↓
/* Blob.js* A Blob implementation.* -05-27** By Eli Grey, * By Devin Samarin, /eboyjr* License: X11/MIT* See LICENSE.md*//*global self, unescape *//*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,plusplus: true *//*! @source /github/Blob.js/blob/master/Blob.js */(function (view) {"use strict";view.URL = view.URL || view.webkitURL;if (view.Blob && view.URL) {try {new Blob;return;} catch (e) {}}// Internally we use a BlobBuilder implementation to base Blob off of// in order to support older browsers that only have BlobBuildervar BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {varget_class = function(object) {return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];}, FakeBlobBuilder = function BlobBuilder() {this.data = [];}, FakeBlob = function Blob(data, type, encoding) {this.data = data;this.size = data.length;this.type = type;this.encoding = encoding;}, FBB_proto = FakeBlobBuilder.prototype, FB_proto = FakeBlob.prototype, FileReaderSync = view.FileReaderSync, FileException = function(type) {this.code = this[this.name = type];}, file_ex_codes = ("NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR").split(" "), file_ex_code = file_ex_codes.length, real_URL = view.URL || view.webkitURL || view, real_create_object_URL = real_URL.createObjectURL, real_revoke_object_URL = real_URL.revokeObjectURL, URL = real_URL, btoa = view.btoa, atob = view.atob, ArrayBuffer = view.ArrayBuffer, Uint8Array = view.Uint8Array;FakeBlob.fake = FB_proto.fake = true;while (file_ex_code--) {FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;}if (!real_URL.createObjectURL) {URL = view.URL = {};}URL.createObjectURL = function(blob) {vartype = blob.type, data_URI_header;if (type === null) {type = "application/octet-stream";}if (blob instanceof FakeBlob) {data_URI_header = "data:" + type;if (blob.encoding === "base64") {return data_URI_header + ";base64," + blob.data;} else if (blob.encoding === "URI") {return data_URI_header + "," + decodeURIComponent(blob.data);} if (btoa) {return data_URI_header + ";base64," + btoa(blob.data);} else {return data_URI_header + "," + encodeURIComponent(blob.data);}} else if (real_create_object_URL) {return real_create_object_URL.call(real_URL, blob);}};URL.revokeObjectURL = function(object_URL) {if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {real_revoke_object_URL.call(real_URL, object_URL);}};FBB_proto.append = function(data/*, endings*/) {var bb = this.data;// decode data to a binary stringif (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {varstr = "", buf = new Uint8Array(data), i = 0, buf_len = buf.length;for (; i < buf_len; i++) {str += String.fromCharCode(buf[i]);}bb.push(str);} else if (get_class(data) === "Blob" || get_class(data) === "File") {if (FileReaderSync) {var fr = new FileReaderSync;bb.push(fr.readAsBinaryString(data));} else {// async FileReader won't work as BlobBuilder is syncthrow new FileException("NOT_READABLE_ERR");}} else if (data instanceof FakeBlob) {if (data.encoding === "base64" && atob) {bb.push(atob(data.data));} else if (data.encoding === "URI") {bb.push(decodeURIComponent(data.data));} else if (data.encoding === "raw") {bb.push(data.data);}} else {if (typeof data !== "string") {data += ""; // convert unsupported types to strings}// decode UTF-16 to binary stringbb.push(unescape(encodeURIComponent(data)));}};FBB_proto.getBlob = function(type) {if (!arguments.length) {type = null;}return new FakeBlob(this.data.join(""), type, "raw");};FBB_proto.toString = function() {return "[object BlobBuilder]";};FB_proto.slice = function(start, end, type) {var args = arguments.length;if (args < 3) {type = null;}return new FakeBlob(this.data.slice(start, args > 1 ? end : this.data.length), type, this.encoding);};FB_proto.toString = function() {return "[object Blob]";};FB_proto.close = function() {this.size = this.data.length = 0;};return FakeBlobBuilder;}(view));view.Blob = function Blob(blobParts, options) {var type = options ? (options.type || "") : "";var builder = new BlobBuilder();if (blobParts) {for (var i = 0, len = blobParts.length; i < len; i++) {builder.append(blobParts[i]);}}return builder.getBlob(type);};}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
Export2Excel.js↓
// require('script-loader!file-saver');//保存文件用// // require('script-loader!@/excel/Blob');//转二进制用// require('./Blob');//转二进制用// require('script-loader!xlsx/dist/xlsx.core.min');//xlsx核心import { saveAs } from "file-saver";// import XLSX from 'xlsx'import * as XLSX from "xlsx";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 rangesranges.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 Spanif (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 ValueoutRow.push(cellValue !== "" ? cellValue : null);//Handle Colspanif (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);var ws_name = "SheetJS";var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);/* 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")}
Export2ExcelPro.js↓
/* eslint-disable */import { saveAs } from 'file-saver'import XLSX from 'xlsx-style'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.innerTextif (cellValue !== '' && cellValue == +cellValue) cellValue = +cellValue//Skip rangesranges.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 Spanif (rowspan || colspan) {rowspan = rowspan || 1colspan = colspan || 1ranges.push({s: {r: R,c: outRow.length},e: {r: R + rowspan - 1,c: outRow.length + colspan - 1}})}//Handle ValueoutRow.push(cellValue !== '' ? cellValue : null)//Handle Colspanif (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 += 1462var 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, fontStyle) {var ws = {}var range = {s: {c: 10000000,r: 10000000},e: {c: 0,r: 0}}let _opts = []if (opts) {_opts = opts}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 = Rif (range.s.c > C) range.s.c = Cif (range.e.r < R) range.e.r = Rif (range.e.c < C) range.e.c = Cvar cell = {v: data[R][C]}//给单个表头添加斜线样式if (_opts) {if (R == _opts[0] && C == _opts[1]) {cell = {v: data[R][C],s: defaultCellStyle}}}//给某一单元格的字体自定义样式if (fontStyle) {if (R == data.length - 3 && C == 1) {cell = {v: data[R][C],s: fontCellStyle}}}if (cell.v == null) continuevar 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}let fontCellStyle = {font: {name: '宋体',sz: 18,color: { rgb: 'ff0000' },bold: true},alignment: {//对齐方式horizontal: 'center', //水平居中vertical: 'center' //竖直居中}}let defaultCellStyle = {alignment: {horizontal: 'center',vertical: 'center',indent: 0},border: {diagonalDown: true, //斜线方向diagonal: {color: { rgb: '303133' },style: 'thin'} //diagonalDown与diagonal必须同时使用}}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) & 0xffreturn 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] = wsvar wbout = XLSX.write(wb, {bookType: 'xlsx',bookSST: false,type: 'binary'})saveAs(new Blob([s2ab(wbout)], {type: 'application/octet-stream'}),'test.xlsx')}/*自定义表头样式*/let arr = ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1', 'J1', 'K1', 'L1', 'M1', 'N1', 'O1', 'P1', 'Q1', 'R1', 'S1', 'T1', 'U1', 'V1', 'W1', 'X1', 'Y1', 'Z1']let arr1 = ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2', 'J2', 'K2', 'L2', 'M2', 'N2', 'O2', 'P2', 'Q2', 'R2', 'S2', 'T2', 'U2', 'V2', 'W2', 'X2', 'Y2', 'Z2']let arr2 = ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3', 'J3', 'K3', 'L3', 'M3', 'N3', 'O3', 'P3', 'Q3', 'R3', 'S3', 'T3', 'U3', 'V3', 'W3', 'X3', 'Y3', 'Z3']let style = {font: {color: { rgb: '000000' },bold: true},border: {color: {auto: 1},top: {style: 'thin'},bottom: {style: 'thin'},left: {style: 'thin'},right: {style: 'thin'}},alignment: {horizontal: 'center',vertical: 'center'},fill: {fgColor: { rgb: 'DCDFE6' }}}/*二级表头*/export function export_json_to_excelPro({multiHeader = [], // 第一行表头multiHeader2 = [], // 第二行表头data,filename, //文件名merges = [], // 合并autoWidth = true,bookType = 'xlsx',diagonal = [], //斜线fontStyle = false} = {}) {/* original data */filename = filename || '列表'data = [...data]/*data.unshift(header);*/var ws_name = 'SheetJS'data.unshift(multiHeader)data.unshift(multiHeader2)var wb = new Workbook() //加样式,如斜线if (diagonal) {var ws = sheet_from_array_of_arrays(data, diagonal, fontStyle)} else {var ws = sheet_from_array_of_arrays(data)}if (merges.length > 0) {if (!ws['!merges']) ws['!merges'] = []merges.forEach(item => {ws['!merges'].push(XLSX.utils.decode_range(item))})}if (autoWidth) {/*设置worksheet每列的最大宽度*/const colWidth = data.map(row =>row.map(val => {/*先判断是否为null/undefined*/if (val == null) {return {wch: 10}} else if (val.toString().charCodeAt(0) > 255) {/*再判断是否为中文*/return {wch: val.toString().length * 2}} else {return {wch: val.toString().length}}}))/*以第一行为初始值*/let result = colWidth[0]for (let i = 1; i < colWidth.length; i++) {for (let j = 0; j < colWidth[i].length; j++) {if (result[j]['wch'] < colWidth[i][j]['wch']) {result[j]['wch'] = colWidth[i][j]['wch']}}}ws['!cols'] = result}/* add worksheet to workbook */wb.SheetNames.push(ws_name)wb.Sheets[ws_name] = wsvar dataInfo = wb.Sheets[wb.SheetNames[0]]for (var i = 0; i < multiHeader.length; i++) {dataInfo[arr[i]].s = style}for (var j = 0; j < multiHeader2.length; j++) {dataInfo[arr1[j]].s = style}var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' })saveAs(new Blob([s2ab(wbout)], { type: 'application/octet-stream' }), `${filename}.${bookType}`)}/*三级表头的*/export function export_json_to_excelProPlus({multiHeader = [], // 第一行表头multiHeader2 = [], // 第二行表头header, // 第三行表头data,filename, //文件名merges = [], // 合并autoWidth = true,bookType = 'xlsx'} = {}) {/* original data */filename = filename || '列表'data = [...data]var ws_name = 'SheetJS'data.unshift(multiHeader)data.unshift(multiHeader2)data.unshift(header)var wb = new Workbook()var ws = sheet_from_array_of_arrays(data)if (merges.length > 0) {if (!ws['!merges']) ws['!merges'] = []merges.forEach(item => {ws['!merges'].push(XLSX.utils.decode_range(item))})}if (autoWidth) {/*设置worksheet每列的最大宽度*/const colWidth = data.map(row =>row.map(val => {/*先判断是否为null/undefined*/if (val == null) {return {wch: 10}} else if (val.toString().charCodeAt(0) > 255) {/*再判断是否为中文*/return {wch: val.toString().length * 2}} else {return {wch: val.toString().length}}}))/*以第一行为初始值*/let result = colWidth[0]for (let i = 1; i < colWidth.length; i++) {for (let j = 0; j < colWidth[i].length; j++) {if (result[j]['wch'] < colWidth[i][j]['wch']) {result[j]['wch'] = colWidth[i][j]['wch']}}}ws['!cols'] = result}/* add worksheet to workbook */wb.SheetNames.push(ws_name)wb.Sheets[ws_name] = wsvar dataInfo = wb.Sheets[wb.SheetNames[0]]for (var i = 0; i < multiHeader.length; i++) {dataInfo[arr[i]].s = style}for (var j = 0; j < multiHeader2.length; j++) {dataInfo[arr1[j]].s = style}for (var k = 0; k < header.length; k++) {dataInfo[arr2[k]].s = style}var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' })saveAs(new Blob([s2ab(wbout)], { type: 'application/octet-stream' }), `${filename}.${bookType}`)}
excelExport.js↓
import { export_json_to_excel } from '../vendor/Export2Excel'import { export_json_to_excelPro } from '../vendor/Export2ExcelPro'import { export_json_to_excelProPlus } from '../vendor/Export2ExcelPro'function exportExcel({ header, filterVal, filename, tableData }) {var data = formatJson(filterVal, tableData)export_json_to_excel(header, data, filename)}function exportExcelPro({ multiHeader, multiHeader2, data, merges, filename, filterVal }) {var data = formatJson(filterVal, data)export_json_to_excelPro({multiHeader,multiHeader2,data,merges,filename,filterVal})}function exportExcelProPlus({header,multiHeader,multiHeader2,data,merges,filename,filterVal}) {var data = formatJson(filterVal, data)export_json_to_excelProPlus({header,multiHeader,multiHeader2,data,merges,filename,filterVal})}function formatJson(filterVal, tableData) {return tableData.map(v => {return filterVal.map(j => {return v[j]})})}let export_method = {exportExcel: exportExcel,exportExcelPro: exportExcelPro,exportExcelProPlus: exportExcelProPlus}export default export_method
使用↓ 其中导出2是 带有二级标题的表格导出, 导出1(参考上一篇文章)
<template><div><button @click="exports1">导出基础表头</button><button @click="exports2">导出二级表头</button><button @click="exports3">导出三级表头</button><el-table :data="tableData" border style="width: 100%"><el-table-column prop="date" label="日期" width="180"> </el-table-column><el-table-column prop="name" label="姓名" width="180"> </el-table-column><el-table-column prop="address" label="地址"> </el-table-column></el-table></div></template><script>import export_method from "../utils/excelExport.js";export default {data() {return {tableData: [{date: "-05-02",name: "王小虎",address: "上海市普陀区金沙江路 1518 弄",age: 1,sex: "男",},{date: "-05-04",name: "王小虎",address: "上海市普陀区金沙江路 1517 弄",age: 1,sex: "男",},{date: "-05-01",name: "王小虎",address: "上海市普陀区金沙江路 1519 弄",age: 1,sex: "男",},{date: "-05-03",name: "王小虎",address: "上海市普陀区金沙江路 1516 弄",age: 1,sex: "男",},],};},methods: {exports1() {var header = ["日期", "姓名", "地址"];var filterVal = ["date", "name", "address"];var filename = "demoExcel";export_method.exportExcel({header,filterVal,filename,tableData: this.tableData,});},exports2() {const multiHeader2 = ["日期", "姓名", "其他", "", ""]; //第一层 数量相同const multiHeader = ["", "", "地址", "年龄", "性别"]; //第二层 数量相同const filterVal = ["date", "name", "address", "age", "sex"];const merges = ["A1:A2", "B1:B2", "C1:E1"]; // multiHeader2const filterName = "测试";export_method.exportExcelPro({multiHeader,multiHeader2,data: this.tableData,merges,autoWidth: true,filename: filterName,filterVal,});},exports3() {const header = ["日期", "姓名", "其他", "", ""];const multiHeader2 = ["", "", "个人", "", "家庭"];const multiHeader = ["", "中文名", "年龄", "性别", "地址"];const filterVal = ["date", "name", "age", "sex", "address"];// 这里要注意 header 和 filterVal 的数组长度一定要相等,否则会报错const merges = ["A1:A3", "B1:B2", "C1:E1", "C2:D2"];const filterName = "测试";export_method.exportExcelProPlus({header,multiHeader,multiHeader2,data: this.tableData,merges,autoWidth: true,filename: filterName,filterVal,});},},};</script>
难点:↓
const header = ["日期", "姓名", "其他", "", ""]; //表头第一行const multiHeader2 = ["", "", "个人", "", "家庭"];//表头第二行const multiHeader = ["", "中文名", "年龄", "性别", "地址"]; //表头第三行const filterVal = ["date", "name", "age", "sex", "address"];//对应字段// 这里要注意 header 和 filterVal 的数组长度一定要相等,否则会报错const merges = ["A1:A3", "B1:B2", "C1:E1", "C2:D2"];//从左到右存放横跨两个单元格的记录
一一对应去理解