@canvas-components/utils 0.2.3 → 0.2.5
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/dist/export/index.cjs +47 -16
- package/dist/export/index.cjs.map +1 -1
- package/dist/export/index.d.cts +10 -0
- package/dist/export/index.d.ts +10 -0
- package/dist/export/index.js +47 -16
- package/dist/export/index.js.map +1 -1
- package/dist/index.cjs +77 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +76 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/export/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as XLSX from 'xlsx';
|
|
2
|
+
|
|
1
3
|
/** XLSX 导出阶段;编码阶段无法提供准确的百分比。 */
|
|
2
4
|
interface XlsxExportProgress {
|
|
3
5
|
phase: "preparing" | "encoding" | "saving" | "done";
|
|
@@ -30,6 +32,10 @@ interface XlsxExportParams<Row> extends XlsxExportOptions {
|
|
|
30
32
|
columns: ExportTableColumn<Row>[];
|
|
31
33
|
sheetName?: string;
|
|
32
34
|
footerRows?: unknown[][];
|
|
35
|
+
/** 多级表头;省略时使用 columns.title。 */
|
|
36
|
+
headerRows?: string[][];
|
|
37
|
+
/** 基于完整工作表(含表头)的合并范围,分文件时自动裁切并平移。 */
|
|
38
|
+
merges?: XLSX.Range[];
|
|
33
39
|
}
|
|
34
40
|
/** 按行数和列数双重限制自动分文件,逐份生成、保存并释放。 */
|
|
35
41
|
declare function exportTableToXlsx<Row>(params: XlsxExportParams<Row>): Promise<void>;
|
|
@@ -52,6 +58,7 @@ declare function mapRowsToExportRecords<Row>(params: {
|
|
|
52
58
|
declare function buildCsvContent<Row>(params: {
|
|
53
59
|
rows: Row[];
|
|
54
60
|
columns: ExportTableColumn<Row>[];
|
|
61
|
+
footerRows?: unknown[][];
|
|
55
62
|
}): string;
|
|
56
63
|
/**
|
|
57
64
|
* 将二维数据序列化为 CSV 文本。
|
|
@@ -63,6 +70,7 @@ declare function serializeCsvRows(rows: readonly (readonly unknown[])[], lineBre
|
|
|
63
70
|
declare function buildTxtContent<Row>(params: {
|
|
64
71
|
rows: Row[];
|
|
65
72
|
columns: ExportTableColumn<Row>[];
|
|
73
|
+
footerRows?: unknown[][];
|
|
66
74
|
}): string;
|
|
67
75
|
/**
|
|
68
76
|
* 下载 JSON 文件。
|
|
@@ -79,6 +87,7 @@ declare function exportTableToCsv<Row>(params: {
|
|
|
79
87
|
fileName: string;
|
|
80
88
|
rows: Row[];
|
|
81
89
|
columns: ExportTableColumn<Row>[];
|
|
90
|
+
footerRows?: unknown[][];
|
|
82
91
|
}): Promise<void>;
|
|
83
92
|
/**
|
|
84
93
|
* 导出表格 TXT 文件。
|
|
@@ -87,6 +96,7 @@ declare function exportTableToTxt<Row>(params: {
|
|
|
87
96
|
fileName: string;
|
|
88
97
|
rows: Row[];
|
|
89
98
|
columns: ExportTableColumn<Row>[];
|
|
99
|
+
footerRows?: unknown[][];
|
|
90
100
|
}): Promise<void>;
|
|
91
101
|
|
|
92
102
|
export { type ExportTableColumn, type XlsxExportOptions, type XlsxExportParams, type XlsxExportProgress, buildCsvContent, buildTxtContent, downloadJsonFile, exportTableToCsv, exportTableToTxt, exportTableToXlsx, mapRowsToExportRecords, serializeCsvRows };
|
package/dist/export/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as XLSX from 'xlsx';
|
|
2
|
+
|
|
1
3
|
/** XLSX 导出阶段;编码阶段无法提供准确的百分比。 */
|
|
2
4
|
interface XlsxExportProgress {
|
|
3
5
|
phase: "preparing" | "encoding" | "saving" | "done";
|
|
@@ -30,6 +32,10 @@ interface XlsxExportParams<Row> extends XlsxExportOptions {
|
|
|
30
32
|
columns: ExportTableColumn<Row>[];
|
|
31
33
|
sheetName?: string;
|
|
32
34
|
footerRows?: unknown[][];
|
|
35
|
+
/** 多级表头;省略时使用 columns.title。 */
|
|
36
|
+
headerRows?: string[][];
|
|
37
|
+
/** 基于完整工作表(含表头)的合并范围,分文件时自动裁切并平移。 */
|
|
38
|
+
merges?: XLSX.Range[];
|
|
33
39
|
}
|
|
34
40
|
/** 按行数和列数双重限制自动分文件,逐份生成、保存并释放。 */
|
|
35
41
|
declare function exportTableToXlsx<Row>(params: XlsxExportParams<Row>): Promise<void>;
|
|
@@ -52,6 +58,7 @@ declare function mapRowsToExportRecords<Row>(params: {
|
|
|
52
58
|
declare function buildCsvContent<Row>(params: {
|
|
53
59
|
rows: Row[];
|
|
54
60
|
columns: ExportTableColumn<Row>[];
|
|
61
|
+
footerRows?: unknown[][];
|
|
55
62
|
}): string;
|
|
56
63
|
/**
|
|
57
64
|
* 将二维数据序列化为 CSV 文本。
|
|
@@ -63,6 +70,7 @@ declare function serializeCsvRows(rows: readonly (readonly unknown[])[], lineBre
|
|
|
63
70
|
declare function buildTxtContent<Row>(params: {
|
|
64
71
|
rows: Row[];
|
|
65
72
|
columns: ExportTableColumn<Row>[];
|
|
73
|
+
footerRows?: unknown[][];
|
|
66
74
|
}): string;
|
|
67
75
|
/**
|
|
68
76
|
* 下载 JSON 文件。
|
|
@@ -79,6 +87,7 @@ declare function exportTableToCsv<Row>(params: {
|
|
|
79
87
|
fileName: string;
|
|
80
88
|
rows: Row[];
|
|
81
89
|
columns: ExportTableColumn<Row>[];
|
|
90
|
+
footerRows?: unknown[][];
|
|
82
91
|
}): Promise<void>;
|
|
83
92
|
/**
|
|
84
93
|
* 导出表格 TXT 文件。
|
|
@@ -87,6 +96,7 @@ declare function exportTableToTxt<Row>(params: {
|
|
|
87
96
|
fileName: string;
|
|
88
97
|
rows: Row[];
|
|
89
98
|
columns: ExportTableColumn<Row>[];
|
|
99
|
+
footerRows?: unknown[][];
|
|
90
100
|
}): Promise<void>;
|
|
91
101
|
|
|
92
102
|
export { type ExportTableColumn, type XlsxExportOptions, type XlsxExportParams, type XlsxExportProgress, buildCsvContent, buildTxtContent, downloadJsonFile, exportTableToCsv, exportTableToTxt, exportTableToXlsx, mapRowsToExportRecords, serializeCsvRows };
|
package/dist/export/index.js
CHANGED
|
@@ -11,7 +11,10 @@ var __export = (target, all) => {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// src/export/xlsx-runtime.ts
|
|
14
|
-
function createXlsxWriter(xlsx, header, sheetName, maxRows = 1048576) {
|
|
14
|
+
function createXlsxWriter(xlsx, header, sheetName, maxRows = 1048576, merges = []) {
|
|
15
|
+
const headerRows = Array.isArray(header[0]) ? header : [header];
|
|
16
|
+
if (headerRows.length >= maxRows)
|
|
17
|
+
throw new Error("XLSX \u8868\u5934\u884C\u6570\u8D85\u8FC7\u5DE5\u4F5C\u8868\u5BB9\u91CF");
|
|
15
18
|
const workbook = xlsx.utils.book_new();
|
|
16
19
|
let worksheet;
|
|
17
20
|
let rowCount = 0;
|
|
@@ -19,7 +22,7 @@ function createXlsxWriter(xlsx, header, sheetName, maxRows = 1048576) {
|
|
|
19
22
|
const baseName = sheetName.replace(/[\\/?*[\]:]/g, "_").replace(/^'+|'+$/g, "") || "Sheet1";
|
|
20
23
|
const addSheet = () => {
|
|
21
24
|
const suffix = sheetIndex === 0 ? "" : `_${sheetIndex + 1}`;
|
|
22
|
-
worksheet = xlsx.utils.aoa_to_sheet(
|
|
25
|
+
worksheet = xlsx.utils.aoa_to_sheet(headerRows, {
|
|
23
26
|
dense: true
|
|
24
27
|
});
|
|
25
28
|
xlsx.utils.book_append_sheet(
|
|
@@ -27,7 +30,8 @@ function createXlsxWriter(xlsx, header, sheetName, maxRows = 1048576) {
|
|
|
27
30
|
worksheet,
|
|
28
31
|
baseName.slice(0, 31 - suffix.length) + suffix
|
|
29
32
|
);
|
|
30
|
-
|
|
33
|
+
if (merges.length && sheetIndex === 0) worksheet["!merges"] = merges;
|
|
34
|
+
rowCount = headerRows.length;
|
|
31
35
|
sheetIndex += 1;
|
|
32
36
|
};
|
|
33
37
|
addSheet();
|
|
@@ -103,7 +107,7 @@ let writer;
|
|
|
103
107
|
self.onmessage = function(event) {
|
|
104
108
|
try {
|
|
105
109
|
const message = event.data;
|
|
106
|
-
if (message.type === 'init') writer = createWriter(XLSX, message.header, message.sheetName);
|
|
110
|
+
if (message.type === 'init') writer = createWriter(XLSX, message.header, message.sheetName, undefined, message.merges);
|
|
107
111
|
else if (message.type === 'append') writer.append(message.rows);
|
|
108
112
|
else if (message.type === 'finish') {
|
|
109
113
|
const buffer = writer.finish();
|
|
@@ -151,8 +155,22 @@ async function buildXlsxPart(params, rangeStart, rangeEnd) {
|
|
|
151
155
|
throw new Error(
|
|
152
156
|
"\u5355\u4EFD XLSX \u6570\u636E\u8D85\u8FC7\u5B89\u5168\u9650\u5236\uFF0C\u8BF7\u4F7F\u7528 exportTableToXlsx \u81EA\u52A8\u5206\u6587\u4EF6\u5BFC\u51FA"
|
|
153
157
|
);
|
|
154
|
-
const header = columns.map((column) => column.title);
|
|
155
|
-
|
|
158
|
+
const header = params.headerRows?.length ? params.headerRows : [columns.map((column) => column.title)];
|
|
159
|
+
const headerRowCount = header.length;
|
|
160
|
+
const merges = (params.merges ?? []).flatMap((merge) => {
|
|
161
|
+
if (merge.e.r < headerRowCount) return [merge];
|
|
162
|
+
const start = Math.max(merge.s.r, headerRowCount + rangeStart);
|
|
163
|
+
const end = Math.min(merge.e.r, headerRowCount + rangeEnd - 1);
|
|
164
|
+
return end < start || end === start && merge.s.c === merge.e.c ? [] : [
|
|
165
|
+
{
|
|
166
|
+
s: { r: start - rangeStart, c: merge.s.c },
|
|
167
|
+
e: { r: end - rangeStart, c: merge.e.c }
|
|
168
|
+
}
|
|
169
|
+
];
|
|
170
|
+
});
|
|
171
|
+
if (header.some((row) => row.length > columns.length))
|
|
172
|
+
throw new Error("XLSX \u8868\u5934\u5355\u5143\u683C\u6570\u4E0D\u80FD\u8D85\u8FC7\u5BFC\u51FA\u5217\u6570");
|
|
173
|
+
let textSize = header.flat().reduce((size, title) => size + title.length, 0);
|
|
156
174
|
const normalizeExportCell = (value) => {
|
|
157
175
|
const cell = normalizeCell(value);
|
|
158
176
|
if (typeof cell === "string") {
|
|
@@ -166,7 +184,7 @@ async function buildXlsxPart(params, rangeStart, rangeEnd) {
|
|
|
166
184
|
}
|
|
167
185
|
return cell;
|
|
168
186
|
};
|
|
169
|
-
header.forEach((title) => {
|
|
187
|
+
header.flat().forEach((title) => {
|
|
170
188
|
if (title.length > 32767) throw new Error("XLSX \u8868\u5934\u8D85\u8FC7 32767 \u5B57\u7B26");
|
|
171
189
|
});
|
|
172
190
|
if (textSize > XLSX_MAX_TEXT_CHARS_PER_FILE)
|
|
@@ -218,7 +236,7 @@ async function buildXlsxPart(params, rangeStart, rangeEnd) {
|
|
|
218
236
|
() => fail(new Error("XLSX Worker \u542F\u52A8\u8D85\u65F6")),
|
|
219
237
|
1e4
|
|
220
238
|
);
|
|
221
|
-
await request({ type: "init", header, sheetName });
|
|
239
|
+
await request({ type: "init", header, sheetName, merges });
|
|
222
240
|
} catch {
|
|
223
241
|
worker?.terminate();
|
|
224
242
|
worker = void 0;
|
|
@@ -228,7 +246,7 @@ async function buildXlsxPart(params, rangeStart, rangeEnd) {
|
|
|
228
246
|
}
|
|
229
247
|
}
|
|
230
248
|
checkAbort();
|
|
231
|
-
const writer = worker ? void 0 : createXlsxWriter(XLSX, header, sheetName);
|
|
249
|
+
const writer = worker ? void 0 : createXlsxWriter(XLSX, header, sheetName, void 0, merges);
|
|
232
250
|
const totalRows = rangeEnd - rangeStart;
|
|
233
251
|
const report = (phase, completedRows) => params.onProgress?.({
|
|
234
252
|
phase,
|
|
@@ -359,7 +377,7 @@ function getXlsxRowsPerFile(params) {
|
|
|
359
377
|
throw new Error("XLSX \u5206\u6587\u4EF6\u4E0A\u9650\u5FC5\u987B\u4E3A\u6B63\u6574\u6570");
|
|
360
378
|
const byCells = Math.floor(
|
|
361
379
|
Math.min(cellLimit, XLSX_MAX_CELLS_PER_FILE) / Math.max(1, params.columns.length)
|
|
362
|
-
) - 1;
|
|
380
|
+
) - (params.headerRows?.length || 1);
|
|
363
381
|
if (byCells < 1) throw new Error("XLSX \u5355\u5143\u683C\u9884\u7B97\u4E0D\u8DB3\u4EE5\u5BB9\u7EB3\u8868\u5934\u548C\u4E00\u884C\u6570\u636E");
|
|
364
382
|
return Math.min(rowLimit, XLSX_MAX_ROWS_PER_FILE, byCells);
|
|
365
383
|
}
|
|
@@ -381,7 +399,8 @@ function buildCsvContent(params) {
|
|
|
381
399
|
columns.map((column) => column.title),
|
|
382
400
|
...rows.map(
|
|
383
401
|
(row, rowIndex) => columns.map((column) => column.value(row, rowIndex))
|
|
384
|
-
)
|
|
402
|
+
),
|
|
403
|
+
...params.footerRows ?? []
|
|
385
404
|
]);
|
|
386
405
|
}
|
|
387
406
|
function serializeCsvRows(rows, lineBreak = "\n") {
|
|
@@ -393,7 +412,13 @@ function buildTxtContent(params) {
|
|
|
393
412
|
const body = rows.map(
|
|
394
413
|
(row, rowIndex) => columns.map((column) => normalizeTextCell(column.value(row, rowIndex))).join(" ")
|
|
395
414
|
);
|
|
396
|
-
return [
|
|
415
|
+
return [
|
|
416
|
+
header,
|
|
417
|
+
...body,
|
|
418
|
+
...(params.footerRows ?? []).map(
|
|
419
|
+
(row) => row.map(normalizeTextCell).join(" ")
|
|
420
|
+
)
|
|
421
|
+
].join("\n");
|
|
397
422
|
}
|
|
398
423
|
async function downloadJsonFile(params) {
|
|
399
424
|
const { data, fileName, space = 2 } = params;
|
|
@@ -405,8 +430,8 @@ async function downloadJsonFile(params) {
|
|
|
405
430
|
});
|
|
406
431
|
}
|
|
407
432
|
async function exportTableToCsv(params) {
|
|
408
|
-
const { fileName, rows, columns } = params;
|
|
409
|
-
const content = await buildCsvContentAsync({ rows, columns });
|
|
433
|
+
const { fileName, rows, columns, footerRows } = params;
|
|
434
|
+
const content = await buildCsvContentAsync({ rows, columns, footerRows });
|
|
410
435
|
downloadTextFile({
|
|
411
436
|
fileName: ensureExtension(fileName, "csv"),
|
|
412
437
|
content,
|
|
@@ -414,8 +439,8 @@ async function exportTableToCsv(params) {
|
|
|
414
439
|
});
|
|
415
440
|
}
|
|
416
441
|
async function exportTableToTxt(params) {
|
|
417
|
-
const { fileName, rows, columns } = params;
|
|
418
|
-
const content = await buildTxtContentAsync({ rows, columns });
|
|
442
|
+
const { fileName, rows, columns, footerRows } = params;
|
|
443
|
+
const content = await buildTxtContentAsync({ rows, columns, footerRows });
|
|
419
444
|
downloadTextFile({
|
|
420
445
|
fileName: ensureExtension(fileName, "txt"),
|
|
421
446
|
content,
|
|
@@ -459,6 +484,9 @@ async function buildCsvContentAsync(params) {
|
|
|
459
484
|
}
|
|
460
485
|
await yieldToMainThread();
|
|
461
486
|
}
|
|
487
|
+
for (const footer of params.footerRows ?? []) {
|
|
488
|
+
parts.push("\n", footer.map(escapeCsvCell).join(","));
|
|
489
|
+
}
|
|
462
490
|
return parts.join("");
|
|
463
491
|
}
|
|
464
492
|
async function buildTxtContentAsync(params) {
|
|
@@ -474,6 +502,9 @@ async function buildTxtContentAsync(params) {
|
|
|
474
502
|
}
|
|
475
503
|
await yieldToMainThread();
|
|
476
504
|
}
|
|
505
|
+
for (const footer of params.footerRows ?? []) {
|
|
506
|
+
parts.push("\n", footer.map(normalizeTextCell).join(" "));
|
|
507
|
+
}
|
|
477
508
|
return parts.join("");
|
|
478
509
|
}
|
|
479
510
|
function escapeCsvCell(value) {
|