需求前提
导师提出一个需求实现一个交叉表的展示。简单的说就是在一个excel表格中有一部分主要的列,还有一部分列是不确定的,可能只有一列,也可能有很多列,并且这些数据的样式是可以通过配置去渲染,因此对于这个样式以及数据等等都是动态的。当前台获取到后台的数据的时候,对该数据做了一些格式的转换,并且对表格的样式进行了渲染,所以,如果此时在把表格数据传回后台去做导出,不管是数据还是样式都不太方便,因此,提出了一个前端直接导出Excel的需求。
刚开始没有任何的头绪,后来在找资料的过程中发现了一个叫做js-xlsx的插件(插件的github地址),当然仅仅用这一个插件是不能实现导出的,它必须配合fileSaver才能实现导出,之前也提到说要对表格的样式进行相应的渲染,然而在js-xlsx的api中并没有给出相应的功能,因此,此时还需要用到一个插件叫做xlsx-style,通过这个插件可以给表格设置相应的样式,具体样式如下表所示:
样式属性 | 子属性 | 取值 |
---|---|---|
fill | patternType | “solid” or “none” |
fgColor | COLOR_SPEC | |
bgColor | COLOR_SPEC | |
font | name | “Calibri” // default |
sz | “11” // font size in points | |
color | COLOR_SPEC | |
bold | true or false | |
underline | true or false | |
italic | true or false | |
strike | true or false | |
outline | true or false | |
shadow | true or false | |
vertAlign | true or false | |
numFmt | “0” | |
“0.00%” | ||
“0.0%” | ||
“0.00%;\(0.00%\);\-;@” | ||
“m/dd/yy” | ||
alignment | vertical | “bottom” or “center” or “top” |
horizontal | “bottom” or “center” or “top” | |
wrapText | true or false | |
readingOrder | 2 // for right-to-left | |
textRotation | Number from 0 to 180 or 255 (default is 0) | |
90 is rotated up 90 degrees | ||
45 is rotated up 45 degrees | ||
135 is rotated down 45 degrees | ||
180 is rotated down 180 degrees | ||
255 is special, aligned vertically | ||
border | top | { style: BORDER_STYLE, color: COLOR_SPEC } |
bottom | { style: BORDER_STYLE, color: COLOR_SPEC } | |
left | { style: BORDER_STYLE, color: COLOR_SPEC } | |
right | { style: BORDER_STYLE, color: COLOR_SPEC } | |
diagonal | { style: BORDER_STYLE, color: COLOR_SPEC } | |
diagonalUp | true or false | |
diagonalDown | true or false |
上表就是整个excel支持的全部样式。
操作步骤
- 首先我是基于vue去实现的,因此我首先是需要下载依赖,分别下载依赖是xlsx、fileSaver、xlsx-style。
在使用的地方导入相关依赖
1
2
3const XLSX = require("xlsx");
import FileSaver from "file-saver";
const xlsxStyle =require("xlsx-style");具体的实现
我在实现的过程中因为要对表格的样式进行渲染,因此我需要去做判断哪些需要渲染,这一块是稍微复杂一点的东西。具体的代码含义在代码注释里面也已写出。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133static downloadData(fileName,tableDatas,borderAll){
let domTable = document.querySelector("#tableKey");
//raw:true表示导出的数据全部以字符串形式展示,解决可能出现的格式是百分比,导出却是小数
let wb = XLSX.utils.table_to_book(domTable,{raw:true});
let wbSheet = wb.Sheets.Sheet1;
//转化为ASC码的形式
let ascCol = ((wbSheet["!ref"].split(":")[1].charAt(0)).charCodeAt() - 1);
//得到最大的列
let maxCol = String.fromCharCode(ascCol);
//设置每一个单元格的宽度
wbSheet['!cols'] = this.setCols(wbSheet,100,maxCol,tableDatas);;
let count = 0;
//对每个单元格的样式进行设置
for(let key in wb.Sheets.Sheet1){
if((/[A-Z]/.test((key.charAt(0)))) && (key.charAt(0)) <= maxCol){
wb.Sheets.Sheet1[key].s = {
border:borderAll,
numFmt:'',
alignment:{
horizontal: "center",
vertical: "center"
},
font: {
name: '宋体',
sz: 11,
color: {rgb: "#FF000000"},
//bold: true,
italic: false,
underline: false
},
}
}
if((/[B-Z]/.test((key.charAt(0))))){
if((key.charAt(0)) == maxCol){
if(tableDatas[count] && tableDatas[count].color){
wb.Sheets.Sheet1[key].s = {
border:borderAll,
numFmt:'',
alignment:{
horizontal: "center",
vertical: "center"
},
font: {
name: '宋体',
sz: 11,
color: {rgb: "#FF000000"},
//bold: true,
italic: false,
underline: false
},
fill:{
fgColor:{
rgb: tableDatas[count].color.split('#')[1]
}
}
}
}
count = 0;
}else{
if(((key.charAt(0)).charCodeAt()) <= ascCol){
if(tableDatas[count] && tableDatas[count].color){
wb.Sheets.Sheet1[key].s = {
border:borderAll,
numFmt:'',
alignment:{
horizontal: "center",
vertical: "center"
},
font: {
name: '宋体',
sz: 11,
color: {rgb: "#FF000000"},
//bold: true,
italic: false,
underline: false
},
fill:{
fgColor: {
rgb: tableDatas[count].color.split('#')[1]
}
}
}
}
count++;
}
}
}
}
//通过fileSaver导出Excel表格
let wbout = xlsxStyle.write(wb, {
bookType: 'xlsx',
bookSST: true,
type: 'buffer'
});
try{
FileSaver.saveAs(new Blob([wbout],{type : 'application/octet-stream;charset=utf-8' }), fileName + ".xlsx");
}catch(e){
console.log(e,wb);
}
return wb;
}
//给每个单元格进行宽度设置的具体实现
static setCols(sheet,orderNumber,maxCol,tableDatas){
let cols = [];
let temp = {};
let count = 0;
let tempWidth = 0;
for(let key in sheet){
if((/[A-Z]/.test((key.charAt(0))))){
if((key.charAt(0)) == 'A'){
temp = {wpx:orderNumber};
cols.push(temp);
}
else if((key.charAt(0)) == maxCol){
if(tableDatas[count] && tableDatas[count].width){
tempWidth = parseInt(tableDatas[count].width);
temp = {wpx:tempWidth};
cols.push(temp);
}
break;
}
else{
if(tableDatas[count] && tableDatas[count].width){
tempWidth = parseInt(tableDatas[count].width);
temp = {wpx:tempWidth}
cols.push(temp);
}
}
}
count++;
}
return cols;
}以上是已经实现的导出功能,能够实现基本的导出Excel并且将设置的样式导出,但是我在写样式的时候发现一个问题,如果我此时需要每一个单元格自动换行,查了一下资料,发现并没有这个功能。这一块对导出表格的样式大打折扣了。
还有一个问题没有解决,就是现在有一个需求是说要在表格前面加一个选择框,可以单选也可以批量操作,在项目里面使用的elementUI框架,在表格里面有提供一个type=section的属性可以实现这个操作,可是因为在导出的时候导出的是整个的table,然而这个section也是table的一列,因此在导出的时候也会把这个选择框也导出来,最后导出的Excel中的第一列就是空白的。现在正在解决这个问题。希望能尽快解决。