diff --git a/packages/editor/src/layouts/props-panel/PropsPanel.vue b/packages/editor/src/layouts/props-panel/PropsPanel.vue index baaa8dea..d1ab7d62 100644 --- a/packages/editor/src/layouts/props-panel/PropsPanel.vue +++ b/packages/editor/src/layouts/props-panel/PropsPanel.vue @@ -164,17 +164,25 @@ const submit = async ( }; if (v.style) { - Object.entries(v.style).forEach(([key, value]) => { - if (value !== '' && newValue.style) { - newValue.style[key] = value; - } - }); + // 空字符串样式值表示「清除该样式」,需保留才能在 doUpdate 的 mergeWith 中覆盖旧值。 + if (eventData) { + // 表单编辑:先过滤掉空字符串(避免表单默认空值污染 DSL), + // 再按 changeRecords 恢复被主动清空的字段。 + Object.entries(v.style).forEach(([key, value]) => { + if (value !== '' && newValue.style) { + newValue.style[key] = value; + } + }); - eventData?.changeRecords?.forEach((record) => { - if (record.propPath?.startsWith('style') && record.value === '') { - setValueByKeyPath(record.propPath, record.value, newValue); - } - }); + eventData.changeRecords?.forEach((record) => { + if (record.propPath?.startsWith('style') && record.value === '') { + setValueByKeyPath(record.propPath, record.value, newValue); + } + }); + } else { + // 源码编辑器保存(无 eventData):style 原样保留,其中的空字符串视为用户主动清除该样式。 + newValue.style = { ...v.style }; + } } // 区分操作途径:表单字段编辑(MForm @change)会带上 eventData(含 changeRecords); diff --git a/packages/editor/tests/unit/layouts/props-panel/PropsPanel.spec.ts b/packages/editor/tests/unit/layouts/props-panel/PropsPanel.spec.ts index 64d2a7fa..1ab43ee7 100644 --- a/packages/editor/tests/unit/layouts/props-panel/PropsPanel.spec.ts +++ b/packages/editor/tests/unit/layouts/props-panel/PropsPanel.spec.ts @@ -95,7 +95,7 @@ vi.mock('@editor/layouts/props-panel/FormPanel.vue', () => ({ // 模拟 CodeEditor 源码保存:仅传 values,无 eventData、无 error(对应 saveCode 路径) h('button', { class: 'code-save-btn', - onClick: () => emit('submit', { id: 'n1', style: { color: 'red' } }), + onClick: () => emit('submit', { id: 'n1', style: { color: 'red', width: '' } }), }), h('button', { class: 'submit-err-btn', onClick: () => emit('submit-error', new Error('e')) }), h('button', { class: 'form-err-btn', onClick: () => emit('form-error', new Error('e')) }), @@ -239,6 +239,16 @@ describe('PropsPanel', () => { expect(options.historySource).toBe('code'); }); + test('CodeEditor 源码保存时 style 中的空字符串值被保留(表示清除该样式)', async () => { + const wrapper = mount(PropsPanel, { props: {} as any }); + await new Promise((r) => setTimeout(r, 0)); + await wrapper.find('.code-save-btn').trigger('click'); + + const calledNode = (editorService.update.mock.calls[0] as any)[0]; + expect(calledNode.style.color).toBe('red'); + expect(calledNode.style.width).toBe(''); + }); + test('mounted 事件 emit', async () => { const wrapper = mount(PropsPanel, { props: {} as any }); await wrapper.find('.mounted-btn').trigger('click');