@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,6 +1,11 @@
|
|
|
1
1
|
import BaseTemplate from "./BaseTemplate";
|
|
2
2
|
import type DataColumn from "./DataColumn";
|
|
3
3
|
import type { DataColumn as TableColumn } from "@ticatec/uniface-element/DataTable";
|
|
4
|
+
export interface ValidationResult {
|
|
5
|
+
valid: boolean;
|
|
6
|
+
hint?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
4
9
|
export default abstract class BaseEncodingTemplate extends BaseTemplate {
|
|
5
10
|
private hintColumn;
|
|
6
11
|
private validColumn;
|
|
@@ -16,11 +21,15 @@ export default abstract class BaseEncodingTemplate extends BaseTemplate {
|
|
|
16
21
|
* @param row
|
|
17
22
|
* @protected
|
|
18
23
|
*/
|
|
19
|
-
protected abstract validateData(row: any): Promise<
|
|
24
|
+
protected abstract validateData(row: any): ValidationResult | Promise<ValidationResult>;
|
|
20
25
|
/**
|
|
21
26
|
* 数据集是否有效
|
|
22
27
|
*/
|
|
23
28
|
get valid(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* 获取无效数据的数量
|
|
31
|
+
*/
|
|
32
|
+
get invalidCount(): number;
|
|
24
33
|
/**
|
|
25
34
|
* 从服务器抓取数据,然后根据主键进行数据合并
|
|
26
35
|
* @param rows
|
|
@@ -28,4 +37,12 @@ export default abstract class BaseEncodingTemplate extends BaseTemplate {
|
|
|
28
37
|
*/
|
|
29
38
|
protected consolidateData(rows: Array<any>): Promise<Array<any>>;
|
|
30
39
|
get columns(): Array<TableColumn>;
|
|
40
|
+
/**
|
|
41
|
+
* 获取所有有效的数据
|
|
42
|
+
*/
|
|
43
|
+
get validDataList(): Array<any>;
|
|
44
|
+
/**
|
|
45
|
+
* 获取所有无效的数据
|
|
46
|
+
*/
|
|
47
|
+
get invalidDataList(): Array<any>;
|
|
31
48
|
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import BaseTemplate from "./BaseTemplate";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const InvalidData = `<span style="color: #ff3e00">${getI18nText(i18nKeys.textInvalid)}</span>`;
|
|
2
|
+
import i18nRes from "./i18n_res";
|
|
3
|
+
const ValidData = `<span style="color: #76FF03">${i18nRes.textValid}</span>`;
|
|
4
|
+
const InvalidData = `<span style="color: #ff3e00">${i18nRes.textInvalid.key}</span>`;
|
|
6
5
|
export default class BaseEncodingTemplate extends BaseTemplate {
|
|
7
6
|
hintColumn = {
|
|
8
|
-
text:
|
|
7
|
+
text: i18nRes.labelHint,
|
|
9
8
|
field: "hint",
|
|
10
9
|
width: 150,
|
|
11
10
|
resizable: true
|
|
12
11
|
};
|
|
13
12
|
validColumn = {
|
|
14
|
-
text:
|
|
13
|
+
text: i18nRes.labelValid,
|
|
15
14
|
field: "valid",
|
|
16
15
|
width: 90,
|
|
17
16
|
align: 'center',
|
|
18
|
-
escapeHTML:
|
|
17
|
+
escapeHTML: false, // 修复:需要渲染HTML
|
|
19
18
|
formatter: valid => valid ? ValidData : InvalidData
|
|
20
19
|
};
|
|
21
20
|
constructor(columns, rowOffset = 1) {
|
|
@@ -27,25 +26,80 @@ export default class BaseEncodingTemplate extends BaseTemplate {
|
|
|
27
26
|
get valid() {
|
|
28
27
|
return this._list.filter(row => !row.valid).length == 0;
|
|
29
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* 获取无效数据的数量
|
|
31
|
+
*/
|
|
32
|
+
get invalidCount() {
|
|
33
|
+
return this._list.filter(row => row.valid !== true).length;
|
|
34
|
+
}
|
|
30
35
|
/**
|
|
31
36
|
* 从服务器抓取数据,然后根据主键进行数据合并
|
|
32
37
|
* @param rows
|
|
33
38
|
* @protected
|
|
34
39
|
*/
|
|
35
40
|
async consolidateData(rows) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let result = this.validateData(data);
|
|
42
|
-
item.valid = result.valid;
|
|
43
|
-
item.hint = result.hint;
|
|
41
|
+
try {
|
|
42
|
+
let list = await this.encodeData(this.extractData(rows));
|
|
43
|
+
// 验证返回数据长度是否匹配
|
|
44
|
+
if (list.length !== rows.length) {
|
|
45
|
+
console.warn(`Encoded data length (${list.length}) doesn't match input length (${rows.length})`);
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
// 并行处理验证以提高性能
|
|
48
|
+
const validationPromises = rows.map(async (item, idx) => {
|
|
49
|
+
if (list[idx]) {
|
|
50
|
+
let data = list[idx];
|
|
51
|
+
item.data = { ...item.data, ...data };
|
|
52
|
+
try {
|
|
53
|
+
let result = await Promise.resolve(this.validateData(data));
|
|
54
|
+
item.valid = result.valid;
|
|
55
|
+
item.hint = result.hint || '';
|
|
56
|
+
item.error = result.error;
|
|
57
|
+
}
|
|
58
|
+
catch (validationError) {
|
|
59
|
+
console.error(`Validation error for row ${idx}:`, validationError);
|
|
60
|
+
item.valid = false;
|
|
61
|
+
item.hint = 'Validation failed';
|
|
62
|
+
item.error = validationError instanceof Error ? validationError.message : 'Unknown validation error';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// 没有对应的编码数据
|
|
67
|
+
item.valid = false;
|
|
68
|
+
item.hint = 'No encoded data available';
|
|
69
|
+
item.error = 'Missing encoded data';
|
|
70
|
+
}
|
|
71
|
+
return item;
|
|
72
|
+
});
|
|
73
|
+
return await Promise.all(validationPromises);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('Error during data consolidation:', error);
|
|
77
|
+
// 标记所有行为无效
|
|
78
|
+
return rows.map(item => ({
|
|
79
|
+
...item,
|
|
80
|
+
valid: false,
|
|
81
|
+
hint: 'Encoding failed',
|
|
82
|
+
error: error instanceof Error ? error.message : 'Unknown encoding error'
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
47
85
|
}
|
|
48
86
|
get columns() {
|
|
49
87
|
return [...super.columns, this.hintColumn, this.validColumn];
|
|
50
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* 获取所有有效的数据
|
|
91
|
+
*/
|
|
92
|
+
get validDataList() {
|
|
93
|
+
return this._list
|
|
94
|
+
.filter(row => row.valid === true)
|
|
95
|
+
.map(row => row.data);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 获取所有无效的数据
|
|
99
|
+
*/
|
|
100
|
+
get invalidDataList() {
|
|
101
|
+
return this._list
|
|
102
|
+
.filter(row => row.valid !== true)
|
|
103
|
+
.map(row => ({ ...row.data, _error: row.error, _hint: row.hint }));
|
|
104
|
+
}
|
|
51
105
|
}
|
package/dist/BaseTemplate.d.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type DataColumn from "./DataColumn";
|
|
2
2
|
import type { DataColumn as TableColumn } from "@ticatec/uniface-element/DataTable";
|
|
3
3
|
export default abstract class BaseTemplate {
|
|
4
|
-
protected readonly _columns: Array<DataColumn>;
|
|
5
|
-
protected readonly rowOffset: number;
|
|
6
4
|
protected _list: Array<any>;
|
|
7
5
|
/**
|
|
8
6
|
*
|
|
9
|
-
* @param columns
|
|
10
|
-
* @param rowOffset
|
|
11
7
|
* @protected
|
|
12
8
|
*/
|
|
13
|
-
protected constructor(
|
|
9
|
+
protected constructor();
|
|
10
|
+
protected abstract getMetaColumns(): Array<DataColumn>;
|
|
11
|
+
protected getRowOffset(): number;
|
|
14
12
|
/**
|
|
15
13
|
* 整理数据,在子类可以通过重载完成数据的二次处理
|
|
16
14
|
* @param rows
|
package/dist/BaseTemplate.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import * as XLSX from 'xlsx';
|
|
2
2
|
import utils from "./utils";
|
|
3
3
|
export default class BaseTemplate {
|
|
4
|
-
_columns;
|
|
5
|
-
rowOffset;
|
|
6
4
|
_list = [];
|
|
7
5
|
/**
|
|
8
6
|
*
|
|
9
|
-
* @param columns
|
|
10
|
-
* @param rowOffset
|
|
11
7
|
* @protected
|
|
12
8
|
*/
|
|
13
|
-
constructor(
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
constructor() {
|
|
10
|
+
}
|
|
11
|
+
getRowOffset() {
|
|
12
|
+
return 1;
|
|
16
13
|
}
|
|
17
14
|
/**
|
|
18
15
|
* 整理数据,在子类可以通过重载完成数据的二次处理
|
|
@@ -27,31 +24,74 @@ export default class BaseTemplate {
|
|
|
27
24
|
* @param file
|
|
28
25
|
*/
|
|
29
26
|
async parseExcelFile(file) {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
const columns = this.getMetaColumns();
|
|
28
|
+
const rowOffset = this.getRowOffset();
|
|
29
|
+
try {
|
|
30
|
+
const buffer = await file.arrayBuffer();
|
|
31
|
+
const workbook = XLSX.read(buffer, { type: 'array' });
|
|
32
|
+
// 验证工作簿是否有工作表
|
|
33
|
+
if (!workbook.SheetNames || workbook.SheetNames.length === 0) {
|
|
34
|
+
throw new Error('Excel file contains no worksheets');
|
|
35
|
+
}
|
|
36
|
+
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
|
37
|
+
// 验证工作表是否存在且有数据
|
|
38
|
+
if (!sheet || !sheet['!ref']) {
|
|
39
|
+
throw new Error('Worksheet is empty or invalid');
|
|
40
|
+
}
|
|
41
|
+
const range = XLSX.utils.decode_range(sheet['!ref']); // 获取范围
|
|
42
|
+
// 验证是否有足够的行数
|
|
43
|
+
if (range.e.r < rowOffset) {
|
|
44
|
+
throw new Error(`Not enough rows in file. Expected at least ${rowOffset + 1} rows`);
|
|
45
|
+
}
|
|
46
|
+
const rows = [];
|
|
47
|
+
for (let rowIndex = range.s.r + rowOffset; rowIndex <= range.e.r; rowIndex++) {
|
|
48
|
+
const rowObject = {};
|
|
49
|
+
let dummyCount = 0;
|
|
50
|
+
let hasData = false;
|
|
51
|
+
for (let i = 0; i < columns.length; i++) {
|
|
52
|
+
const colDef = columns[i];
|
|
53
|
+
if (colDef.dummy) {
|
|
54
|
+
dummyCount++;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// 确保列索引不会为负数
|
|
58
|
+
const actualColIndex = i - dummyCount;
|
|
59
|
+
if (actualColIndex < 0) {
|
|
60
|
+
console.warn(`Invalid column index for ${colDef.field}: ${actualColIndex}`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const cellAddress = { r: rowIndex, c: actualColIndex };
|
|
64
|
+
const cellRef = XLSX.utils.encode_cell(cellAddress);
|
|
65
|
+
const cell = sheet[cellRef];
|
|
66
|
+
const rawValue = cell?.v;
|
|
67
|
+
// 检查是否有实际数据
|
|
68
|
+
if (rawValue !== undefined && rawValue !== null && rawValue !== '') {
|
|
69
|
+
hasData = true;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const formattedValue = colDef.parser ? colDef.parser(rawValue) : rawValue;
|
|
73
|
+
utils.setNestedValue(rowObject, colDef.field, formattedValue);
|
|
74
|
+
}
|
|
75
|
+
catch (parseError) {
|
|
76
|
+
console.warn(`Failed to parse cell ${cellRef}:`, parseError);
|
|
77
|
+
utils.setNestedValue(rowObject, colDef.field, rawValue);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
42
80
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const cell = sheet[cellRef];
|
|
47
|
-
const rawValue = cell?.v;
|
|
48
|
-
const formattedValue = colDef.parser ? colDef.parser(rawValue) : rawValue;
|
|
49
|
-
utils.setNestedValue(rowObject, colDef.field, formattedValue);
|
|
81
|
+
// 只添加有数据的行
|
|
82
|
+
if (hasData) {
|
|
83
|
+
rows.push(this.wrapData(rowObject));
|
|
50
84
|
}
|
|
51
85
|
}
|
|
52
|
-
rows.
|
|
86
|
+
if (rows.length === 0) {
|
|
87
|
+
throw new Error('No valid data rows found in the file');
|
|
88
|
+
}
|
|
89
|
+
this._list = await this.consolidateData(rows);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error('Failed to parse Excel file:', error);
|
|
93
|
+
throw new Error(`Failed to parse Excel file: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
53
94
|
}
|
|
54
|
-
this._list = await this.consolidateData(rows);
|
|
55
95
|
}
|
|
56
96
|
/**
|
|
57
97
|
* 获取实际待上传的数据
|
|
@@ -60,8 +100,9 @@ export default class BaseTemplate {
|
|
|
60
100
|
extractData(arr) {
|
|
61
101
|
let list = arr.map(item => {
|
|
62
102
|
let result = {};
|
|
63
|
-
for (let col of this.
|
|
64
|
-
|
|
103
|
+
for (let col of this.getMetaColumns()) {
|
|
104
|
+
// 修复逻辑错误:ignore应该为true时才忽略,visible为false时才隐藏
|
|
105
|
+
if (col.visible != false && col.ignore != true && !col.dummy) {
|
|
65
106
|
let data = item.data;
|
|
66
107
|
utils.setNestedValue(result, col.field, utils.getNestedValue(data, col.field));
|
|
67
108
|
}
|
|
@@ -82,7 +123,9 @@ export default class BaseTemplate {
|
|
|
82
123
|
* 获取表格的列定义
|
|
83
124
|
*/
|
|
84
125
|
get columns() {
|
|
85
|
-
return this.
|
|
126
|
+
return this.getMetaColumns()
|
|
127
|
+
.filter(col => col.visible !== false)
|
|
128
|
+
.map(col => ({ ...col, field: `data.${col.field}` }));
|
|
86
129
|
}
|
|
87
130
|
/**
|
|
88
131
|
* 获取数据
|
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import BaseTemplate from "./BaseTemplate";
|
|
2
2
|
import type { DataColumn as TableColumn } from "@ticatec/uniface-element/DataTable";
|
|
3
|
-
import type DataColumn from "./DataColumn";
|
|
4
3
|
export type UploadFun = (arr: Array<any>) => Promise<Array<any>>;
|
|
5
4
|
export type UpdateProgressStatus = () => void;
|
|
5
|
+
export interface UploadResult {
|
|
6
|
+
error?: any;
|
|
7
|
+
errorText?: string;
|
|
8
|
+
success?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ExportErrorOptions {
|
|
11
|
+
includeAllData?: boolean;
|
|
12
|
+
separateSheets?: boolean;
|
|
13
|
+
originalFormat?: boolean;
|
|
14
|
+
}
|
|
6
15
|
export default abstract class BaseUploadTemplate extends BaseTemplate {
|
|
7
16
|
protected batchSize: number;
|
|
8
17
|
protected updateProgressStatus: UpdateProgressStatus | null;
|
|
9
|
-
|
|
18
|
+
private _uploadAborted;
|
|
19
|
+
protected constructor(batchSize?: number);
|
|
10
20
|
/**
|
|
11
21
|
* 状态更新的监听器
|
|
12
22
|
* @param value
|
|
13
23
|
*/
|
|
14
24
|
setProgressStatusListener(value: UpdateProgressStatus): void;
|
|
15
25
|
protected abstract uploadData(list: Array<any>): Promise<Array<any>>;
|
|
26
|
+
/**
|
|
27
|
+
* 中止上传
|
|
28
|
+
*/
|
|
29
|
+
abortUpload(): void;
|
|
30
|
+
/**
|
|
31
|
+
* 重置上传状态
|
|
32
|
+
*/
|
|
33
|
+
resetUploadStatus(): void;
|
|
16
34
|
/**
|
|
17
35
|
* 上传数据
|
|
18
36
|
*/
|
|
@@ -28,8 +46,55 @@ export default abstract class BaseUploadTemplate extends BaseTemplate {
|
|
|
28
46
|
*/
|
|
29
47
|
get columns(): Array<TableColumn>;
|
|
30
48
|
/**
|
|
31
|
-
*
|
|
49
|
+
* 获取上传统计信息
|
|
50
|
+
*/
|
|
51
|
+
get uploadStats(): {
|
|
52
|
+
total: number;
|
|
53
|
+
pending: number;
|
|
54
|
+
uploading: number;
|
|
55
|
+
completed: number;
|
|
56
|
+
success: number;
|
|
57
|
+
failed: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* 导出处理异常的数据 - 基础版本(保持向后兼容)
|
|
32
61
|
* @param filename
|
|
33
62
|
*/
|
|
34
63
|
exportErrorRowsToExcel(filename: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* 导出错误数据 - 增强版本
|
|
66
|
+
* @param filename
|
|
67
|
+
* @param options 导出选项
|
|
68
|
+
*/
|
|
69
|
+
exportErrorData(filename: string, options?: ExportErrorOptions): void;
|
|
70
|
+
/**
|
|
71
|
+
* 导出原始格式 - 可以重新导入和上传
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
private _exportOriginalFormat;
|
|
75
|
+
/**
|
|
76
|
+
* 导出错误详情 - 作为第二个工作表
|
|
77
|
+
* @private
|
|
78
|
+
*/
|
|
79
|
+
private _exportErrorDetails;
|
|
80
|
+
/**
|
|
81
|
+
* 格式化值用于导出
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
private _formatValueForExport;
|
|
85
|
+
/**
|
|
86
|
+
* 设置列宽
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
89
|
+
private _setColumnWidths;
|
|
90
|
+
/**
|
|
91
|
+
* 快速导出错误数据用于重新上传
|
|
92
|
+
* @param filename
|
|
93
|
+
*/
|
|
94
|
+
exportErrorsForReupload(filename: string): void;
|
|
95
|
+
/**
|
|
96
|
+
* 导出完整报告(包含所有数据和详情)
|
|
97
|
+
* @param filename
|
|
98
|
+
*/
|
|
99
|
+
exportFullReport(filename: string): void;
|
|
35
100
|
}
|