<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Vue 3 - tag - 沐木</title><link>https://oldletter.cn/tags/vue-3/</link><description>Vue 3 - tag - 沐木</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Mon, 20 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://oldletter.cn/tags/vue-3/" rel="self" type="application/rss+xml"/><item><title>问卷考试系统设计（五）：前端引擎——渲染、状态管理与防作弊</title><link>https://oldletter.cn/posts/qnr-05-frontend/</link><pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-05-frontend/</guid><description><![CDATA[<p><strong>上一篇：<a href="/posts/qnr-04-quality-control/" rel="">问卷考试系统设计（四）：质量控制</a></strong></p>
<p>后端写好了，接口也通了。前端要解决四个问题：怎么把后端返回的 JSON 渲染成可交互的答题界面、状态怎么管、考试场景怎么防作弊、手机上表格挤成一团怎么办。</p>
<p>前端答题状态要围绕一份本地快照流转，草稿和提交使用同一套校验结果：</p>
<div class="mermaid" id="id-2" data-mermaid-definition="c3RhdGVEaWFncmFtLXYyCiAgICAgICAgICAgICAgICAgICAgWypdIC0tPiBMb2FkaW5nRHNsCiAgICAgICAgICAgICAgICAgICAgTG9hZGluZ0RzbCAtLT4gSW5pdFN0b3JlOiDliqDovb3pl67ljbcgRFNMCiAgICAgICAgICAgICAgICAgICAgSW5pdFN0b3JlIC0tPiBBbnN3ZXJpbmc6IOWIneWni&#43;WMliBQaW5pYQogICAgICAgICAgICAgICAgICAgIEFuc3dlcmluZyAtLT4gUmVjb21wdXRlTG9naWM6IOmAiemhueWPmOWMlgogICAgICAgICAgICAgICAgICAgIFJlY29tcHV0ZUxvZ2ljIC0tPiBBbnN3ZXJpbmc6IOabtOaWsOi3s&#43;mimOe7k&#43;aenAogICAgICAgICAgICAgICAgICAgIEFuc3dlcmluZyAtLT4gU2F2aW5nRHJhZnQ6IOWumuaXtuaIluWkseeEpgogICAgICAgICAgICAgICAgICAgIFNhdmluZ0RyYWZ0IC0tPiBBbnN3ZXJpbmc6IOS/neWtmOaIkOWKnwogICAgICAgICAgICAgICAgICAgIEFuc3dlcmluZyAtLT4gU3VibWl0Q2hlY2s6IOeCueWHu&#43;aPkOS6pAogICAgICAgICAgICAgICAgICAgIFN1Ym1pdENoZWNrIC0tPiBBbnN3ZXJpbmc6IOagoemqjOWksei0pQogICAgICAgICAgICAgICAgICAgIFN1Ym1pdENoZWNrIC0tPiBTdWJtaXRBbnN3ZXI6IOagoemqjOmAmui/hwogICAgICAgICAgICAgICAgICAgIFN1Ym1pdEFuc3dlciAtLT4gU3VibWl0U2VjdXJpdHk6IOmZhOW4pui0qOmHj&#43;S4jumYsuS9nOW8iuaVsOaNrgogICAgICAgICAgICAgICAgICAgIFN1Ym1pdFNlY3VyaXR5IC0tPiBEb25lCiAgICAgICAgICAgICAgICAgICAgRG9uZSAtLT4gWypd">stateDiagram-v2
                    [*] --&gt; LoadingDsl
                    LoadingDsl --&gt; InitStore: 加载问卷 DSL
                    InitStore --&gt; Answering: 初始化 Pinia
                    Answering --&gt; RecomputeLogic: 选项变化
                    RecomputeLogic --&gt; Answering: 更新跳题结果
                    Answering --&gt; SavingDraft: 定时或失焦
                    SavingDraft --&gt; Answering: 保存成功
                    Answering --&gt; SubmitCheck: 点击提交
                    SubmitCheck --&gt; Answering: 校验失败
                    SubmitCheck --&gt; SubmitAnswer: 校验通过
                    SubmitAnswer --&gt; SubmitSecurity: 附带质量与防作弊数据
                    SubmitSecurity --&gt; Done
                    Done --&gt; [*]</div><hr>
<h2 id="一注册表模式一套-dsl-驱动所有题型渲染">一、注册表模式：一套 DSL 驱动所有题型渲染</h2>
<h3 id="硬编码的坑">硬编码的坑</h3>
<p>直观的写法是 if-else：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-vue">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">SingleChoice</span> <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;q.type === &#39;single_choice&#39;&#34; :question=&#34;q&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">MultipleChoice</span> <span class="nt">v-else-if</span><span class="o">=</span><span class="s">&#34;q.type === &#39;multiple_choice&#39;&#34; :question=&#34;q&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">LikertScale</span> <span class="nt">v-else-if</span><span class="o">=</span><span class="s">&#34;q.type === &#39;likert&#39;&#34; :question=&#34;q&#34;</span><span class="p"> /&gt;</span></span></span></code></pre></div></div>
<p>每加一种题型，渲染层、校验层、状态层都得改。后面会越来越难维护。</p>]]></description></item></channel></rss>