当导出文件的时候遇到结构复杂的表格(如:表头合并);或者数据分页的时候,后端就很难操作了,难以保持表格原有结构。所以就需要前端导出。
1.组装数据
2.标记表头合并项,计算合并项
3.添加样式
4.转buffer,file-saver导出文件
所谓组装数据的意义在于,手动决定他的结构
举个例子
表格外观是这样的
打印一下导出的数据结构是这样的
分别查看
这个!cols
数组是每一列的宽度(第一行每一格的宽度)
这个
!merges
是表格所有的合并项
这个数据组装的有点问题,因为没那么多数据,应该是横着排的。主要是能填充数据,能导出,而且样式正常,就达到了目的,数据格式再慢慢调整
npm install --save xlsx file-saver
npm install --save xlsx-style
<template>
<div>
<el-button type="primary" @click="setExport2Excel" icon="el-icon-download"
>导出</el-button
>
<el-table
:data="tableData3"
:id="'mytable'"
border
style="width: 100%">
<el-table-column
prop="date"
label="门架"
width="150">
</el-table-column>
<el-table-column
prop="date"
label="识别车型"
width="150">
</el-table-column>
<el-table-column label="车型不一致">
<el-table-column
prop="province"
label="问题车数量"
width="120">
</el-table-column>
<el-table-column
prop="city"
label="差额"
width="120">
</el-table-column>
</el-table-column>
<el-table-column label="交易缺失">
<el-table-column
prop="province"
label="问题车数量"
width="120">
</el-table-column>
<el-table-column
prop="city"
label="差额"
width="120">
</el-table-column>
</el-table-column>
<el-table-column label="清分缺失">
<el-table-column
prop="province"
label="问题车数量"
width="120">
</el-table-column>
<el-table-column
prop="city"
label="差额"
width="120">
</el-table-column>
</el-table-column>
<el-table-column
prop="date"
label="差额统计"
width="150">
</el-table-column>
<el-table-column label="配送信息">
<el-table-column
prop="name"
label="姓名"
width="120">
</el-table-column>
<el-table-column label="地址">
<el-table-column
prop="province"
label="省份"
width="120">
</el-table-column>
<el-table-column
prop="city"
label="市区"
width="120">
</el-table-column>
</el-table-column>
</el-table-column>
</el-table>
</div>
</template>
<script>
// import FileSaver from 'file-saver'
import excel from './export2excel';
export default {
data() {
return {
tableData3: [{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-08',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-06',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}]
}
},
methods:{
setExport2Excel(){
// 接口请求回来的数据
let value = [[],[],[],[],[],[],[]]
//把每个格子的数据结构出来 一行一行的排列
this.tableData3.forEach((item,index)=>{
value[0].push(item.date)
value[1].push(item.name)
value[2].push(item.province)
value[3].push(item.city)
value[4].push(item.address)
value[5].push(item.zip)
})
//表示最后一行表头中,保持原状,没有进行合并的表头
let header=[
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'省份',
'市区',
]
//文件名
let filename= "2022年01月12日-报表"
//包括标题在内的,上面几行,进行合并的表头
let multiHeader = [
['第一个报表',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',
'',],
[
'门架',
'识别车型',
'车型不一致',
'',
'交易缺失',
'',
'清分缺失',
'',
'差额统计',
'配送信息',
'',
'',
],
[
'',
'',
'问题车数量',
'差额',
'问题车数量',
'差额',
'问题车数量',
'差额',
'差额统计',
'姓名',
'地址',
''
],
]
//表示所有进行合并的项的起始
let merges = [
'A1:L1',
'C2:D2',
'E2:F2',
'G2:H2',
'J2:L2',
'A2:A4',
'B2:B4',
'C3:C4',
'D3:D4',
'E3:E4',
'F3:F4',
'G3:G4',
'H3:H4',
'I2:I4',
'J3:J4',
'K3:L3',
]
//sheet名
let sheetname = "报表一"
//表格宽度
let mch = 12
let obj1 = {
data:value,
header,
filename,
multiHeader,
merges,
sheetname,
mch
}
let obj2 = {
data:value,
header,
filename,
multiHeader,
merges,
sheetname:'报表二',
mch
}
console.log('看下结构')
//这里数组,那么同理就可以随心所欲组装各种报表数据进行导出,岂不是很快活
excel([obj1,obj2])
}
}
}
</script>
/* eslint-disable */
import {saveAs} from 'file-saver'
import XLSX from 'xlsx-style'
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;
}
let excel = function export_json_to_excel(excelList) {
// 兼容原来的对象写法
// 多表格是数组格式
if (!Array.isArray(excelList)) {
excelList = [excelList, excelList]
}
var wb = new Workbook(),ws = [],bookType='xlsx',blobResult = excelList[0].blobResult,filename = excelList[0].filename || '列表';
excelList.forEach((excelData, index) => {
let multiHeader = excelData.multiHeader || [], // 第二行表头
wch = excelData.wch,
describe = excelData.describe, // 最后一行设置特殊样式
header = excelData.header, // 第四行表头
sheetname = excelData.sheetname, //需要多填写一个sheet名
merges = excelData.merges || [], // 合并
autoWidth = true
/* original data */
//接口数据 包含数据首列行名
let data = [...excelData.data]
//把表头插入
data.unshift(header);
//把上面几行包含标题在内的几行合并表格的表头插入
if (multiHeader.length && multiHeader[0].length > 1) {
for (let i = multiHeader.length - 1; i > -1; i--) {
data.unshift(multiHeader[i])
}
}
//sheet_from_array_of_arrays这个方法使所有数据与excel坐标对应 以及内容类型
// A1: {v: '2022年01月12日-每日稽核报表', t: 's'}
// A2: {v: '时间/类型', t: 's'}
// A3: {v: '', t: 's'}
// A4: {v: 20220112, t: 'n'}
ws.push(sheet_from_array_of_arrays(data))
//这里还没有去标记合并项 只是列出待合并区域为''
if (merges.length > 0) {
if (!ws[index]['!merges']) ws[index]['!merges'] = [];
merges.forEach(item => {
//把自己列出的合并规则 转换为坐标系
//'A1:AD1' 这就是标题行 从第一格到29个
// =>
// {
// e: {c: 29, r: 0} 结束点
// s: {c: 0, r: 0} 起始点
// }
ws[index]['!merges'].push(XLSX.utils.decode_range(item))
})
}
if (autoWidth) {
/*设置worksheet每列的最大宽度*/
const colWidth = data.map(row => row.map(val => {
if (wch) {
return {
'wch': wch
}
} else {
/*先判断是否为null/undefined*/
if (val == null) {
return {
'wch': 10
};
}
/*再判断是否为中文*/
else if (val.toString().charCodeAt(0) > 255) {
return {
'wch': val.toString().length * 2 > 10 ? val.toString().length * 2 : 10
};
} else {
return {
'wch': val.toString().length > 10 ? val.toString().length : 10
};
}
}
}))
/*以第一行为初始值*/
//标记每一个格子的宽度
// colWidth是个数组 表格多少行他就有多少个元素 每个元素也是数组,存放这一行中所有格子的宽度
// [[{wch:12},{wch:12}],[],...]
//但是呢基本上表格十分规则 第一行就可以决定表格宽度了 所以这里取0就得了
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[index]['!cols'] = result;
}
/* add worksheet to workbook */
// for (var k = 0; k < sheetname.length; k++) {
// wb.SheetNames.push(sheetname[k])
// wb.Sheets[sheetname[k]] = ws[k]
// }
/* add worksheet to workbook */
var ws_name = sheetname || ('Sheet' + (index +1))
wb.SheetNames.push(ws_name);
//这里就分sheet了
wb.Sheets[ws_name] = ws[index];
var dataInfo = wb.Sheets[wb.SheetNames[index]];
// 设置单元格框线
const borderAll = {
top: {
style: "thin"
},
bottom: {
style: "thin"
},
left: {
style: "thin"
},
right: {
style: "thin"
}
};
// 给所有单元格加上边框,内容居中,字体,字号,标题表头特殊格式部分后面替换
for (var i in dataInfo) {
if (
i == "!ref" ||
i == "!merges" ||
i == "!cols" ||
i == "!rows" ||
i == "A1"
) { } else {
dataInfo[i + ""].s = {
border: borderAll,
alignment: {
horizontal: "center",
vertical: "center"
},
font: {
name: "微软雅黑",
sz: 10
}
};
}
}
// 设置表格样式
let arrabc = []
const abc = ["A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z"
]
let arrabcLength = data[0].length
for (let i = 0;i < arrabcLength;i++) {
let mergesNum = parseInt(i / 26)
let l = abc[mergesNum-1] ? abc[mergesNum-1] : ''
arrabc.push(l+abc[i % 26])
}
//这个arrabc算是给第一行标题行标记excel字母坐标
// 给标题、表格描述信息、表头等部分加上特殊格式 居中之类的
arrabc.some(function (v, index) {
describe && describe.forEach((d) => {
let alignment = {
horizontal: "left",
vertical: "center"
}
if (index === 0) {
alignment = {
horizontal: "center",
vertical: "center"
}
}
dataInfo[v + d].s = {
border: borderAll,
font: {
name: "微软雅黑",
sz: 10,
},
alignment: alignment
}
})
for (let j = 1; j < multiHeader.length + 2; j++) {
const _v = v + j
if (dataInfo[_v]) {
dataInfo[_v].s = {};
// 标题部分A1-Z1
if (j == 1) {
dataInfo[v + j].s = {
font: {
name: "微软雅黑",
sz: 12,
color: {
rgb: "000000"
},
bold: true,
italic: false,
underline: false
},
alignment: {
horizontal: "center",
vertical: "center"
}
};
} else {
// 表头部分,根据表头特殊格式设置
if (multiHeader.length == 0) {
// multiHeader.length = 0 时表头没有合并单元格,表头只占1行A2-Z2
const fv = v + (multiHeader.length + 2)
dataInfo[fv].s = {
border: borderAll,
font: {
name: "微软雅黑",
sz: 11,
bold: true
},
alignment: {
horizontal: "center",
vertical: "center"
},
fill: {
fgColor: {
rgb: "f0f0f0"
},
},
}
} else if (multiHeader.length == 1) {
// multiHeader.length = 0 时表头有合并单元格,表头只占2行A2-Z2,A3-Z3,这是没有描述信息只有表头合并的
dataInfo[v + j].s = {
border: borderAll,
font: {
name: "微软雅黑",
sz: 11,
},
alignment: {
horizontal: "center",
vertical: "center"
},
fill: {
fgColor: {
rgb: "f0f0f0"
}
},
}
} else {
// multiHeader.length = 0 时表头有合并单元格,表头多行
dataInfo[v + j].s = {
border: borderAll,
font: {
name: "微软雅黑",
sz: 10,
},
alignment: {
horizontal: "center",
vertical: "center"
}
}
}
}
// multiHeader.length + 2 是表头的最后1行
// if (!dataInfo[v + (multiHeader.length + 2)]) return
// dataInfo[v + (multiHeader.length + 2)].s = {
// border: borderAll,
// font: {
// name: "微软雅黑",
// sz: 10,
// },
// alignment: {
// horizontal: "center",
// vertical: "center"
// },
// fill: {
// fgColor: {
// rgb: "f0f0f0"
// }
// },
// }
}
}
});
})
console.log('咋回事',wb)
//循环之后使用插件写入文件数据
var wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary'
});
//转buffer然后bold导出
if (blobResult) {
return new Blob([s2ab(wbout)], {
type: "application/octet-stream"
})
} else {
saveAs(new Blob([s2ab(wbout)], {
type: "application/octet-stream"
}), `${filename}.${bookType}`);
}
}
export default excel