diff --git a/common/changes/@visactor/vtable/fix-sorted-sync-record-index_2026-09-23-09-50.json b/common/changes/@visactor/vtable/fix-sorted-sync-record-index_2026-09-23-09-50.json new file mode 100644 index 000000000..a5c183b77 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-sorted-sync-record-index_2026-09-23-09-50.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix(vtable): map sorted view index to the source record in deleteRecords/updateRecords", + "type": "patch", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "20151622+william-xue@users.noreply.github.com" +} diff --git a/packages/vtable/__tests__/data-update/listTable-data-events.test.ts b/packages/vtable/__tests__/data-update/listTable-data-events.test.ts index ebfa613ef..2a3723c09 100644 --- a/packages/vtable/__tests__/data-update/listTable-data-events.test.ts +++ b/packages/vtable/__tests__/data-update/listTable-data-events.test.ts @@ -427,4 +427,64 @@ describe('listTable data events test', () => { table.updateFilterRules(table.dataSource.dataConfig.filterRules as any); expect((table.records as any[]).map(r => r.name)).toEqual(['小明2', '小明3']); }); + + test('deleteRecords on a sorted table should remove the source record shown in that row', () => { + table.release(); + const records = [ + { id: 1, name: 'Employee 1' }, + { id: 2, name: 'Employee 2' }, + { id: 3, name: 'Employee 3' }, + { id: 4, name: 'Employee 4' }, + { id: 5, name: 'Employee 5' } + ]; + + table = new ListTable({ + container: containerDom, + columns: [ + { field: 'id', title: 'ID' }, + { field: 'name', title: 'Name' } + ], + records, + sortState: { field: 'id', order: 'desc' }, + syncRecordOperationsToSourceRecords: true + }); + + // 降序排列后 body 第一行是 id 为 5 的记录 + expect(table.getCellValue(0, table.columnHeaderLevelCount)).toBe(5); + + table.deleteRecords([0]); + + // 删除的是显示在第一行的记录(id 5),而不是数据源数组的第 0 条 + expect(records.map(record => record.id)).toEqual([1, 2, 3, 4]); + expect(table.getCellValue(0, table.columnHeaderLevelCount)).toBe(4); + }); + + test('updateRecords on a sorted table should update the source record shown in that row', () => { + table.release(); + const records = [ + { id: 1, name: 'Employee 1' }, + { id: 2, name: 'Employee 2' }, + { id: 3, name: 'Employee 3' }, + { id: 4, name: 'Employee 4' }, + { id: 5, name: 'Employee 5' } + ]; + + table = new ListTable({ + container: containerDom, + columns: [ + { field: 'id', title: 'ID' }, + { field: 'name', title: 'Name' } + ], + records, + sortState: { field: 'id', order: 'desc' }, + syncRecordOperationsToSourceRecords: true + }); + + table.updateRecords([{ id: 5, name: 'Employee 5 updated' }], [0]); + + // 更新的同样是显示在第一行的记录(id 5,数据源下标 4) + expect(records.map(record => record.id)).toEqual([1, 2, 3, 4, 5]); + expect(records[4].name).toBe('Employee 5 updated'); + expect(table.getCellValue(1, table.columnHeaderLevelCount)).toBe('Employee 5 updated'); + }); }); diff --git a/packages/vtable/src/data/DataSource.ts b/packages/vtable/src/data/DataSource.ts index 99e3a183d..331cb50a1 100644 --- a/packages/vtable/src/data/DataSource.ts +++ b/packages/vtable/src/data/DataSource.ts @@ -962,6 +962,16 @@ export class DataSource extends EventTarget implements DataSourceAPI { private _hasFilterInEffect(): boolean { return (this.dataConfig?.filterRules?.length ?? 0) >= 1 || (this.lastFilterRules?.length ?? 0) >= 1; } + /** + * body 中的显示索引 → this.records(数据源数组)索引。 + * 排序或筛选后 currentIndexedData 保存的是「视图顺序 → 数据源顺序」的映射, + * 未排序/未筛选时它是恒等映射,此时直接返回 viewIndex。 + */ + private _getRecordIndexFromViewIndex(viewIndex: number): number { + const indexedData = this.currentIndexedData; + const mappedIndex = Array.isArray(indexedData) ? indexedData[viewIndex] : undefined; + return typeof mappedIndex === 'number' ? mappedIndex : viewIndex; + } private _normalizeInsertIndex(index: number, length: number): number { if (index === undefined || index === null) { return length; @@ -1251,7 +1261,8 @@ export class DataSource extends EventTarget implements DataSourceAPI { if (viewIndex >= this.records.length || viewIndex < 0) { continue; } - const deletedRecord = this.records[viewIndex]; + // viewIndex 是 body 中的显示索引:排序后需先映射回数据源索引,否则会删到别的记录 + const deletedRecord = this.records[this._getRecordIndexFromViewIndex(viewIndex)]; const rawIndex = rawRecords.indexOf(deletedRecord); if (rawIndex >= 0) { rawRecords.splice(rawIndex, 1); @@ -1349,7 +1360,7 @@ export class DataSource extends EventTarget implements DataSourceAPI { if (recordIndex >= this.records.length || recordIndex < 0) { continue; } - const oldRecord = this.records[recordIndex]; + const oldRecord = this.records[this._getRecordIndexFromViewIndex(recordIndex)]; const rawIndex = rawRecords.indexOf(oldRecord); if (rawIndex >= 0) { rawRecords[rawIndex] = records[index];