diff --git a/packages/editor/src/hooks/use-stage.ts b/packages/editor/src/hooks/use-stage.ts index 496032ee..da7821fb 100644 --- a/packages/editor/src/hooks/use-stage.ts +++ b/packages/editor/src/hooks/use-stage.ts @@ -8,7 +8,7 @@ import editorService from '@editor/services/editor'; import uiService from '@editor/services/ui'; import type { StageOptions } from '@editor/type'; import { H_GUIDE_LINE_STORAGE_KEY, UI_SELECT_MODE_EVENT_NAME, V_GUIDE_LINE_STORAGE_KEY } from '@editor/utils/const'; -import { getGuideLineFromCache } from '@editor/utils/editor'; +import { buildChangeRecords, getGuideLineFromCache } from '@editor/utils/editor'; const root = computed(() => editorService.get('root')); const page = computed(() => editorService.get('page')); @@ -94,7 +94,15 @@ export const useStage = (stageOptions: StageOptions) => { return; } - editorService.update(ev.data.map((data) => ({ id: getIdFromEl()(data.el) || '', style: data.style }))); + // 为每个元素单独更新,确保 changeRecords 与对应的元素关联 + ev.data.forEach((data) => { + const id = getIdFromEl()(data.el); + if (!id) return; + + const { style = {} } = data; + + editorService.update({ id, style }, { changeRecords: buildChangeRecords(style, 'style') }); + }); }); stage.on('sort', (ev: SortEventData) => { diff --git a/packages/editor/src/utils/editor.ts b/packages/editor/src/utils/editor.ts index c9189f28..9dbb161c 100644 --- a/packages/editor/src/utils/editor.ts +++ b/packages/editor/src/utils/editor.ts @@ -411,3 +411,28 @@ export const isIncludeDataSource = (node: MNode, oldNode: MNode) => { return isIncludeDataSource; }; + +export const buildChangeRecords = (value: any, basePath: string) => { + const changeRecords: { propPath: string; value: any }[] = []; + + // 递归构建 changeRecords + const buildChangeRecords = (obj: any, basePath: string) => { + Object.entries(obj).forEach(([key, value]) => { + if (value !== undefined) { + const currentPath = basePath ? `${basePath}.${key}` : key; + + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + // 递归处理嵌套对象 + buildChangeRecords(value, currentPath); + } else { + // 处理基础类型值 + changeRecords.push({ propPath: currentPath, value }); + } + } + }); + }; + + buildChangeRecords(value, basePath); + + return changeRecords; +}; diff --git a/packages/editor/tests/unit/utils/editor.spec.ts b/packages/editor/tests/unit/utils/editor.spec.ts index 2e9c494d..ee1a31db 100644 --- a/packages/editor/tests/unit/utils/editor.spec.ts +++ b/packages/editor/tests/unit/utils/editor.spec.ts @@ -154,3 +154,154 @@ describe('moveItemsInContainer', () => { expect(container.items[2].id).toBe(3); }); }); + +describe('buildChangeRecords', () => { + test('基础类型值', () => { + const value = { + name: 'test', + age: 25, + active: true, + }; + const result = editor.buildChangeRecords(value, ''); + + expect(result).toEqual([ + { propPath: 'name', value: 'test' }, + { propPath: 'age', value: 25 }, + { propPath: 'active', value: true }, + ]); + }); + + test('嵌套对象', () => { + const value = { + user: { + name: 'John', + profile: { + age: 30, + city: 'Beijing', + }, + }, + settings: { + theme: 'dark', + }, + }; + const result = editor.buildChangeRecords(value, ''); + + expect(result).toEqual([ + { propPath: 'user.name', value: 'John' }, + { propPath: 'user.profile.age', value: 30 }, + { propPath: 'user.profile.city', value: 'Beijing' }, + { propPath: 'settings.theme', value: 'dark' }, + ]); + }); + + test('带有basePath', () => { + const value = { + style: { + width: 100, + height: 200, + }, + }; + const result = editor.buildChangeRecords(value, 'node'); + + expect(result).toEqual([ + { propPath: 'node.style.width', value: 100 }, + { propPath: 'node.style.height', value: 200 }, + ]); + }); + + test('包含数组', () => { + const value = { + items: [1, 2, 3], + config: { + list: ['a', 'b'], + }, + }; + const result = editor.buildChangeRecords(value, ''); + + expect(result).toEqual([ + { propPath: 'items', value: [1, 2, 3] }, + { propPath: 'config.list', value: ['a', 'b'] }, + ]); + }); + + test('包含null值', () => { + const value = { + data: null, + info: { + value: null, + name: 'test', + }, + }; + const result = editor.buildChangeRecords(value, ''); + + expect(result).toEqual([ + { propPath: 'data', value: null }, + { propPath: 'info.value', value: null }, + { propPath: 'info.name', value: 'test' }, + ]); + }); + + test('跳过undefined值', () => { + const value = { + name: 'test', + age: undefined, + info: { + city: 'Beijing', + country: undefined, + }, + }; + const result = editor.buildChangeRecords(value, ''); + + expect(result).toEqual([ + { propPath: 'name', value: 'test' }, + { propPath: 'info.city', value: 'Beijing' }, + ]); + }); + + test('空对象', () => { + const value = {}; + const result = editor.buildChangeRecords(value, ''); + + expect(result).toEqual([]); + }); + + test('深层嵌套', () => { + const value = { + level1: { + level2: { + level3: { + level4: { + value: 'deep', + }, + }, + }, + }, + }; + const result = editor.buildChangeRecords(value, 'root'); + + expect(result).toEqual([{ propPath: 'root.level1.level2.level3.level4.value', value: 'deep' }]); + }); + + test('混合类型', () => { + const value = { + string: 'text', + number: 42, + boolean: false, + array: [1, 2], + object: { + nested: 'value', + }, + nullValue: null, + }; + const result = editor.buildChangeRecords(value, 'mixed'); + + expect(result).toEqual([ + { propPath: 'mixed.string', value: 'text' }, + { propPath: 'mixed.number', value: 42 }, + { propPath: 'mixed.boolean', value: false }, + { propPath: 'mixed.array', value: [1, 2] }, + { propPath: 'mixed.object.nested', value: 'value' }, + { propPath: 'mixed.nullValue', value: null }, + ]); + }); +});