tmagic-editor/docs/assets/guide_advanced_prop-form-validate.md.CfZaPKFi.js
2026-07-24 09:39:36 +00:00

41 lines
22 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import{At as e,E as t,Ot as n,Xt as r}from"./chunks/framework.ECw2lWOt.js";var i=JSON.parse(`{"title":"属性配置表单校验联动","description":"","frontmatter":{},"headers":[],"relativePath":"guide/advanced/prop-form-validate.md","filePath":"guide/advanced/prop-form-validate.md"}`),a={name:`guide/advanced/prop-form-validate.md`};function o(t,i,a,o,s,c){return r(),n(`div`,null,[...i[0]||=[e(`<h1 id="属性配置表单校验联动" tabindex="-1">属性配置表单校验联动 <a class="header-anchor" href="#属性配置表单校验联动" aria-label="Permalink to “属性配置表单校验联动”"></a></h1><p>编辑器在属性面板(属性表单 / 样式表单)中已支持 <a href="./../../form-config/rules.html">表单校验</a>。默认情况下,表单校验失败时本次改动会被丢弃,不会写入节点。</p><p>「属性配置表单校验联动」能力(<code>enablePropsFormValidate</code>)允许在<strong>校验失败时仍按当前表单值更新节点</strong>,并把错误信息集中记录到 <code>editorService</code>,从而实现:</p><ul><li>组件树(图层)中对出错节点<strong>标红并显示错误图标</strong>,悬停可查看错误文案;</li><li>业务在<strong>保存前拦截</strong>存在校验错误的组件(见 <a href="#保存拦截">保存拦截</a></li><li>校验错误随 DSL 操作写入历史快照,<strong>撤销 / 重做能正确还原</strong>错误状态。</li></ul><blockquote><p>该能力默认关闭,需通过 <code>&lt;m-editor :enable-props-form-validate=&quot;true&quot;&gt;</code> 显式开启。</p></blockquote><h2 id="工作原理" tabindex="-1">工作原理 <a class="header-anchor" href="#工作原理" aria-label="Permalink to “工作原理”"></a></h2><h3 id="错误来源维度" tabindex="-1">错误来源维度 <a class="header-anchor" href="#错误来源维度" aria-label="Permalink to “错误来源维度”"></a></h3><p>属性面板分「属性」与「样式」两个独立的 <code>FormPanel</code>,它们指向同一节点。为避免某个面板校验通过时误清另一个面板记录的错误,错误信息<strong>以来源(<code>source</code>)为键分别保存</strong></p><ul><li><code>&#39;props&#39;</code>:属性表单校验错误</li><li><code>&#39;style&#39;</code>:样式表单校验错误</li></ul><p>节点只要<strong>任一来源存在非空错误文案</strong>即视为出错。</p><p>内部类型定义见 <code>NodeInvalidInfo</code></p><div class="language-ts"><button title="Copy Code" class="copy"></button><span class="lang">ts</span><pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e;" tabindex="0" dir="ltr"><code><span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">interface</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> NodeInvalidInfo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
<span class="line"><span style="--shiki-light:#62687b;--shiki-dark:#818e99;"> /** 属性表单校验错误文案(可能为包含 &lt;br&gt; 的 HTML */</span></span>
<span class="line"><span style="--shiki-light:#c13617;--shiki-dark:#FFAB70;"> props</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">?:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#62687b;--shiki-dark:#818e99;"> /** 样式表单校验错误文案(可能为包含 &lt;br&gt; 的 HTML */</span></span>
<span class="line"><span style="--shiki-light:#c13617;--shiki-dark:#FFAB70;"> style</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">?:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> string</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><h3 id="数据流" tabindex="-1">数据流 <a class="header-anchor" href="#数据流" aria-label="Permalink to “数据流”"></a></h3><ol><li>表单校验失败时(开启了 <code>enablePropsFormValidate</code><code>FormPanel</code> 仍以当前表单值提交,并把错误随提交事件一并抛出;</li><li><code>PropsPanel</code> 在调用 <code>editorService.update()</code> 时,把 <code>invalidInfo: { id, source, error }</code> 一并传入;</li><li><code>editorService</code> 在<strong>写入历史记录之前</strong>落库错误标记(<code>applyInvalidInfo</code>),使历史快照与本次变更对齐;</li><li>错误被记录到 <code>invalidNodeIds</code> 状态,并触发 <code>invalid-node-change</code> 事件;</li><li>组件树节点通过读取 <code>invalidNodeIds</code> 响应式地展示标红与错误图标。</li></ol><p>校验成功时(或源码编辑器保存时)不携带 <code>invalidInfo</code>,保持已有错误状态不变(即不会清除错误)——只有对应来源的表单再次校验通过才会清除该来源的错误。</p><h2 id="快速开始" tabindex="-1">快速开始 <a class="header-anchor" href="#快速开始" aria-label="Permalink to “快速开始”"></a></h2><div class="language-html"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e;" tabindex="0" dir="ltr"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;</span><span style="--shiki-light:#11782a;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> &lt;</span><span style="--shiki-light:#11782a;--shiki-dark:#85E89D;">m-editor</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> :enable-props-form-validate</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;true&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;&lt;/</span><span style="--shiki-light:#11782a;--shiki-dark:#85E89D;">m-editor</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&lt;/</span><span style="--shiki-light:#11782a;--shiki-dark:#85E89D;">template</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">&gt;</span></span></code></pre></div><p>开启后无需其它配置,属性 / 样式表单校验失败即会自动记录错误并标红组件树节点。</p><h2 id="读取与清除错误" tabindex="-1">读取与清除错误 <a class="header-anchor" href="#读取与清除错误" aria-label="Permalink to “读取与清除错误”"></a></h2><p>业务侧可通过 <code>editorService</code> 读取或清除错误标记:</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e;" tabindex="0" dir="ltr"><code><span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { editorService } </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;@tmagic/editor&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#62687b;--shiki-dark:#818e99;">// 读取全部错误节点Map&lt;Id, NodeInvalidInfo&gt;</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> invalidNodeIds</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> editorService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">getInvalidNodeIds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">();</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#62687b;--shiki-dark:#818e99;">// 读取单个节点的错误</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> info</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> editorService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">getInvalidNodeInfo</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;text_123&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#62687b;--shiki-dark:#818e99;">// 手动记录 / 清除错误(一般无需手动调用,属性面板已自动维护)</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">editorService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">setInvalidNode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;text_123&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;props&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;标题不能为空&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">editorService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">deleteInvalidNode</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;text_123&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;props&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">); </span><span style="--shiki-light:#62687b;--shiki-dark:#818e99;">// 仅清除属性表单错误</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">editorService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">resetInvalidNodeId</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(); </span><span style="--shiki-light:#62687b;--shiki-dark:#818e99;">// 清空全部错误</span></span></code></pre></div><p>订阅错误状态变化(非响应式消费方,如自定义工具栏):</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e;" tabindex="0" dir="ltr"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">editorService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;invalid-node-change&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, (</span><span style="--shiki-light:#c13617;--shiki-dark:#FFAB70;">invalidNodeIds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> console.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">log</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;校验错误节点数:&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, invalidNodeIds.size);</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">});</span></span></code></pre></div><blockquote><p>响应式读取(如自定义组件树节点内容)请直接读取 <code>editorService.get(&#39;invalidNodeIds&#39;)</code>,其变化会触发 Vue 响应式更新。</p></blockquote><h2 id="保存拦截" tabindex="-1">保存拦截 <a class="header-anchor" href="#保存拦截" aria-label="Permalink to “保存拦截”"></a></h2><p>开启校验联动后,业务可读取 <code>invalidNodeIds</code>,在存在校验错误时阻止保存并提示:</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki shiki-themes github-light github-dark" style="--shiki-light:#24292e;--shiki-dark:#e1e4e8;--shiki-light-bg:#fff;--shiki-dark-bg:#24292e;" tabindex="0" dir="ltr"><code><span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { editorService } </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;@tmagic/editor&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { tMagicMessage } </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;@tmagic/design&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> checkInvalidNodes</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#c13617;--shiki-dark:#FFAB70;">services</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> invalidNodeIds</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> services?.editorService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">getInvalidNodeIds</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">?.();</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">!</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">invalidNodeIds </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">||</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> invalidNodeIds.size </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">===</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) {</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> names</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">...</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">invalidNodeIds.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">keys</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">()].</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">map</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">((</span><span style="--shiki-light:#c13617;--shiki-dark:#FFAB70;">id</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">=&gt;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> node</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> services?.editorService.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">getNodeById</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(id);</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> node?.name </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">?</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> \`\${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">node</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">.</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">name</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}(\${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">id</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">})\`</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> :</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> \`\${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">id</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}\`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> });</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> tMagicMessage.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">\`以下组件存在配置校验错误,请修复后再保存:\${</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">names</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;、&quot;</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">)</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}\`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#62687b;--shiki-dark:#818e99;">// 在菜单保存按钮 / 预览保存的 handler 中:</span></span>
<span class="line"><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> (</span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">!</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">checkInvalidNodes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(services)) </span><span style="--shiki-light:#c62739;--shiki-dark:#F97583;">return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">save</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">();</span></span></code></pre></div><p>playground 已内置该拦截逻辑,参见 <code>playground/src/pages/composables/use-editor-menu.ts</code>。</p><h2 id="与历史记录的关系" tabindex="-1">与历史记录的关系 <a class="header-anchor" href="#与历史记录的关系" aria-label="Permalink to “与历史记录的关系”"></a></h2><p>错误标记会随 DSL 操作(<code>add</code> / <code>update</code> 等)一并写入历史快照。编辑器内部在「操作前」与「操作后」分别留存错误状态快照:</p><ul><li><strong>撤销undo</strong>:还原到操作前的错误状态(撤销一个「校验失败」的改动后错误消失);</li><li><strong>重做redo</strong>:还原到操作后的错误状态(重做后错误恢复)。</li></ul><p>整体替换 <code>root</code>(如加载新 DSL后会自动清理不存在于新 DSL 中的失效节点错误记录,避免残留误报;删除节点时也会同步清理其子树相关的错误记录。</p>`,32)]])}var s=t(a,[[`render`,o]]);export{i as __pageData,s as default};