@fe-free/file 1.4.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/.babelrc +18 -0
- package/CHANGELOG.md +262 -0
- package/README.md +3 -0
- package/package.json +23 -0
- package/src/index.ts +1 -0
- package/src/xlsx/helper.ts +154 -0
- package/src/xlsx/index.ts +3 -0
- package/src/xlsx/json_to_xlsx.ts +33 -0
- package/src/xlsx/xlsx.stories.tsx +205 -0
- package/src/xlsx/xlsx_to_json.ts +30 -0
- package/src/xlsx/xlsx_to_json_and_validate.ts +88 -0
package/.babelrc
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"presets": [
|
|
3
|
+
[
|
|
4
|
+
"@babel/preset-env",
|
|
5
|
+
{
|
|
6
|
+
"modules": false,
|
|
7
|
+
"useBuiltIns": "usage",
|
|
8
|
+
"corejs": 3
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"@babel/preset-react",
|
|
12
|
+
"@babel/preset-typescript"
|
|
13
|
+
],
|
|
14
|
+
"plugins": [
|
|
15
|
+
"@babel/plugin-proposal-export-default-from",
|
|
16
|
+
"@babel/plugin-transform-runtime"
|
|
17
|
+
]
|
|
18
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# @ones-wb/file
|
|
2
|
+
|
|
3
|
+
## 1.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat: file
|
|
8
|
+
|
|
9
|
+
## 1.14.13
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- @ones-wb/tool@1.14.13
|
|
14
|
+
|
|
15
|
+
## 1.14.12
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- @ones-wb/tool@1.14.12
|
|
20
|
+
|
|
21
|
+
## 1.14.11
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- @ones-wb/tool@1.14.11
|
|
26
|
+
|
|
27
|
+
## 1.14.10
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- @ones-wb/tool@1.14.10
|
|
32
|
+
|
|
33
|
+
## 1.14.9
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- @ones-wb/tool@1.14.9
|
|
38
|
+
|
|
39
|
+
## 1.14.8
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- f74c458: chore: 依赖梳理
|
|
44
|
+
- Updated dependencies [f74c458]
|
|
45
|
+
- @ones-wb/tool@1.14.8
|
|
46
|
+
|
|
47
|
+
## 1.14.7
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- @ones-wb/tool@1.14.7
|
|
52
|
+
|
|
53
|
+
## 1.14.6
|
|
54
|
+
|
|
55
|
+
### Patch Changes
|
|
56
|
+
|
|
57
|
+
- @ones-wb/tool@1.14.6
|
|
58
|
+
|
|
59
|
+
## 1.14.5
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- @ones-wb/tool@1.14.5
|
|
64
|
+
|
|
65
|
+
## 1.14.4
|
|
66
|
+
|
|
67
|
+
### Patch Changes
|
|
68
|
+
|
|
69
|
+
- @ones-wb/tool@1.14.4
|
|
70
|
+
|
|
71
|
+
## 1.14.3
|
|
72
|
+
|
|
73
|
+
### Patch Changes
|
|
74
|
+
|
|
75
|
+
- @ones-wb/tool@1.14.3
|
|
76
|
+
|
|
77
|
+
## 1.14.2
|
|
78
|
+
|
|
79
|
+
### Patch Changes
|
|
80
|
+
|
|
81
|
+
- @ones-wb/tool@1.14.2
|
|
82
|
+
|
|
83
|
+
## 1.14.1
|
|
84
|
+
|
|
85
|
+
### Patch Changes
|
|
86
|
+
|
|
87
|
+
- @ones-wb/tool@1.14.1
|
|
88
|
+
|
|
89
|
+
## 1.14.0
|
|
90
|
+
|
|
91
|
+
### Minor Changes
|
|
92
|
+
|
|
93
|
+
- feat: ids
|
|
94
|
+
|
|
95
|
+
### Patch Changes
|
|
96
|
+
|
|
97
|
+
- Updated dependencies
|
|
98
|
+
- @ones-wb/tool@1.14.0
|
|
99
|
+
|
|
100
|
+
## 1.13.0
|
|
101
|
+
|
|
102
|
+
### Patch Changes
|
|
103
|
+
|
|
104
|
+
- @ones-wb/tool@1.13.0
|
|
105
|
+
|
|
106
|
+
## 1.12.0
|
|
107
|
+
|
|
108
|
+
### Patch Changes
|
|
109
|
+
|
|
110
|
+
- @ones-wb/tool@1.12.0
|
|
111
|
+
|
|
112
|
+
## 1.11.0
|
|
113
|
+
|
|
114
|
+
### Patch Changes
|
|
115
|
+
|
|
116
|
+
- @ones-wb/tool@1.11.0
|
|
117
|
+
|
|
118
|
+
## 1.10.3
|
|
119
|
+
|
|
120
|
+
### Patch Changes
|
|
121
|
+
|
|
122
|
+
- @ones-wb/tool@1.10.3
|
|
123
|
+
|
|
124
|
+
## 1.10.2
|
|
125
|
+
|
|
126
|
+
### Patch Changes
|
|
127
|
+
|
|
128
|
+
- @ones-wb/tool@1.10.2
|
|
129
|
+
|
|
130
|
+
## 1.10.1
|
|
131
|
+
|
|
132
|
+
### Patch Changes
|
|
133
|
+
|
|
134
|
+
- 8ca1be5: fix: xlsxToJSONAndValidate 调整返回值
|
|
135
|
+
- @ones-wb/tool@1.10.1
|
|
136
|
+
|
|
137
|
+
## 1.10.0
|
|
138
|
+
|
|
139
|
+
### Minor Changes
|
|
140
|
+
|
|
141
|
+
- 85bab00: fix: xlsx 用法变更
|
|
142
|
+
|
|
143
|
+
### Patch Changes
|
|
144
|
+
|
|
145
|
+
- @ones-wb/tool@1.10.0
|
|
146
|
+
|
|
147
|
+
## 1.9.0
|
|
148
|
+
|
|
149
|
+
### Patch Changes
|
|
150
|
+
|
|
151
|
+
- Updated dependencies [7cf6c1e]
|
|
152
|
+
- @ones-wb/tool@1.9.0
|
|
153
|
+
|
|
154
|
+
## 1.8.3
|
|
155
|
+
|
|
156
|
+
### Patch Changes
|
|
157
|
+
|
|
158
|
+
- @ones-wb/tool@1.8.3
|
|
159
|
+
|
|
160
|
+
## 1.8.2
|
|
161
|
+
|
|
162
|
+
### Patch Changes
|
|
163
|
+
|
|
164
|
+
- @ones-wb/tool@1.8.2
|
|
165
|
+
|
|
166
|
+
## 1.8.1
|
|
167
|
+
|
|
168
|
+
### Patch Changes
|
|
169
|
+
|
|
170
|
+
- @ones-wb/tool@1.8.1
|
|
171
|
+
|
|
172
|
+
## 1.8.0
|
|
173
|
+
|
|
174
|
+
### Patch Changes
|
|
175
|
+
|
|
176
|
+
- Updated dependencies [9c69791]
|
|
177
|
+
- @ones-wb/tool@1.8.0
|
|
178
|
+
|
|
179
|
+
## 1.7.2
|
|
180
|
+
|
|
181
|
+
### Patch Changes
|
|
182
|
+
|
|
183
|
+
- @ones-wb/tool@1.7.2
|
|
184
|
+
|
|
185
|
+
## 1.7.1
|
|
186
|
+
|
|
187
|
+
### Patch Changes
|
|
188
|
+
|
|
189
|
+
- ec92357: fix: xlsx 导入文件解析,忽略空行
|
|
190
|
+
- @ones-wb/tool@1.7.1
|
|
191
|
+
|
|
192
|
+
## 1.7.0
|
|
193
|
+
|
|
194
|
+
### Patch Changes
|
|
195
|
+
|
|
196
|
+
- Updated dependencies [bab0a97]
|
|
197
|
+
- @ones-wb/tool@1.7.0
|
|
198
|
+
|
|
199
|
+
## 1.6.0
|
|
200
|
+
|
|
201
|
+
### Patch Changes
|
|
202
|
+
|
|
203
|
+
- @ones-wb/tool@1.6.0
|
|
204
|
+
|
|
205
|
+
## 1.5.0
|
|
206
|
+
|
|
207
|
+
### Patch Changes
|
|
208
|
+
|
|
209
|
+
- Updated dependencies [a5191b1]
|
|
210
|
+
- @ones-wb/tool@1.5.0
|
|
211
|
+
|
|
212
|
+
## 1.4.1
|
|
213
|
+
|
|
214
|
+
### Patch Changes
|
|
215
|
+
|
|
216
|
+
- @ones-wb/tool@1.4.1
|
|
217
|
+
|
|
218
|
+
## 1.4.0
|
|
219
|
+
|
|
220
|
+
### Patch Changes
|
|
221
|
+
|
|
222
|
+
- @ones-wb/tool@1.4.0
|
|
223
|
+
|
|
224
|
+
## 1.3.3
|
|
225
|
+
|
|
226
|
+
### Patch Changes
|
|
227
|
+
|
|
228
|
+
- @ones-wb/tool@1.3.3
|
|
229
|
+
|
|
230
|
+
## 1.3.2
|
|
231
|
+
|
|
232
|
+
### Patch Changes
|
|
233
|
+
|
|
234
|
+
- Updated dependencies [f71184e]
|
|
235
|
+
- @ones-wb/tool@1.3.2
|
|
236
|
+
|
|
237
|
+
## 1.3.1
|
|
238
|
+
|
|
239
|
+
### Patch Changes
|
|
240
|
+
|
|
241
|
+
- feat: dayjs => baseTime
|
|
242
|
+
- Updated dependencies
|
|
243
|
+
- @ones-wb/tool@1.3.1
|
|
244
|
+
|
|
245
|
+
## 1.3.0
|
|
246
|
+
|
|
247
|
+
### Minor Changes
|
|
248
|
+
|
|
249
|
+
- 1.3.0
|
|
250
|
+
|
|
251
|
+
### Patch Changes
|
|
252
|
+
|
|
253
|
+
- Updated dependencies
|
|
254
|
+
- @ones-wb/tool@1.3.0
|
|
255
|
+
|
|
256
|
+
## 1.2.1
|
|
257
|
+
|
|
258
|
+
## 1.2.0
|
|
259
|
+
|
|
260
|
+
### Minor Changes
|
|
261
|
+
|
|
262
|
+
- feat: add @one-wb/file. 增加前端处理 xlsx 库.
|
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fe-free/file",
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org/"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"exceljs": "^4.4.0",
|
|
14
|
+
"file-saver": "^2.0.5",
|
|
15
|
+
"lodash-es": "^4.17.21"
|
|
16
|
+
},
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"dayjs": "~1.11.10"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { JSONToXlsx, xlsxToJSON, xlsxToJSONAndValidate } from './xlsx';
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Cell, Workbook, Worksheet } from 'exceljs';
|
|
2
|
+
import ExcelJS from 'exceljs';
|
|
3
|
+
import FileSaver from 'file-saver';
|
|
4
|
+
import { isNumber } from 'lodash-es';
|
|
5
|
+
|
|
6
|
+
type JSONSheetItem = {
|
|
7
|
+
name: string;
|
|
8
|
+
data: any[][];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function numberToDate(excelDate: number) {
|
|
12
|
+
if (!isNumber(excelDate)) {
|
|
13
|
+
return excelDate;
|
|
14
|
+
}
|
|
15
|
+
// let utc_days = Math.floor(excelDate - (25567 + 2))
|
|
16
|
+
// Excel 的 bug, 会把 1900 年当作闰年
|
|
17
|
+
// https://docs.microsoft.com/en-us/office/troubleshoot/excel/wrongly-assumes-1900-is-leap-year
|
|
18
|
+
// 东半球 & 0 时区 => 25567 + 2
|
|
19
|
+
// 西半球 => 25567 + 1
|
|
20
|
+
const step = new Date().getTimezoneOffset() <= 0 ? 25567 + 2 : 25567 + 1;
|
|
21
|
+
const utc_days = Math.floor(excelDate - step);
|
|
22
|
+
// 86400 => 24 * 60 * 60 => 一天的总秒数
|
|
23
|
+
const utc_value = utc_days * 86400;
|
|
24
|
+
// 一天的总毫秒数
|
|
25
|
+
const date_info = new Date(utc_value * 1000);
|
|
26
|
+
|
|
27
|
+
// 误差处理
|
|
28
|
+
const fractional_day = excelDate - Math.floor(excelDate) + 0.0000001;
|
|
29
|
+
// 自 1970 年至今的总秒数
|
|
30
|
+
let total_seconds = Math.floor(86400 * fractional_day);
|
|
31
|
+
const seconds = total_seconds % 60;
|
|
32
|
+
total_seconds -= seconds;
|
|
33
|
+
const hours = Math.floor(total_seconds / (60 * 60));
|
|
34
|
+
const minutes = Math.floor(total_seconds / 60) % 60;
|
|
35
|
+
|
|
36
|
+
return new Date(
|
|
37
|
+
date_info.getFullYear(),
|
|
38
|
+
date_info.getMonth(),
|
|
39
|
+
date_info.getDate(),
|
|
40
|
+
hours,
|
|
41
|
+
minutes,
|
|
42
|
+
seconds,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 1 worksheet.eachRow 本身会跳过空行
|
|
47
|
+
// 2 row.eachCell 会跳过空列, includeEmpty: 试了也没用,不能使用,具体可看源码。
|
|
48
|
+
// 尝试 maxColIndex 解决
|
|
49
|
+
function eachRowAndCell(worksheet: Worksheet, callback) {
|
|
50
|
+
let maxColIndex = 0;
|
|
51
|
+
|
|
52
|
+
worksheet.eachRow(function (row, rowNumber) {
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
maxColIndex = Math.max(maxColIndex, row._cells.length as number);
|
|
55
|
+
|
|
56
|
+
for (let colIndex = 0; colIndex < maxColIndex; colIndex++) {
|
|
57
|
+
const cell = row.getCell(colIndex + 1);
|
|
58
|
+
const colNumber = colIndex + 1;
|
|
59
|
+
|
|
60
|
+
callback({ cell, rowNumber, colNumber });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function worksheetToJSON(
|
|
66
|
+
worksheet: Worksheet,
|
|
67
|
+
processCell?: (info: { cell: Cell; colNumber: number; rowNumber: number }) => void,
|
|
68
|
+
) {
|
|
69
|
+
const sheet: any[][] = [];
|
|
70
|
+
|
|
71
|
+
eachRowAndCell(worksheet, ({ cell, colNumber, rowNumber }) => {
|
|
72
|
+
if (processCell) {
|
|
73
|
+
processCell({ cell, colNumber, rowNumber });
|
|
74
|
+
}
|
|
75
|
+
sheet[rowNumber - 1] = sheet[rowNumber - 1] || [];
|
|
76
|
+
sheet[rowNumber - 1][colNumber - 1] = cell.value;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// 挪掉空行。 空行会显示 null
|
|
80
|
+
return sheet.filter(Boolean);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface JSONToWorksheetOption {
|
|
84
|
+
processCell?: (info: { value: any; colNumber: number; rowNumber: number }) => object;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function JSONToWorksheet(json: any[][], worksheet: Worksheet, options?: JSONToWorksheetOption) {
|
|
88
|
+
const { processCell = (info) => ({ value: info.value }) } = options || {};
|
|
89
|
+
|
|
90
|
+
let maxColIndex = 0;
|
|
91
|
+
// 不能使用 Array.forEach,会忽略数组的 empty [,1,,]。 行不会 empty
|
|
92
|
+
for (let rowIndex = 0; rowIndex < json.length; rowIndex++) {
|
|
93
|
+
const row = json[rowIndex];
|
|
94
|
+
// 列需要取最大的,可能存在一系列因为 empty 导致 length 小。
|
|
95
|
+
maxColIndex = Math.max(maxColIndex, row.length);
|
|
96
|
+
for (let colIndex = 0; colIndex < maxColIndex; colIndex++) {
|
|
97
|
+
const value = row[colIndex];
|
|
98
|
+
const nCell = processCell({
|
|
99
|
+
value,
|
|
100
|
+
colNumber: colIndex + 1,
|
|
101
|
+
rowNumber: rowIndex + 1,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
Object.assign(worksheet.getCell(rowIndex + 1, colIndex + 1), nCell);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 复制 value note font
|
|
110
|
+
async function copyWorkbook(workbook: Workbook): Promise<Workbook> {
|
|
111
|
+
const newWorkbook = new ExcelJS.Workbook() as Workbook;
|
|
112
|
+
|
|
113
|
+
workbook.eachSheet((worksheet) => {
|
|
114
|
+
const newWorksheet = newWorkbook.addWorksheet(worksheet.name);
|
|
115
|
+
|
|
116
|
+
eachRowAndCell(worksheet, ({ cell, colNumber, rowNumber }) => {
|
|
117
|
+
newWorksheet.getCell(rowNumber, colNumber).value = cell.value;
|
|
118
|
+
|
|
119
|
+
if (cell.note) {
|
|
120
|
+
newWorksheet.getCell(rowNumber, colNumber).note = cell.note;
|
|
121
|
+
newWorksheet.getCell(rowNumber, colNumber).font = {
|
|
122
|
+
color: {
|
|
123
|
+
argb: 'FFFF0000',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return newWorkbook;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function readFile(file: File): Promise<Buffer> {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
const reader = new FileReader();
|
|
136
|
+
|
|
137
|
+
reader.onload = (data) => {
|
|
138
|
+
resolve(data.target?.result as Buffer);
|
|
139
|
+
};
|
|
140
|
+
reader.readAsArrayBuffer(file);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function exportFile(workbook: Workbook, fileName?: string) {
|
|
145
|
+
const buffer = await workbook.xlsx.writeBuffer();
|
|
146
|
+
const blob = new window.Blob([buffer], {
|
|
147
|
+
type: 'application/octet-stream',
|
|
148
|
+
});
|
|
149
|
+
// fileName 中不能包含特殊字符
|
|
150
|
+
FileSaver.saveAs(blob, fileName ? fileName.replace(/[<>\\:;?/*|]/g, '-') : 'data.xlsx');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { copyWorkbook, exportFile, JSONToWorksheet, numberToDate, readFile, worksheetToJSON };
|
|
154
|
+
export type { JSONSheetItem };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import ExcelJS from 'exceljs';
|
|
2
|
+
import type { JSONSheetItem } from './helper';
|
|
3
|
+
import { exportFile, JSONToWorksheet } from './helper';
|
|
4
|
+
|
|
5
|
+
interface JSONToXlsxOptions {
|
|
6
|
+
/** 默认 data.xlsx */
|
|
7
|
+
fileName?: string;
|
|
8
|
+
/** 用法参考 exceljs。可做比如 标红、备注等 */
|
|
9
|
+
processCell?: (info: { value: any; colNumber: number; rowNumber: number }) => {
|
|
10
|
+
value: any;
|
|
11
|
+
font?: object;
|
|
12
|
+
note?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** xlsxJSON 转 xlsx,其中日期格式按字符串输出 */
|
|
17
|
+
async function JSONToXlsx(xlsxJSON: JSONSheetItem[], options?: JSONToXlsxOptions): Promise<void> {
|
|
18
|
+
const { fileName = 'data.xlsx', processCell } = options || {};
|
|
19
|
+
const workbook = new ExcelJS.Workbook();
|
|
20
|
+
|
|
21
|
+
// json 写入 workbook
|
|
22
|
+
xlsxJSON.forEach((sheet) => {
|
|
23
|
+
const worksheet = workbook.addWorksheet(sheet.name);
|
|
24
|
+
JSONToWorksheet(sheet.data, worksheet, {
|
|
25
|
+
processCell,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// export
|
|
30
|
+
await exportFile(workbook, fileName);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { JSONToXlsx };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { JSONToXlsx, xlsxToJSON, xlsxToJSONAndValidate } from '@fe-free/file';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import { isDate, isNumber, isString } from 'lodash-es';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: '@fe-free/file/xlsx',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const XlsxToJSON = () => (
|
|
11
|
+
<>
|
|
12
|
+
<div>
|
|
13
|
+
导入 xlsx
|
|
14
|
+
<br />
|
|
15
|
+
<input
|
|
16
|
+
type="file"
|
|
17
|
+
onChange={(e) => {
|
|
18
|
+
const file = e.target.files?.[0];
|
|
19
|
+
if (file) {
|
|
20
|
+
xlsxToJSON(file).then((json) => {
|
|
21
|
+
const element = document.querySelector('.data1');
|
|
22
|
+
if (element) {
|
|
23
|
+
element.innerHTML = JSON.stringify(json, null, 2);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
<pre className="data1" />
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const JSONToXlsxExample = () => (
|
|
35
|
+
<>
|
|
36
|
+
<pre>
|
|
37
|
+
数据源:
|
|
38
|
+
{JSON.stringify(
|
|
39
|
+
[
|
|
40
|
+
{
|
|
41
|
+
name: 'sheet1',
|
|
42
|
+
data: [
|
|
43
|
+
['日期', '必填项'],
|
|
44
|
+
['2025/1/1', '是'],
|
|
45
|
+
['2025/12/31', '否'],
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
null,
|
|
50
|
+
2,
|
|
51
|
+
)}
|
|
52
|
+
</pre>
|
|
53
|
+
<div>
|
|
54
|
+
<button
|
|
55
|
+
onClick={() => {
|
|
56
|
+
JSONToXlsx([
|
|
57
|
+
{
|
|
58
|
+
name: 'sheet1',
|
|
59
|
+
data: [
|
|
60
|
+
['日期', '必填项'],
|
|
61
|
+
['2025/1/1', '是'],
|
|
62
|
+
['2025/12/31', '否'],
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
export
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
<div>
|
|
72
|
+
<button
|
|
73
|
+
onClick={() => {
|
|
74
|
+
JSONToXlsx(
|
|
75
|
+
[
|
|
76
|
+
{
|
|
77
|
+
name: 'sheet1',
|
|
78
|
+
data: [
|
|
79
|
+
['日期', '必填项'],
|
|
80
|
+
['2025/1/1', '是'],
|
|
81
|
+
['2024/12/31', '否'],
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
{
|
|
86
|
+
processCell: ({ rowNumber, colNumber, value }) => {
|
|
87
|
+
if (colNumber === 1 && rowNumber > 1) {
|
|
88
|
+
const isValid = dayjs(value).year() < dayjs().year();
|
|
89
|
+
if (isValid) {
|
|
90
|
+
return {
|
|
91
|
+
value,
|
|
92
|
+
font: {
|
|
93
|
+
color: {
|
|
94
|
+
argb: 'FFFF0000',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
note: '日期需要当前年份',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { value };
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
export(标红&备注)
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
</>
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
export const XlsxToJSONAndValidate = () => {
|
|
114
|
+
const [validateResult, setValidateResult] = React.useState<any>(null);
|
|
115
|
+
|
|
116
|
+
const validate = ({ cell, colNumber }: { cell: any; colNumber: number }) => {
|
|
117
|
+
if (
|
|
118
|
+
isString(cell.value) &&
|
|
119
|
+
(cell.value?.startsWith('日期') || cell.value?.startsWith('必填项'))
|
|
120
|
+
) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let message = '';
|
|
125
|
+
|
|
126
|
+
// 日期列
|
|
127
|
+
if (colNumber === 1) {
|
|
128
|
+
// 空值
|
|
129
|
+
if (!cell.value) {
|
|
130
|
+
message = '错误';
|
|
131
|
+
} else {
|
|
132
|
+
// 如果是数字
|
|
133
|
+
if (isNumber(cell.value)) {
|
|
134
|
+
message = '格式错误';
|
|
135
|
+
}
|
|
136
|
+
// 如果是日期类型
|
|
137
|
+
if (isDate(cell.value)) {
|
|
138
|
+
const d = dayjs(cell.value);
|
|
139
|
+
// 非法
|
|
140
|
+
if (!d.isValid()) {
|
|
141
|
+
message = '格式错误';
|
|
142
|
+
}
|
|
143
|
+
// 超出范围
|
|
144
|
+
if (Math.abs(d.year() - dayjs().year()) > 1) {
|
|
145
|
+
message = '范围错误';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// 如果是字符串
|
|
149
|
+
else if (isString(cell.value)) {
|
|
150
|
+
const reg = /^\d{4}([./-])\d{1,2}([./-])\d{1,2}$/g;
|
|
151
|
+
|
|
152
|
+
// 不合法
|
|
153
|
+
if (!reg.test(cell.value)) {
|
|
154
|
+
message = '格式错误';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 超出范围
|
|
158
|
+
const d = dayjs(cell.value);
|
|
159
|
+
if (Math.abs(d.year() - dayjs().year()) > 1) {
|
|
160
|
+
message = '范围错误';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// 类型列
|
|
166
|
+
else if (colNumber === 2) {
|
|
167
|
+
// 空值
|
|
168
|
+
if (!cell.value) {
|
|
169
|
+
message = '不能为空';
|
|
170
|
+
} else {
|
|
171
|
+
const isValid = ['是', '否'].includes(cell.value);
|
|
172
|
+
if (!isValid) {
|
|
173
|
+
message = '格式错误';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (message) {
|
|
179
|
+
return { message };
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<>
|
|
185
|
+
<div>
|
|
186
|
+
导入 xlsx 和 校验(假如第一列为日期限制当前年份,第二列为[是、否])
|
|
187
|
+
<input
|
|
188
|
+
type="file"
|
|
189
|
+
onChange={(e) => {
|
|
190
|
+
const file = e.target.files?.[0];
|
|
191
|
+
if (file) {
|
|
192
|
+
xlsxToJSONAndValidate(file, validate).then((res) => {
|
|
193
|
+
setValidateResult(res);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
<pre>{JSON.stringify(validateResult, null, 2)}</pre>
|
|
200
|
+
<button onClick={() => validateResult?.exportXlsx()} disabled={!validateResult}>
|
|
201
|
+
export with validate
|
|
202
|
+
</button>
|
|
203
|
+
</>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Workbook } from 'exceljs';
|
|
2
|
+
import ExcelJS from 'exceljs';
|
|
3
|
+
import type { JSONSheetItem } from './helper';
|
|
4
|
+
import { readFile, worksheetToJSON } from './helper';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* xlsx 转 json [ {name: 'sheet1', data: [ [], [] ]} ]
|
|
8
|
+
*/
|
|
9
|
+
async function xlsxToJSON(file: File): Promise<JSONSheetItem[]> {
|
|
10
|
+
const workbook = new ExcelJS.Workbook() as Workbook;
|
|
11
|
+
|
|
12
|
+
// 读 file
|
|
13
|
+
const buffer = await readFile(file);
|
|
14
|
+
await workbook.xlsx.load(buffer);
|
|
15
|
+
|
|
16
|
+
// 转换成 json
|
|
17
|
+
const xlsxJSON: JSONSheetItem[] = [];
|
|
18
|
+
workbook.eachSheet((worksheet) => {
|
|
19
|
+
const data = worksheetToJSON(worksheet);
|
|
20
|
+
|
|
21
|
+
xlsxJSON.push({
|
|
22
|
+
name: worksheet.name,
|
|
23
|
+
data,
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return xlsxJSON;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { xlsxToJSON };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Cell, Workbook } from 'exceljs';
|
|
2
|
+
import ExcelJS from 'exceljs';
|
|
3
|
+
import type { JSONSheetItem } from './helper';
|
|
4
|
+
import { copyWorkbook, exportFile, readFile, worksheetToJSON } from './helper';
|
|
5
|
+
|
|
6
|
+
// 如果错误这返回错误信息,这将会修改 xlsx,且标红和批注.
|
|
7
|
+
// col row 从 1 开始
|
|
8
|
+
type xlsxToJSONAndValidateValidate = (info: {
|
|
9
|
+
cell: Cell;
|
|
10
|
+
colNumber: number;
|
|
11
|
+
rowNumber: number;
|
|
12
|
+
}) => undefined | { message };
|
|
13
|
+
|
|
14
|
+
interface xlsxToJSONAndValidateOptions {
|
|
15
|
+
fileName?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface xlsxToJSONAndValidateResult {
|
|
19
|
+
data: JSONSheetItem[];
|
|
20
|
+
// 每个 sheet 是否有错误
|
|
21
|
+
hasErrors: boolean[];
|
|
22
|
+
// 第一个 sheet 是否有错误
|
|
23
|
+
firstError: boolean;
|
|
24
|
+
exportXlsx: () => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function xlsxToJSONAndValidate(
|
|
28
|
+
file: File,
|
|
29
|
+
validate: xlsxToJSONAndValidateValidate,
|
|
30
|
+
options?: xlsxToJSONAndValidateOptions,
|
|
31
|
+
): Promise<xlsxToJSONAndValidateResult> {
|
|
32
|
+
const { fileName = 'data.xlsx' } = options || {};
|
|
33
|
+
|
|
34
|
+
const workbook = new ExcelJS.Workbook() as Workbook;
|
|
35
|
+
|
|
36
|
+
// 读 file
|
|
37
|
+
const buffer = await readFile(file);
|
|
38
|
+
await workbook.xlsx.load(buffer);
|
|
39
|
+
|
|
40
|
+
// 错误处理
|
|
41
|
+
const hasErrors: boolean[] = [];
|
|
42
|
+
function doValidate(info, sheetIndex) {
|
|
43
|
+
const validateResult = validate(info);
|
|
44
|
+
if (validateResult) {
|
|
45
|
+
Object.assign(info.cell, {
|
|
46
|
+
note: validateResult.message,
|
|
47
|
+
font: {
|
|
48
|
+
color: {
|
|
49
|
+
argb: 'FFFF0000',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
hasErrors[sheetIndex] = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let sheetIndex = 0;
|
|
59
|
+
// 转换成 json
|
|
60
|
+
const xlsxJSON: JSONSheetItem[] = [];
|
|
61
|
+
workbook.eachSheet((worksheet) => {
|
|
62
|
+
const data = worksheetToJSON(worksheet, (info) => {
|
|
63
|
+
doValidate(info, sheetIndex);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
xlsxJSON.push({
|
|
67
|
+
name: worksheet.name,
|
|
68
|
+
data,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
sheetIndex += 1;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// 导出错误的 xlsx
|
|
75
|
+
async function exportXlsx() {
|
|
76
|
+
const newWorkbook = await copyWorkbook(workbook);
|
|
77
|
+
await exportFile(newWorkbook, fileName);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
data: xlsxJSON,
|
|
82
|
+
hasErrors,
|
|
83
|
+
firstError: !!hasErrors[0],
|
|
84
|
+
exportXlsx,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { xlsxToJSONAndValidate };
|