@svadmin/core 0.31.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.
@@ -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
  }