export-to-exceljs-util 1.0.8 → 1.0.9
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/defaultExport.js +19 -5
- package/index.js +23 -19
- package/package.json +1 -1
- package/utils/index.js +208 -207
package/defaultExport.js
CHANGED
@@ -20,7 +20,7 @@ const borderStyle = {
|
|
20
20
|
right: { style: "thin" },
|
21
21
|
};
|
22
22
|
export default class DefaultExport {
|
23
|
-
constructor(columns, data, filename = "export.xlsx", describe=
|
23
|
+
constructor(columns, data, filename = "export.xlsx", describe = "") {
|
24
24
|
this.columns = columns;
|
25
25
|
this.data = data;
|
26
26
|
this.filename = filename;
|
@@ -180,19 +180,33 @@ export default class DefaultExport {
|
|
180
180
|
|
181
181
|
generateDescribe(worksheet) {
|
182
182
|
if (this.describe) {
|
183
|
-
//
|
184
|
-
|
185
|
-
|
186
|
-
worksheet.mergeCells(`A${startCell}:E${endCell}`);
|
183
|
+
// 定义开始和结束单元格的位置
|
184
|
+
let startCell = this.data.length + 7;
|
185
|
+
let endCell = 0;
|
187
186
|
|
188
187
|
// 在表格底部加上文字解释
|
189
188
|
let combinedText = this.describe;
|
189
|
+
|
190
|
+
// 合并单元格处理,根据描述信息的类型
|
191
|
+
if (typeof this.describe === "string") {
|
192
|
+
endCell = startCell + 10;
|
193
|
+
} else if (Array.isArray(this.describe)) {
|
194
|
+
endCell = startCell + this.describe.length;
|
195
|
+
combinedText = this.describe.join("\n");
|
196
|
+
} else {
|
197
|
+
endCell = startCell;
|
198
|
+
}
|
199
|
+
worksheet.mergeCells(`A${startCell}:H${endCell}`);
|
200
|
+
|
190
201
|
const mergedCells = [
|
191
202
|
`A${startCell}`,
|
192
203
|
`B${startCell}`,
|
193
204
|
`C${startCell}`,
|
194
205
|
`D${startCell}`,
|
195
206
|
`E${startCell}`,
|
207
|
+
`F${startCell}`,
|
208
|
+
`G${startCell}`,
|
209
|
+
`H${startCell}`,
|
196
210
|
];
|
197
211
|
|
198
212
|
// 设置每个合并单元格的自动换行属性
|
package/index.js
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
import { saveAs } from "file-saver";
|
2
2
|
import DefaultExport from "./defaultExport.js";
|
3
|
-
import {
|
4
|
-
|
5
|
-
class ExcelExporter1 {
|
3
|
+
import { workerScript, retainTitleAndKey } from "./utils/index";
|
4
|
+
class ExcelExporter {
|
6
5
|
constructor(columns, data, filename = "导出数据.xlsx", describe = []) {
|
7
6
|
this.columns = columns;
|
8
7
|
this.data = data;
|
@@ -10,27 +9,28 @@ class ExcelExporter1 {
|
|
10
9
|
this.describe = describe;
|
11
10
|
}
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
await this.exportSmallDataSet();
|
19
|
-
}
|
20
|
-
} catch (error) {
|
21
|
-
console.error("Export failed:", error);
|
12
|
+
exportToExcel() {
|
13
|
+
if (this.data.length > 10000) {
|
14
|
+
this.exportLargeDataSet();
|
15
|
+
} else {
|
16
|
+
this.exportSmallDataSet();
|
22
17
|
}
|
23
18
|
}
|
24
19
|
|
25
|
-
|
20
|
+
exportLargeDataSet() {
|
21
|
+
// 创建 Blob 对象
|
22
|
+
const blob = new Blob([workerScript()], { type: "text/javascript" });
|
23
|
+
|
24
|
+
// 创建指向 Blob 的 URL
|
25
|
+
const workerUrl = URL.createObjectURL(blob);
|
26
26
|
|
27
27
|
// 创建 Web Worker 实例
|
28
28
|
const writeBufferWorker = new Worker(workerUrl);
|
29
29
|
|
30
|
-
//
|
30
|
+
// 处理数据格式,保留标题和键
|
31
31
|
const formatColumns = retainTitleAndKey(this.columns);
|
32
32
|
|
33
|
-
//
|
33
|
+
// 向 Web Worker 实例发送数据
|
34
34
|
writeBufferWorker.postMessage({
|
35
35
|
columns: formatColumns,
|
36
36
|
data: this.data,
|
@@ -38,24 +38,28 @@ class ExcelExporter1 {
|
|
38
38
|
filename: this.filename,
|
39
39
|
});
|
40
40
|
|
41
|
-
// 监听 Web Worker
|
41
|
+
// 监听 Web Worker 实例的消息
|
42
42
|
writeBufferWorker.onmessage = (e) => {
|
43
|
+
// 下载文件
|
43
44
|
const { buffer, filename } = e.data;
|
44
45
|
const blob = new Blob([buffer], { type: "application/octet-stream" });
|
45
|
-
// 下载文件
|
46
46
|
saveAs(blob, filename);
|
47
|
+
|
47
48
|
// 释放对象 URL
|
48
49
|
URL.revokeObjectURL(workerUrl);
|
50
|
+
|
49
51
|
// 终止 Web Worker
|
50
52
|
writeBufferWorker.terminate();
|
51
53
|
};
|
52
54
|
|
53
55
|
writeBufferWorker.onerror = (error) => {
|
54
56
|
console.error("Worker error:", error);
|
57
|
+
URL.revokeObjectURL(workerUrl);
|
58
|
+
writeBufferWorker.terminate();
|
55
59
|
};
|
56
60
|
}
|
57
61
|
|
58
|
-
|
62
|
+
exportSmallDataSet() {
|
59
63
|
const defaultExport = new DefaultExport(
|
60
64
|
this.columns,
|
61
65
|
this.data,
|
@@ -66,4 +70,4 @@ class ExcelExporter1 {
|
|
66
70
|
}
|
67
71
|
}
|
68
72
|
|
69
|
-
export default
|
73
|
+
export default ExcelExporter;
|
package/package.json
CHANGED
package/utils/index.js
CHANGED
@@ -1,220 +1,221 @@
|
|
1
1
|
export function retainTitleAndKey(items) {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
}
|
7
|
-
// 如果项目是一个对象,处理 title, key 和 children 属性
|
8
|
-
if (typeof item === "object" && item !== null) {
|
9
|
-
// 创建一个新对象,只包含 title 和 key 属性
|
10
|
-
let newItem = {
|
11
|
-
title: item.title,
|
12
|
-
key: item.key,
|
13
|
-
};
|
14
|
-
// 如果存在 children 属性,递归处理它
|
15
|
-
if (item.children && Array.isArray(item.children)) {
|
16
|
-
newItem.children = retainTitleAndKey(item.children);
|
2
|
+
return items.map((item) => {
|
3
|
+
// 如果项目是一个数组,递归调用这个函数
|
4
|
+
if (Array.isArray(item)) {
|
5
|
+
return retainTitleAndKey(item);
|
17
6
|
}
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
size: 10,
|
31
|
-
bold: true, // 设置文字加粗
|
32
|
-
color: { argb: "FF000000" },
|
33
|
-
};
|
34
|
-
const alignmentStyle = (vertical = "middle", horizontal = "center") => {
|
35
|
-
return {
|
36
|
-
vertical,
|
37
|
-
horizontal,
|
38
|
-
};
|
39
|
-
};
|
40
|
-
|
41
|
-
const borderStyle = {
|
42
|
-
top: { style: "thin" },
|
43
|
-
left: { style: "thin" },
|
44
|
-
bottom: { style: "thin" },
|
45
|
-
right: { style: "thin" },
|
46
|
-
};
|
47
|
-
|
48
|
-
function generateHeaders(worksheet, columns, rowIndex = 1, colIndexStart = 1, maxDepth = null) {
|
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;
|
7
|
+
// 如果项目是一个对象,处理 title, key 和 children 属性
|
8
|
+
if (typeof item === "object" && item !== null) {
|
9
|
+
// 创建一个新对象,只包含 title 和 key 属性
|
10
|
+
let newItem = {
|
11
|
+
title: item.title,
|
12
|
+
key: item.key,
|
13
|
+
};
|
14
|
+
// 如果存在 children 属性,递归处理它
|
15
|
+
if (item.children && Array.isArray(item.children)) {
|
16
|
+
newItem.children = retainTitleAndKey(item.children);
|
17
|
+
}
|
18
|
+
return newItem;
|
115
19
|
}
|
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
|
-
|
20
|
+
// 如果项目既不是数组也不是对象,直接返回它
|
21
|
+
return item;
|
22
|
+
});
|
23
|
+
}
|
24
|
+
|
25
|
+
export function workerScript() {
|
26
|
+
const workerScript = `
|
27
|
+
importScripts("https://cdn.bootcdn.net/ajax/libs/exceljs/4.4.0/exceljs.min.js");
|
28
|
+
const fontStyle = {
|
29
|
+
name: "Arial",
|
30
|
+
size: 10,
|
31
|
+
bold: true, // 设置文字加粗
|
32
|
+
color: { argb: "FF000000" },
|
33
|
+
};
|
34
|
+
const alignmentStyle = (vertical = "middle", horizontal = "center") => {
|
35
|
+
return {
|
36
|
+
vertical,
|
37
|
+
horizontal,
|
38
|
+
};
|
39
|
+
};
|
40
|
+
|
41
|
+
const borderStyle = {
|
42
|
+
top: { style: "thin" },
|
43
|
+
left: { style: "thin" },
|
44
|
+
bottom: { style: "thin" },
|
45
|
+
right: { style: "thin" },
|
46
|
+
};
|
47
|
+
|
48
|
+
function generateHeaders(worksheet, columns, rowIndex = 1, colIndexStart = 1, maxDepth = null) {
|
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
|
+
const rows = data.map((rowData) =>
|
140
|
+
columns.flatMap((col) => extractValues(rowData, col))
|
141
|
+
);
|
142
|
+
worksheet.addRows(rows);
|
143
|
+
}
|
144
|
+
|
145
|
+
function autoFitColumns(worksheet) {
|
146
|
+
worksheet.columns.forEach((column) => {
|
147
|
+
let maxLength = 0;
|
148
|
+
column.eachCell({ includeEmpty: true }, (cell) => {
|
149
|
+
const columnLength = cell.value ? cell.value.toString().length : 10;
|
150
|
+
if (columnLength > maxLength) {
|
151
|
+
maxLength = columnLength;
|
152
|
+
}
|
153
|
+
});
|
154
|
+
column.width = maxLength < 10 ? maxLength + 5 : maxLength;
|
155
|
+
});
|
156
|
+
}
|
157
|
+
|
158
|
+
function generateDescribe(worksheet, describe, data) {
|
159
159
|
if (describe) {
|
160
|
-
//
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
// 合并单元格的范围
|
165
|
-
const mergeStart = 'A' + startCell;
|
166
|
-
const mergeEnd = 'E' + endCell;
|
167
|
-
worksheet.mergeCells(mergeStart + ':' + mergeEnd);
|
168
|
-
|
160
|
+
// 定义开始和结束单元格的位置
|
161
|
+
let startCell = data.length + 7;
|
162
|
+
let endCell = 0;
|
169
163
|
// 在表格底部加上文字解释
|
170
164
|
let combinedText = describe;
|
165
|
+
if (typeof describe === "string") {
|
166
|
+
endCell = startCell + 10;
|
167
|
+
} else if (Array.isArray(describe)) {
|
168
|
+
endCell = startCell + describe.length;
|
169
|
+
combinedText = describe.join("\\n");
|
170
|
+
} else {
|
171
|
+
endCell = startCell;
|
172
|
+
}
|
173
|
+
// 合并单元格的范围
|
174
|
+
const mergeStart = 'A' + startCell;
|
175
|
+
const mergeEnd = 'H' + endCell;
|
171
176
|
const mergedCells = [
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
+
'A' + startCell,
|
178
|
+
'B' + startCell,
|
179
|
+
'C' + startCell,
|
180
|
+
'D' + startCell,
|
181
|
+
'E' + startCell,
|
182
|
+
'F' + startCell,
|
183
|
+
'G' + startCell,
|
184
|
+
'H' + startCell
|
177
185
|
];
|
178
|
-
|
179
186
|
// 设置每个合并单元格的自动换行属性
|
180
187
|
mergedCells.forEach(function(cell) {
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
188
|
+
const currentCell = worksheet.getCell(cell);
|
189
|
+
currentCell.value = combinedText;
|
190
|
+
currentCell.font = fontStyle;
|
191
|
+
currentCell.alignment = {
|
192
|
+
vertical: "top",
|
193
|
+
horizontal: "left",
|
194
|
+
wrapText: true // 设置自动换行
|
195
|
+
};
|
189
196
|
});
|
190
197
|
worksheet.getCell(mergeStart).border = borderStyle;
|
191
198
|
}
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
// 创建指向 Blob 的 URL
|
217
|
-
return URL.createObjectURL(blob);
|
218
|
-
}
|
219
|
-
|
220
|
-
export const workerUrl = getworkerUrl();
|
199
|
+
}
|
200
|
+
|
201
|
+
function extractValues(rowData, column) {
|
202
|
+
if (column.children && column.children.length > 0) {
|
203
|
+
return column.children.flatMap((child) => extractValues(rowData, child));
|
204
|
+
} else {
|
205
|
+
return [rowData[column.key]];
|
206
|
+
}
|
207
|
+
}
|
208
|
+
self.onmessage = async function (e) {
|
209
|
+
const { data, columns, describe, filename } = e.data;
|
210
|
+
const workbook = new ExcelJS.Workbook();
|
211
|
+
const worksheet = workbook.addWorksheet("Sheet1");
|
212
|
+
generateHeaders(worksheet, columns);
|
213
|
+
fillData(worksheet, data, columns);
|
214
|
+
generateDescribe(worksheet, describe, data);
|
215
|
+
const buffer = await workbook.xlsx.writeBuffer();
|
216
|
+
self.postMessage({ buffer, filename });
|
217
|
+
const endTime = performance.now();
|
218
|
+
};`;
|
219
|
+
return workerScript
|
220
|
+
}
|
221
|
+
|