diff --git a/common/changes/@visactor/vtable/fix-issue-4761-update-records-render_2026-07-27-19-55.json b/common/changes/@visactor/vtable/fix-issue-4761-update-records-render_2026-07-27-19-55.json new file mode 100644 index 0000000000..e61242adf9 --- /dev/null +++ b/common/changes/@visactor/vtable/fix-issue-4761-update-records-render_2026-07-27-19-55.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "fix: refresh rows when updateRecords is called without record indexes", + "type": "patch" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} diff --git a/docs/assets/api/en/methods.md b/docs/assets/api/en/methods.md index 9bbbf11349..f41c4a192c 100644 --- a/docs/assets/api/en/methods.md +++ b/docs/assets/api/en/methods.md @@ -2016,9 +2016,9 @@ Modify data, supports multiple data items /** * Modify data, supports multiple data items * @param records Modified data items - * @param recordIndexs Corresponding index of modified data (index displayed in body, i.e., which row of data in the body part to modify), in tree (grouping) structures, recordIndex may be an array, representing the index position of each level from the root node for that node. + * @param recordIndexs Corresponding index of modified data (index displayed in body, i.e., which row of data in the body part to modify), in tree (grouping) structures, recordIndex may be an array, representing the index position of each level from the root node for that node. When omitted, the records are updated by their array order. */ - updateRecords(records: any[], recordIndexs: number[]|number[][]) + updateRecords(records: any[], recordIndexs?: number[]|number[][]) ``` ## getBodyVisibleCellRange(Function) diff --git a/docs/assets/api/zh/methods.md b/docs/assets/api/zh/methods.md index 0d64ac15c9..e10c0b8330 100644 --- a/docs/assets/api/zh/methods.md +++ b/docs/assets/api/zh/methods.md @@ -2015,9 +2015,9 @@ changeCellValuesByRecords 的别名形式(位置参数)。 /** * 修改数据 支持多条数据 * @param records 修改数据条目 - * @param recordIndexs 对应修改数据的索引(显示在body中的索引,即要修改的是body部分的第几行数据),在树形(分组)结构中,recordIndex可能是一个数组,代表改节点从根节点开始的每级索引位置。 + * @param recordIndexs 对应修改数据的索引(显示在body中的索引,即要修改的是body部分的第几行数据),在树形(分组)结构中,recordIndex可能是一个数组,代表改节点从根节点开始的每级索引位置。省略时会按 records 顺序更新对应索引。 */ - updateRecords(records: any[], recordIndexs: number[]|number[][]) + updateRecords(records: any[], recordIndexs?: number[]|number[][]) ``` ## getBodyVisibleCellRange(Function) diff --git a/packages/vtable/examples/debug/issue-4761-update-records-edit-render.ts b/packages/vtable/examples/debug/issue-4761-update-records-edit-render.ts new file mode 100644 index 0000000000..372b176081 --- /dev/null +++ b/packages/vtable/examples/debug/issue-4761-update-records-edit-render.ts @@ -0,0 +1,75 @@ +import * as VTable from '../../src'; +import { InputEditor } from '@visactor/vtable-editors'; + +const CONTAINER_ID = 'vTable'; +const inputEditor = new InputEditor({}); +VTable.register.editor('input', inputEditor); + +const records = [ + { name: 'John', age: 20 }, + { name: 'Jane', age: 21 } +]; + +const createStatusBar = () => { + const container = document.getElementById(CONTAINER_ID)!; + const status = document.createElement('div'); + status.id = 'issue4761Status'; + status.style.cssText = 'height: 32px; line-height: 32px; font-size: 13px; color: #333;'; + status.textContent = 'Click "Check updateRecords render" to verify issue #4761.'; + + const button = document.createElement('button'); + button.textContent = 'Check updateRecords render'; + button.style.cssText = 'margin: 0 0 8px 8px;'; + button.onclick = () => checkUpdateRecordsRender(); + + container.parentElement?.insertBefore(status, container); + status.appendChild(button); +}; + +const getRenderedCellText = (tableInstance: VTable.ListTable, col: number, row: number) => { + const cellGroup = tableInstance.scenegraph.getCell(col, row); + const texts: string[] = []; + cellGroup?.forEachChildren((child: any) => { + const text = child?.attribute?.text; + if (typeof text === 'string') { + texts.push(text); + } else if (Array.isArray(text)) { + texts.push(text.join('')); + } + }); + return texts.join(''); +}; + +const checkUpdateRecordsRender = async () => { + const tableInstance = (window as any).tableInstance as VTable.ListTable; + const status = document.getElementById('issue4761Status')!; + records[0].name = 'aaa'; + tableInstance.updateRecords(records); + + await new Promise(resolve => requestAnimationFrame(resolve)); + + const renderedText = getRenderedCellText(tableInstance, 0, tableInstance.columnHeaderLevelCount); + const dataValue = tableInstance.getCellValue(0, tableInstance.columnHeaderLevelCount); + const pass = renderedText.includes('aaa') && dataValue === 'aaa'; + status.textContent = `${pass ? 'PASS' : 'FAIL'} | rendered=${renderedText}, data=${dataValue}`; + return status.textContent; +}; + +export function createTable() { + const option: VTable.ListTableConstructorOptions = { + records, + columns: [ + { field: 'name', title: 'First Name', width: 180, editor: 'input' }, + { field: 'age', title: 'Age', width: 120, editor: 'input' } + ], + editCellTrigger: 'doubleclick', + editor: 'input', + widthMode: 'standard', + defaultRowHeight: 36 + }; + + createStatusBar(); + const tableInstance = new VTable.ListTable(document.getElementById(CONTAINER_ID)!, option); + (window as any).tableInstance = tableInstance; + (window as any).issue4761Run = checkUpdateRecordsRender; +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index 8253e772fc..13b2c16107 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -54,6 +54,10 @@ export const menus = [ path: 'debug', name: 'issue-5213-row-series-number-aggregation' }, + { + path: 'debug', + name: 'issue-4761-update-records-edit-render' + }, { path: 'debug', name: 'issue-4810-edit-cell-double-click-blank' diff --git a/packages/vtable/src/ListTable.ts b/packages/vtable/src/ListTable.ts index c72ae28f23..8366170c65 100644 --- a/packages/vtable/src/ListTable.ts +++ b/packages/vtable/src/ListTable.ts @@ -2422,14 +2422,14 @@ export class ListTable extends BaseTable implements ListTableAPI { * 基本表格中显示在body中的索引,即要修改的是body部分的第几行数据; * 如果是树形结构的话 recordIndexs 为数组,数组中每个元素为data的原始数据索引; */ - updateRecords(records: any[], recordIndexs: (number | number[])[], triggerEvent = true) { - listTableUpdateRecords(records, recordIndexs, this); + updateRecords(records: any[], recordIndexs?: (number | number[])[], triggerEvent = true) { + const updateRecordIndexs = recordIndexs ?? records?.map((_, index) => index) ?? []; + listTableUpdateRecords(records, updateRecordIndexs, this); - // 触发更新数据记录事件 - 假设操作成功 if (triggerEvent) { this.fireListeners(TABLE_EVENT_TYPE.UPDATE_RECORD, { records, - recordIndexs, + recordIndexs: updateRecordIndexs, updateCount: records.length }); } diff --git a/packages/vtable/src/data/CachedDataSource.ts b/packages/vtable/src/data/CachedDataSource.ts index 03e0552055..2dec3e5885 100644 --- a/packages/vtable/src/data/CachedDataSource.ts +++ b/packages/vtable/src/data/CachedDataSource.ts @@ -274,6 +274,9 @@ export class CachedDataSource extends DataSource { continue; } const originRecordIndex = this.getOriginRecordIndexForGroup(recordIndex); + if (!isValid(originRecordIndex)) { + continue; + } this.beforeChangedRecordsMap.delete(originRecordIndex.toString()); this.dataSourceObj.records.splice(originRecordIndex, 1); @@ -292,6 +295,9 @@ export class CachedDataSource extends DataSource { continue; } const originRecordIndex = this.getOriginRecordIndexForGroup(recordIndex); + if (!isValid(originRecordIndex)) { + continue; + } this.beforeChangedRecordsMap.delete(originRecordIndex.toString()); this.dataSourceObj.records[originRecordIndex] = records[index]; } diff --git a/packages/vtable/src/ts-types/table-engine.ts b/packages/vtable/src/ts-types/table-engine.ts index 25021a6e07..5655c12ad8 100644 --- a/packages/vtable/src/ts-types/table-engine.ts +++ b/packages/vtable/src/ts-types/table-engine.ts @@ -478,7 +478,7 @@ export interface ListTableAPI extends BaseTableAPI { addRecord: (record: any, recordIndex?: number | number[], triggerEvent?: boolean) => void; addRecords: (records: any[], recordIndex?: number | number[], triggerEvent?: boolean) => void; deleteRecords: (recordIndexs: number[] | number[][], triggerEvent?: boolean) => void; - updateRecords: (records: any[], recordIndexs: (number | number[])[], triggerEvent?: boolean) => void; + updateRecords: (records: any[], recordIndexs?: (number | number[])[], triggerEvent?: boolean) => void; updateFilterRules: (filterRules: FilterRules, options: { clearRowHeightCache?: boolean }) => void; getAggregateValuesByField: (field: string | number) => { col: number;