export-to-exceljs-util 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/defaultExport.js +3 -3
- package/index.js +217 -3
- package/package.json +1 -1
- package/public/writeBufferWorker.js +0 -200
package/README.md
CHANGED
@@ -15,11 +15,13 @@
|
|
15
15
|
```bash
|
16
16
|
npm i export-to-exceljs-util
|
17
17
|
```
|
18
|
+
|
18
19
|
使用 yarn 安装:
|
19
20
|
|
20
21
|
```bash
|
21
22
|
yarn add export-to-exceljs-util
|
22
23
|
```
|
24
|
+
|
23
25
|
### 代码示例
|
24
26
|
|
25
27
|
```javascript
|
@@ -29,7 +31,16 @@ import ExcelExporter from "export-to-exceljs-util";
|
|
29
31
|
const columns = [
|
30
32
|
{
|
31
33
|
title: "姓名",
|
32
|
-
|
34
|
+
children: [
|
35
|
+
{
|
36
|
+
title: "姓",
|
37
|
+
key: "first_name",
|
38
|
+
},
|
39
|
+
{
|
40
|
+
title: "名",
|
41
|
+
key: "last_name",
|
42
|
+
},
|
43
|
+
],
|
33
44
|
},
|
34
45
|
{
|
35
46
|
title: "年龄",
|
@@ -54,7 +65,7 @@ const data = [
|
|
54
65
|
const fileName = "测试数据.xlsx";
|
55
66
|
|
56
67
|
// 其他信息(可选)
|
57
|
-
const otherInfo =
|
68
|
+
const otherInfo = null;
|
58
69
|
|
59
70
|
// 创建 Excel 导出实例
|
60
71
|
const excelExporter = new ExcelExporter(columns, data, fileName, otherInfo);
|
@@ -62,3 +73,9 @@ const excelExporter = new ExcelExporter(columns, data, fileName, otherInfo);
|
|
62
73
|
// 执行导出操作
|
63
74
|
excelExporter.exportToExcel();
|
64
75
|
```
|
76
|
+
|
77
|
+
### 注意事项
|
78
|
+
|
79
|
+
- data 数组的每一项必须包含与 columns 数组中相同的 key。
|
80
|
+
- 当时数据量大于1w行时,工具类ExcelExporter参数属性必须是可被复制的,比如示例中的columns,data中的属性都是简单的数字、字符串、布尔值等,如使用vue3的ref声明的对象,需要使用toRaw()方法转换为普通对象,否则会报错。
|
81
|
+
- 此工具类未压缩代码,需要自定义样式的开发者可以自提代码进行修改。
|
package/defaultExport.js
CHANGED
@@ -179,14 +179,14 @@ export default class DefaultExport {
|
|
179
179
|
}
|
180
180
|
|
181
181
|
generateDescribe(worksheet) {
|
182
|
-
if (this.describe
|
182
|
+
if (this.describe) {
|
183
183
|
// 定义开始的位置
|
184
184
|
const startCell = this.data.length + 7;
|
185
|
-
const endCell = startCell
|
185
|
+
const endCell = startCell + 9;
|
186
186
|
worksheet.mergeCells(`A${startCell}:E${endCell}`);
|
187
187
|
|
188
188
|
// 在表格底部加上文字解释
|
189
|
-
let combinedText = this.describe
|
189
|
+
let combinedText = this.describe;
|
190
190
|
const mergedCells = [
|
191
191
|
`A${startCell}`,
|
192
192
|
`B${startCell}`,
|
package/index.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
import { saveAs } from "file-saver";
|
2
|
-
import { retainTitleAndKey } from
|
2
|
+
import { retainTitleAndKey } from './utils/index.js'
|
3
3
|
import DefaultExport from "./defaultExport.js";
|
4
4
|
|
5
5
|
class ExcelExporter {
|
6
|
-
constructor(columns, data, filename = "
|
6
|
+
constructor(columns, data, filename = "导出数据.xlsx", describe = []) {
|
7
7
|
this.columns = columns;
|
8
8
|
this.data = data;
|
9
9
|
this.filename = filename;
|
@@ -22,9 +22,217 @@ class ExcelExporter {
|
|
22
22
|
}
|
23
23
|
}
|
24
24
|
|
25
|
+
// Web Worker 脚本代码存储在变量 workerScript 中
|
25
26
|
async exportLargeDataSet() {
|
27
|
+
const workerScript = `
|
28
|
+
importScripts("https://cdn.bootcdn.net/ajax/libs/exceljs/4.4.0/exceljs.min.js");
|
29
|
+
const fontStyle = {
|
30
|
+
name: "Arial",
|
31
|
+
size: 10,
|
32
|
+
bold: true, // 设置文字加粗
|
33
|
+
color: { argb: "FF000000" },
|
34
|
+
};
|
35
|
+
const alignmentStyle = (vertical = "middle", horizontal = "center") => {
|
36
|
+
return {
|
37
|
+
vertical,
|
38
|
+
horizontal,
|
39
|
+
};
|
40
|
+
};
|
41
|
+
|
42
|
+
const borderStyle = {
|
43
|
+
top: { style: "thin" },
|
44
|
+
left: { style: "thin" },
|
45
|
+
bottom: { style: "thin" },
|
46
|
+
right: { style: "thin" },
|
47
|
+
};
|
48
|
+
|
49
|
+
function generateHeaders(
|
50
|
+
worksheet,
|
51
|
+
columns,
|
52
|
+
rowIndex = 1,
|
53
|
+
colIndexStart = 1,
|
54
|
+
maxDepth = null
|
55
|
+
) {
|
56
|
+
let maxRowIndex = rowIndex;
|
57
|
+
let colIndex = colIndexStart;
|
58
|
+
|
59
|
+
// 计算整个表头的最大深度(用于单级表头行合并)
|
60
|
+
if (maxDepth === null) {
|
61
|
+
maxDepth = getMaxDepth(columns);
|
62
|
+
}
|
63
|
+
|
64
|
+
columns.forEach((column) => {
|
65
|
+
const currentRowIndex = rowIndex;
|
66
|
+
const currentColIndex = colIndex;
|
67
|
+
|
68
|
+
// 计算列的跨度和行的跨度
|
69
|
+
const colSpan = getColSpan(column);
|
70
|
+
const rowSpan = getRowSpan(column, maxDepth - rowIndex + 1);
|
71
|
+
|
72
|
+
// 设置表头单元格值并应用样式
|
73
|
+
const cell = worksheet.getCell(currentRowIndex, currentColIndex);
|
74
|
+
cell.value = column.title;
|
75
|
+
cell.alignment = alignmentStyle();
|
76
|
+
cell.border = borderStyle;
|
77
|
+
cell.font = fontStyle;
|
78
|
+
cell.fill = {
|
79
|
+
type: "pattern",
|
80
|
+
pattern: "solid",
|
81
|
+
fgColor: { argb: "87CEEB" },
|
82
|
+
};
|
83
|
+
|
84
|
+
// 合并单元格处理
|
85
|
+
if (colSpan > 1) {
|
86
|
+
worksheet.mergeCells(
|
87
|
+
currentRowIndex,
|
88
|
+
currentColIndex,
|
89
|
+
currentRowIndex,
|
90
|
+
currentColIndex + colSpan - 1
|
91
|
+
);
|
92
|
+
}
|
93
|
+
|
94
|
+
// 处理跨行合并的情况
|
95
|
+
if (rowSpan > 1) {
|
96
|
+
worksheet.mergeCells(
|
97
|
+
currentRowIndex,
|
98
|
+
currentColIndex,
|
99
|
+
currentRowIndex + rowSpan - 1,
|
100
|
+
currentColIndex
|
101
|
+
);
|
102
|
+
}
|
103
|
+
|
104
|
+
// 处理子列
|
105
|
+
if (column.children && column.children.length > 0) {
|
106
|
+
const childMaxRowIndex = generateHeaders(
|
107
|
+
worksheet,
|
108
|
+
column.children,
|
109
|
+
currentRowIndex + 1,
|
110
|
+
currentColIndex,
|
111
|
+
maxDepth
|
112
|
+
);
|
113
|
+
maxRowIndex = Math.max(maxRowIndex, childMaxRowIndex);
|
114
|
+
} else {
|
115
|
+
maxRowIndex = Math.max(maxRowIndex, currentRowIndex + rowSpan - 1);
|
116
|
+
}
|
117
|
+
|
118
|
+
colIndex += colSpan;
|
119
|
+
});
|
120
|
+
|
121
|
+
return maxRowIndex;
|
122
|
+
}
|
123
|
+
|
124
|
+
function getColSpan(column) {
|
125
|
+
if (!column.children || column.children.length === 0) {
|
126
|
+
return 1;
|
127
|
+
}
|
128
|
+
return column.children.reduce((sum, child) => sum + getColSpan(child), 0);
|
129
|
+
}
|
130
|
+
|
131
|
+
function getRowSpan(column, remainingDepth) {
|
132
|
+
if (!column.children || column.children.length === 0) {
|
133
|
+
return remainingDepth;
|
134
|
+
}
|
135
|
+
return 1;
|
136
|
+
}
|
137
|
+
|
138
|
+
function getMaxDepth(columns) {
|
139
|
+
return columns.reduce((max, column) => {
|
140
|
+
const depth = column.children ? 1 + getMaxDepth(column.children) : 1;
|
141
|
+
return Math.max(max, depth);
|
142
|
+
}, 1);
|
143
|
+
}
|
144
|
+
|
145
|
+
function fillData(worksheet, data, columns) {
|
146
|
+
data.forEach((rowData) => {
|
147
|
+
const row = worksheet.addRow(
|
148
|
+
columns.flatMap((col) => extractValues(rowData, col))
|
149
|
+
);
|
150
|
+
row.eachCell((cell) => {
|
151
|
+
cell.alignment = alignmentStyle();
|
152
|
+
});
|
153
|
+
});
|
154
|
+
}
|
155
|
+
|
156
|
+
function autoFitColumns(worksheet) {
|
157
|
+
worksheet.columns.forEach((column) => {
|
158
|
+
let maxLength = 0;
|
159
|
+
column.eachCell({ includeEmpty: true }, (cell) => {
|
160
|
+
const columnLength = cell.value ? cell.value.toString().length : 10;
|
161
|
+
if (columnLength > maxLength) {
|
162
|
+
maxLength = columnLength;
|
163
|
+
}
|
164
|
+
});
|
165
|
+
column.width = maxLength < 10 ? maxLength + 5 : maxLength;
|
166
|
+
});
|
167
|
+
}
|
168
|
+
|
169
|
+
function generateDescribe(worksheet, describe, data) {
|
170
|
+
if (describe) {
|
171
|
+
console.log('describe',describe);
|
172
|
+
// 定义开始的位置
|
173
|
+
const startCell = data.length + 7;
|
174
|
+
const endCell = startCell + 9;
|
175
|
+
|
176
|
+
// 合并单元格的范围
|
177
|
+
const mergeStart = 'A' + startCell;
|
178
|
+
const mergeEnd = 'E' + endCell;
|
179
|
+
worksheet.mergeCells(mergeStart + ':' + mergeEnd);
|
180
|
+
|
181
|
+
// 在表格底部加上文字解释
|
182
|
+
let combinedText = describe
|
183
|
+
const mergedCells = [
|
184
|
+
'A' + startCell,
|
185
|
+
'B' + startCell,
|
186
|
+
'C' + startCell,
|
187
|
+
'D' + startCell,
|
188
|
+
'E' + startCell,
|
189
|
+
];
|
190
|
+
|
191
|
+
// 设置每个合并单元格的自动换行属性
|
192
|
+
mergedCells.forEach(function(cell) {
|
193
|
+
const currentCell = worksheet.getCell(cell);
|
194
|
+
currentCell.value = combinedText;
|
195
|
+
currentCell.font = fontStyle;
|
196
|
+
currentCell.alignment = {
|
197
|
+
vertical: "top",
|
198
|
+
horizontal: "left",
|
199
|
+
wrapText: true // 设置自动换行
|
200
|
+
};
|
201
|
+
});
|
202
|
+
worksheet.getCell(mergeStart).border = borderStyle;
|
203
|
+
}
|
204
|
+
}
|
205
|
+
|
206
|
+
function extractValues(rowData, column) {
|
207
|
+
if (column.children && column.children.length > 0) {
|
208
|
+
return column.children.flatMap((child) => extractValues(rowData, child));
|
209
|
+
} else {
|
210
|
+
return [rowData[column.key]];
|
211
|
+
}
|
212
|
+
}
|
213
|
+
self.onmessage = async function (e) {
|
214
|
+
const { data, columns, describe, filename } = e.data;
|
215
|
+
const workbook = new ExcelJS.Workbook();
|
216
|
+
const worksheet = workbook.addWorksheet("Sheet1");
|
217
|
+
generateHeaders(worksheet, columns);
|
218
|
+
fillData(worksheet, data, columns);
|
219
|
+
generateDescribe(worksheet, describe, data);
|
220
|
+
const buffer = await workbook.xlsx.writeBuffer();
|
221
|
+
self.postMessage({ buffer, filename });
|
222
|
+
};`;
|
223
|
+
|
224
|
+
// 创建 Blob 对象
|
225
|
+
const blob = new Blob([workerScript], { type: "text/javascript" });
|
226
|
+
|
227
|
+
// 创建指向 Blob 的 URL
|
228
|
+
const workerUrl = URL.createObjectURL(blob);
|
229
|
+
|
230
|
+
// 创建 Web Worker 实例
|
231
|
+
const writeBufferWorker = new Worker(workerUrl);
|
232
|
+
|
233
|
+
// 剩余的代码与代码2相同
|
234
|
+
|
26
235
|
const formatColumns = retainTitleAndKey(this.columns);
|
27
|
-
const writeBufferWorker = new Worker("/writeBufferWorker.js");
|
28
236
|
writeBufferWorker.postMessage({
|
29
237
|
columns: formatColumns,
|
30
238
|
data: this.data,
|
@@ -36,6 +244,12 @@ class ExcelExporter {
|
|
36
244
|
const { buffer, filename } = e.data;
|
37
245
|
const blob = new Blob([buffer], { type: "application/octet-stream" });
|
38
246
|
saveAs(blob, filename);
|
247
|
+
|
248
|
+
// 释放对象 URL
|
249
|
+
URL.revokeObjectURL(workerUrl);
|
250
|
+
|
251
|
+
// 终止 Web Worker
|
252
|
+
writeBufferWorker.terminate();
|
39
253
|
};
|
40
254
|
|
41
255
|
writeBufferWorker.onerror = (error) => {
|
package/package.json
CHANGED
@@ -1,200 +0,0 @@
|
|
1
|
-
importScripts("https://cdn.bootcdn.net/ajax/libs/exceljs/4.4.0/exceljs.min.js");
|
2
|
-
|
3
|
-
self.onmessage = async function (e) {
|
4
|
-
const { data, columns, describe, filename } = e.data;
|
5
|
-
const workbook = new ExcelJS.Workbook();
|
6
|
-
const worksheet = workbook.addWorksheet("Sheet1");
|
7
|
-
// 设置表头样式
|
8
|
-
generateHeaders(worksheet, columns);
|
9
|
-
|
10
|
-
// 填充数据
|
11
|
-
fillData(worksheet, data, columns);
|
12
|
-
|
13
|
-
// 自动调整列宽
|
14
|
-
// autoFitColumns(worksheet);
|
15
|
-
|
16
|
-
// 处理描述信息
|
17
|
-
generateDescribe(worksheet, describe, data);
|
18
|
-
|
19
|
-
const buffer = await workbook.xlsx.writeBuffer();
|
20
|
-
self.postMessage({ buffer, filename });
|
21
|
-
};
|
22
|
-
const fontStyle = {
|
23
|
-
name: "Arial",
|
24
|
-
size: 10,
|
25
|
-
bold: true, // 设置文字加粗
|
26
|
-
color: { argb: "FF000000" },
|
27
|
-
};
|
28
|
-
const alignmentStyle = (vertical = "middle", horizontal = "center") => {
|
29
|
-
return {
|
30
|
-
vertical,
|
31
|
-
horizontal,
|
32
|
-
};
|
33
|
-
};
|
34
|
-
|
35
|
-
const borderStyle = {
|
36
|
-
top: { style: "thin" },
|
37
|
-
left: { style: "thin" },
|
38
|
-
bottom: { style: "thin" },
|
39
|
-
right: { style: "thin" },
|
40
|
-
};
|
41
|
-
|
42
|
-
function generateHeaders(
|
43
|
-
worksheet,
|
44
|
-
columns,
|
45
|
-
rowIndex = 1,
|
46
|
-
colIndexStart = 1,
|
47
|
-
maxDepth = null
|
48
|
-
) {
|
49
|
-
let maxRowIndex = rowIndex;
|
50
|
-
let colIndex = colIndexStart;
|
51
|
-
|
52
|
-
// 计算整个表头的最大深度(用于单级表头行合并)
|
53
|
-
if (maxDepth === null) {
|
54
|
-
maxDepth = getMaxDepth(columns);
|
55
|
-
}
|
56
|
-
|
57
|
-
columns.forEach((column) => {
|
58
|
-
const currentRowIndex = rowIndex;
|
59
|
-
const currentColIndex = colIndex;
|
60
|
-
|
61
|
-
// 计算列的跨度和行的跨度
|
62
|
-
const colSpan = getColSpan(column);
|
63
|
-
const rowSpan = getRowSpan(column, maxDepth - rowIndex + 1);
|
64
|
-
|
65
|
-
// 设置表头单元格值并应用样式
|
66
|
-
const cell = worksheet.getCell(currentRowIndex, currentColIndex);
|
67
|
-
cell.value = column.title;
|
68
|
-
cell.alignment = alignmentStyle();
|
69
|
-
cell.border = borderStyle;
|
70
|
-
cell.font = fontStyle;
|
71
|
-
cell.fill = {
|
72
|
-
type: "pattern",
|
73
|
-
pattern: "solid",
|
74
|
-
fgColor: { argb: "87CEEB" },
|
75
|
-
};
|
76
|
-
|
77
|
-
// 合并单元格处理
|
78
|
-
if (colSpan > 1) {
|
79
|
-
worksheet.mergeCells(
|
80
|
-
currentRowIndex,
|
81
|
-
currentColIndex,
|
82
|
-
currentRowIndex,
|
83
|
-
currentColIndex + colSpan - 1
|
84
|
-
);
|
85
|
-
}
|
86
|
-
|
87
|
-
// 处理跨行合并的情况
|
88
|
-
if (rowSpan > 1) {
|
89
|
-
worksheet.mergeCells(
|
90
|
-
currentRowIndex,
|
91
|
-
currentColIndex,
|
92
|
-
currentRowIndex + rowSpan - 1,
|
93
|
-
currentColIndex
|
94
|
-
);
|
95
|
-
}
|
96
|
-
|
97
|
-
// 处理子列
|
98
|
-
if (column.children && column.children.length > 0) {
|
99
|
-
const childMaxRowIndex = generateHeaders(
|
100
|
-
worksheet,
|
101
|
-
column.children,
|
102
|
-
currentRowIndex + 1,
|
103
|
-
currentColIndex,
|
104
|
-
maxDepth
|
105
|
-
);
|
106
|
-
maxRowIndex = Math.max(maxRowIndex, childMaxRowIndex);
|
107
|
-
} else {
|
108
|
-
maxRowIndex = Math.max(maxRowIndex, currentRowIndex + rowSpan - 1);
|
109
|
-
}
|
110
|
-
|
111
|
-
colIndex += colSpan;
|
112
|
-
});
|
113
|
-
|
114
|
-
return maxRowIndex;
|
115
|
-
}
|
116
|
-
|
117
|
-
function getColSpan(column) {
|
118
|
-
if (!column.children || column.children.length === 0) {
|
119
|
-
return 1;
|
120
|
-
}
|
121
|
-
return column.children.reduce((sum, child) => sum + getColSpan(child), 0);
|
122
|
-
}
|
123
|
-
|
124
|
-
function getRowSpan(column, remainingDepth) {
|
125
|
-
if (!column.children || column.children.length === 0) {
|
126
|
-
return remainingDepth;
|
127
|
-
}
|
128
|
-
return 1;
|
129
|
-
}
|
130
|
-
|
131
|
-
function getMaxDepth(columns) {
|
132
|
-
return columns.reduce((max, column) => {
|
133
|
-
const depth = column.children ? 1 + getMaxDepth(column.children) : 1;
|
134
|
-
return Math.max(max, depth);
|
135
|
-
}, 1);
|
136
|
-
}
|
137
|
-
|
138
|
-
function fillData(worksheet, data, columns) {
|
139
|
-
data.forEach((rowData) => {
|
140
|
-
const row = worksheet.addRow(
|
141
|
-
columns.flatMap((col) => extractValues(rowData, col))
|
142
|
-
);
|
143
|
-
row.eachCell((cell) => {
|
144
|
-
cell.alignment = alignmentStyle();
|
145
|
-
});
|
146
|
-
});
|
147
|
-
}
|
148
|
-
|
149
|
-
function autoFitColumns(worksheet) {
|
150
|
-
worksheet.columns.forEach((column) => {
|
151
|
-
let maxLength = 0;
|
152
|
-
column.eachCell({ includeEmpty: true }, (cell) => {
|
153
|
-
const columnLength = cell.value ? cell.value.toString().length : 10;
|
154
|
-
if (columnLength > maxLength) {
|
155
|
-
maxLength = columnLength;
|
156
|
-
}
|
157
|
-
});
|
158
|
-
column.width = maxLength < 10 ? maxLength + 5 : maxLength;
|
159
|
-
});
|
160
|
-
}
|
161
|
-
|
162
|
-
function generateDescribe(worksheet, describe, data) {
|
163
|
-
if (describe.length > 0) {
|
164
|
-
// 定义开始的位置
|
165
|
-
const startCell = data.length + 7;
|
166
|
-
const endCell = startCell + describe.length + 9;
|
167
|
-
worksheet.mergeCells(`A${startCell}:E${endCell}`);
|
168
|
-
|
169
|
-
// 在表格底部加上文字解释
|
170
|
-
let combinedText = describe.join("\n");
|
171
|
-
const mergedCells = [
|
172
|
-
`A${startCell}`,
|
173
|
-
`B${startCell}`,
|
174
|
-
`C${startCell}`,
|
175
|
-
`D${startCell}`,
|
176
|
-
`E${startCell}`,
|
177
|
-
];
|
178
|
-
|
179
|
-
// 设置每个合并单元格的自动换行属性
|
180
|
-
mergedCells.forEach((cell) => {
|
181
|
-
const currentCell = worksheet.getCell(cell);
|
182
|
-
currentCell.value = combinedText;
|
183
|
-
currentCell.font = fontStyle;
|
184
|
-
currentCell.alignment = {
|
185
|
-
vertical: "top",
|
186
|
-
horizontal: "left",
|
187
|
-
wrapText: true, // 设置自动换行
|
188
|
-
};
|
189
|
-
});
|
190
|
-
worksheet.getCell(`A${startCell}`).border = borderStyle;
|
191
|
-
}
|
192
|
-
}
|
193
|
-
|
194
|
-
function extractValues(rowData, column) {
|
195
|
-
if (column.children && column.children.length > 0) {
|
196
|
-
return column.children.flatMap((child) => extractValues(rowData, child));
|
197
|
-
} else {
|
198
|
-
return [rowData[column.key]];
|
199
|
-
}
|
200
|
-
}
|