@ticatec/batch-data-uploader 0.1.1 → 0.1.2
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 +0 -2
- package/dist/BaseEncodingTemplate.js +0 -3
- package/dist/BaseTemplate.d.ts +13 -25
- package/dist/BaseTemplate.js +75 -70
- package/dist/DataColumn.d.ts +6 -1
- package/dist/EncodingWizard.svelte +58 -23
- package/dist/EncodingWizard.svelte.d.ts +0 -1
- package/dist/FileUploadWizard.svelte +83 -47
- package/dist/FileUploadWizard.svelte.d.ts +0 -1
- package/dist/ProcessStatus.d.ts +6 -0
- package/dist/ProcessStatus.js +7 -0
- package/dist/SheetNameViewRender.svelte +6 -0
- package/dist/SheetNameViewRender.svelte.d.ts +20 -0
- package/dist/SheetPickupDialog.svelte +35 -0
- package/dist/SheetPickupDialog.svelte.d.ts +22 -0
- package/dist/i18n_res/i18nRes.js +6 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/package.json +3 -3
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import BaseTemplate from "./BaseTemplate";
|
|
2
|
-
import type DataColumn from "./DataColumn";
|
|
3
2
|
import type { DataColumn as TableColumn } from "@ticatec/uniface-element/DataTable";
|
|
4
3
|
export interface ValidationResult {
|
|
5
4
|
valid: boolean;
|
|
@@ -9,7 +8,6 @@ export interface ValidationResult {
|
|
|
9
8
|
export default abstract class BaseEncodingTemplate extends BaseTemplate {
|
|
10
9
|
private hintColumn;
|
|
11
10
|
private validColumn;
|
|
12
|
-
protected constructor(columns: Array<DataColumn>, rowOffset?: number);
|
|
13
11
|
/**
|
|
14
12
|
*
|
|
15
13
|
* @param rows
|
|
@@ -17,9 +17,6 @@ export default class BaseEncodingTemplate extends BaseTemplate {
|
|
|
17
17
|
escapeHTML: false, // 修复:需要渲染HTML
|
|
18
18
|
formatter: valid => valid ? ValidData : InvalidData
|
|
19
19
|
};
|
|
20
|
-
constructor(columns, rowOffset = 1) {
|
|
21
|
-
super(columns, rowOffset);
|
|
22
|
-
}
|
|
23
20
|
/**
|
|
24
21
|
* 数据集是否有效
|
|
25
22
|
*/
|
package/dist/BaseTemplate.d.ts
CHANGED
|
@@ -1,46 +1,34 @@
|
|
|
1
1
|
import type DataColumn from "./DataColumn";
|
|
2
2
|
import type { DataColumn as TableColumn } from "@ticatec/uniface-element/DataTable";
|
|
3
|
+
import * as XLSX from 'xlsx';
|
|
3
4
|
export default abstract class BaseTemplate {
|
|
4
5
|
protected _list: Array<any>;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
protected constructor();
|
|
6
|
+
protected _file: File | null;
|
|
7
|
+
protected _workbook: XLSX.WorkBook | null;
|
|
8
|
+
protected _currentSheetName: string | null;
|
|
10
9
|
protected abstract getMetaColumns(): Array<DataColumn>;
|
|
11
10
|
protected getRowOffset(): number;
|
|
12
11
|
/**
|
|
13
|
-
*
|
|
14
|
-
* @param rows
|
|
15
|
-
* @protected
|
|
12
|
+
* 整理数据,在子类可重载进行二次处理
|
|
16
13
|
*/
|
|
17
14
|
protected consolidateData(rows: Array<any>): Promise<Array<any>>;
|
|
18
15
|
/**
|
|
19
|
-
*
|
|
16
|
+
* 设置要解析的 Excel 文件
|
|
20
17
|
* @param file
|
|
21
18
|
*/
|
|
22
|
-
|
|
19
|
+
setFile(file: File): Promise<string[]>;
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param
|
|
21
|
+
* 解析指定的工作表
|
|
22
|
+
* @param sheetName 如果不传则使用第一个工作表
|
|
26
23
|
*/
|
|
24
|
+
parseSheet(sheetName?: string): Promise<void>;
|
|
27
25
|
protected extractData(arr: Array<any>): any[];
|
|
28
|
-
/**
|
|
29
|
-
* 包裹数据
|
|
30
|
-
* @param data
|
|
31
|
-
* @protected
|
|
32
|
-
*/
|
|
33
26
|
protected wrapData(data: any): any;
|
|
34
|
-
/**
|
|
35
|
-
* 获取表格的列定义
|
|
36
|
-
*/
|
|
37
27
|
get columns(): Array<TableColumn>;
|
|
38
|
-
/**
|
|
39
|
-
* 获取数据
|
|
40
|
-
*/
|
|
41
28
|
get list(): Array<any>;
|
|
29
|
+
get dataList(): Array<any>;
|
|
42
30
|
/**
|
|
43
|
-
*
|
|
31
|
+
* 获取当前解析的工作表名称
|
|
44
32
|
*/
|
|
45
|
-
get
|
|
33
|
+
get currentSheetName(): string | null;
|
|
46
34
|
}
|
package/dist/BaseTemplate.js
CHANGED
|
@@ -2,46 +2,64 @@ import * as XLSX from 'xlsx';
|
|
|
2
2
|
import utils from "./utils";
|
|
3
3
|
export default class BaseTemplate {
|
|
4
4
|
_list = [];
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
constructor() {
|
|
10
|
-
}
|
|
5
|
+
_file = null;
|
|
6
|
+
_workbook = null;
|
|
7
|
+
_currentSheetName = null;
|
|
11
8
|
getRowOffset() {
|
|
12
9
|
return 1;
|
|
13
10
|
}
|
|
14
11
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @param rows
|
|
17
|
-
* @protected
|
|
12
|
+
* 整理数据,在子类可重载进行二次处理
|
|
18
13
|
*/
|
|
19
14
|
async consolidateData(rows) {
|
|
20
15
|
return rows;
|
|
21
16
|
}
|
|
22
17
|
/**
|
|
23
|
-
*
|
|
18
|
+
* 设置要解析的 Excel 文件
|
|
24
19
|
* @param file
|
|
25
20
|
*/
|
|
26
|
-
async
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
async setFile(file) {
|
|
22
|
+
this._file = file;
|
|
23
|
+
this._workbook = null;
|
|
24
|
+
this._currentSheetName = null;
|
|
25
|
+
this._list = [];
|
|
29
26
|
try {
|
|
30
27
|
const buffer = await file.arrayBuffer();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!workbook.SheetNames || workbook.SheetNames.length === 0) {
|
|
28
|
+
this._workbook = XLSX.read(buffer, { type: 'array' });
|
|
29
|
+
if (!this._workbook.SheetNames || this._workbook.SheetNames.length === 0) {
|
|
34
30
|
throw new Error('Excel file contains no worksheets');
|
|
35
31
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
return [...this._workbook.SheetNames];
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.error('Failed to load Excel file:', error);
|
|
36
|
+
throw new Error(`Failed to load Excel file: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 解析指定的工作表
|
|
41
|
+
* @param sheetName 如果不传则使用第一个工作表
|
|
42
|
+
*/
|
|
43
|
+
async parseSheet(sheetName) {
|
|
44
|
+
if (!this._workbook) {
|
|
45
|
+
throw new Error('No Excel file has been set. Call setFile() first.');
|
|
46
|
+
}
|
|
47
|
+
const targetSheetName = sheetName || this._workbook.SheetNames[0];
|
|
48
|
+
if (!this._workbook.SheetNames.includes(targetSheetName)) {
|
|
49
|
+
throw new Error(`Sheet "${targetSheetName}" not found in the workbook`);
|
|
50
|
+
}
|
|
51
|
+
const sheet = this._workbook.Sheets[targetSheetName];
|
|
52
|
+
if (!sheet || !sheet['!ref']) {
|
|
53
|
+
throw new Error(`Sheet "${targetSheetName}" is empty or invalid`);
|
|
54
|
+
}
|
|
55
|
+
this._currentSheetName = targetSheetName;
|
|
56
|
+
this._list = [];
|
|
57
|
+
try {
|
|
58
|
+
const columns = this.getMetaColumns();
|
|
59
|
+
const rowOffset = this.getRowOffset();
|
|
60
|
+
const range = XLSX.utils.decode_range(sheet['!ref']);
|
|
43
61
|
if (range.e.r < rowOffset) {
|
|
44
|
-
throw new Error(`Not enough rows in
|
|
62
|
+
throw new Error(`Not enough rows in sheet "${targetSheetName}". Expected at least ${rowOffset + 1} rows`);
|
|
45
63
|
}
|
|
46
64
|
const rows = [];
|
|
47
65
|
for (let rowIndex = range.s.r + rowOffset; rowIndex <= range.e.r; rowIndex++) {
|
|
@@ -52,91 +70,78 @@ export default class BaseTemplate {
|
|
|
52
70
|
const colDef = columns[i];
|
|
53
71
|
if (colDef.dummy) {
|
|
54
72
|
dummyCount++;
|
|
73
|
+
continue;
|
|
55
74
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
const actualColIndex = i - dummyCount;
|
|
76
|
+
if (actualColIndex < 0) {
|
|
77
|
+
console.warn(`Invalid column index for ${colDef.field}: ${actualColIndex}`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const cellAddress = { r: rowIndex, c: actualColIndex };
|
|
81
|
+
const cellRef = XLSX.utils.encode_cell(cellAddress);
|
|
82
|
+
const cell = sheet[cellRef];
|
|
83
|
+
const rawValue = cell?.v;
|
|
84
|
+
if (rawValue !== undefined && rawValue !== null && rawValue !== '') {
|
|
85
|
+
hasData = true;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const formattedValue = colDef.parser ? colDef.parser(rawValue) : rawValue;
|
|
89
|
+
if (colDef.setValue) {
|
|
90
|
+
colDef.setValue(rowObject, formattedValue);
|
|
70
91
|
}
|
|
71
|
-
|
|
72
|
-
const formattedValue = colDef.parser ? colDef.parser(rawValue) : rawValue;
|
|
92
|
+
else {
|
|
73
93
|
utils.setNestedValue(rowObject, colDef.field, formattedValue);
|
|
74
94
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
}
|
|
96
|
+
catch (parseError) {
|
|
97
|
+
console.warn(`Failed to parse cell ${cellRef} in sheet "${targetSheetName}":`, parseError);
|
|
98
|
+
utils.setNestedValue(rowObject, colDef.field, rawValue);
|
|
79
99
|
}
|
|
80
100
|
}
|
|
81
|
-
// 只添加有数据的行
|
|
82
101
|
if (hasData) {
|
|
83
102
|
rows.push(this.wrapData(rowObject));
|
|
84
103
|
}
|
|
85
104
|
}
|
|
86
105
|
if (rows.length === 0) {
|
|
87
|
-
throw new Error(
|
|
106
|
+
throw new Error(`No valid data rows found in sheet "${targetSheetName}"`);
|
|
88
107
|
}
|
|
89
108
|
this._list = await this.consolidateData(rows);
|
|
90
109
|
}
|
|
91
110
|
catch (error) {
|
|
92
|
-
console.error(
|
|
93
|
-
throw new Error(`Failed to parse
|
|
111
|
+
console.error(`Failed to parse sheet "${targetSheetName}":`, error);
|
|
112
|
+
throw new Error(`Failed to parse sheet "${targetSheetName}": ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
94
113
|
}
|
|
95
114
|
}
|
|
96
|
-
/**
|
|
97
|
-
* 获取实际待上传的数据
|
|
98
|
-
* @param arr
|
|
99
|
-
*/
|
|
100
115
|
extractData(arr) {
|
|
101
|
-
|
|
116
|
+
return arr.map(item => {
|
|
102
117
|
let result = {};
|
|
103
118
|
for (let col of this.getMetaColumns()) {
|
|
104
|
-
|
|
105
|
-
if (col.visible != false && col.ignore != true && !col.dummy) {
|
|
119
|
+
if (col.visible != false && col.ignore != true) {
|
|
106
120
|
let data = item.data;
|
|
107
121
|
utils.setNestedValue(result, col.field, utils.getNestedValue(data, col.field));
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
return result;
|
|
111
125
|
});
|
|
112
|
-
return list;
|
|
113
126
|
}
|
|
114
|
-
/**
|
|
115
|
-
* 包裹数据
|
|
116
|
-
* @param data
|
|
117
|
-
* @protected
|
|
118
|
-
*/
|
|
119
127
|
wrapData(data) {
|
|
120
128
|
return { data };
|
|
121
129
|
}
|
|
122
|
-
/**
|
|
123
|
-
* 获取表格的列定义
|
|
124
|
-
*/
|
|
125
130
|
get columns() {
|
|
126
131
|
return this.getMetaColumns()
|
|
127
132
|
.filter(col => col.visible !== false)
|
|
128
133
|
.map(col => ({ ...col, field: `data.${col.field}` }));
|
|
129
134
|
}
|
|
130
|
-
/**
|
|
131
|
-
* 获取数据
|
|
132
|
-
*/
|
|
133
135
|
get list() {
|
|
134
136
|
return [...this._list];
|
|
135
137
|
}
|
|
136
|
-
/**
|
|
137
|
-
* 获取实际的数据列表
|
|
138
|
-
*/
|
|
139
138
|
get dataList() {
|
|
140
139
|
return this._list.map(row => row.data);
|
|
141
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* 获取当前解析的工作表名称
|
|
143
|
+
*/
|
|
144
|
+
get currentSheetName() {
|
|
145
|
+
return this._currentSheetName;
|
|
146
|
+
}
|
|
142
147
|
}
|
package/dist/DataColumn.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { DataColumn as TableColumn } from "@ticatec/uniface-element/DataTable";
|
|
2
2
|
export type ParserText = (text: string) => any;
|
|
3
|
+
export type SetAttributeValue = (data: string, value: any) => void;
|
|
3
4
|
export default interface DataColumn extends TableColumn {
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* 对应的字段
|
|
6
7
|
*/
|
|
7
8
|
field: string;
|
|
8
9
|
/**
|
|
@@ -17,4 +18,8 @@ export default interface DataColumn extends TableColumn {
|
|
|
17
18
|
* 伪列,不解析,仅用于回显
|
|
18
19
|
*/
|
|
19
20
|
dummy?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* 手工设定值
|
|
23
|
+
*/
|
|
24
|
+
setValue?: SetAttributeValue;
|
|
20
25
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Dialog from "@ticatec/uniface-element/Dialog";
|
|
3
3
|
import type {ButtonAction, ButtonActions} from "@ticatec/uniface-element/ActionBar";
|
|
4
|
-
import DataTable, {type IndicatorColumn} from "@ticatec/uniface-element/DataTable";
|
|
4
|
+
import DataTable, {type IndicatorColumn, type DataColumn as TableColumn} from "@ticatec/uniface-element/DataTable";
|
|
5
5
|
import Box from "@ticatec/uniface-element/Box"
|
|
6
6
|
import {onMount} from "svelte";
|
|
7
|
-
import type DataColumn from "./DataColumn";
|
|
8
7
|
import type BaseEncodingTemplate from "./BaseEncodingTemplate";
|
|
9
8
|
import i18nRes from "./i18n_res/i18nRes";
|
|
10
9
|
import {i18nUtils} from "@ticatec/i18n";
|
|
10
|
+
import SheetPickupDialog from "./SheetPickupDialog.svelte";
|
|
11
|
+
|
|
11
12
|
|
|
12
13
|
export let title: string;
|
|
13
14
|
export let width: string = "800px";
|
|
14
15
|
export let height: string = "600px"
|
|
15
|
-
export let closeHandler: any;
|
|
16
16
|
export let template: BaseEncodingTemplate;
|
|
17
17
|
export let confirmCallback: any;
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
const btnChoose: ButtonAction = {
|
|
21
21
|
label: i18nRes.button.open,
|
|
22
22
|
type: 'primary',
|
|
23
|
-
handler: () => {
|
|
23
|
+
handler: async () => {
|
|
24
24
|
uploadField.value = '';
|
|
25
25
|
uploadField.click();
|
|
26
26
|
}
|
|
@@ -29,38 +29,73 @@
|
|
|
29
29
|
const btnConfirm: ButtonAction = {
|
|
30
30
|
label: i18nRes.button.confirm,
|
|
31
31
|
type: 'primary',
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
disabled: true,
|
|
33
|
+
handler: async () => {
|
|
34
|
+
let result = await confirmCallback?.(template.dataList);
|
|
35
|
+
console.log("处理结果",result);
|
|
36
|
+
return true;
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
let actions: ButtonActions = [btnChoose];
|
|
40
|
+
let actions: ButtonActions = [btnChoose, btnConfirm];
|
|
39
41
|
let uploadField: any;
|
|
40
42
|
let list: Array<any> = [];
|
|
41
|
-
|
|
43
|
+
|
|
44
|
+
$: {
|
|
45
|
+
btnConfirm.disabled = list.length == 0;
|
|
46
|
+
actions = [...actions]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const showSheetChooseDialog = (sheetNames: Array<any>): Promise<any> => {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
window.Dialog.showModal(SheetPickupDialog, {
|
|
52
|
+
confirmCallback: (sheet: string) => {
|
|
53
|
+
resolve(sheet);
|
|
54
|
+
}, onClose: () => {
|
|
55
|
+
resolve(null);
|
|
56
|
+
}, sheets: sheetNames
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const openExcelFile = async (excelFile: File) => {
|
|
62
|
+
window.Indicator.show(i18nRes.parsing.toString());
|
|
63
|
+
try {
|
|
64
|
+
return await template.setFile(excelFile);
|
|
65
|
+
} catch (ex) {
|
|
66
|
+
console.error('Parse file error:', ex);
|
|
67
|
+
window.Toast.show(i18nUtils.formatText(i18nRes.parseFailure, {name: excelFile.name}));
|
|
68
|
+
throw ex;
|
|
69
|
+
} finally {
|
|
70
|
+
window.Indicator.hide();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
42
73
|
|
|
43
74
|
const parseExcelFile = async (excelFile: File) => {
|
|
44
75
|
if (excelFile) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
const sheetNames = await openExcelFile(excelFile);
|
|
77
|
+
console.log('sheets', sheetNames)
|
|
78
|
+
let sheetName = sheetNames[0];
|
|
79
|
+
if (sheetNames.length > 1) {
|
|
80
|
+
sheetName = await showSheetChooseDialog(sheetNames)
|
|
81
|
+
}
|
|
82
|
+
if (sheetName) {
|
|
83
|
+
window.Indicator.show(i18nRes.parsing.toString());
|
|
84
|
+
try {
|
|
85
|
+
await template.parseSheet(sheetName);
|
|
86
|
+
list = template.list;
|
|
87
|
+
} catch (ex) {
|
|
88
|
+
console.error('Parse file error:', ex);
|
|
89
|
+
window.Toast.show(i18nUtils.formatText(i18nRes.parseFailure, {name: excelFile.name}));
|
|
90
|
+
} finally {
|
|
91
|
+
window.Indicator.hide();
|
|
52
92
|
}
|
|
53
|
-
} catch (ex) {
|
|
54
|
-
console.error(ex);
|
|
55
|
-
window.Toast.show(i18nUtils.formatText(i18nRes.parseFailure, {name: excelFile.name}));
|
|
56
|
-
} finally {
|
|
57
|
-
window.Indicator.hide();
|
|
58
93
|
}
|
|
59
94
|
}
|
|
60
95
|
}
|
|
61
96
|
|
|
62
97
|
|
|
63
|
-
let columns: Array<
|
|
98
|
+
let columns: Array<TableColumn>;
|
|
64
99
|
|
|
65
100
|
onMount(async () => {
|
|
66
101
|
columns = template.columns;
|
|
@@ -76,7 +111,7 @@
|
|
|
76
111
|
|
|
77
112
|
</script>
|
|
78
113
|
|
|
79
|
-
<Dialog {title} {
|
|
114
|
+
<Dialog {title} {actions}
|
|
80
115
|
content$style="width: {width}; height: {height}; padding: 12px;">
|
|
81
116
|
<Box style="border: 1px solid var(--uniface-editor-border-color, #F8FAFC); width: 100%; height: 100%; "
|
|
82
117
|
round>
|
|
@@ -2,29 +2,28 @@
|
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import Dialog from "@ticatec/uniface-element/Dialog";
|
|
4
4
|
import type {ButtonAction, ButtonActions} from "@ticatec/uniface-element/ActionBar";
|
|
5
|
-
import DataTable, {type IndicatorColumn} from "@ticatec/uniface-element/DataTable";
|
|
5
|
+
import DataTable, {type DataColumn as TableColumn, type IndicatorColumn} from "@ticatec/uniface-element/DataTable";
|
|
6
6
|
import Box from "@ticatec/uniface-element/Box"
|
|
7
7
|
import {onMount} from "svelte";
|
|
8
|
-
import type DataColumn from "./DataColumn";
|
|
9
8
|
import type BaseUploadTemplate from "./BaseUploadTemplate.js";
|
|
10
9
|
import i18nRes from "./i18n_res/i18nRes";
|
|
11
10
|
import {i18nUtils} from "@ticatec/i18n";
|
|
11
|
+
import {ProcessStatus} from "./ProcessStatus";
|
|
12
|
+
import SheetPickupDialog from "./SheetPickupDialog.svelte";
|
|
12
13
|
|
|
13
14
|
export let title: string;
|
|
14
15
|
export let width: string = "800px";
|
|
15
16
|
export let height: string = "600px"
|
|
16
|
-
export let closeHandler: any;
|
|
17
17
|
export let template: BaseUploadTemplate;
|
|
18
|
-
export let afterUploaded: ()=>Promise<void>;
|
|
18
|
+
export let afterUploaded: () => Promise<void>;
|
|
19
19
|
|
|
20
|
-
type ProcessStatus = 'Init' | 'Pending' | 'Uploading' | 'Done'; //初始状态,待上传,上传中,处理完成
|
|
21
20
|
|
|
22
|
-
let status: ProcessStatus =
|
|
21
|
+
let status: ProcessStatus = ProcessStatus.Init;
|
|
23
22
|
|
|
24
23
|
const btnChoose: ButtonAction = {
|
|
25
24
|
label: i18nRes.button.open,
|
|
26
25
|
type: 'primary',
|
|
27
|
-
handler: () => {
|
|
26
|
+
handler: async () => {
|
|
28
27
|
uploadField.value = '';
|
|
29
28
|
uploadField.click();
|
|
30
29
|
}
|
|
@@ -33,13 +32,13 @@
|
|
|
33
32
|
const btnUpload: ButtonAction = {
|
|
34
33
|
label: i18nRes.button.upload,
|
|
35
34
|
type: 'primary',
|
|
36
|
-
handler: async ()=> {
|
|
37
|
-
status =
|
|
35
|
+
handler: async () => {
|
|
36
|
+
status = ProcessStatus.Uploading;
|
|
38
37
|
try {
|
|
39
38
|
await template.upload();
|
|
40
39
|
await afterUploaded?.();
|
|
41
40
|
} finally {
|
|
42
|
-
status =
|
|
41
|
+
status = ProcessStatus.Done;
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
}
|
|
@@ -48,7 +47,7 @@
|
|
|
48
47
|
const btnSave: ButtonAction = {
|
|
49
48
|
label: i18nRes.button.save,
|
|
50
49
|
type: 'primary',
|
|
51
|
-
handler: async ()=> {
|
|
50
|
+
handler: async () => {
|
|
52
51
|
const baseFilename = filename.replace(/\.[^/.]+$/, ""); // 移除扩展名
|
|
53
52
|
template.exportErrorRowsToExcel(`error-${baseFilename}.xlsx`);
|
|
54
53
|
}
|
|
@@ -56,11 +55,11 @@
|
|
|
56
55
|
|
|
57
56
|
// 新增:导出用于重新上传的数据
|
|
58
57
|
const btnExportForReupload: ButtonAction = {
|
|
59
|
-
label:
|
|
58
|
+
label: i18nRes.buttonExportException,
|
|
60
59
|
type: 'secondary',
|
|
61
|
-
handler: async ()=> {
|
|
60
|
+
handler: async () => {
|
|
62
61
|
const baseFilename = filename.replace(/\.[^/.]+$/, ""); // 移除扩展名
|
|
63
|
-
template.exportErrorData(
|
|
62
|
+
template.exportErrorData(`${baseFilename}.xlsx`, {
|
|
64
63
|
includeAllData: false,
|
|
65
64
|
separateSheets: true,
|
|
66
65
|
originalFormat: true
|
|
@@ -70,11 +69,11 @@
|
|
|
70
69
|
|
|
71
70
|
// 新增:导出完整报告
|
|
72
71
|
const btnExportFullReport: ButtonAction = {
|
|
73
|
-
label:
|
|
72
|
+
label: i18nRes.buttonExportFull,
|
|
74
73
|
type: 'secondary',
|
|
75
|
-
handler: async ()=> {
|
|
74
|
+
handler: async () => {
|
|
76
75
|
const baseFilename = filename.replace(/\.[^/.]+$/, ""); // 移除扩展名
|
|
77
|
-
template.exportErrorData(
|
|
76
|
+
template.exportErrorData(`report-${baseFilename}.xlsx`, {
|
|
78
77
|
includeAllData: true,
|
|
79
78
|
separateSheets: true,
|
|
80
79
|
originalFormat: true
|
|
@@ -84,12 +83,12 @@
|
|
|
84
83
|
|
|
85
84
|
// 新增:重置失败数据状态
|
|
86
85
|
const btnResetErrors: ButtonAction = {
|
|
87
|
-
label:
|
|
86
|
+
label: i18nRes.buttonReset,
|
|
88
87
|
type: 'secondary',
|
|
89
|
-
handler: async ()=> {
|
|
88
|
+
handler: async () => {
|
|
90
89
|
template.resetUploadStatus();
|
|
91
90
|
list = template.list;
|
|
92
|
-
status =
|
|
91
|
+
status = ProcessStatus.Pending;
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
|
|
@@ -98,29 +97,63 @@
|
|
|
98
97
|
let list: Array<any> = [];
|
|
99
98
|
let filename: string;
|
|
100
99
|
|
|
100
|
+
const showSheetChooseDialog = (sheetNames: Array<any>): Promise<any> => {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
window.Dialog.showModal(SheetPickupDialog, {
|
|
103
|
+
confirmCallback: (sheet: string) => {
|
|
104
|
+
resolve(sheet);
|
|
105
|
+
}, onClose: () => {
|
|
106
|
+
resolve(null);
|
|
107
|
+
}, sheets: sheetNames
|
|
108
|
+
})
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const openExcelFile = async (excelFile: File) => {
|
|
113
|
+
window.Indicator.show(i18nRes.parsing.toString());
|
|
114
|
+
try {
|
|
115
|
+
return await template.setFile(excelFile);
|
|
116
|
+
} catch (ex) {
|
|
117
|
+
console.error('Parse file error:', ex);
|
|
118
|
+
window.Toast.show(i18nUtils.formatText(i18nRes.parseFailure, {name: excelFile.name}));
|
|
119
|
+
throw ex;
|
|
120
|
+
} finally {
|
|
121
|
+
window.Indicator.hide();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
const parseExcelFile = async (excelFile: File) => {
|
|
102
126
|
if (excelFile) {
|
|
127
|
+
const sheetNames = await openExcelFile(excelFile);
|
|
103
128
|
filename = excelFile.name;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
129
|
+
let sheetName = sheetNames[0];
|
|
130
|
+
if (sheetNames.length > 1) {
|
|
131
|
+
sheetName = await showSheetChooseDialog(sheetNames)
|
|
132
|
+
}
|
|
133
|
+
if (sheetName) {
|
|
134
|
+
window.Indicator.show(i18nRes.parsing.toString());
|
|
135
|
+
try {
|
|
136
|
+
await template.parseSheet(sheetName);
|
|
137
|
+
list = template.list;
|
|
138
|
+
status = list.length > 0 ? ProcessStatus.Pending : ProcessStatus.Init;
|
|
139
|
+
} catch (ex) {
|
|
140
|
+
console.error('Parse file error:', ex);
|
|
141
|
+
window.Toast.show(i18nUtils.formatText(i18nRes.parseFailure, {name: excelFile.name}));
|
|
142
|
+
status = ProcessStatus.Init; // 确保解析失败时重置状态
|
|
143
|
+
} finally {
|
|
144
|
+
window.Indicator.hide();
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
status = ProcessStatus.Init; // 没有选择退出重置状态
|
|
115
148
|
}
|
|
116
149
|
}
|
|
117
150
|
}
|
|
118
151
|
|
|
119
|
-
let columns: Array<
|
|
152
|
+
let columns: Array<TableColumn>;
|
|
120
153
|
|
|
121
154
|
onMount(async () => {
|
|
122
155
|
columns = template.columns;
|
|
123
|
-
template.setProgressStatusListener(()=> {
|
|
156
|
+
template.setProgressStatusListener(() => {
|
|
124
157
|
list = template.list;
|
|
125
158
|
})
|
|
126
159
|
});
|
|
@@ -131,20 +164,20 @@
|
|
|
131
164
|
displayNo: true
|
|
132
165
|
}
|
|
133
166
|
|
|
134
|
-
|
|
135
|
-
switch (
|
|
136
|
-
case
|
|
167
|
+
const invalidateStatus = (ps: ProcessStatus) => {
|
|
168
|
+
switch (ps) {
|
|
169
|
+
case ProcessStatus.Init:
|
|
137
170
|
actions = [btnChoose];
|
|
138
171
|
break;
|
|
139
|
-
case
|
|
172
|
+
case ProcessStatus.Pending:
|
|
140
173
|
actions = [btnUpload, btnChoose];
|
|
141
174
|
break;
|
|
142
|
-
case
|
|
175
|
+
case ProcessStatus.Uploading:
|
|
143
176
|
btnUpload.disabled = true;
|
|
144
177
|
btnChoose.disabled = true;
|
|
145
178
|
actions = [...actions];
|
|
146
179
|
break;
|
|
147
|
-
case
|
|
180
|
+
case ProcessStatus.Done:
|
|
148
181
|
btnUpload.disabled = false;
|
|
149
182
|
btnChoose.disabled = false;
|
|
150
183
|
|
|
@@ -181,8 +214,10 @@
|
|
|
181
214
|
}
|
|
182
215
|
}
|
|
183
216
|
|
|
184
|
-
|
|
185
|
-
|
|
217
|
+
$: invalidateStatus(status);
|
|
218
|
+
|
|
219
|
+
const confirmCloseDialog = async (): Promise<boolean> => {
|
|
220
|
+
if (status == ProcessStatus.Uploading) {
|
|
186
221
|
window.Toast.show(i18nRes.waitUploading);
|
|
187
222
|
return false;
|
|
188
223
|
} else {
|
|
@@ -192,26 +227,27 @@
|
|
|
192
227
|
|
|
193
228
|
// 显示上传统计信息
|
|
194
229
|
$: uploadStatsText = (() => {
|
|
195
|
-
if (status ===
|
|
230
|
+
if (status === ProcessStatus.Done && list.length > 0) {
|
|
196
231
|
const stats = template.uploadStats;
|
|
197
|
-
return
|
|
232
|
+
return i18nUtils.formatText(i18nRes.uploadStatText, stats);
|
|
198
233
|
}
|
|
199
234
|
return '';
|
|
200
235
|
})();
|
|
201
236
|
|
|
202
237
|
</script>
|
|
203
238
|
|
|
204
|
-
<Dialog {title} {
|
|
205
|
-
content$style="width: {width}; height: {height}; padding: 12px;">
|
|
239
|
+
<Dialog {title} {actions} closeConfirm={confirmCloseDialog}
|
|
240
|
+
content$style="width: {width}; height: {height}; padding: 12px; display: flex; flex-direction: column;">
|
|
206
241
|
|
|
207
242
|
<!-- 添加状态信息显示 -->
|
|
208
243
|
{#if uploadStatsText}
|
|
209
|
-
<div style="margin-bottom: 8px; padding: 8px; background: #f5f5f5; border-radius: 4px; font-size: 14px;">
|
|
244
|
+
<div style="margin-bottom: 8px; padding: 8px; background: #f5f5f5; border-radius: 4px; font-size: 14px; flex: 0 0 auto">
|
|
210
245
|
{uploadStatsText}
|
|
211
246
|
</div>
|
|
212
247
|
{/if}
|
|
213
248
|
|
|
214
|
-
<Box style="border: 1px solid var(--uniface-editor-border-color, #F8FAFC); width: 100%; height: 100%; cursor: {status ==
|
|
249
|
+
<Box style="flex: 1 1 auto; border: 1px solid var(--uniface-editor-border-color, #F8FAFC); width: 100%; height: 100%; cursor: {status == ProcessStatus.Uploading ? 'progress' : 'default'}"
|
|
250
|
+
round>
|
|
215
251
|
<DataTable style="width: 100%; height: 100%" {list} {indicatorColumn} {columns}>
|
|
216
252
|
</DataTable>
|
|
217
253
|
</Box>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export var ProcessStatus;
|
|
2
|
+
(function (ProcessStatus) {
|
|
3
|
+
ProcessStatus[ProcessStatus["Init"] = 0] = "Init";
|
|
4
|
+
ProcessStatus[ProcessStatus["Pending"] = 1] = "Pending";
|
|
5
|
+
ProcessStatus[ProcessStatus["Uploading"] = 2] = "Uploading";
|
|
6
|
+
ProcessStatus[ProcessStatus["Done"] = 3] = "Done";
|
|
7
|
+
})(ProcessStatus || (ProcessStatus = {}));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: Props & {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const SheetNameViewRender: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
+
item: any;
|
|
16
|
+
}, {
|
|
17
|
+
[evt: string]: CustomEvent<any>;
|
|
18
|
+
}, {}, {}, string>;
|
|
19
|
+
type SheetNameViewRender = InstanceType<typeof SheetNameViewRender>;
|
|
20
|
+
export default SheetNameViewRender;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!-- 更新的FileUploadWizard.svelte - 彻底清理includeMetadata -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import CommonDialog from "@ticatec/uniface-element/CommonDialog";
|
|
4
|
+
import Box from "@ticatec/uniface-element/Box"
|
|
5
|
+
import i18nRes from "./i18n_res/i18nRes";
|
|
6
|
+
import ListBox from "@ticatec/uniface-element/ListBox";
|
|
7
|
+
import SheetNameViewRender from "./SheetNameViewRender.svelte";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export let confirmCallback: any;
|
|
11
|
+
|
|
12
|
+
export let sheets: Array<string>;
|
|
13
|
+
|
|
14
|
+
export let onClose: any;
|
|
15
|
+
|
|
16
|
+
let selectedSheet: any;
|
|
17
|
+
|
|
18
|
+
let width: string = "360px";
|
|
19
|
+
let height: string = "480px";
|
|
20
|
+
|
|
21
|
+
const confirmSelection = () => {
|
|
22
|
+
confirmCallback?.(selectedSheet);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<CommonDialog title={i18nRes.titleChooseSheet} {onClose} confirmHandler={confirmSelection} enableConfirm={selectedSheet != null}
|
|
30
|
+
content$style="width: {width}; height: {height}; padding: 12px;">
|
|
31
|
+
<Box style="border: 1px solid var(--uniface-editor-border-color, #F8FAFC); width: 100%; height: 100%;" round>
|
|
32
|
+
<ListBox style="width: 100%; height: 100%" list={sheets} selectMode="single" bind:selectedItem={selectedSheet}
|
|
33
|
+
itemRender={SheetNameViewRender}/>
|
|
34
|
+
</Box>
|
|
35
|
+
</CommonDialog>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: Props & {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const SheetPickupDialog: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
+
confirmCallback: any;
|
|
16
|
+
sheets: Array<string>;
|
|
17
|
+
onClose: any;
|
|
18
|
+
}, {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
}, {}, {}, string>;
|
|
21
|
+
type SheetPickupDialog = InstanceType<typeof SheetPickupDialog>;
|
|
22
|
+
export default SheetPickupDialog;
|
package/dist/i18n_res/i18nRes.js
CHANGED
|
@@ -15,13 +15,18 @@ const langRes = {
|
|
|
15
15
|
open: "Open",
|
|
16
16
|
confirm: "Confirm"
|
|
17
17
|
},
|
|
18
|
+
titleChooseSheet: 'Choose a sheet',
|
|
18
19
|
errorTitle: "Error",
|
|
19
20
|
sheetName: "Abnormal data",
|
|
20
21
|
labelStatus: "Status",
|
|
21
22
|
labelValid: "Validity",
|
|
22
23
|
textValid: "Yes",
|
|
23
24
|
textInvalid: "No",
|
|
24
|
-
labelHint: "Hint"
|
|
25
|
+
labelHint: "Hint",
|
|
26
|
+
uploadStatText: "Total: {{total}}, Success: {{success}}, Failed: {{failed}}",
|
|
27
|
+
buttonExportException: "Error report",
|
|
28
|
+
buttonExportFull: "Full report",
|
|
29
|
+
buttonReset: "Reset"
|
|
25
30
|
};
|
|
26
31
|
const i18nRes = i18nUtils.createResourceProxy(langRes, 'batchUploading');
|
|
27
32
|
export default i18nRes;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ticatec/batch-data-uploader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A reusable Svelte component for batch uploading Excel data with support for error handling, multi-language, and preprocessing.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite dev",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"@sveltejs/kit": "^2.0.0",
|
|
74
74
|
"@sveltejs/package": "^2.0.0",
|
|
75
75
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
|
76
|
-
"@ticatec/i18n": "^0.2.
|
|
77
|
-
"@ticatec/uniface-element": "^0.
|
|
76
|
+
"@ticatec/i18n": "^0.2.3",
|
|
77
|
+
"@ticatec/uniface-element": "^0.3.5",
|
|
78
78
|
"@ticatec/uniface-google-material-icons": "^0.1.2",
|
|
79
79
|
"dayjs": "^1.11.10",
|
|
80
80
|
"publint": "^0.3.2",
|