@ticatec/batch-data-uploader 0.1.0 → 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.
@@ -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
@@ -1,5 +1,5 @@
1
1
  import BaseTemplate from "./BaseTemplate";
2
- import i18nRes from "./i18n_resources/i18nRes";
2
+ import i18nRes from "./i18n_res";
3
3
  const ValidData = `<span style="color: #76FF03">${i18nRes.textValid}</span>`;
4
4
  const InvalidData = `<span style="color: #ff3e00">${i18nRes.textInvalid.key}</span>`;
5
5
  export default class BaseEncodingTemplate extends BaseTemplate {
@@ -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
  */
@@ -1,48 +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
- protected readonly _columns: Array<DataColumn>;
5
- protected readonly rowOffset: number;
6
5
  protected _list: Array<any>;
6
+ protected _file: File | null;
7
+ protected _workbook: XLSX.WorkBook | null;
8
+ protected _currentSheetName: string | null;
9
+ protected abstract getMetaColumns(): Array<DataColumn>;
10
+ protected getRowOffset(): number;
7
11
  /**
8
- *
9
- * @param columns
10
- * @param rowOffset
11
- * @protected
12
- */
13
- protected constructor(columns: Array<DataColumn>, rowOffset?: number);
14
- /**
15
- * 整理数据,在子类可以通过重载完成数据的二次处理
16
- * @param rows
17
- * @protected
12
+ * 整理数据,在子类可重载进行二次处理
18
13
  */
19
14
  protected consolidateData(rows: Array<any>): Promise<Array<any>>;
20
15
  /**
21
- * 解析一个excel文件
16
+ * 设置要解析的 Excel 文件
22
17
  * @param file
23
18
  */
24
- parseExcelFile(file: File): Promise<void>;
19
+ setFile(file: File): Promise<string[]>;
25
20
  /**
26
- * 获取实际待上传的数据
27
- * @param arr
21
+ * 解析指定的工作表
22
+ * @param sheetName 如果不传则使用第一个工作表
28
23
  */
24
+ parseSheet(sheetName?: string): Promise<void>;
29
25
  protected extractData(arr: Array<any>): any[];
30
- /**
31
- * 包裹数据
32
- * @param data
33
- * @protected
34
- */
35
26
  protected wrapData(data: any): any;
36
- /**
37
- * 获取表格的列定义
38
- */
39
27
  get columns(): Array<TableColumn>;
40
- /**
41
- * 获取数据
42
- */
43
28
  get list(): Array<any>;
29
+ get dataList(): Array<any>;
44
30
  /**
45
- * 获取实际的数据列表
31
+ * 获取当前解析的工作表名称
46
32
  */
47
- get dataList(): Array<any>;
33
+ get currentSheetName(): string | null;
48
34
  }
@@ -1,143 +1,147 @@
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
- /**
8
- *
9
- * @param columns
10
- * @param rowOffset
11
- * @protected
12
- */
13
- constructor(columns, rowOffset = 1) {
14
- this._columns = columns;
15
- this.rowOffset = rowOffset;
5
+ _file = null;
6
+ _workbook = null;
7
+ _currentSheetName = null;
8
+ getRowOffset() {
9
+ return 1;
16
10
  }
17
11
  /**
18
- * 整理数据,在子类可以通过重载完成数据的二次处理
19
- * @param rows
20
- * @protected
12
+ * 整理数据,在子类可重载进行二次处理
21
13
  */
22
14
  async consolidateData(rows) {
23
15
  return rows;
24
16
  }
25
17
  /**
26
- * 解析一个excel文件
18
+ * 设置要解析的 Excel 文件
27
19
  * @param file
28
20
  */
29
- async parseExcelFile(file) {
21
+ async setFile(file) {
22
+ this._file = file;
23
+ this._workbook = null;
24
+ this._currentSheetName = null;
25
+ this._list = [];
30
26
  try {
31
27
  const buffer = await file.arrayBuffer();
32
- const workbook = XLSX.read(buffer, { type: 'array' });
33
- // 验证工作簿是否有工作表
34
- 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) {
35
30
  throw new Error('Excel file contains no worksheets');
36
31
  }
37
- const sheet = workbook.Sheets[workbook.SheetNames[0]];
38
- // 验证工作表是否存在且有数据
39
- if (!sheet || !sheet['!ref']) {
40
- throw new Error('Worksheet is empty or invalid');
41
- }
42
- const range = XLSX.utils.decode_range(sheet['!ref']); // 获取范围
43
- // 验证是否有足够的行数
44
- if (range.e.r < this.rowOffset) {
45
- throw new Error(`Not enough rows in file. Expected at least ${this.rowOffset + 1} rows`);
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']);
61
+ if (range.e.r < rowOffset) {
62
+ throw new Error(`Not enough rows in sheet "${targetSheetName}". Expected at least ${rowOffset + 1} rows`);
46
63
  }
47
64
  const rows = [];
48
- for (let rowIndex = range.s.r + this.rowOffset; rowIndex <= range.e.r; rowIndex++) {
65
+ for (let rowIndex = range.s.r + rowOffset; rowIndex <= range.e.r; rowIndex++) {
49
66
  const rowObject = {};
50
67
  let dummyCount = 0;
51
68
  let hasData = false;
52
- for (let i = 0; i < this._columns.length; i++) {
53
- const colDef = this._columns[i];
69
+ for (let i = 0; i < columns.length; i++) {
70
+ const colDef = columns[i];
54
71
  if (colDef.dummy) {
55
72
  dummyCount++;
73
+ continue;
56
74
  }
57
- else {
58
- // 确保列索引不会为负数
59
- const actualColIndex = i - dummyCount;
60
- if (actualColIndex < 0) {
61
- console.warn(`Invalid column index for ${colDef.field}: ${actualColIndex}`);
62
- continue;
63
- }
64
- const cellAddress = { r: rowIndex, c: actualColIndex };
65
- const cellRef = XLSX.utils.encode_cell(cellAddress);
66
- const cell = sheet[cellRef];
67
- const rawValue = cell?.v;
68
- // 检查是否有实际数据
69
- if (rawValue !== undefined && rawValue !== null && rawValue !== '') {
70
- hasData = true;
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);
71
91
  }
72
- try {
73
- const formattedValue = colDef.parser ? colDef.parser(rawValue) : rawValue;
92
+ else {
74
93
  utils.setNestedValue(rowObject, colDef.field, formattedValue);
75
94
  }
76
- catch (parseError) {
77
- console.warn(`Failed to parse cell ${cellRef}:`, parseError);
78
- utils.setNestedValue(rowObject, colDef.field, rawValue);
79
- }
95
+ }
96
+ catch (parseError) {
97
+ console.warn(`Failed to parse cell ${cellRef} in sheet "${targetSheetName}":`, parseError);
98
+ utils.setNestedValue(rowObject, colDef.field, rawValue);
80
99
  }
81
100
  }
82
- // 只添加有数据的行
83
101
  if (hasData) {
84
102
  rows.push(this.wrapData(rowObject));
85
103
  }
86
104
  }
87
105
  if (rows.length === 0) {
88
- throw new Error('No valid data rows found in the file');
106
+ throw new Error(`No valid data rows found in sheet "${targetSheetName}"`);
89
107
  }
90
108
  this._list = await this.consolidateData(rows);
91
109
  }
92
110
  catch (error) {
93
- console.error('Failed to parse Excel file:', error);
94
- throw new Error(`Failed to parse Excel file: ${error instanceof Error ? error.message : 'Unknown error'}`);
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'}`);
95
113
  }
96
114
  }
97
- /**
98
- * 获取实际待上传的数据
99
- * @param arr
100
- */
101
115
  extractData(arr) {
102
- let list = arr.map(item => {
116
+ return arr.map(item => {
103
117
  let result = {};
104
- for (let col of this._columns) {
105
- // 修复逻辑错误:ignore应该为true时才忽略,visible为false时才隐藏
106
- if (col.visible != false && col.ignore != true && !col.dummy) {
118
+ for (let col of this.getMetaColumns()) {
119
+ if (col.visible != false && col.ignore != true) {
107
120
  let data = item.data;
108
121
  utils.setNestedValue(result, col.field, utils.getNestedValue(data, col.field));
109
122
  }
110
123
  }
111
124
  return result;
112
125
  });
113
- return list;
114
126
  }
115
- /**
116
- * 包裹数据
117
- * @param data
118
- * @protected
119
- */
120
127
  wrapData(data) {
121
128
  return { data };
122
129
  }
123
- /**
124
- * 获取表格的列定义
125
- */
126
130
  get columns() {
127
- return this._columns
131
+ return this.getMetaColumns()
128
132
  .filter(col => col.visible !== false)
129
133
  .map(col => ({ ...col, field: `data.${col.field}` }));
130
134
  }
131
- /**
132
- * 获取数据
133
- */
134
135
  get list() {
135
136
  return [...this._list];
136
137
  }
137
- /**
138
- * 获取实际的数据列表
139
- */
140
138
  get dataList() {
141
139
  return this._list.map(row => row.data);
142
140
  }
141
+ /**
142
+ * 获取当前解析的工作表名称
143
+ */
144
+ get currentSheetName() {
145
+ return this._currentSheetName;
146
+ }
143
147
  }
@@ -1,6 +1,5 @@
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;
6
5
  export interface UploadResult {
@@ -17,7 +16,7 @@ export default abstract class BaseUploadTemplate extends BaseTemplate {
17
16
  protected batchSize: number;
18
17
  protected updateProgressStatus: UpdateProgressStatus | null;
19
18
  private _uploadAborted;
20
- protected constructor(columns: Array<DataColumn>, batchSize?: number, rowOffset?: number);
19
+ protected constructor(batchSize?: number);
21
20
  /**
22
21
  * 状态更新的监听器
23
22
  * @param value
@@ -1,9 +1,8 @@
1
1
  // 改进的BaseUploadTemplate.ts - 完全清理元数据相关代码
2
2
  import BaseTemplate from "./BaseTemplate";
3
- import i18nKeys from "./i18n_resources/i18nKeys";
4
3
  import utils from "./utils";
5
4
  import * as XLSX from 'xlsx';
6
- import i18nRes from "./i18n_resources/i18nRes";
5
+ import i18nRes from "./i18n_res/i18nRes";
7
6
  const statusColumn = {
8
7
  text: i18nRes.labelStatus,
9
8
  width: 240,
@@ -29,8 +28,8 @@ export default class BaseUploadTemplate extends BaseTemplate {
29
28
  batchSize;
30
29
  updateProgressStatus = null;
31
30
  _uploadAborted = false;
32
- constructor(columns, batchSize = 50, rowOffset = 1) {
33
- super(columns, rowOffset);
31
+ constructor(batchSize = 50) {
32
+ super();
34
33
  this.batchSize = Math.max(1, batchSize);
35
34
  }
36
35
  /**
@@ -213,7 +212,7 @@ export default class BaseUploadTemplate extends BaseTemplate {
213
212
  return;
214
213
  }
215
214
  // 获取可见且非虚拟的列
216
- const exportColumns = this._columns.filter(col => col.visible !== false && !col.dummy && !col.ignore);
215
+ const exportColumns = this.getMetaColumns().filter(col => col.visible !== false && !col.dummy && !col.ignore);
217
216
  // 创建标题行(与原始导入格式一致)
218
217
  const headers = exportColumns.map(col => col.text || col.field);
219
218
  // 创建数据行
@@ -242,7 +241,7 @@ export default class BaseUploadTemplate extends BaseTemplate {
242
241
  : this._list.filter(row => row.status === 'D' && row.error);
243
242
  if (errorRows.length === 0)
244
243
  return;
245
- const visibleColumns = this._columns.filter(col => col.visible !== false && !col.dummy);
244
+ const visibleColumns = this.getMetaColumns().filter(col => col.visible !== false && !col.dummy);
246
245
  const headers = [
247
246
  '行号',
248
247
  ...visibleColumns.map(col => col.text || col.field),
@@ -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
- import i18nRes from "./i18n_resources/i18nRes";
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
- handler: ()=> {
33
- confirmCallback?.(template.dataList);
34
- closeHandler?.();
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
- let filename: string;
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
- filename = excelFile.name;
46
- window.Indicator.show(i18nRes.parsing);
47
- try {
48
- await template.parseExcelFile(excelFile);
49
- list = template.list;
50
- if (template.valid) {
51
- actions = [btnConfirm, ...actions]
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<DataColumn>;
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} {closeHandler} {actions}
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>
@@ -16,7 +16,6 @@ declare const EncodingWizard: $$__sveltets_2_IsomorphicComponent<{
16
16
  title: string;
17
17
  width?: string;
18
18
  height?: string;
19
- closeHandler: any;
20
19
  template: BaseEncodingTemplate;
21
20
  confirmCallback: any;
22
21
  }, {