json转Excel下载

将 json 转为 excel 表并下载

背景

通过访问接口,获取 json 数据,将 json 转为 excel 表并下载。

工具

  • react
  • js-xlsx

步骤

安装 jsx-xlsx 包

1
npm i js-xlsx -S

引入

1
const XLSX = require("xlsx");

实现效果

假设我们从后端返回的数据如下:

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
student: [
{
id: 1,
stuNo: 20190801,
name: "王二二",
age: 21,
major: "计算机科学与技术",
isParty: 1,
sex: 0,
},
{
id: 2,
stuNo: 20190802,
name: "李冬梅",
age: 20,
major: "计算机科学与技术",
isParty: 0,
sex: 1,
},
{
id: 3,
stuNo: 20190803,
name: "张晓芳",
age: 21,
major: "汉语言文学",
isParty: 1,
sex: 1,
},
{
id: 4,
stuNo: 20190804,
name: "张雨",
age: 24,
major: "汉语言文学",
isParty: 1,
sex: 0,
},
{
id: 5,
stuNo: 20190805,
name: "张子豪",
age: 22,
major: "通信工程",
isParty: 0,
sex: 0,
},
];

数据含义说明

属性名称 中文含义 备注
id id
stuNo 学号 8 位
name 名字
age 年龄
major 专业
isParty 是否是党员 否:0,是:1
sex 性别 男:0, 女:1

最终想要实现的效果如下:

实现效果

实现思路

因为 js-xlsx 对应的数据组合如下:

1
2
3
4
5
6
[
['行1列1(表头)','行1列1(表头2)','行1列1(表3)'],
['行2列1','行2列2','行2列3'],
['行3列1','行3列2','行3列3']
.....
]

所以我们需要将数据进行组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//	置换数据,将是否党员的0和1换成是和否;将性别中的0和1换成'男'和'女'
assembleStudentData(data) {
data = JSON.parse(JSON.stringify(data));
return data.map(function(item) {
item.isParty = item.isParty === 0 ? '否' : '是';
item.sex = item.sex === 0 ? '男' : '女';
return item;
});
}
// 转换数据的格式
transferTableData(arr) {
const tableData = arr.map(function(obj, arrIndex) {
const row = [];
Object.keys(obj).forEach(function(key) {
row.push(obj[key]);
});
return row;
});
return tableData;
}

组合之后,使用 xlxs 转换为 excel 表并提供下载即可。

合并单元格

只涉及列合并

如果我们想将上下相邻且单元格合并,那么需要操作 sheet[‘!merges’]属性。

1
2
3
4
!merges :[
{ s: { r: -1, c: -1 }, e: { r: -1, c: -1 } },
{ s: { r: -1, c: -1 }, e: { r: -1, c: -1 } }
];

!merges 属性说明

  • s: start 开始位置

  • r: row 行

  • c: column 列

!merges 中的每一个元素表示的是合并的位置。合并的位置由两部分组成,一部分是合并开始的位置{s:{r:-1,c:-1}};

另一部分是合并结束的位置{e:{r:-1,c:-1}}。如假如有一组元素 [{s:{r:1,c:1}},{e:{r:3,c:1}}]表示的是合并第二列、第二行至第四行的数据

首先经过转换,我们的数据应该如下,拿到这个数组。

1
2
3
4
5
6
7
[
[1, 20190801, "王二二", 21, "计算机科学与技术", "是", "男"],
[2, 20190802, "李冬梅", 20, "计算机科学与技术", "否", "女"],
[3, 20190803, "张晓芳", 21, "汉语言文学", "是", "女"],
[4, 20190804, "张雨", 24, "汉语言文学", "是", "男"],
[5, 20190805, "张子豪", 22, "通信工程", "否", "男"],
];

找出上下相邻并相等的数据,记录下来

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
/**
* Note:记录数组中上下相等且连续的元素开始位置和结束位置
* @param {array} data 用来记录的数组
* @return {array} allPos 所有记录的位置
* @note 循环数组,循环数组的每一行元素,如果当前行的元素与下一行的元素相等,那么将其记录加一;如果不相等* 并且存在记录,那么将该纪录弹出,存入所有位置数组中。
* @note 弹出时位置的计算方法 列 = 当前元素所在列 开始行 = 当前元素所在行 - 记录中的数 结束行=当前行
* @note 所有记录默认为0
*/
getMergeData(data) {
let sameValue = data[0].map(function(item) {
return 0;
});
let allPos = [];
let pos = { s: { r: -1, c: -1 }, e: { r: -1, c: -1 } };
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].length; j++) {
if (i < data.length - 1 && data[i][j] === data[i + 1][j]) {
sameValue[j] = sameValue[j] + 1;
} else {
if (sameValue[j] !== 0) {
pos['s']['c'] = j;
pos['e']['c'] = j;
pos['s']['r'] = i - sameValue[j];
pos['e']['r'] = i;
const samePos = JSON.parse(JSON.stringify(pos));
allPos.push(samePos);
sameValue[j] = 0;
}
}
}
}
return allPos;
}

给工作表加上!merge 属性

1
2
3
4
5
let merges = this.getMergeData(studentTable);
if (!sheet["!merges"]) {
sheet["!merges"] = [];
}
sheet["!merges"] = merges;

合并效果:

合并后表格

全部代码

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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
constructor(props) {
super(props);
this.exportExcel = this.exportExcel.bind(this);
this.state = {
student: [
{
id: 1,
stuNo: 20190801,
name: '王二二',
age: 21,
major: '计算机科学与技术',
isParty: 1,
sex: 0
},
{
id: 2,
stuNo: 20190802,
name: '李冬梅',
age: 20,
major: '计算机科学与技术',
isParty: 0,
sex: 1
},
{
id: 3,
stuNo: 20190803,
name: '张晓芳',
age: 21,
major: '汉语言文学',
isParty: 1,
sex: 1
},
{
id: 4,
stuNo: 20190804,
name: '张雨',
age: 24,
major: '汉语言文学',
isParty: 1,
sex: 0
},
{
id: 5,
stuNo: 20190805,
name: '张子豪',
age: 22,
major: '通信工程',
isParty: 0,
sex: 0
}
]
};
}

render() {
return (
<div>
<button onClick={this.exportExcel}>导出表格</button>
</div>
);
}
// 组合student数据
assembleStudentData(data) {
data = JSON.parse(JSON.stringify(data));
return data.map(function(item) {
item.isParty = item.isParty === 0 ? '否' : '是';
item.sex = item.sex === 0 ? '男' : '女';
return item;
});
}
// 导出excel
exportExcel() {
const student = this.assembleStudentData(this.state.student);
let studentTable = this.transferTableData(student);
studentTable.unshift(['#', 'Id', '姓名', '年龄', '专业', '是否党员', '性别']);
const sheet = XLSX.utils.aoa_to_sheet(studentTable);
let merges = this.getMergeData(studentTable);
if (!sheet['!merges']) {
sheet['!merges'] = [];
}
sheet['!merges'] = merges;
this.openDownloadDialog(this.sheet2blob(sheet), 'student.xlsx');
}
/**
* Note:记录数组中上下相等且连续的元素开始位置和结束位置
* @param {array} data 用来记录的数组
* @param {boolean} needHeader 是否需要表头(如果需要表头,弹出时将记录中所有元素的行数加一,防止表头与第一行内容相同的情况)
* @return {array} allPos 所有记录的位置
* @note 循环数组,循环数组的每一行元素,如果当前行的元素与下一行的元素相等,那么将其记录加一;如果不相等并且存在记录,那么将该纪录弹 出,存入所有位置数组中。
* @note 弹出时位置的计算方法 列 = 当前元素所在列 开始行 = 当前元素所在行 - 记录中的数 结束行=当前行
* @note 所有记录默认为0
*/
getMergeData(data) {
let sameValue = data[0].map(function(item) {
return 0;
});
let allPos = [];
let pos = { s: { r: -1, c: -1 }, e: { r: -1, c: -1 } };
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].length; j++) {
if (i < data.length - 1 && data[i][j] === data[i + 1][j]) {
sameValue[j] = sameValue[j] + 1;
} else {
if (sameValue[j] !== 0) {
pos['s']['c'] = j;
pos['e']['c'] = j;
pos['s']['r'] = i - sameValue[j];
pos['e']['r'] = i;
const samePos = JSON.parse(JSON.stringify(pos));
allPos.push(samePos);
sameValue[j] = 0;
}
}
}
}
return allPos;
}
// 转换数据
transferTableData(arr) {
const tableData = arr.map(function(obj, arrIndex) {
const row = [];
Object.keys(obj).forEach(function(key) {
row.push(obj[key]);
});
return row;
});
return tableData;
}
// 打开下载对话框
openDownloadDialog(url, saveName) {
if (typeof url == 'object' && url instanceof Blob) {
url = URL.createObjectURL(url);
}
var aLink = document.createElement('a');
aLink.href = url;
aLink.download = saveName || '';
var event;
if (window.MouseEvent) event = new MouseEvent('click');
else {
event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
}
aLink.dispatchEvent(event);
}
// 将workbook装化成blob对象
sheet2blob(sheet, sheetName) {
sheetName = sheetName || 'sheet1';
var workbook = {
SheetNames: [sheetName],
Sheets: {}
};
workbook.Sheets[sheetName] = sheet;
// 生成excel的配置项
var wopts = {
bookType: 'xlsx', // 要生成的文件类型
bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
type: 'binary'
};
var wbout = XLSX.write(workbook, wopts);
var blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' });
// 字符串转ArrayBuffer
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;
}
return blob;
}
}
const app = document.getElementById('app');
ReactDOM.render(<Page />, app);

其他

将 json 数据转为字符串,最后组合成 csv 文件下载。

1
2
3
4
5
6
7
const data = "..."; // 这里填CSV内容的字符串
const blob = new Blob([data], { type: "text/plain" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = "filename.csv"; // 这里填保存成的文件名
link.click();
URL.revokeObjectURL(link.href);

参考

SheetJS/js-xlsx: SheetJS Community Edition – Spreadsheet Toolkit

如何使用 JavaScript 实现纯前端读取和导出 excel 文件-好记的博客如何使用 JavaScript 实现纯前端读取和导出 excel 文件-好记的博客

0%