From 5e7ce11a94b8ae053b84131446adadda67e36da8 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 5 Feb 2026 14:29:58 +0800 Subject: [PATCH 1/3] fix: cell formula cache error after delete row record --- .../sheet-delete-record-formula.test.ts | 71 +++++++++++++++++++ .../table-plugins-formula-detection.test.ts | 34 +++++++++ packages/vtable-sheet/examples/menu.ts | 4 ++ .../examples/sheet/sheet-deleteRecord.ts | 60 ++++++++++++++++ .../vtable-sheet/src/core/table-plugins.ts | 21 ++++-- .../src/managers/formula-manager.ts | 12 ++-- 6 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 packages/vtable-sheet/__tests__/sheet-delete-record-formula.test.ts create mode 100644 packages/vtable-sheet/__tests__/table-plugins-formula-detection.test.ts create mode 100644 packages/vtable-sheet/examples/sheet/sheet-deleteRecord.ts diff --git a/packages/vtable-sheet/__tests__/sheet-delete-record-formula.test.ts b/packages/vtable-sheet/__tests__/sheet-delete-record-formula.test.ts new file mode 100644 index 000000000..6cc5d66d2 --- /dev/null +++ b/packages/vtable-sheet/__tests__/sheet-delete-record-formula.test.ts @@ -0,0 +1,71 @@ +// @ts-nocheck +import { VTableSheet } from '../src/index'; +import { createDiv, removeDom } from './dom'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(global as any).__VERSION__ = 'none'; + +describe('VTableSheet deleteRecords - 公式调整', () => { + let container: HTMLDivElement; + let sheetInstance: VTableSheet | null = null; + + beforeEach(() => { + container = createDiv(); + container.style.position = 'relative'; + container.style.width = '1000px'; + container.style.height = '800px'; + }); + + afterEach(() => { + if (sheetInstance) { + sheetInstance.release(); + sheetInstance = null; + } + removeDom(container); + }); + + test('删除第3行后,C7公式应移动并调整范围', () => { + sheetInstance = new VTableSheet(container, { + showSheetTab: true, + sheets: [ + { + rowCount: 200, + columnCount: 10, + sheetKey: 'sheet1', + sheetTitle: 'sheet1', + filter: true, + columns: [ + { + title: '名称', + sort: true, + width: 100 + } + ], + data: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], ['放到', '个', '哦'], [], [null, null, 30]], + formulas: { + C8: '=sum(C2:C5)' + }, + active: true, + firstRowAsHeader: false + } + ] + }); + + // 删除第3行数据(索引2) + sheetInstance.getActiveSheet().tableInstance?.deleteRecords([2]); + + const formulaEngine = sheetInstance.formulaManager.formulaEngine; + const formula = sheetInstance.formulaManager.getCellFormula({ sheet: 'sheet1', row: 6, col: 2 }); + expect(formula).toBe('=SUM(C2:C4)'); + expect(sheetInstance.formulaManager.isCellFormula({ sheet: 'sheet1', row: 6, col: 2 })).toBe(true); + expect(formulaEngine.getCellFormula({ sheet: 'sheet1', row: 6, col: 2 })).toBe('=SUM(C2:C4)'); + expect(formulaEngine.getCellFormula({ sheet: 'sheet1', row: 7, col: 2 })).toBeUndefined(); + + const exportedFormulas = formulaEngine.exportFormulas('sheet1'); + expect(exportedFormulas.C7).toBe('=SUM(C2:C4)'); + expect(exportedFormulas.C8).toBeUndefined(); + + const resultValue = sheetInstance.formulaManager.getCellValue({ sheet: 'sheet1', row: 6, col: 2 }).value; + expect(resultValue).toBe(21); + }); +}); diff --git a/packages/vtable-sheet/__tests__/table-plugins-formula-detection.test.ts b/packages/vtable-sheet/__tests__/table-plugins-formula-detection.test.ts new file mode 100644 index 000000000..5c8233ddc --- /dev/null +++ b/packages/vtable-sheet/__tests__/table-plugins-formula-detection.test.ts @@ -0,0 +1,34 @@ +// @ts-nocheck +import { createFormulaDetectionOptions } from '../src/core/table-plugins'; + +describe('table-plugins formula detection', () => { + test('should use active worksheet key for formula lookup', () => { + const formulaManager = { + isCellFormula: jest.fn().mockReturnValue(true), + getCellFormula: jest.fn().mockReturnValue('=SUM(C2:C4)'), + setCellContent: jest.fn() + }; + + const vtableSheet = { + formulaManager, + getActiveSheet: () => ({ + getKey: () => 'sheet1' + }), + sheetManager: { + _activeSheetKey: 'Sheet1', + getActiveSheet: () => ({ sheetKey: 'sheet1' }) + } + }; + + const detection = createFormulaDetectionOptions(undefined, undefined, vtableSheet); + + detection.isFormulaCell(2, 6, null, {} as any); + expect(formulaManager.isCellFormula).toHaveBeenCalledWith({ sheet: 'sheet1', row: 6, col: 2 }); + + detection.getCellFormula(2, 6, null, {} as any); + expect(formulaManager.getCellFormula).toHaveBeenCalledWith({ sheet: 'sheet1', row: 6, col: 2 }); + + detection.setCellFormula(2, 6, '=SUM(C2:C4)', {} as any); + expect(formulaManager.setCellContent).toHaveBeenCalledWith({ sheet: 'sheet1', row: 6, col: 2 }, '=SUM(C2:C4)'); + }); +}); diff --git a/packages/vtable-sheet/examples/menu.ts b/packages/vtable-sheet/examples/menu.ts index 5c2400af5..11652c42a 100644 --- a/packages/vtable-sheet/examples/menu.ts +++ b/packages/vtable-sheet/examples/menu.ts @@ -14,5 +14,9 @@ export const menus = [ { path: 'sheet', name: 'sheet-update' + }, + { + path: 'sheet', + name: 'sheet-deleteRecord' } ]; diff --git a/packages/vtable-sheet/examples/sheet/sheet-deleteRecord.ts b/packages/vtable-sheet/examples/sheet/sheet-deleteRecord.ts new file mode 100644 index 000000000..c0e599902 --- /dev/null +++ b/packages/vtable-sheet/examples/sheet/sheet-deleteRecord.ts @@ -0,0 +1,60 @@ +import { VTableSheet, TYPES, VTable } from '../../src/index'; +import * as VTablePlugins from '@visactor/vtable-plugins'; +import { VTableSheetEventType } from '../../src/ts-types/spreadsheet-events'; +const CONTAINER_ID = 'vTable'; +export function createTable() { + // 清理之前的实例(如果存在) + if ((window as any).sheetInstance) { + (window as any).sheetInstance.release(); + (window as any).sheetInstance = null; + } + + const sheetInstance = new VTableSheet(document.getElementById(CONTAINER_ID)!, { + showSheetTab: true, + // defaultRowHeight: 25, + // defaultColWidth: 100, + sheets: [ + { + rowCount: 200, + columnCount: 10, + sheetKey: 'sheet1', + sheetTitle: 'sheet1', + filter: true, + columns: [ + { + title: '名称', + sort: true, + width: 100 + } + ], + data: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], ['放到', '个', '哦'], [], [null, null, 30]], + formulas: { + C8: '=sum(C2:C5)' + }, + active: true + } + ] + }); + window.sheetInstance = sheetInstance; + //删除第3行数据 + sheetInstance.getActiveSheet().tableInstance?.deleteRecords([2]); + // const tableInstance = sheetInstance.getActiveSheet().tableInstance; + + // sheetInstance.formulaManager.setCellContent({ sheet: 'sheet1', row: 7, col: 3 }, '=SUM(C2:C5)'); + // const result = sheetInstance.formulaManager.getCellValue({ + // sheet: 'sheet1', + // row: 7, + // col: 3 + // }); + // sheetInstance + // .getActiveSheet() + // .tableInstance?.changeCellValue(3, 7, result.error ? '#ERROR!' : result.value, false, false); + //删除第3行数据 + // sheetInstance.getActiveSheet().tableInstance?.deleteRecords([2]); + // debugger; + // sheetInstance.getActiveSheet().tableInstance?.startEditCell(3, 6); + + // bindDebugTool(sheetInstance.activeWorkSheet.scenegraph.stage as any, { + // customGrapicKeys: ['role', '_updateTag'] + // }); +} diff --git a/packages/vtable-sheet/src/core/table-plugins.ts b/packages/vtable-sheet/src/core/table-plugins.ts index 959036341..7fc5e9ffc 100644 --- a/packages/vtable-sheet/src/core/table-plugins.ts +++ b/packages/vtable-sheet/src/core/table-plugins.ts @@ -435,7 +435,20 @@ function handleDisableFirstRowAsHeader(table: VTable.ListTable): void { /** * 创建公式检测选项,使用vtable-sheet的公式引擎 */ -function createFormulaDetectionOptions(sheetDefine?: ISheetDefine, options?: IVTableSheetOptions, vtableSheet?: any) { +export function createFormulaDetectionOptions( + sheetDefine?: ISheetDefine, + options?: IVTableSheetOptions, + vtableSheet?: any +) { + const getActiveSheetKey = (): string => { + return ( + vtableSheet?.getActiveSheet?.()?.getKey?.() || + vtableSheet?.sheetManager?.getActiveSheet?.()?.sheetKey || + sheetDefine?.sheetKey || + 'Sheet1' + ); + }; + return { /** * 自定义公式检测函数 - 使用vtable-sheet的公式引擎判断是否为公式 @@ -444,7 +457,7 @@ function createFormulaDetectionOptions(sheetDefine?: ISheetDefine, options?: IVT // 首先尝试使用vtable-sheet的公式管理器 if (vtableSheet?.formulaManager) { try { - const sheetName = (vtableSheet.sheetManager as any)?._activeSheetKey || 'Sheet1'; + const sheetName = getActiveSheetKey(); return vtableSheet.formulaManager.isCellFormula({ sheet: sheetName, row: row, @@ -472,7 +485,7 @@ function createFormulaDetectionOptions(sheetDefine?: ISheetDefine, options?: IVT // 首先尝试使用vtable-sheet的公式管理器 if (vtableSheet?.formulaManager) { try { - const sheetName = (vtableSheet.sheetManager as any)?._activeSheetKey || 'Sheet1'; + const sheetName = getActiveSheetKey(); return vtableSheet.formulaManager.getCellFormula({ sheet: sheetName, row: row, @@ -499,7 +512,7 @@ function createFormulaDetectionOptions(sheetDefine?: ISheetDefine, options?: IVT // 首先尝试使用vtable-sheet的公式管理器 if (vtableSheet?.formulaManager) { try { - const sheetName = (vtableSheet.sheetManager as any)?._activeSheetKey || 'Sheet1'; + const sheetName = getActiveSheetKey(); vtableSheet.formulaManager.setCellContent( { sheet: sheetName, diff --git a/packages/vtable-sheet/src/managers/formula-manager.ts b/packages/vtable-sheet/src/managers/formula-manager.ts index a342b3fea..4f5eb5861 100644 --- a/packages/vtable-sheet/src/managers/formula-manager.ts +++ b/packages/vtable-sheet/src/managers/formula-manager.ts @@ -784,7 +784,7 @@ export class FormulaManager implements IFormulaManager { }); this.sheet .getActiveSheet() - .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value); + .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value, false, false); }); } catch (error) { console.error('Failed to add rows:', error); @@ -828,7 +828,7 @@ export class FormulaManager implements IFormulaManager { }); this.sheet .getActiveSheet() - .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value); + .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value, false, false); }); } catch (error) { console.error('Failed to remove rows:', error); @@ -870,7 +870,7 @@ export class FormulaManager implements IFormulaManager { }); this.sheet .getActiveSheet() - .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value); + .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value, false, false); }); } catch (error) { console.error('Failed to add columns:', error); @@ -913,7 +913,7 @@ export class FormulaManager implements IFormulaManager { }); this.sheet .getActiveSheet() - .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value); + .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value, false, false); }); } catch (error) { console.error('Failed to remove columns:', error); @@ -955,7 +955,7 @@ export class FormulaManager implements IFormulaManager { const result = this.getCellValue(cell); this.sheet .getActiveSheet() - .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value); + .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value, false, false); } // Log completion info @@ -999,7 +999,7 @@ export class FormulaManager implements IFormulaManager { const result = this.getCellValue(cell); this.sheet .getActiveSheet() - .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value); + .tableInstance?.changeCellValue(cell.col, cell.row, result.error ? '#ERROR!' : result.value, false, false); } // Log completion info From 1a095a2a47adb0201cfdd13730db2bed19547b6b Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 5 Feb 2026 14:30:27 +0800 Subject: [PATCH 2/3] docs: update changlog of rush --- ...-delete_record_formula_error_2026-02-05-06-30.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/fix-delete_record_formula_error_2026-02-05-06-30.json diff --git a/common/changes/@visactor/vtable/fix-delete_record_formula_error_2026-02-05-06-30.json b/common/changes/@visactor/vtable/fix-delete_record_formula_error_2026-02-05-06-30.json new file mode 100644 index 000000000..5688da31e --- /dev/null +++ b/common/changes/@visactor/vtable/fix-delete_record_formula_error_2026-02-05-06-30.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: cell formula cache error after delete row record\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From 834a14f86101c0d9ff418b0c464558441a86904c Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 5 Feb 2026 14:47:29 +0800 Subject: [PATCH 3/3] docs: update custom icon --- docs/assets/guide/en/custom_define/custom_icon.md | 1 + docs/assets/guide/zh/custom_define/custom_icon.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/assets/guide/en/custom_define/custom_icon.md b/docs/assets/guide/en/custom_define/custom_icon.md index 6cc660d09..e866047cf 100644 --- a/docs/assets/guide/en/custom_define/custom_icon.md +++ b/docs/assets/guide/en/custom_define/custom_icon.md @@ -211,6 +211,7 @@ The list of resettable internal icons is as follows: | Fixed Columns | VTable. TYPES. IconFuncTypeEnum.frozen | "frozen" | Fixed Columns Function Icon, Fixed Status | | Fixed Columns | VTable. TYPES. IconFuncTypeEnum.frozen | "frozenCurrent" | Fixed Columns Function Icon, Frozen When Columns | | Image or video address invalid | VTable. TYPES. IconFuncTypeEnum.damagePic | "damage_pic" | Multimedia resource parsing failed | +| Loading | VTable. TYPES. IconFuncTypeEnum.loading_pic | "loading_pic" | Loading status | | Tree Structure Folding | VTable. TYPES. IconFuncTypeEnum.collapse | "collapse" | Tree Structure Folded State | | Tree Structure Expand | VTable. TYPES. IconFuncTypeEnum.expand | "expand" | Tree Structure Expand State | | Tree Structure Folding | VTable. TYPES. IconFuncTypeEnum.collapse | "collapse" | Tree Structure Folded State | diff --git a/docs/assets/guide/zh/custom_define/custom_icon.md b/docs/assets/guide/zh/custom_define/custom_icon.md index 0acb6721a..9d333bccb 100644 --- a/docs/assets/guide/zh/custom_define/custom_icon.md +++ b/docs/assets/guide/zh/custom_define/custom_icon.md @@ -211,6 +211,7 @@ VTable.register.icon('frozenCurrent', { | 固定列 | VTable.TYPES.IconFuncTypeEnum.frozen | "frozen" | 固定列功能图标 已固定状态 | | 固定列 | VTable.TYPES.IconFuncTypeEnum.frozen | "frozenCurrent" | 固定列功能图标 被冻结当列 | | 图片 or 视频地址失效 | VTable.TYPES.IconFuncTypeEnum.damagePic | "damage_pic" | 多媒体资源解析失败 | +| 加载中 | VTable.TYPES.IconFuncTypeEnum.loading_pic | "loading_pic" | 加载中状态 | | 树形结构折叠 | VTable.TYPES.IconFuncTypeEnum.collapse | "collapse" | 树形结构折叠状态 | | 树形结构展开 | VTable.TYPES.IconFuncTypeEnum.expand | "expand" | 树形结构展开状态 | | 树形结构折叠 | VTable.TYPES.IconFuncTypeEnum.collapse | "collapse" | 树形结构折叠状态 |