@ticatec/batch-data-uploader 0.0.12 → 0.1.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/dist/BaseEncodingTemplate.d.ts +18 -1
- package/dist/BaseEncodingTemplate.js +71 -17
- package/dist/BaseTemplate.d.ts +3 -5
- package/dist/BaseTemplate.js +74 -31
- package/dist/BaseUploadTemplate.d.ts +68 -3
- package/dist/BaseUploadTemplate.js +274 -43
- package/dist/EncodingWizard.svelte +6 -6
- package/dist/FileUploadWizard.svelte +102 -19
- package/dist/i18n_res/i18nRes.d.ts +2 -0
- package/dist/i18n_res/i18nRes.js +27 -0
- package/dist/i18n_res/index.d.ts +2 -0
- package/dist/i18n_res/index.js +2 -0
- package/package.json +3 -3
- package/dist/i18n_resources/batch_cn_resource.d.ts +0 -22
- package/dist/i18n_resources/batch_cn_resource.js +0 -22
- package/dist/i18n_resources/batch_en_resource.d.ts +0 -27
- package/dist/i18n_resources/batch_en_resource.js +0 -27
- package/dist/i18n_resources/i18nKeys.d.ts +0 -79
- package/dist/i18n_resources/i18nKeys.js +0 -80
- package/dist/i18n_resources/index.d.ts +0 -3
- package/dist/i18n_resources/index.js +0 -3
|
@@ -1,25 +1,25 @@
|
|
|
1
|
+
// 改进的BaseUploadTemplate.ts - 完全清理元数据相关代码
|
|
1
2
|
import BaseTemplate from "./BaseTemplate";
|
|
2
|
-
import { getI18nText } from "@ticatec/i18n";
|
|
3
|
-
import i18nKeys from "./i18n_resources/i18nKeys";
|
|
4
3
|
import utils from "./utils";
|
|
5
4
|
import * as XLSX from 'xlsx';
|
|
5
|
+
import i18nRes from "./i18n_res/i18nRes";
|
|
6
6
|
const statusColumn = {
|
|
7
|
-
text:
|
|
7
|
+
text: i18nRes.labelStatus,
|
|
8
8
|
width: 240,
|
|
9
9
|
resizable: true,
|
|
10
10
|
formatter: row => {
|
|
11
11
|
if (row.status == 'P') {
|
|
12
|
-
return
|
|
12
|
+
return i18nRes.status.pending;
|
|
13
13
|
}
|
|
14
14
|
else if (row.status == 'U') {
|
|
15
|
-
return
|
|
15
|
+
return i18nRes.status.uploading;
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
18
|
if (row.error) {
|
|
19
19
|
return row.errorText;
|
|
20
20
|
}
|
|
21
21
|
else {
|
|
22
|
-
return
|
|
22
|
+
return i18nRes.status.successful;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -27,9 +27,10 @@ const statusColumn = {
|
|
|
27
27
|
export default class BaseUploadTemplate extends BaseTemplate {
|
|
28
28
|
batchSize;
|
|
29
29
|
updateProgressStatus = null;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
_uploadAborted = false;
|
|
31
|
+
constructor(batchSize = 50) {
|
|
32
|
+
super();
|
|
33
|
+
this.batchSize = Math.max(1, batchSize);
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
36
|
* 状态更新的监听器
|
|
@@ -38,25 +39,83 @@ export default class BaseUploadTemplate extends BaseTemplate {
|
|
|
38
39
|
setProgressStatusListener(value) {
|
|
39
40
|
this.updateProgressStatus = value;
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* 中止上传
|
|
44
|
+
*/
|
|
45
|
+
abortUpload() {
|
|
46
|
+
this._uploadAborted = true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 重置上传状态
|
|
50
|
+
*/
|
|
51
|
+
resetUploadStatus() {
|
|
52
|
+
this._uploadAborted = false;
|
|
53
|
+
this._list.forEach(item => {
|
|
54
|
+
if (item.status !== 'D') {
|
|
55
|
+
item.status = 'P';
|
|
56
|
+
delete item.error;
|
|
57
|
+
delete item.errorText;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
41
61
|
/**
|
|
42
62
|
* 上传数据
|
|
43
63
|
*/
|
|
44
64
|
async upload() {
|
|
65
|
+
this._uploadAborted = false;
|
|
45
66
|
for (let i = 0; i < this._list.length; i += this.batchSize) {
|
|
67
|
+
// 检查是否需要中止
|
|
68
|
+
if (this._uploadAborted) {
|
|
69
|
+
console.log('Upload aborted by user');
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
46
72
|
const chunk = this._list.slice(i, i + this.batchSize);
|
|
47
|
-
chunk.forEach(item =>
|
|
73
|
+
chunk.forEach(item => {
|
|
74
|
+
if (item.status !== 'D') { // 不重复处理已完成的项目
|
|
75
|
+
item.status = 'U';
|
|
76
|
+
delete item.error;
|
|
77
|
+
delete item.errorText;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
48
80
|
this.updateProgressStatus?.();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
try {
|
|
82
|
+
// 只上传未完成的项目
|
|
83
|
+
const pendingItems = chunk.filter(item => item.status === 'U');
|
|
84
|
+
if (pendingItems.length === 0) {
|
|
85
|
+
continue; // 跳过已完成的批次
|
|
86
|
+
}
|
|
87
|
+
let list = await this.uploadData(this.extractData(pendingItems));
|
|
88
|
+
// 验证结果长度是否匹配
|
|
89
|
+
if (list.length !== pendingItems.length) {
|
|
90
|
+
console.warn(`Upload results length (${list.length}) doesn't match pending items (${pendingItems.length})`);
|
|
91
|
+
}
|
|
92
|
+
for (let j = 0; j < pendingItems.length; j++) {
|
|
93
|
+
const item = pendingItems[j];
|
|
94
|
+
item.status = 'D';
|
|
95
|
+
if (list[j]) {
|
|
96
|
+
if (list[j].error) {
|
|
97
|
+
item.error = list[j].error;
|
|
98
|
+
item.errorText = list[j].errorText;
|
|
99
|
+
}
|
|
56
100
|
}
|
|
57
101
|
}
|
|
58
102
|
}
|
|
103
|
+
catch (batchError) {
|
|
104
|
+
console.error(`Batch upload error for items ${i}-${i + this.batchSize - 1}:`, batchError);
|
|
105
|
+
// 标记整个批次为失败
|
|
106
|
+
chunk.forEach(item => {
|
|
107
|
+
if (item.status === 'U') {
|
|
108
|
+
item.status = 'D';
|
|
109
|
+
item.error = batchError;
|
|
110
|
+
item.errorText = batchError instanceof Error ? batchError.message : 'Batch upload failed';
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
59
114
|
this.updateProgressStatus?.();
|
|
115
|
+
// 添加小延迟以避免过快的请求
|
|
116
|
+
if (i + this.batchSize < this._list.length && !this._uploadAborted) {
|
|
117
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
118
|
+
}
|
|
60
119
|
}
|
|
61
120
|
}
|
|
62
121
|
/**
|
|
@@ -74,40 +133,212 @@ export default class BaseUploadTemplate extends BaseTemplate {
|
|
|
74
133
|
return [...super.columns, statusColumn];
|
|
75
134
|
}
|
|
76
135
|
/**
|
|
77
|
-
*
|
|
136
|
+
* 获取上传统计信息
|
|
137
|
+
*/
|
|
138
|
+
get uploadStats() {
|
|
139
|
+
const pending = this._list.filter(item => item.status === 'P').length;
|
|
140
|
+
const uploading = this._list.filter(item => item.status === 'U').length;
|
|
141
|
+
const completed = this._list.filter(item => item.status === 'D').length;
|
|
142
|
+
const success = this._list.filter(item => item.status === 'D' && !item.error).length;
|
|
143
|
+
const failed = this._list.filter(item => item.status === 'D' && item.error).length;
|
|
144
|
+
return {
|
|
145
|
+
total: this._list.length,
|
|
146
|
+
pending,
|
|
147
|
+
uploading,
|
|
148
|
+
completed,
|
|
149
|
+
success,
|
|
150
|
+
failed
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 导出处理异常的数据 - 基础版本(保持向后兼容)
|
|
78
155
|
* @param filename
|
|
79
156
|
*/
|
|
80
157
|
exportErrorRowsToExcel(filename) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
158
|
+
this.exportErrorData(filename, { originalFormat: false });
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 导出错误数据 - 增强版本
|
|
162
|
+
* @param filename
|
|
163
|
+
* @param options 导出选项
|
|
164
|
+
*/
|
|
165
|
+
exportErrorData(filename, options = {}) {
|
|
166
|
+
const { includeAllData = false, separateSheets = true, // 默认分离工作表
|
|
167
|
+
originalFormat = true } = options;
|
|
168
|
+
try {
|
|
169
|
+
const workbook = XLSX.utils.book_new();
|
|
170
|
+
// 第一页:重传数据(原始格式)- 用户可以直接修改和重新导入
|
|
171
|
+
if (originalFormat) {
|
|
172
|
+
this._exportOriginalFormat(workbook, includeAllData);
|
|
173
|
+
}
|
|
174
|
+
// 第二页:异常详情 - 用于查看错误信息
|
|
175
|
+
if (separateSheets) {
|
|
176
|
+
this._exportErrorDetails(workbook, includeAllData);
|
|
177
|
+
}
|
|
178
|
+
const wbout = XLSX.write(workbook, {
|
|
179
|
+
bookType: 'xlsx',
|
|
180
|
+
type: 'array'
|
|
181
|
+
});
|
|
182
|
+
// 创建 Blob 并触发下载
|
|
183
|
+
const blob = new Blob([wbout], {
|
|
184
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
185
|
+
});
|
|
186
|
+
const url = URL.createObjectURL(blob);
|
|
187
|
+
const a = document.createElement('a');
|
|
188
|
+
a.href = url;
|
|
189
|
+
a.download = filename.endsWith('.xlsx') ? filename : `${filename}.xlsx`;
|
|
190
|
+
a.style.display = 'none';
|
|
191
|
+
document.body.appendChild(a);
|
|
192
|
+
a.click();
|
|
193
|
+
document.body.removeChild(a);
|
|
194
|
+
URL.revokeObjectURL(url);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error('Failed to export error data:', error);
|
|
198
|
+
throw new Error(`Failed to export error data: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 导出原始格式 - 可以重新导入和上传
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
_exportOriginalFormat(workbook, includeAllData) {
|
|
206
|
+
// 获取需要导出的数据
|
|
207
|
+
const dataToExport = includeAllData
|
|
208
|
+
? this._list
|
|
209
|
+
: this._list.filter(row => row.status === 'D' && row.error);
|
|
210
|
+
if (dataToExport.length === 0) {
|
|
211
|
+
console.warn('No data to export in original format');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// 获取可见且非虚拟的列
|
|
215
|
+
const exportColumns = this.getMetaColumns().filter(col => col.visible !== false && !col.dummy && !col.ignore);
|
|
216
|
+
// 创建标题行(与原始导入格式一致)
|
|
217
|
+
const headers = exportColumns.map(col => col.text || col.field);
|
|
218
|
+
// 创建数据行
|
|
219
|
+
const rows = dataToExport.map(item => {
|
|
220
|
+
return exportColumns.map(col => {
|
|
221
|
+
const value = utils.getNestedValue(item.data, col.field);
|
|
222
|
+
// 如果有格式化函数的逆向操作,在这里处理
|
|
223
|
+
return this._formatValueForExport(value, col);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
const worksheetData = [headers, ...rows];
|
|
227
|
+
const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
|
|
228
|
+
// 设置列宽
|
|
229
|
+
this._setColumnWidths(worksheet, [headers, ...rows]);
|
|
230
|
+
// 添加到工作簿 - 重传数据作为第一个工作表
|
|
231
|
+
const sheetName = includeAllData ? '全部数据重传' : '失败数据重传';
|
|
232
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 导出错误详情 - 作为第二个工作表
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
_exportErrorDetails(workbook, includeAllData) {
|
|
239
|
+
const errorRows = includeAllData
|
|
240
|
+
? this._list
|
|
241
|
+
: this._list.filter(row => row.status === 'D' && row.error);
|
|
242
|
+
if (errorRows.length === 0)
|
|
243
|
+
return;
|
|
244
|
+
const visibleColumns = this.getMetaColumns().filter(col => col.visible !== false && !col.dummy);
|
|
245
|
+
const headers = [
|
|
246
|
+
'行号',
|
|
247
|
+
...visibleColumns.map(col => col.text || col.field),
|
|
248
|
+
'结果',
|
|
249
|
+
'错误原因'
|
|
250
|
+
];
|
|
251
|
+
const rows = errorRows.map((row, index) => {
|
|
252
|
+
const values = visibleColumns.map(col => {
|
|
87
253
|
return utils.getNestedValue(row.data, col.field);
|
|
88
254
|
});
|
|
89
|
-
|
|
255
|
+
// 简化状态:只有成功或失败
|
|
256
|
+
const result = row.error ? '✗ 失败' : '✓ 成功';
|
|
257
|
+
return [
|
|
258
|
+
index + 1, // 行号
|
|
259
|
+
...values,
|
|
260
|
+
result,
|
|
261
|
+
row.errorText || row.error || ''
|
|
262
|
+
];
|
|
90
263
|
});
|
|
91
|
-
const worksheetData = [
|
|
264
|
+
const worksheetData = [headers, ...rows];
|
|
92
265
|
const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
266
|
+
// 设置列宽 - 错误原因列设置更宽
|
|
267
|
+
const colWidths = headers.map((header, colIndex) => {
|
|
268
|
+
let maxLength = header.length;
|
|
269
|
+
// 计算该列的最大长度
|
|
270
|
+
rows.forEach(row => {
|
|
271
|
+
const cellValue = String(row[colIndex] || '');
|
|
272
|
+
maxLength = Math.max(maxLength, cellValue.length);
|
|
273
|
+
});
|
|
274
|
+
// 错误原因列设置更宽
|
|
275
|
+
if (header === '错误原因') {
|
|
276
|
+
return { wch: Math.min(Math.max(maxLength, 20), 60) };
|
|
277
|
+
}
|
|
278
|
+
return { wch: Math.min(Math.max(maxLength + 2, 10), 30) };
|
|
279
|
+
});
|
|
280
|
+
worksheet['!cols'] = colWidths;
|
|
281
|
+
const sheetName = includeAllData ? '上传详情' : '异常详情';
|
|
282
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* 格式化值用于导出
|
|
286
|
+
* @private
|
|
287
|
+
*/
|
|
288
|
+
_formatValueForExport(value, column) {
|
|
289
|
+
// 如果列有解析器,可能需要逆向格式化
|
|
290
|
+
// 这里可以根据具体的parser类型进行逆向处理
|
|
291
|
+
if (value === null || value === undefined) {
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
// 日期格式化
|
|
295
|
+
if (value instanceof Date) {
|
|
296
|
+
return value.toISOString().split('T')[0]; // YYYY-MM-DD格式
|
|
297
|
+
}
|
|
298
|
+
// 数字格式化
|
|
299
|
+
if (typeof value === 'number') {
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
// 布尔值格式化
|
|
303
|
+
if (typeof value === 'boolean') {
|
|
304
|
+
return value ? '是' : '否';
|
|
305
|
+
}
|
|
306
|
+
return String(value);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* 设置列宽
|
|
310
|
+
* @private
|
|
311
|
+
*/
|
|
312
|
+
_setColumnWidths(worksheet, data) {
|
|
313
|
+
const colWidths = data[0]?.map((_, colIndex) => {
|
|
314
|
+
const maxLength = Math.max(...data.map(row => {
|
|
315
|
+
const cellValue = row[colIndex];
|
|
316
|
+
return String(cellValue || '').length;
|
|
317
|
+
}));
|
|
318
|
+
return { wch: Math.min(Math.max(maxLength + 2, 10), 50) };
|
|
319
|
+
}) || [];
|
|
320
|
+
worksheet['!cols'] = colWidths;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* 快速导出错误数据用于重新上传
|
|
324
|
+
* @param filename
|
|
325
|
+
*/
|
|
326
|
+
exportErrorsForReupload(filename) {
|
|
327
|
+
this.exportErrorData(filename, {
|
|
328
|
+
includeAllData: false,
|
|
329
|
+
separateSheets: false,
|
|
330
|
+
originalFormat: true
|
|
98
331
|
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* 导出完整报告(包含所有数据和详情)
|
|
335
|
+
* @param filename
|
|
336
|
+
*/
|
|
337
|
+
exportFullReport(filename) {
|
|
338
|
+
this.exportErrorData(filename, {
|
|
339
|
+
includeAllData: true,
|
|
340
|
+
separateSheets: true,
|
|
341
|
+
originalFormat: true
|
|
102
342
|
});
|
|
103
|
-
const url = URL.createObjectURL(blob);
|
|
104
|
-
const a = document.createElement('a');
|
|
105
|
-
a.href = url;
|
|
106
|
-
a.download = filename;
|
|
107
|
-
a.style.display = 'none';
|
|
108
|
-
document.body.appendChild(a);
|
|
109
|
-
a.click();
|
|
110
|
-
document.body.removeChild(a);
|
|
111
|
-
URL.revokeObjectURL(url);
|
|
112
343
|
}
|
|
113
344
|
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import Dialog from "@ticatec/uniface-element/Dialog";
|
|
3
3
|
import type {ButtonAction, ButtonActions} from "@ticatec/uniface-element/ActionBar";
|
|
4
4
|
import DataTable, {type IndicatorColumn} from "@ticatec/uniface-element/DataTable";
|
|
5
|
-
import {getI18nText} from "@ticatec/i18n";
|
|
6
5
|
import Box from "@ticatec/uniface-element/Box"
|
|
7
6
|
import {onMount} from "svelte";
|
|
8
7
|
import type DataColumn from "./DataColumn";
|
|
9
|
-
import i18nKeys from "./i18n_resources/i18nKeys";
|
|
10
8
|
import type BaseEncodingTemplate from "./BaseEncodingTemplate";
|
|
9
|
+
import i18nRes from "./i18n_res/i18nRes";
|
|
10
|
+
import {i18nUtils} from "@ticatec/i18n";
|
|
11
11
|
|
|
12
12
|
export let title: string;
|
|
13
13
|
export let width: string = "800px";
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
const btnChoose: ButtonAction = {
|
|
21
|
-
label:
|
|
21
|
+
label: i18nRes.button.open,
|
|
22
22
|
type: 'primary',
|
|
23
23
|
handler: () => {
|
|
24
24
|
uploadField.value = '';
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const btnConfirm: ButtonAction = {
|
|
30
|
-
label:
|
|
30
|
+
label: i18nRes.button.confirm,
|
|
31
31
|
type: 'primary',
|
|
32
32
|
handler: ()=> {
|
|
33
33
|
confirmCallback?.(template.dataList);
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
const parseExcelFile = async (excelFile: File) => {
|
|
44
44
|
if (excelFile) {
|
|
45
45
|
filename = excelFile.name;
|
|
46
|
-
window.Indicator.show(
|
|
46
|
+
window.Indicator.show(i18nRes.parsing);
|
|
47
47
|
try {
|
|
48
48
|
await template.parseExcelFile(excelFile);
|
|
49
49
|
list = template.list;
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
} catch (ex) {
|
|
54
54
|
console.error(ex);
|
|
55
|
-
window.Toast.show(
|
|
55
|
+
window.Toast.show(i18nUtils.formatText(i18nRes.parseFailure, {name: excelFile.name}));
|
|
56
56
|
} finally {
|
|
57
57
|
window.Indicator.hide();
|
|
58
58
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
<!-- 更新的FileUploadWizard.svelte - 彻底清理includeMetadata -->
|
|
1
2
|
<script lang="ts">
|
|
2
3
|
import Dialog from "@ticatec/uniface-element/Dialog";
|
|
3
4
|
import type {ButtonAction, ButtonActions} from "@ticatec/uniface-element/ActionBar";
|
|
4
5
|
import DataTable, {type IndicatorColumn} from "@ticatec/uniface-element/DataTable";
|
|
5
|
-
import {getI18nText} from "@ticatec/i18n";
|
|
6
6
|
import Box from "@ticatec/uniface-element/Box"
|
|
7
7
|
import {onMount} from "svelte";
|
|
8
8
|
import type DataColumn from "./DataColumn";
|
|
9
|
-
import i18nKeys from "./i18n_resources/i18nKeys";
|
|
10
9
|
import type BaseUploadTemplate from "./BaseUploadTemplate.js";
|
|
10
|
+
import i18nRes from "./i18n_res/i18nRes";
|
|
11
|
+
import {i18nUtils} from "@ticatec/i18n";
|
|
11
12
|
|
|
12
13
|
export let title: string;
|
|
13
14
|
export let width: string = "800px";
|
|
@@ -20,9 +21,8 @@
|
|
|
20
21
|
|
|
21
22
|
let status: ProcessStatus = 'Init';
|
|
22
23
|
|
|
23
|
-
|
|
24
24
|
const btnChoose: ButtonAction = {
|
|
25
|
-
label:
|
|
25
|
+
label: i18nRes.button.open,
|
|
26
26
|
type: 'primary',
|
|
27
27
|
handler: () => {
|
|
28
28
|
uploadField.value = '';
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const btnUpload: ButtonAction = {
|
|
34
|
-
label:
|
|
34
|
+
label: i18nRes.button.upload,
|
|
35
35
|
type: 'primary',
|
|
36
36
|
handler: async ()=> {
|
|
37
37
|
status = 'Uploading';
|
|
@@ -44,11 +44,52 @@
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// 原有的保存按钮(向后兼容)
|
|
47
48
|
const btnSave: ButtonAction = {
|
|
48
|
-
label:
|
|
49
|
+
label: i18nRes.button.save,
|
|
49
50
|
type: 'primary',
|
|
50
51
|
handler: async ()=> {
|
|
51
|
-
|
|
52
|
+
const baseFilename = filename.replace(/\.[^/.]+$/, ""); // 移除扩展名
|
|
53
|
+
template.exportErrorRowsToExcel(`error-${baseFilename}.xlsx`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 新增:导出用于重新上传的数据
|
|
58
|
+
const btnExportForReupload: ButtonAction = {
|
|
59
|
+
label: '导出失败数据',
|
|
60
|
+
type: 'secondary',
|
|
61
|
+
handler: async ()=> {
|
|
62
|
+
const baseFilename = filename.replace(/\.[^/.]+$/, ""); // 移除扩展名
|
|
63
|
+
template.exportErrorData(`重传-${baseFilename}.xlsx`, {
|
|
64
|
+
includeAllData: false,
|
|
65
|
+
separateSheets: true,
|
|
66
|
+
originalFormat: true
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 新增:导出完整报告
|
|
72
|
+
const btnExportFullReport: ButtonAction = {
|
|
73
|
+
label: '导出完整报告',
|
|
74
|
+
type: 'secondary',
|
|
75
|
+
handler: async ()=> {
|
|
76
|
+
const baseFilename = filename.replace(/\.[^/.]+$/, ""); // 移除扩展名
|
|
77
|
+
template.exportErrorData(`报告-${baseFilename}.xlsx`, {
|
|
78
|
+
includeAllData: true,
|
|
79
|
+
separateSheets: true,
|
|
80
|
+
originalFormat: true
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 新增:重置失败数据状态
|
|
86
|
+
const btnResetErrors: ButtonAction = {
|
|
87
|
+
label: '重置失败数据',
|
|
88
|
+
type: 'secondary',
|
|
89
|
+
handler: async ()=> {
|
|
90
|
+
template.resetUploadStatus();
|
|
91
|
+
list = template.list;
|
|
92
|
+
status = 'Pending';
|
|
52
93
|
}
|
|
53
94
|
}
|
|
54
95
|
|
|
@@ -60,20 +101,21 @@
|
|
|
60
101
|
const parseExcelFile = async (excelFile: File) => {
|
|
61
102
|
if (excelFile) {
|
|
62
103
|
filename = excelFile.name;
|
|
63
|
-
window.Indicator.show(
|
|
104
|
+
window.Indicator.show(i18nRes.parsing);
|
|
64
105
|
try {
|
|
65
106
|
await template.parseExcelFile(excelFile);
|
|
66
107
|
list = template.list;
|
|
67
108
|
status = list.length > 0 ? 'Pending' : 'Init';
|
|
68
109
|
} catch (ex) {
|
|
69
|
-
|
|
110
|
+
console.error('Parse file error:', ex);
|
|
111
|
+
window.Toast.show(i18nUtils.formatText(i18nRes.parseFailure, {name: excelFile.name}));
|
|
112
|
+
status = 'Init'; // 确保解析失败时重置状态
|
|
70
113
|
} finally {
|
|
71
114
|
window.Indicator.hide();
|
|
72
115
|
}
|
|
73
116
|
}
|
|
74
117
|
}
|
|
75
118
|
|
|
76
|
-
|
|
77
119
|
let columns: Array<DataColumn>;
|
|
78
120
|
|
|
79
121
|
onMount(async () => {
|
|
@@ -83,15 +125,12 @@
|
|
|
83
125
|
})
|
|
84
126
|
});
|
|
85
127
|
|
|
86
|
-
|
|
87
|
-
|
|
88
128
|
const indicatorColumn: IndicatorColumn = {
|
|
89
129
|
width: 40,
|
|
90
130
|
selectable: false,
|
|
91
131
|
displayNo: true
|
|
92
132
|
}
|
|
93
133
|
|
|
94
|
-
|
|
95
134
|
$: {
|
|
96
135
|
switch (status) {
|
|
97
136
|
case 'Init':
|
|
@@ -108,32 +147,76 @@
|
|
|
108
147
|
case 'Done':
|
|
109
148
|
btnUpload.disabled = false;
|
|
110
149
|
btnChoose.disabled = false;
|
|
111
|
-
|
|
112
|
-
|
|
150
|
+
|
|
151
|
+
// 只有在Done状态时才获取统计信息并判断按钮显示
|
|
152
|
+
const stats = template.uploadStats;
|
|
153
|
+
const hasError = stats.failed > 0;
|
|
154
|
+
const hasSuccess = stats.success > 0;
|
|
155
|
+
|
|
156
|
+
// 根据上传结果提供不同的操作选项
|
|
157
|
+
if (hasError && hasSuccess) {
|
|
158
|
+
// 部分成功:提供重传错误数据、导出报告等选项
|
|
159
|
+
actions = [
|
|
160
|
+
btnExportForReupload,
|
|
161
|
+
btnExportFullReport,
|
|
162
|
+
btnResetErrors,
|
|
163
|
+
btnSave, // 保持向后兼容
|
|
164
|
+
btnChoose
|
|
165
|
+
];
|
|
166
|
+
} else if (hasError && !hasSuccess) {
|
|
167
|
+
// 全部失败:提供重传和导出选项
|
|
168
|
+
actions = [
|
|
169
|
+
btnExportForReupload,
|
|
170
|
+
btnSave,
|
|
171
|
+
btnChoose
|
|
172
|
+
];
|
|
173
|
+
} else if (hasSuccess && !hasError) {
|
|
174
|
+
// 全部成功:只提供导出报告和重新上传
|
|
175
|
+
actions = [btnExportFullReport, btnChoose];
|
|
176
|
+
} else {
|
|
177
|
+
// 没有数据或其他情况
|
|
178
|
+
actions = [btnChoose];
|
|
179
|
+
}
|
|
113
180
|
break;
|
|
114
181
|
}
|
|
115
182
|
}
|
|
116
183
|
|
|
117
|
-
|
|
118
184
|
const confirmCloseDialog = async ():Promise<boolean> => {
|
|
119
185
|
if (status == 'Uploading') {
|
|
120
|
-
window.Toast.show(
|
|
186
|
+
window.Toast.show(i18nRes.waitUploading);
|
|
121
187
|
return false;
|
|
122
188
|
} else {
|
|
123
189
|
return true;
|
|
124
190
|
}
|
|
125
191
|
}
|
|
126
192
|
|
|
193
|
+
// 显示上传统计信息
|
|
194
|
+
$: uploadStatsText = (() => {
|
|
195
|
+
if (status === 'Done' && list.length > 0) {
|
|
196
|
+
const stats = template.uploadStats;
|
|
197
|
+
return `总计: ${stats.total}, 成功: ${stats.success}, 失败: ${stats.failed}`;
|
|
198
|
+
}
|
|
199
|
+
return '';
|
|
200
|
+
})();
|
|
201
|
+
|
|
127
202
|
</script>
|
|
128
203
|
|
|
129
204
|
<Dialog {title} {closeHandler} {actions} closeConfirm={confirmCloseDialog}
|
|
130
205
|
content$style="width: {width}; height: {height}; padding: 12px;">
|
|
206
|
+
|
|
207
|
+
<!-- 添加状态信息显示 -->
|
|
208
|
+
{#if uploadStatsText}
|
|
209
|
+
<div style="margin-bottom: 8px; padding: 8px; background: #f5f5f5; border-radius: 4px; font-size: 14px;">
|
|
210
|
+
{uploadStatsText}
|
|
211
|
+
</div>
|
|
212
|
+
{/if}
|
|
213
|
+
|
|
131
214
|
<Box style="border: 1px solid var(--uniface-editor-border-color, #F8FAFC); width: 100%; height: 100%; cursor: {status == 'Uploading' ? 'progress' : 'default'}" round>
|
|
132
215
|
<DataTable style="width: 100%; height: 100%" {list} {indicatorColumn} {columns}>
|
|
133
|
-
|
|
134
216
|
</DataTable>
|
|
135
217
|
</Box>
|
|
218
|
+
|
|
136
219
|
<input type="file" bind:this={uploadField} on:change={(e) => parseExcelFile(e.target.files?.[0])} style="display: none"
|
|
137
220
|
accept=".xls,.xlsx,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
|
|
138
221
|
|
|
139
|
-
</Dialog>
|
|
222
|
+
</Dialog>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { i18nUtils } from "@ticatec/i18n";
|
|
2
|
+
const langRes = {
|
|
3
|
+
status: {
|
|
4
|
+
pending: "To upload",
|
|
5
|
+
uploading: "Uploading...",
|
|
6
|
+
successful: "Success",
|
|
7
|
+
fail: "Failure"
|
|
8
|
+
},
|
|
9
|
+
parsing: "Parsing file...",
|
|
10
|
+
parseFailure: "Cannot parse file: {{name}}",
|
|
11
|
+
waitUploading: "Cannot exit during uploading!",
|
|
12
|
+
button: {
|
|
13
|
+
upload: "Upload",
|
|
14
|
+
save: "Save error data",
|
|
15
|
+
open: "Open",
|
|
16
|
+
confirm: "Confirm"
|
|
17
|
+
},
|
|
18
|
+
errorTitle: "Error",
|
|
19
|
+
sheetName: "Abnormal data",
|
|
20
|
+
labelStatus: "Status",
|
|
21
|
+
labelValid: "Validity",
|
|
22
|
+
textValid: "Yes",
|
|
23
|
+
textInvalid: "No",
|
|
24
|
+
labelHint: "Hint"
|
|
25
|
+
};
|
|
26
|
+
const i18nRes = i18nUtils.createResourceProxy(langRes, 'batchUploading');
|
|
27
|
+
export default i18nRes;
|