From 8803a35996c02a0fe9d5ab83f1ce56f31086f44b Mon Sep 17 00:00:00 2001 From: rookie4show Date: Fri, 27 Feb 2026 22:29:33 +0800 Subject: [PATCH] Revert "perf(vector-node): batch resize sync and cut redundant rerenders" This reverts commit e344c2272e2e5e186d43bf2a45072168cf7efe10. --- docs/test/acceptance.md | 16 +- src/__tests__/vectorNodeSync.test.ts | 104 ---------- .../flow/nodes/common/VectorNode.vue | 180 ++++++++++-------- .../flow/nodes/common/VectorNodeModel.ts | 22 +-- .../flow/nodes/common/vectorNodeSync.ts | 123 ------------ 5 files changed, 116 insertions(+), 329 deletions(-) delete mode 100644 src/__tests__/vectorNodeSync.test.ts delete mode 100644 src/components/flow/nodes/common/vectorNodeSync.ts diff --git a/docs/test/acceptance.md b/docs/test/acceptance.md index 7cbc9bc..bc76fe8 100644 --- a/docs/test/acceptance.md +++ b/docs/test/acceptance.md @@ -193,18 +193,18 @@ - [x] 用户素材删除与持久化通过(删除后刷新不复活)。 - [ ] 缺失资产降级策略通过(不阻断导出/渲染)。 - [x] Dynamic Group 分组基础行为通过(分组信息写入 `meta.groupId`,复制分组会携带组内节点)。 -- [x] 分组规则静态检查通过(冲突与供火提示正确且可实时更新)。 -- [x] 规则管理通过(规则列表表格化、弹窗编辑、导入导出可用)。 -- [x] 矢量节点快速缩放性能回归通过(无明显卡顿/卡死)。 +- [ ] 分组规则静态检查通过(冲突与供火提示正确且可实时更新)。 +- [ ] 规则管理通过(规则列表表格化、弹窗编辑、导入导出可用)。 +- [ ] 矢量节点快速缩放性能回归通过(无明显卡顿/卡死)。 - [ ] 导出到 wiki 数据兼容通过(wiki 侧可 normalize 与预览)。 - [ ] 跨项目素材互通通过(同 origin 可复用素材,跨 origin 不互通)。 - [ ] 跨项目规则互通方案确认(共享配置源定义、两侧读取一致)。 - [x] 导出图片时隐藏 Dynamic Group 通过(导出前隐藏,导出后恢复)。 当前状态(2026-02-27): -- 已通过:8 项(基础启动与构建、用户素材上传与使用、用户素材删除与持久化、Dynamic Group 分组基础行为、分组规则静态检查、规则管理、矢量节点快速缩放性能回归、导出图片时隐藏 Dynamic Group)。 +- 已通过:5 项(基础启动与构建、用户素材上传与使用、用户素材删除与持久化、Dynamic Group 分组基础行为、导出图片时隐藏 Dynamic Group)。 - 部分通过:1 项(跨项目规则互通方案确认)。 -- 未通过/待验证:4 项(其余项待完整手测或跨仓联调)。 +- 未通过/待验证:7 项(其余项待完整手测或跨仓联调)。 逐项状态: - 基础启动与构建:已通过 @@ -213,9 +213,9 @@ - 用户素材删除与持久化:已通过 - 缺失资产降级策略:未通过(待手测) - Dynamic Group 分组基础行为:已通过 -- 分组规则静态检查:已通过 -- 规则管理(表格化/导入导出):已通过 -- 矢量节点快速缩放性能回归:已通过 +- 分组规则静态检查:未通过(待手测) +- 规则管理(表格化/导入导出):未通过(待手测) +- 矢量节点快速缩放性能回归:未通过(待手测) - 导出到 wiki 数据兼容:未通过(待跨仓联测) - 跨项目素材互通:未通过(待同 origin 联测) - 跨项目规则互通方案确认:部分通过(yys-editor 已落地,wiki 待读取同源配置) diff --git a/src/__tests__/vectorNodeSync.test.ts b/src/__tests__/vectorNodeSync.test.ts deleted file mode 100644 index e52610a..0000000 --- a/src/__tests__/vectorNodeSync.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { - DEFAULT_VECTOR_CONFIG, - buildNextVectorConfig, - createRafLatestScheduler, - type VectorConfig -} from '@/components/flow/nodes/common/vectorNodeSync'; - -function createFakeRaf() { - let id = 0; - const callbacks = new Map(); - - const requestFrame = vi.fn((cb: FrameRequestCallback) => { - id += 1; - callbacks.set(id, cb); - return id; - }); - - const cancelFrame = vi.fn((handle: number) => { - callbacks.delete(handle); - }); - - const runFrame = () => { - const pending = Array.from(callbacks.values()); - callbacks.clear(); - pending.forEach((cb) => cb(Date.now())); - }; - - return { - requestFrame, - cancelFrame, - runFrame - }; -} - -describe('vectorNodeSync', () => { - it('只在 vector 配置变化时生成下一次提交配置', () => { - const current: VectorConfig = { ...DEFAULT_VECTOR_CONFIG }; - - expect(buildNextVectorConfig(current, { ...current })).toBeNull(); - - const next = buildNextVectorConfig(current, { - fill: '#000000', - tileWidth: 64 - }); - - expect(next).toMatchObject({ - fill: '#000000', - tileWidth: 64 - }); - expect(next?.tileHeight).toBe(current.tileHeight); - }); - - it('同一帧连续缩放事件只提交最后一次更新,避免重复抖动', () => { - const fakeRaf = createFakeRaf(); - const commits: VectorConfig[] = []; - const scheduler = createRafLatestScheduler( - (payload) => { - commits.push(payload); - }, - { - requestFrame: fakeRaf.requestFrame, - cancelFrame: fakeRaf.cancelFrame - } - ); - - scheduler.enqueue({ ...DEFAULT_VECTOR_CONFIG, strokeWidth: 1 }); - scheduler.enqueue({ ...DEFAULT_VECTOR_CONFIG, strokeWidth: 2 }); - scheduler.enqueue({ ...DEFAULT_VECTOR_CONFIG, strokeWidth: 3 }); - - expect(fakeRaf.requestFrame).toHaveBeenCalledTimes(1); - expect(commits).toHaveLength(0); - - fakeRaf.runFrame(); - - expect(commits).toHaveLength(1); - expect(commits[0].strokeWidth).toBe(3); - }); - - it('连续缩放仅变化尺寸时不会触发矢量配置重复提交', () => { - const fakeRaf = createFakeRaf(); - const commits: VectorConfig[] = []; - const scheduler = createRafLatestScheduler( - (payload) => { - commits.push(payload); - }, - { - requestFrame: fakeRaf.requestFrame, - cancelFrame: fakeRaf.cancelFrame - } - ); - - const current = { ...DEFAULT_VECTOR_CONFIG }; - for (let i = 0; i < 40; i += 1) { - const next = buildNextVectorConfig(current, { ...current }); - if (next) { - scheduler.enqueue(next); - } - } - - expect(fakeRaf.requestFrame).not.toHaveBeenCalled(); - expect(commits).toHaveLength(0); - }); -}); diff --git a/src/components/flow/nodes/common/VectorNode.vue b/src/components/flow/nodes/common/VectorNode.vue index 2db3ea2..33a1778 100644 --- a/src/components/flow/nodes/common/VectorNode.vue +++ b/src/components/flow/nodes/common/VectorNode.vue @@ -1,95 +1,120 @@ @@ -103,6 +128,11 @@ const patternFill = computed(() => `url(#${patternId})`); } .vector-content { + width: 100%; + height: 100%; +} + +.vector-content :deep(svg) { display: block; width: 100%; height: 100%; diff --git a/src/components/flow/nodes/common/VectorNodeModel.ts b/src/components/flow/nodes/common/VectorNodeModel.ts index 7889299..38c1a2e 100644 --- a/src/components/flow/nodes/common/VectorNodeModel.ts +++ b/src/components/flow/nodes/common/VectorNodeModel.ts @@ -33,25 +33,9 @@ class VectorNodeModel extends HtmlNodeModel { resize(deltaX: number, deltaY: number) { const result = super.resize?.(deltaX, deltaY); - const nextWidth = this.width; - const nextHeight = this.height; - - // 宽高无变化时跳过,避免高频缩放中的无效属性变更事件。 - if (this.properties?.width === nextWidth && this.properties?.height === nextHeight) { - return result; - } - - // 持久化宽高到 properties(单次提交,减少事件抖动)。 - const setProperties = (this as any).setProperties as ((props: Record) => void) | undefined; - if (setProperties) { - setProperties.call(this, { - width: nextWidth, - height: nextHeight - }); - } else { - this.setProperty('width', nextWidth); - this.setProperty('height', nextHeight); - } + // 持久化宽高到 properties + this.setProperty('width', this.width); + this.setProperty('height', this.height); return result; } diff --git a/src/components/flow/nodes/common/vectorNodeSync.ts b/src/components/flow/nodes/common/vectorNodeSync.ts deleted file mode 100644 index 1f150e5..0000000 --- a/src/components/flow/nodes/common/vectorNodeSync.ts +++ /dev/null @@ -1,123 +0,0 @@ -export interface VectorConfig { - kind: string; - svgContent: string; - path: string; - tileWidth: number; - tileHeight: number; - fill: string; - stroke: string; - strokeWidth: number; -} - -export const DEFAULT_VECTOR_CONFIG: VectorConfig = { - kind: 'rect', - svgContent: '', - path: '', - tileWidth: 50, - tileHeight: 50, - fill: '#409EFF', - stroke: '#303133', - strokeWidth: 1 -}; - -const VECTOR_CONFIG_KEYS: Array = [ - 'kind', - 'svgContent', - 'path', - 'tileWidth', - 'tileHeight', - 'fill', - 'stroke', - 'strokeWidth' -]; - -type FrameRequest = (callback: FrameRequestCallback) => number; -type FrameCancel = (handle: number) => void; - -const getDefaultRequestFrame = (): FrameRequest | undefined => - typeof globalThis.requestAnimationFrame === 'function' - ? globalThis.requestAnimationFrame.bind(globalThis) - : undefined; - -const getDefaultCancelFrame = (): FrameCancel | undefined => - typeof globalThis.cancelAnimationFrame === 'function' - ? globalThis.cancelAnimationFrame.bind(globalThis) - : undefined; - -export function buildNextVectorConfig( - current: VectorConfig, - incoming?: Record | null -): VectorConfig | null { - if (!incoming || typeof incoming !== 'object') { - return null; - } - - let changed = false; - const next = { ...current }; - - for (const key of VECTOR_CONFIG_KEYS) { - if (!(key in incoming)) { - continue; - } - const incomingValue = incoming[key]; - if (incomingValue === undefined || incomingValue === current[key]) { - continue; - } - (next as Record)[key] = incomingValue; - changed = true; - } - - return changed ? next : null; -} - -export function createRafLatestScheduler( - apply: (payload: T) => void, - options?: { - requestFrame?: FrameRequest; - cancelFrame?: FrameCancel; - } -) { - const requestFrame = options?.requestFrame ?? getDefaultRequestFrame(); - const cancelFrame = options?.cancelFrame ?? getDefaultCancelFrame(); - - let rafId: number | null = null; - let pendingPayload: T | null = null; - - const flush = () => { - if (pendingPayload === null) { - return; - } - const latestPayload = pendingPayload; - pendingPayload = null; - apply(latestPayload); - }; - - const schedule = () => { - if (!requestFrame) { - flush(); - return; - } - if (rafId !== null) { - return; - } - rafId = requestFrame(() => { - rafId = null; - flush(); - }); - }; - - return { - enqueue(payload: T) { - pendingPayload = payload; - schedule(); - }, - flush, - cancel() { - if (rafId !== null && cancelFrame) { - cancelFrame(rafId); - } - rafId = null; - pendingPayload = null; - } - }; -}