Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 163 additions & 18 deletions packages/vtable-sheet/src/core/WorkSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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<string, any>, 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<string, any>, 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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<string, any>, 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<string, any>, 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<string, any>, column.field) : undefined
)
);
}
/**
* 获取指定坐标的单元格值
Expand All @@ -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;
}
/**
* 获取指定坐标的单元格值
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -1074,7 +1216,6 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource {
return [];
}

const data = this.getData();
const result: string[][] = [];

// 获取第一个选择范围
Expand All @@ -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({
Expand All @@ -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('');
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/vtable-sheet/src/core/table-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
Expand Down
Loading