diff --git a/docs/assets/option/en/common/option-secondary.md b/docs/assets/option/en/common/option-secondary.md index abc43c2089..4d3227f667 100644 --- a/docs/assets/option/en/common/option-secondary.md +++ b/docs/assets/option/en/common/option-secondary.md @@ -721,6 +721,13 @@ Whether to adjust the column width, support configuring functions, the function canResizeColumn?: (col: number, row: number, table: BaseTableAPI) => boolean; ``` +##${prefix} realtime(boolean) = true + +Whether to respond in real time when dragging to resize columns/rows. + +- `true` (default): Column widths / row heights update in real time during dragging, keeping the historical behavior. +- `false`: Only the indicator line moves while dragging; the final size is applied when the mouse is released. Recommended for large datasets to significantly reduce lag while dragging. + #${prefix} dragOrder(Object) #${prefix} dragOrder(Object) diff --git a/docs/assets/option/zh/common/option-secondary.md b/docs/assets/option/zh/common/option-secondary.md index cc560547bf..b96f8471f2 100644 --- a/docs/assets/option/zh/common/option-secondary.md +++ b/docs/assets/option/zh/common/option-secondary.md @@ -719,6 +719,13 @@ animationAppear?: boolean | { canResizeColumn?: (col: number, row: number, table: BaseTableAPI) => boolean; ``` +##${prefix} realtime(boolean) = true + +拖拽调整行列尺寸时是否实时响应。 + +- `true`(默认):拖拽过程中实时更新列宽/行高,与历史行为一致。 +- `false`:拖拽过程中仅移动指示线,鼠标释放后才应用最终尺寸变化。适用于大数据量场景,可显著缓解拖拽时的卡顿。 + #${prefix} dragOrder(Object) 拖拽移动位置的配置。 diff --git a/packages/vtable-gantt/__tests__/gantt-non-realtime-resize-sync.test.ts b/packages/vtable-gantt/__tests__/gantt-non-realtime-resize-sync.test.ts new file mode 100644 index 0000000000..c7189d99f4 --- /dev/null +++ b/packages/vtable-gantt/__tests__/gantt-non-realtime-resize-sync.test.ts @@ -0,0 +1,104 @@ +// @ts-nocheck + +global.__VERSION__ = 'none'; + +import { createDiv } from './dom'; +import { Gantt } from '../src/index'; +// 真正发出 RESIZE_COLUMN / RESIZE_ROW 事件的是 table-group 中的 end wrapper(pointerup 路径), +// 这里直接复用它来模拟拖拽结束,验证跨包布局同步。 +import { endResizeCol } from '../../vtable/src/event/listener/table-group'; + +function createGantt(resize: any) { + const container = createDiv(); + const option = { + records: [{ id: 1, title: '任务', startDate: '2024-07-01', endDate: '2024-07-02', progress: 50 }], + taskListTable: { + columns: [ + { field: 'title', title: '任务名称', width: 180 }, + { field: 'id', title: 'ID', width: 120 } + ], + tableWidth: 'auto', + resize + }, + timelineHeader: { + colWidth: 50, + scales: [{ unit: 'day', step: 1 }] + }, + minDate: '2024-07-01', + maxDate: '2024-07-07' + }; + const gantt = new Gantt(container, option); + return { gantt, container }; +} + +/** + * 忠实模拟一次把第 col 列加宽 delta 像素的拖拽:start -> move(含过程事件) -> up。 + * 过程事件 RESIZE_COLUMN 在真实场景中由 container-dom 的 pointermove 回调发出,这里一并复刻, + * 以便区分实时/非实时两种模式下过程事件携带的列宽是否为最新值。 + */ +function dragResizeCol(table: any, col: number, delta: number) { + const startX = table.getColsWidth(0, col); // 列右边界的大致 x + table.stateManager.startResizeCol(col, startX, 50); + // 一次 pointermove,传入完整位移 + table.stateManager.updateResizeCol(startX + delta, 50); + // container-dom 在每次 pointermove 后发出 RESIZE_COLUMN,携带“当前” getColWidth + if (table.hasListeners('resize_column')) { + table.fireListeners('resize_column', { col, colWidth: table.getColWidth(col) }); + } + // pointerup:走真实的 end wrapper,非实时模式在此提交并补发过程事件 + endResizeCol(table); +} + +describe('gantt non-realtime resize layout sync', () => { + test('non-realtime column resize should sync taskTableWidth after commit', () => { + const { gantt, container } = createGantt({ realtime: false }); + const table = gantt.taskListTableInstance; + const border = gantt.parsedOptions.outerFrameStyle.borderLineWidth as number; + expect(table.internalProps.resizeRealtime).toBe(false); + + const widthBefore = gantt.taskTableWidth; + const colWidthBefore = table.getColWidth(0); + + dragResizeCol(table, 0, 60); + + const colWidthAfter = table.getColWidth(0); + // 列宽确实变宽 + expect(colWidthAfter).toBeGreaterThan(colWidthBefore); + // 关键断言:甘特图任务列表宽度已随最终列宽同步,而不是停留在旧值。 + // 修复前,拖拽过程事件携带旧列宽、结束时又不再发 resize_column,taskTableWidth 会停在 widthBefore。 + expect(gantt.taskTableWidth).toBe(table.getAllColsWidth() + border); + expect(gantt.taskTableWidth).toBeGreaterThan(widthBefore); + + gantt.release?.(); + container.remove(); + }); + + test('non-realtime shrink should also sync (no leftover gap)', () => { + const { gantt, container } = createGantt({ realtime: false }); + const table = gantt.taskListTableInstance; + const border = gantt.parsedOptions.outerFrameStyle.borderLineWidth as number; + + const widthBefore = gantt.taskTableWidth; + dragResizeCol(table, 0, -60); + + expect(gantt.taskTableWidth).toBe(table.getAllColsWidth() + border); + expect(gantt.taskTableWidth).toBeLessThan(widthBefore); + + gantt.release?.(); + container.remove(); + }); + + test('realtime mode keeps syncing as before', () => { + const { gantt, container } = createGantt({ realtime: true }); + const table = gantt.taskListTableInstance; + const border = gantt.parsedOptions.outerFrameStyle.borderLineWidth as number; + expect(table.internalProps.resizeRealtime).not.toBe(false); + + dragResizeCol(table, 0, 60); + + expect(gantt.taskTableWidth).toBe(table.getAllColsWidth() + border); + + gantt.release?.(); + container.remove(); + }); +}); diff --git a/packages/vtable/__tests__/visual/cases/index.mjs b/packages/vtable/__tests__/visual/cases/index.mjs index 1072fdd97c..111ad6aeea 100644 --- a/packages/vtable/__tests__/visual/cases/index.mjs +++ b/packages/vtable/__tests__/visual/cases/index.mjs @@ -162,6 +162,12 @@ export const cases = [ { id: 'pivot-empty-string-dimension', purpose: '透视空字符串列维度与普通列并存', file: './pivot/empty-string-dimension.mjs', bundles: ['vtable'] }, { id: 'pivot-chart-pie-multi-select', purpose: '透视饼图连续扇区多选', file: './pivot-chart/pie-multi-select.mjs', bundles: ['vchart'] }, { id: 'resize-row-height', purpose: '列表选择后拖动行边界调整行高', file: './interaction/resize-row-height.mjs', bundles: ['vtable'] }, + { id: 'resize-column-non-realtime', purpose: 'resize.realtime=false 时列宽仅在鼠标松开后应用', file: './interaction/resize-column-non-realtime.mjs', bundles: ['vtable'] }, + { id: 'resize-column-realtime-default', purpose: 'resize.realtime 默认 true 时列宽实时随拖拽变化', file: './interaction/resize-column-realtime-default.mjs', bundles: ['vtable'] }, + { id: 'resize-column-non-realtime-min-clamp', purpose: '非实时模式越过 minWidth 时按目标方向 clamp 而非反向 resize', file: './interaction/resize-column-non-realtime-min-clamp.mjs', bundles: ['vtable'] }, + { id: 'resize-column-non-realtime-preview-clamp', purpose: '非实时拖拽预览指示线/标签按最终可应用尺寸约束', file: './interaction/resize-column-non-realtime-preview-clamp.mjs', bundles: ['vtable'] }, + { id: 'resize-column-adaptive-neighbor', purpose: 'adaptive 列宽相邻列位移不被重复扣减', file: './interaction/resize-column-adaptive-neighbor.mjs', bundles: ['vtable'] }, + { id: 'resize-row-adaptive-neighbor', purpose: 'adaptive 行高相邻行位移不被重复扣减', file: './interaction/resize-row-adaptive-neighbor.mjs', bundles: ['vtable'] }, { id: 'pivot-resize-indicator-row', purpose: '指标行上的透视表拖动行高', file: './pivot/resize-indicator-row.mjs', bundles: ['vtable'] }, { id: 'pivot-corner-sort-one-dimension', purpose: '单级角头维度初始降序状态与排序图标', file: './pivot/corner-sort-one-dimension.mjs', bundles: ['vtable'] }, { id: 'pivot-corner-sort-two-dimensions', purpose: '两级角头维度初始降序状态与排序图标', file: './pivot/corner-sort-two-dimensions.mjs', bundles: ['vtable'] }, diff --git a/packages/vtable/__tests__/visual/cases/interaction/resize-column-adaptive-neighbor.mjs b/packages/vtable/__tests__/visual/cases/interaction/resize-column-adaptive-neighbor.mjs new file mode 100644 index 0000000000..e894e6ba77 --- /dev/null +++ b/packages/vtable/__tests__/visual/cases/interaction/resize-column-adaptive-neighbor.mjs @@ -0,0 +1,65 @@ +/** + * 验证目的:adaptive 列宽下,相邻右列承接相反方向的位移后不应被重复扣减。 + * 右列已是 rightColWidthCache - detaX,若再用 rightColWidth - detaX 判断 limitMinWidth 会把位移错误截短, + * 导致最终列宽与指示线终点、实时拖动不一致。本例一次性拖动一个较大位移以触发该重复扣减。 + */ +export default { + mount(container) { + // 固定容器宽度让 adaptive 把两列各分到约 150px,便于数值断言。 + container.style.width = '300px'; + container.style.height = '300px'; + return new window.VTable.ListTable(container, { + columns: [ + { field: 'name', title: 'Name', width: 150 }, + { field: 'value', title: 'Value', width: 150 } + ], + records: Array.from({ length: 5 }, (_, index) => ({ + name: `Item ${index + 1}`, + value: index + 1 + })), + defaultRowHeight: 32, + widthMode: 'adaptive', + resize: { columnResizeMode: 'all' } + }); + }, + async exercise(page) { + const point = await page.evaluate(() => { + const table = window.__visualTable; + const b = table.getCellRect(0, 0).bounds; + const host = document.getElementById('table').getBoundingClientRect(); + window.__leftBefore = table.getColWidth(0); + window.__rightBefore = table.getColWidth(1); + // 选取使 rightBefore - drag >= limitMinWidth(10) 但 rightBefore - 2*drag < 10 的位移, + // 正确实现应保留全部位移,旧实现会二次扣减并截短。 + window.__drag = window.__rightBefore - 20; + return { edge: { x: host.x + b.x2 - 1, y: host.y + (b.y1 + b.y2) / 2 }, drag: window.__rightBefore - 20 }; + }); + // 单步拖动一个大位移,使一次 updateResizeColumn 收到完整 detaX。 + await page.mouse.move(point.edge.x, point.edge.y); + await page.mouse.down(); + await page.mouse.move(point.edge.x + point.drag, point.edge.y); + await page.mouse.up(); + }, + async verify(page) { + await page.evaluate(() => { + const table = window.__visualTable; + const leftBefore = window.__leftBefore; + const rightBefore = window.__rightBefore; + const drag = window.__drag; + const leftAfter = table.getColWidth(0); + const rightAfter = table.getColWidth(1); + // 右列应收缩到约 rightBefore - drag(≈20);重复扣减会把它停在 rightBefore - 10 附近。 + if (rightAfter > rightBefore - drag + 5) { + throw new Error( + `adaptive 右列位移被重复扣减截短:rightBefore=${rightBefore}, drag=${drag}, rightAfter=${rightAfter}` + ); + } + // 左列应吃满整段位移,验证尺寸与指示线终点一致。 + if (leftAfter < leftBefore + drag - 5) { + throw new Error( + `adaptive 左列未吃满位移:leftBefore=${leftBefore}, drag=${drag}, leftAfter=${leftAfter}` + ); + } + }); + } +}; diff --git a/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime-min-clamp.mjs b/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime-min-clamp.mjs new file mode 100644 index 0000000000..6e0f2cd827 --- /dev/null +++ b/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime-min-clamp.mjs @@ -0,0 +1,50 @@ +/** + * 验证目的:非实时模式把整段累计位移一次传入 min/max 约束时,应按目标值的越界方向 clamp。 + * 列宽 180、minWidth 100、maxWidth 200,向左累计拖动 100px 目标为 80,必须贴 minWidth 收到 100, + * 不能因“当前宽度离哪个边界更近”而反向扩到 maxWidth 200。 + */ +export default { + mount(container) { + return new window.VTable.ListTable(container, { + columns: [ + { field: 'name', title: 'Name', width: 180, minWidth: 100, maxWidth: 200 }, + { field: 'value', title: 'Value', width: 150 } + ], + records: Array.from({ length: 5 }, (_, index) => ({ + name: `Item ${index + 1}`, + value: index + 1 + })), + defaultRowHeight: 32, + resize: { columnResizeMode: 'all', realtime: false } + }); + }, + async exercise(page) { + const point = await page.evaluate(() => { + const table = window.__visualTable; + const b = table.getCellRect(0, 0).bounds; + const host = document.getElementById('table').getBoundingClientRect(); + window.__colWidthBefore = table.getColWidth(0); + return { edge: { x: host.x + b.x2 - 1, y: host.y + (b.y1 + b.y2) / 2 } }; + }); + // 非实时模式:拖拽过程只移动指示线,向左累计 100px,松开鼠标后统一提交。 + await page.mouse.move(point.edge.x, point.edge.y); + await page.mouse.down(); + await page.mouse.move(point.edge.x - 100, point.edge.y, { steps: 10 }); + await page.mouse.up(); + }, + async verify(page) { + await page.evaluate(() => { + const table = window.__visualTable; + const before = window.__colWidthBefore; + const after = table.getColWidth(0); + // 目标宽度 80 越过下界,必须 clamp 到 minWidth 100 并相对初始缩窄。 + if (after !== 100) { + throw new Error(`非实时越界列宽未按 minWidth clamp:before=${before}, after=${after}`); + } + // 反向 resize 会扩到 maxWidth 200,这里显式拦截该回归。 + if (after >= before) { + throw new Error(`非实时向左拖动反而没有缩窄,疑似反向 resize:before=${before}, after=${after}`); + } + }); + } +}; diff --git a/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime-preview-clamp.mjs b/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime-preview-clamp.mjs new file mode 100644 index 0000000000..07091c3c18 --- /dev/null +++ b/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime-preview-clamp.mjs @@ -0,0 +1,71 @@ +/** + * 验证目的:非实时模式拖拽越过 minWidth 时,指示线预览与标签应显示“最终可应用的尺寸”, + * 而不是未约束的 pendingDeltaX。列宽 180、minWidth 100,向左拖 100px 目标为 80, + * 预览标签必须显示 100px(与松手后的最终列宽一致),不能显示未约束的 80px。 + */ +export default { + mount(container) { + return new window.VTable.ListTable(container, { + columns: [ + { field: 'name', title: 'Name', width: 180, minWidth: 100, maxWidth: 200 }, + { field: 'value', title: 'Value', width: 150 } + ], + records: Array.from({ length: 5 }, (_, index) => ({ + name: `Item ${index + 1}`, + value: index + 1 + })), + defaultRowHeight: 32, + resize: { columnResizeMode: 'all', realtime: false } + }); + }, + async exercise(page) { + const point = await page.evaluate(() => { + const table = window.__visualTable; + const b = table.getCellRect(0, 0).bounds; + const host = document.getElementById('table').getBoundingClientRect(); + return { edge: { x: host.x + b.x2 - 1, y: host.y + (b.y1 + b.y2) / 2 } }; + }); + // 非实时模式:按下并向左累计拖动 100px,但先不松手,停在拖拽预览状态。 + await page.mouse.move(point.edge.x, point.edge.y); + await page.mouse.down(); + await page.mouse.move(point.edge.x - 100, point.edge.y, { steps: 10 }); + // 捕获松手前的预览标签文案与指示线位置。 + await page.evaluate(() => { + const table = window.__visualTable; + const component = table.scenegraph.component; + window.__previewLabel = component.columnResizeLabel.lastChild.attribute.text; + window.__previewLineX = component.columnResizeLine.attribute.x; + // 基准线:当前列右边界 x(无偏移)。 + const b = table.getCellRect(0, 0).bounds; + window.__colBaseX = b.x2; + window.__colWidthBefore = table.getColWidth(0); + }); + await page.mouse.up(); + }, + async verify(page) { + await page.evaluate(() => { + const table = window.__visualTable; + const previewLabel = window.__previewLabel; + const previewLineX = window.__previewLineX; + const colBaseX = window.__colBaseX; + const after = table.getColWidth(0); + + // 松手后最终列宽应 clamp 到 minWidth 100。 + if (after !== 100) { + throw new Error(`最终列宽未按 minWidth clamp:after=${after}`); + } + // 预览标签必须显示最终可应用尺寸 100px,而不是未约束的 80px。 + if (previewLabel !== '100px') { + throw new Error(`预览标签未按最终尺寸约束:previewLabel=${previewLabel}, 期望 100px`); + } + // 指示线位置应对应有效位移(最终宽度 100 相对初始 180 向左收 80px), + // 即基准线左移 (180-100)=80px;若用未约束的 100px 位移会多移 20px。 + const expectedLineX = colBaseX - 80; + if (Math.abs(previewLineX - expectedLineX) > 1) { + throw new Error( + `预览指示线位置未按最终尺寸约束:previewLineX=${previewLineX}, 期望≈${expectedLineX}(基准 ${colBaseX})` + ); + } + }); + } +}; diff --git a/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime.mjs b/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime.mjs new file mode 100644 index 0000000000..74f74c57b2 --- /dev/null +++ b/packages/vtable/__tests__/visual/cases/interaction/resize-column-non-realtime.mjs @@ -0,0 +1,66 @@ +/** + * 验证目的:resize.realtime=false 时,列宽调整仅在鼠标松开后应用一次。 + * 拖拽过程中列宽保持不变,松开鼠标时列宽等于累计偏移量。 + */ +export default { + mount(container) { + return new window.VTable.ListTable(container, { + columns: [ + { field: 'name', title: 'Name', width: 150 }, + { field: 'value', title: 'Value', width: 150 } + ], + records: Array.from({ length: 5 }, (_, index) => ({ + name: `Item ${index + 1}`, + value: index + 1 + })), + defaultRowHeight: 32, + resize: { columnResizeMode: 'all', realtime: false } + }); + }, + async exercise(page) { + const point = await page.evaluate(() => { + const table = window.__visualTable; + const b = table.getCellRect(0, 0).bounds; + const host = document.getElementById('table').getBoundingClientRect(); + window.__colWidthBefore = table.getColWidth(0); + return { + cell: { x: host.x + (b.x1 + b.x2) / 2, y: host.y + (b.y1 + b.y2) / 2 }, + edge: { x: host.x + b.x2 - 1, y: host.y + (b.y1 + b.y2) / 2 } + }; + }); + await page.mouse.move(point.edge.x, point.edge.y); + await page.mouse.down(); + // 拖拽过程中每步都记录列宽,确认拖拽时列宽未变化 + await page.mouse.move(point.edge.x + 40, point.edge.y, { steps: 10 }); + const widthDuringDrag = await page.evaluate(() => window.__visualTable.getColWidth(0)); + await page.mouse.move(point.edge.x + 80, point.edge.y, { steps: 10 }); + const widthDuringDrag2 = await page.evaluate(() => window.__visualTable.getColWidth(0)); + await page.mouse.up(); + await page.evaluate( + ({ w1, w2 }) => { + window.__widthDuringDrag = w1; + window.__widthDuringDrag2 = w2; + }, + { w1: widthDuringDrag, w2: widthDuringDrag2 } + ); + }, + async verify(page) { + await page.evaluate(() => { + const table = window.__visualTable; + const before = window.__colWidthBefore; + const during1 = window.__widthDuringDrag; + const during2 = window.__widthDuringDrag2; + const after = table.getColWidth(0); + // 拖拽中列宽不应变化 + if (during1 !== before || during2 !== before) { + throw new Error( + `非实时响应模式下拖拽过程中列宽发生了变化: before=${before}, during1=${during1}, during2=${during2}, after=${after}` + ); + } + // 松开鼠标后列宽应增加(累计偏移量 80px 应用) + if (after <= before + 40) { + throw new Error(`鼠标松开后列宽未按累计偏移量应用: before=${before}, after=${after}`); + } + }); + } +}; diff --git a/packages/vtable/__tests__/visual/cases/interaction/resize-column-realtime-default.mjs b/packages/vtable/__tests__/visual/cases/interaction/resize-column-realtime-default.mjs new file mode 100644 index 0000000000..9cb7cc3f77 --- /dev/null +++ b/packages/vtable/__tests__/visual/cases/interaction/resize-column-realtime-default.mjs @@ -0,0 +1,60 @@ +/** + * 验证目的:resize.realtime=true(默认)时保持历史行为,拖拽过程中列宽实时变化。 + * 作为 resize-column-non-realtime 的对照用例,防止改动回归默认行为。 + */ +export default { + mount(container) { + return new window.VTable.ListTable(container, { + columns: [ + { field: 'name', title: 'Name', width: 150 }, + { field: 'value', title: 'Value', width: 150 } + ], + records: Array.from({ length: 5 }, (_, index) => ({ + name: `Item ${index + 1}`, + value: index + 1 + })), + defaultRowHeight: 32, + resize: { columnResizeMode: 'all' } + }); + }, + async exercise(page) { + const point = await page.evaluate(() => { + const table = window.__visualTable; + const b = table.getCellRect(0, 0).bounds; + const host = document.getElementById('table').getBoundingClientRect(); + window.__colWidthBefore = table.getColWidth(0); + return { + edge: { x: host.x + b.x2 - 1, y: host.y + (b.y1 + b.y2) / 2 } + }; + }); + await page.mouse.move(point.edge.x, point.edge.y); + await page.mouse.down(); + await page.mouse.move(point.edge.x + 40, point.edge.y, { steps: 10 }); + const widthDuringDrag = await page.evaluate(() => window.__visualTable.getColWidth(0)); + await page.mouse.move(point.edge.x + 80, point.edge.y, { steps: 10 }); + const widthDuringDrag2 = await page.evaluate(() => window.__visualTable.getColWidth(0)); + await page.mouse.up(); + await page.evaluate( + ({ w1, w2 }) => { + window.__widthDuringDrag = w1; + window.__widthDuringDrag2 = w2; + }, + { w1: widthDuringDrag, w2: widthDuringDrag2 } + ); + }, + async verify(page) { + await page.evaluate(() => { + const before = window.__colWidthBefore; + const during1 = window.__widthDuringDrag; + const during2 = window.__widthDuringDrag2; + const after = window.__visualTable.getColWidth(0); + // 实时模式:拖拽中列宽应递增 + if (during1 <= before || during2 <= during1) { + throw new Error(`实时模式下列宽未随拖拽变化: before=${before}, during1=${during1}, during2=${during2}`); + } + if (after <= before + 40) { + throw new Error(`鼠标松开后列宽未达到预期: before=${before}, after=${after}`); + } + }); + } +}; diff --git a/packages/vtable/__tests__/visual/cases/interaction/resize-row-adaptive-neighbor.mjs b/packages/vtable/__tests__/visual/cases/interaction/resize-row-adaptive-neighbor.mjs new file mode 100644 index 0000000000..c3f6e90ecf --- /dev/null +++ b/packages/vtable/__tests__/visual/cases/interaction/resize-row-adaptive-neighbor.mjs @@ -0,0 +1,64 @@ +/** + * 验证目的:adaptive 行高下,下方相邻行承接相反方向的位移后不应被重复扣减。 + * 行高路径与列相同:bottomRowHeight 已是 bottomRowHeightCache - detaY,若再用 bottomRowHeight - detaY + * 判断 limitMinHeight 会把位移错误截短,使最终行高与指示线终点、实时拖动不一致。本例一次拖动大位移触发该重复扣减。 + */ +export default { + mount(container) { + // 固定容器高度让 adaptive 把数据行撑高,便于数值断言。 + container.style.width = '300px'; + container.style.height = '400px'; + return new window.VTable.ListTable(container, { + columns: [ + { field: 'name', title: 'Name', width: 150 }, + { field: 'value', title: 'Value', width: 150 } + ], + records: Array.from({ length: 3 }, (_, index) => ({ + name: `Item ${index + 1}`, + value: index + 1 + })), + defaultRowHeight: 80, + heightMode: 'adaptive', + resize: { rowResizeMode: 'all' } + }); + }, + async exercise(page) { + const point = await page.evaluate(() => { + const table = window.__visualTable; + const b = table.getCellRect(0, 1).bounds; + const host = document.getElementById('table').getBoundingClientRect(); + window.__topBefore = table.getRowHeight(1); + window.__bottomBefore = table.getRowHeight(2); + // 选取使 bottomBefore - drag >= limitMinHeight(10) 但 bottomBefore - 2*drag < 10 的位移。 + window.__drag = window.__bottomBefore - 20; + return { edge: { x: host.x + (b.x1 + b.x2) / 2, y: host.y + b.y2 - 1 }, drag: window.__bottomBefore - 20 }; + }); + // 单步向下拖动一个大位移,使一次 updateResizeRow 收到完整 detaY。 + await page.mouse.move(point.edge.x, point.edge.y); + await page.mouse.down(); + await page.mouse.move(point.edge.x, point.edge.y + point.drag); + await page.mouse.up(); + }, + async verify(page) { + await page.evaluate(() => { + const table = window.__visualTable; + const topBefore = window.__topBefore; + const bottomBefore = window.__bottomBefore; + const drag = window.__drag; + const topAfter = table.getRowHeight(1); + const bottomAfter = table.getRowHeight(2); + // 下方行应收缩到约 bottomBefore - drag(≈20);重复扣减会把它停在 bottomBefore - 10 附近。 + if (bottomAfter > bottomBefore - drag + 5) { + throw new Error( + `adaptive 下方行位移被重复扣减截短:bottomBefore=${bottomBefore}, drag=${drag}, bottomAfter=${bottomAfter}` + ); + } + // 拖动行应吃满整段位移,验证尺寸与指示线终点一致。 + if (topAfter < topBefore + drag - 5) { + throw new Error( + `adaptive 拖动行未吃满位移:topBefore=${topBefore}, drag=${drag}, topAfter=${topAfter}` + ); + } + }); + } +}; diff --git a/packages/vtable/examples/interactive/resize-realtime.ts b/packages/vtable/examples/interactive/resize-realtime.ts new file mode 100644 index 0000000000..e74c3a7a3d --- /dev/null +++ b/packages/vtable/examples/interactive/resize-realtime.ts @@ -0,0 +1,75 @@ +import * as VTable from '../../src'; + +const ListTable = VTable.ListTable; +const CONTAINER_ID = 'vTable'; + +export function createTable() { + // 生成较大数据量,便于对比实时与非实时响应下的拖拽流畅度 + const records = Array.from({ length: 10000 }, (_, index) => ({ + id: index + 1, + name: `记录 ${index + 1}`, + dept: ['研发', '产品', '运营', '销售'][index % 4], + city: ['杭州', '北京', '上海', '成都', '广州'][index % 5], + score: Math.round(Math.random() * 100), + salary: Math.round(4000 + Math.random() * 20000) + })); + + const option: any = { + columns: [ + { field: 'id', title: 'ID', width: 80 }, + { field: 'name', title: '姓名', width: 160 }, + { field: 'dept', title: '部门', width: 120 }, + { field: 'city', title: '城市', width: 120 }, + { field: 'score', title: '评分', width: 120 }, + { field: 'salary', title: '薪资', width: 140 } + ], + records, + defaultRowHeight: 32, + resize: { + columnResizeMode: 'all', + rowResizeMode: 'all', + // 关键:设为 false 时,拖拽过程只移动指示线,鼠标松开后才应用列宽/行高变化, + // 可显著缓解大数据量下的拖拽卡顿。默认为 true,保持原有行为。 + realtime: false + } + }; + + const instance = new ListTable(document.getElementById(CONTAINER_ID)!, option); + (window as any).tableInstance = instance; + + // 提供简单的运行时切换按钮,便于对比两种模式的手感 + const bar = document.createElement('div'); + bar.style.cssText = [ + 'position:absolute', + 'top:10px', + 'right:10px', + 'z-index:9999', + 'background:#fff', + 'padding:8px 12px', + 'border:1px solid #ddd', + 'border-radius:4px', + 'box-shadow:0 1px 4px rgba(0,0,0,.1)', + 'font-family:sans-serif', + 'font-size:12px' + ].join(';'); + bar.innerHTML = ` +