diff --git a/packages/vtable-sheet/src/core/WorkSheet.ts b/packages/vtable-sheet/src/core/WorkSheet.ts index 0473123dea..e5c8c16a33 100644 --- a/packages/vtable-sheet/src/core/WorkSheet.ts +++ b/packages/vtable-sheet/src/core/WorkSheet.ts @@ -48,6 +48,14 @@ type WorkSheetUpdateOptions = Pick< theme?: TYPES.VTableThemes.ITableThemeDefine; }; +type WorkSheetColumn = IWorkSheetOptions['columns'][number] & { + columns?: IWorkSheetOptions['columns']; + children?: IWorkSheetOptions['columns']; +}; + +const getChildColumns = (column: IWorkSheetOptions['columns'][number]): IWorkSheetOptions['columns'] | undefined => + (column as WorkSheetColumn).columns ?? (column as WorkSheetColumn).children; + const normalizeColumnsField = (columns: IWorkSheetOptions['columns'], startFieldIndex = 0): number => { if (!columns?.length) { return startFieldIndex; @@ -56,8 +64,7 @@ const normalizeColumnsField = (columns: IWorkSheetOptions['columns'], startField let fieldIndex = startFieldIndex; columns.forEach(column => { - const childColumns = (column as IWorkSheetOptions['columns'][number] & { columns?: IWorkSheetOptions['columns'] }) - .columns; + const childColumns = getChildColumns(column); if (childColumns?.length) { fieldIndex = normalizeColumnsField(childColumns, fieldIndex); @@ -76,6 +83,87 @@ const normalizeColumnsField = (columns: IWorkSheetOptions['columns'], startField return fieldIndex; }; +const collectLeafColumns = (columns: IWorkSheetOptions['columns'] = []): IWorkSheetOptions['columns'] => { + const leafColumns: IWorkSheetOptions['columns'] = []; + + columns.forEach(column => { + const childColumns = getChildColumns(column); + if (childColumns?.length) { + leafColumns.push(...collectLeafColumns(childColumns)); + return; + } + leafColumns.push(column); + }); + + return leafColumns; +}; + +const hasObjectFieldColumn = (columns: IWorkSheetOptions['columns'] = []): boolean => + columns.some(column => { + const childColumns = getChildColumns(column); + if (childColumns?.length) { + return hasObjectFieldColumn(childColumns); + } + return isValid(column.field) && typeof column.field !== 'number'; + }); + +const getAddRecordRule = (options: IWorkSheetOptions): ListTableConstructorOptions['addRecordRule'] => { + if (options.addRecordRule) { + return options.addRecordRule; + } + + const hasObjectRecord = options.data?.some(record => record && typeof record === 'object' && !Array.isArray(record)); + return hasObjectRecord || hasObjectFieldColumn(options.columns) ? 'Object' : 'Array'; +}; + +const getValueByField = (record: Record, field: any): any => { + if (Array.isArray(field)) { + return field.reduce((value, key) => value?.[key], record); + } + if (typeof field === 'string') { + if (Object.prototype.hasOwnProperty.call(record, field)) { + return record[field]; + } + return field.split('.').reduce((value, key) => value?.[key], record); + } + return record[field]; +}; + +const setValueByField = (record: Record, field: any, value: any): void => { + if (Array.isArray(field)) { + let target = record; + field.forEach((key, index) => { + if (index === field.length - 1) { + target[key] = value; + return; + } + if (typeof target[key] !== 'object' || target[key] === null) { + target[key] = {}; + } + target = target[key]; + }); + return; + } + + if (typeof field === 'string' && !Object.prototype.hasOwnProperty.call(record, field) && field.includes('.')) { + const keys = field.split('.'); + let target = record; + keys.forEach((key, index) => { + if (index === keys.length - 1) { + target[key] = value; + return; + } + if (typeof target[key] !== 'object' || target[key] === null) { + target[key] = {}; + } + target = target[key]; + }); + return; + } + + record[field] = value; +}; + export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { /** 选项 */ options: IWorkSheetOptions; @@ -333,12 +421,13 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { showCopyCellBorder: true, cutSelected: true }; + const addRecordRule = getAddRecordRule(this.options); return { ...(this.options as any), dragOrder: { maintainArrayDataOrder: true }, - addRecordRule: 'Array', + addRecordRule, syncRecordOperationsToSourceRecords: true, defaultCursor: 'cell', records: this.options.data, @@ -787,9 +876,66 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { return this.options.data || []; } + private getLeafColumns(): IWorkSheetOptions['columns'] { + return collectLeafColumns(this.options.columns); + } + + private getDataCellByTableCell(col: number, row: number): { col: number; row: number } | null { + const bodyIndex = this.tableInstance?.getBodyIndexByTableIndex?.(col, row) ?? { col, row }; + if (bodyIndex.col < 0 || bodyIndex.row < 0) { + return null; + } + return bodyIndex; + } + + private getDataCellValue(col: number, row: number): any { + const rowData = this.getData()[row]; + if (Array.isArray(rowData)) { + return rowData[col]; + } + if (rowData && typeof rowData === 'object') { + const field = this.getLeafColumns()[col]?.field; + if (isValid(field)) { + return getValueByField(rowData as Record, field); + } + } + return undefined; + } + + private getDataColumnCount(rowData: any): number { + return Array.isArray(rowData) ? rowData.length : this.getLeafColumns().length; + } + + private setDataCellValue(col: number, row: number, value: any, tableCol?: number, tableRow?: number): void { + const rowData = this.getData()[row]; + if (Array.isArray(rowData)) { + rowData[col] = value; + } else if (rowData && typeof rowData === 'object') { + const field = this.getLeafColumns()[col]?.field; + if (isValid(field)) { + setValueByField(rowData as Record, field, value); + } + } + + if (this.tableInstance) { + const tableIndex = + isValid(tableCol) && isValid(tableRow) + ? { col: tableCol, row: tableRow } + : this.tableInstance.getTableIndexByBodyIndex(col, row); + this.tableInstance.changeCellValue(tableIndex.col, tableIndex.row, value); + } + } + getCopiedData(): any[][] { // 为了避免影响当前数据,所以需要复制一份数据 - return this.getData().map(row => (Array.isArray(row) ? row.slice() : [])); + const leafColumns = this.getLeafColumns(); + return this.getData().map(row => + Array.isArray(row) + ? row.slice() + : leafColumns.map(column => + row && typeof row === 'object' ? getValueByField(row as Record, column.field) : undefined + ) + ); } /** * 获取指定坐标的单元格值 @@ -810,7 +956,8 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { if (Array.isArray(rowData) && rowData[col] !== undefined) { return rowData[col]; } - return null; + const dataCell = this.getDataCellByTableCell(col, row); + return dataCell ? this.getDataCellValue(dataCell.col, dataCell.row) ?? null : null; } /** * 获取指定坐标的单元格值 @@ -848,14 +995,9 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { * @param value 新值 */ setCellValue(col: number, row: number, value: any): void { - const rowData = this.getData()[row]; - if (Array.isArray(rowData)) { - rowData[col] = value; - - // 更新表格实例 - if (this.tableInstance) { - this.tableInstance.changeCellValue(col, row, value); - } + const dataCell = this.getDataCellByTableCell(col, row); + if (dataCell) { + this.setDataCellValue(dataCell.col, dataCell.row, value, col, row); } } @@ -1074,7 +1216,6 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { return []; } - const data = this.getData(); const result: string[][] = []; // 获取第一个选择范围 @@ -1087,8 +1228,9 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { for (let col = 0; col < cols; col++) { const actualRow = selection.startRow + row; const actualCol = selection.startCol + col; + const dataCell = this.getDataCellByTableCell(actualCol, actualRow); - if (data[actualRow] && data[actualRow][actualCol] !== undefined) { + if (dataCell && this.getData()[dataCell.row]) { // 如果是公式,返回公式字符串;否则返回值 if ( this.vtableSheet.formulaManager.isCellFormula({ @@ -1104,7 +1246,7 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { }); rowData.push(formula); } else { - rowData.push(data[actualRow][actualCol]); + rowData.push(this.getDataCellValue(dataCell.col, dataCell.row) ?? ''); } } else { rowData.push(''); @@ -1149,15 +1291,18 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { for (let col = 0; col < processedData[row].length; col++) { const targetRow = targetStartRow + row; const targetCol = targetStartCol + col; + const dataCell = this.getDataCellByTableCell(targetCol, targetRow); + const rowData = dataCell ? dataArray[dataCell.row] : undefined; + const columnCount = this.getDataColumnCount(rowData); - if (targetRow < dataArray.length && targetCol < dataArray[targetRow].length) { + if (rowData && dataCell && dataCell.col < columnCount) { const value = processedData[row][col]; // 如果是公式,设置公式;否则设置普通值 if (FormulaPasteProcessor.needsFormulaAdjustment(value)) { this.setCellFormula(targetRow, targetCol, value as string); } else { - this.setCellValue(targetRow, targetCol, value); + this.setDataCellValue(dataCell.col, dataCell.row, value, targetCol, targetRow); } } } diff --git a/packages/vtable-sheet/src/core/table-plugins.ts b/packages/vtable-sheet/src/core/table-plugins.ts index 6b1fca355b..322d6470c9 100644 --- a/packages/vtable-sheet/src/core/table-plugins.ts +++ b/packages/vtable-sheet/src/core/table-plugins.ts @@ -77,7 +77,8 @@ export function getTablePlugins( } else { const addRowColumnPlugin = new AddRowColumnPlugin({ addRowCallback: (row: number, tableInstance: VTable.ListTable) => { - tableInstance.addRecord([], row - tableInstance.columnHeaderLevelCount); + const record = tableInstance.options.addRecordRule === 'Object' ? {} : []; + tableInstance.addRecord(record, row - tableInstance.columnHeaderLevelCount); }, ...userPluginOptions });