@svadmin/core 0.32.0 → 0.32.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/core",
3
- "version": "0.32.0",
3
+ "version": "0.32.1",
4
4
  "author": "zuohuadong",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3,6 +3,7 @@ import { useParsed } from './useParsed.svelte';
3
3
  import type { Sort, Filter, BaseRecord } from './types';
4
4
  import { downloadData } from './export-format';
5
5
  import type { ExportFormat } from './export-format';
6
+ import { parseCSV } from './helpers-pure';
6
7
  export { downloadData, toCsv, toJson, toXlsx, escapeCsvField } from './export-format';
7
8
  export type { ExportFormat } from './export-format';
8
9
 
@@ -69,7 +70,9 @@ export function useExport<TData extends BaseRecord = BaseRecord>(options: UseExp
69
70
 
70
71
  return allRecords;
71
72
  } catch (error) {
72
- options.onError?.(error as Error);
73
+ const exportError = error instanceof Error ? error : new Error(String(error));
74
+ if (!options.onError) throw exportError;
75
+ options.onError(exportError);
73
76
  return [];
74
77
  } finally {
75
78
  isLoading = false;
@@ -211,61 +214,3 @@ export function useImport<TData = Record<string, unknown>>(options: UseImportOpt
211
214
  get mutationResult() { return mutationResult; },
212
215
  };
213
216
  }
214
-
215
-
216
- // ─── 解析工具 ────────────────────────────────────────────────────
217
-
218
- /** Parse full CSV text respecting quoted multi-line fields */
219
- function parseCSV(text: string): string[][] {
220
- const rows: string[][] = [];
221
- let currentRow: string[] = [];
222
- let currentVal = '';
223
- let inQuotes = false;
224
- let wasQuoted = false;
225
-
226
- for (let i = 0; i < text.length; i++) {
227
- const ch = text[i];
228
- if (inQuotes) {
229
- if (ch === '"') {
230
- if (i + 1 < text.length && text[i + 1] === '"') {
231
- currentVal += '"';
232
- i++;
233
- } else {
234
- inQuotes = false;
235
- }
236
- } else {
237
- currentVal += ch;
238
- }
239
- } else {
240
- if (ch === '"') {
241
- inQuotes = true;
242
- wasQuoted = true;
243
- } else if (ch === ',') {
244
- currentRow.push(wasQuoted ? currentVal : currentVal.trim());
245
- currentVal = '';
246
- wasQuoted = false;
247
- } else if (ch === '\n' || ch === '\r') {
248
- currentRow.push(wasQuoted ? currentVal : currentVal.trim());
249
- if (currentRow.some(v => v !== '')) {
250
- rows.push(currentRow);
251
- }
252
- currentRow = [];
253
- currentVal = '';
254
- wasQuoted = false;
255
- if (ch === '\r' && i + 1 < text.length && text[i + 1] === '\n') {
256
- i++;
257
- }
258
- } else {
259
- currentVal += ch;
260
- }
261
- }
262
- }
263
-
264
- if (currentVal || currentRow.length > 0) {
265
- currentRow.push(wasQuoted ? currentVal : currentVal.trim());
266
- if (currentRow.some(v => v !== '')) {
267
- rows.push(currentRow);
268
- }
269
- }
270
- return rows;
271
- }
@@ -5,24 +5,28 @@
5
5
  export type ExportFormat = 'csv' | 'json' | 'xlsx';
6
6
 
7
7
  /** CSV 转义,含公式注入防护 */
8
- export function escapeCsvField(s: string): string {
9
- let val = s;
10
- if (/^[=+@\t\r]/.test(val)) val = "'" + val;
11
- return val.includes(',') || val.includes('"') || val.includes('\n') || val.includes('\r')
12
- ? `"${val.replace(/"/g, '""')}"`
13
- : val;
8
+ export function escapeCsvField(fieldValue: string): string {
9
+ let escapedValue = fieldValue;
10
+ if (/^[=+@\t\r]/.test(escapedValue)) escapedValue = "'" + escapedValue;
11
+ return escapedValue.includes(',') || escapedValue.includes('"') || escapedValue.includes('\n') || escapedValue.includes('\r')
12
+ ? `"${escapedValue.replace(/"/g, '""')}"`
13
+ : escapedValue;
14
14
  }
15
15
 
16
16
  /** 将记录数组转为 CSV 字符串 */
17
17
  export function toCsv(records: Record<string, unknown>[]): string {
18
18
  if (records.length === 0) return '';
19
- const fields = Object.keys(records[0]);
20
- const header = fields.map(escapeCsvField).join(',');
19
+ const fieldNames = Object.keys(records[0]);
20
+ const header = fieldNames.map(escapeCsvField).join(',');
21
21
  const rows = records.map(record =>
22
- fields.map(f => {
23
- const val = record[f];
24
- const str = val === null || val === undefined ? '' : typeof val === 'object' ? JSON.stringify(val) : String(val);
25
- return escapeCsvField(str);
22
+ fieldNames.map(fieldName => {
23
+ const rawFieldValue = record[fieldName];
24
+ const serializedFieldValue = rawFieldValue === null || rawFieldValue === undefined
25
+ ? ''
26
+ : typeof rawFieldValue === 'object'
27
+ ? JSON.stringify(rawFieldValue)
28
+ : String(rawFieldValue);
29
+ return escapeCsvField(serializedFieldValue);
26
30
  }).join(',')
27
31
  );
28
32
  return [header, ...rows].join('\n');
@@ -34,8 +38,8 @@ export function toJson(records: Record<string, unknown>[]): string {
34
38
  }
35
39
 
36
40
  /** 转义 XML 特殊字符 */
37
- export function escapeXml(s: string): string {
38
- return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
41
+ export function escapeXml(xmlValue: string): string {
42
+ return xmlValue.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
39
43
  }
40
44
 
41
45
  /**
@@ -43,17 +47,17 @@ export function escapeXml(s: string): string {
43
47
  */
44
48
  export function toXlsx(records: Record<string, unknown>[]): string {
45
49
  if (records.length === 0) return '';
46
- const fields = Object.keys(records[0]);
50
+ const fieldNames = Object.keys(records[0]);
47
51
 
48
- const headerCells = fields.map(f => `<Cell><Data ss:Type="String">${escapeXml(f)}</Data></Cell>`).join('');
52
+ const headerCells = fieldNames.map(fieldName => `<Cell><Data ss:Type="String">${escapeXml(fieldName)}</Data></Cell>`).join('');
49
53
  const dataRows = records.map(record =>
50
- `<Row>${fields.map(f => {
51
- const val = record[f];
52
- if (val === null || val === undefined) return '<Cell><Data ss:Type="String"></Data></Cell>';
53
- if (typeof val === 'number') return `<Cell><Data ss:Type="Number">${val}</Data></Cell>`;
54
- if (typeof val === 'boolean') return `<Cell><Data ss:Type="Boolean">${val ? 1 : 0}</Data></Cell>`;
55
- const str = typeof val === 'object' ? JSON.stringify(val) : String(val);
56
- return `<Cell><Data ss:Type="String">${escapeXml(str)}</Data></Cell>`;
54
+ `<Row>${fieldNames.map(fieldName => {
55
+ const fieldValue = record[fieldName];
56
+ if (fieldValue === null || fieldValue === undefined) return '<Cell><Data ss:Type="String"></Data></Cell>';
57
+ if (typeof fieldValue === 'number') return `<Cell><Data ss:Type="Number">${fieldValue}</Data></Cell>`;
58
+ if (typeof fieldValue === 'boolean') return `<Cell><Data ss:Type="Boolean">${fieldValue ? 1 : 0}</Data></Cell>`;
59
+ const serializedValue = typeof fieldValue === 'object' ? JSON.stringify(fieldValue) : String(fieldValue);
60
+ return `<Cell><Data ss:Type="String">${escapeXml(serializedValue)}</Data></Cell>`;
57
61
  }).join('')}</Row>`
58
62
  ).join('\n');
59
63
 
@@ -84,32 +88,32 @@ export function downloadData(
84
88
 
85
89
  let content: string;
86
90
  let mimeType: string;
87
- let ext: string;
91
+ let extension: string;
88
92
 
89
93
  switch (format) {
90
94
  case 'json':
91
95
  content = toJson(records);
92
96
  mimeType = 'application/json;charset=utf-8;';
93
- ext = 'json';
97
+ extension = 'json';
94
98
  break;
95
99
  case 'xlsx':
96
100
  content = toXlsx(records);
97
101
  mimeType = 'application/vnd.ms-excel;charset=utf-8;';
98
- ext = 'xls';
102
+ extension = 'xls';
99
103
  break;
100
104
  case 'csv':
101
105
  default:
102
106
  content = toCsv(records);
103
107
  mimeType = 'text/csv;charset=utf-8;';
104
- ext = 'csv';
108
+ extension = 'csv';
105
109
  break;
106
110
  }
107
111
 
108
112
  const blob = new Blob(['\uFEFF' + content], { type: mimeType });
109
113
  const url = URL.createObjectURL(blob);
110
- const a = document.createElement('a');
111
- a.href = url;
112
- a.download = `${resource}_export.${ext}`;
113
- a.click();
114
+ const downloadAnchor = document.createElement('a');
115
+ downloadAnchor.href = url;
116
+ downloadAnchor.download = `${resource}_export.${extension}`;
117
+ downloadAnchor.click();
114
118
  setTimeout(() => URL.revokeObjectURL(url), 10000);
115
119
  }