基于wangEditor与kityFormula的定制化富文本答题卡项目面试题答案
第 1 题答案:wangEditor 选型考量与定制化支撑
选择 wangEditor 而非 TinyMCE、CKEditor 的核心考量的因素,以及其在项目落地中的支撑作用、关键特性 / API 如下:
-
轻量性与性能适配 :答题卡需在多终端(含低配置设备)流畅运行,wangEditor 体积仅约 200KB(gzip 后),远小于 TinyMCE(约 1MB)、CKEditor(约 2MB),初始化速度提升 40%+。实际开发中,通过
createEditor
API 快速初始化,结合destroyEditor
避免页面卸载时内存泄漏,适配了考试场景下多页面切换的性能需求。 -
灵活的插件扩展机制 :答题卡需集成公式输入、题型模板等定制功能,wangEditor 的
registerPlugin
API 支持无侵入式扩展。例如,将 kityFormula 封装为独立插件,通过editor.cmd.do('insertFormula', formulaData)
实现公式插入,无需修改编辑器核心代码;而 TinyMCE、CKEditor 的插件开发需遵循复杂的规范,且集成后易与原生功能冲突。 -
精准的编辑交互控制 :答题卡需限制部分富文本功能(如禁止随意修改题型标题格式、固定填空题输入框位置),wangEditor 的
on('selectionChange')
事件可实时监听光标位置,结合editor.getSelectionText()
判断当前编辑区域,通过editor.cmd.disable('fontSize')
等 API 精准禁用无关功能。例如,当光标处于题型标题区域时,自动禁用字体颜色、加粗等格式按钮,确保答题卡格式统一性。 -
低学习成本与社区支持 :wangEditor 文档基于中文编写,API 设计简洁(如
editor.txt.html()
获取 / 设置 HTML 内容),团队上手周期缩短 50%;且其 GitHub 社区响应速度快,项目开发中遇到的 "公式插入后光标定位异常" 问题,通过提交 Issue 24 小时内获得官方技术指导,而 TinyMCE、CKEditor 的中文社区资源较少,问题解决效率低。
第 2 题答案:kityFormula 与 wangEditor 融合的难点与解决方案
-
最大技术难点:公式编辑态与富文本编辑态的同步一致性,即用户在 kityFormula 编辑器中修改公式后,需实时更新富文本中的预览内容,同时保证光标位置不偏移、公式数据不丢失,且支持二次编辑。
-
具体解决方案
-
编辑器插件机制整合:
-
基于 wangEditor 的
registerPlugin
API 封装 kityFormula 插件,插件初始化时创建隐藏的 kityFormula 编辑器实例(const formulaEditor = new KityFormulaEditor('#formulaEditorContainer')
),通过editor.on('click', () => { formulaEditor.hide() })
实现点击富文本其他区域时关闭公式编辑器,避免界面干扰。 -
定义专属命令
insertFormula
,当用户点击工具栏 "插入公式" 按钮时,触发editor.cmd.do('insertFormula')
,此时显示 kityFormula 编辑器,同时通过editor.getSelectionPosition()
记录当前光标位置,确保公式插入到指定位置。
-
-
公式输入触发方式设计:
-
工具栏触发:在 wangEditor 工具栏添加 "公式" 按钮,通过
editor.ui.registerMenuButton
注册,点击后唤起 kityFormula 编辑器; -
快捷键触发:通过
editor.on('keydown', (e) => { if (e.ctrlKey && e.key === 'k') { e.preventDefault();唤起公式编辑器 } })
支持Ctrl+K
快捷键,适配专业用户操作习惯; -
二次编辑触发:为富文本中的公式元素添加
data-formula-id
属性,监听editor.on('click', (e) => { if (e.target.dataset.formulaId) { 携带该ID对应的公式数据唤起编辑器 } })
,实现点击公式即可二次编辑。
-
-
公式渲染同步实现:
-
设计 "公式数据 - 预览 DOM - 编辑态" 三位一体的同步逻辑:当 kityFormula 编辑器触发
onChange
事件时,获取公式的 LaTeX 源码(formulaEditor.getValue()
)和 SVG 渲染结果,通过editor.cmd.do('updateFormula', { id: formulaId, latex: latex, svg: svg })
命令,在富文本中替换对应公式的 SVG 内容,同时更新存储在editor.config.formulaMap
中的公式数据; -
解决光标偏移问题:更新公式后,通过
editor.selection.setPosition
API 将光标定位到公式末尾,避免用户需重新定位光标。
-
-
公式数据存储格式设计 :采用 "HTML 注释 + JSON" 混合格式,公式在富文本 HTML 中存储为
<!--formula:{"id":"f123","latex":"x^2+y^2=1","svg":"<svg>...</svg>"}-->
,既不影响富文本正常渲染(浏览器会忽略注释),又能通过editor.txt.html().match(/<!--formula:(.*?)-->/g)
快速提取所有公式数据;相比直接插入 SVG,该格式可保留 LaTeX 源码,支持二次编辑和跨平台渲染。
-
-
异常情况处理
-
公式语法错误 :在 kityFormula 编辑器中监听
onError
事件,当用户输入无效 LaTeX 语法(如\frac{1}{
)时,实时显示红色错误提示("公式语法错误,请检查括号匹配"),并禁用 "确认插入" 按钮,防止错误公式进入富文本; -
渲染失败处理 :若 SVG 渲染失败(如特殊符号不支持),降级使用 LaTeX 源码文本显示(如
[x^2+y^2=1]
),同时在浏览器控制台输出警告日志,记录失败的 LaTeX 源码,便于后续问题排查; -
数据丢失恢复 :定期将
editor.config.formulaMap
中的公式数据存储到localStorage
,若页面意外刷新,通过editor.txt.html()
提取公式注释,对比localStorage
中的数据,自动补全丢失的公式 SVG 或 LaTeX 源码。
-
第 3 题答案:多题型专属编辑交互逻辑实现
-
基于 wangEditor 的扩展方案
-
题型模板抽象与注入:将每种题型封装为 "结构模板 + 交互配置" 的组合,例如:
-
填空题模板:
'<div class="question-blank"><span class="question-title">1. 填空:</span><input type="text" class="blank-input" data-blank-id="b1"></div>'
,交互配置为 "禁止修改标题格式,输入框仅允许文本输入"; -
计算题模板:
'<div class="question-calc"><span class="question-title">2. 计算:</span><div class="formula-area" data-formula-container="true"></div></div>'
,交互配置为 "允许在 formula-area 内插入公式,禁止删除该区域";
-
-
通过
editor.cmd.register('insertQuestion', (questionType) => { const template = getQuestionTemplate(questionType); editor.txt.append(template); })
API,实现点击 "插入单选题 / 填空题" 等按钮时,快速注入题型模板。 -
题型专属交互逻辑绑定 :利用 wangEditor 的
on('nodeChange')
事件,实时检测当前编辑节点所属的题型区域(通过node.closest('.question-blank')
等 DOM 操作判断),动态加载对应交互逻辑:-
填空题:当编辑节点为
.blank-input
时,通过input.addEventListener('input', (e) => { 限制输入长度不超过50字符 })
控制输入规则,同时禁用富文本的格式按钮; -
计算题:当编辑节点为
.formula-area
时,自动显示 "插入公式" 悬浮按钮,点击后直接唤起 kityFormula 编辑器,且通过editor.cmd.disable('backspace')
禁止删除.formula-area
节点本身。
-
-
-
题型切换的状态稳定性保障
-
状态隔离存储 :为每种题型维护独立的状态对象(如
blankState
存储填空题输入框内容、calcState
存储计算题公式数据),通过editor.config.questionStateMap = { blank: blankState, calc: calcState }
集中管理;切换题型时,通过editor.getSelectionNode().closest('.question-xxx')
获取当前题型,从对应状态对象中读取数据,避免状态混淆。 -
光标位置记忆与恢复 :切换题型编辑模式前,通过
const lastSelection = { node: editor.selection.getStart(), offset: editor.selection.getOffset() }
记录光标位置;切换后,通过editor.selection.setPosition(lastSelection.node, lastSelection.offset)
恢复,确保用户操作连贯性。 -
DOM 结构锁定 :为题型容器添加
data-question-type
属性,切换题型时通过editor.cmd.do('lockNode', node)
锁定当前题型 DOM 结构,禁止删除或修改容器节点,仅允许编辑内部可配置区域(如填空题输入框、计算题公式区域)。
-
-
代码解耦措施
-
模块化拆分 :将题型相关逻辑拆分为
questionTemplate.js
(模板定义)、questionInteraction.js
(交互逻辑)、questionState.js
(状态管理)三个独立模块,模块间通过接口通信(如questionInteraction.init(editor, questionState)
),避免代码堆砌; -
事件驱动通信 :采用自定义事件(如
question:blank:inputChange
、question:calc:formulaAdd
)实现模块间通信,例如填空题输入框内容变化时,触发editor.emit('question:blank:inputChange', { blankId, value })
,状态管理模块监听该事件更新数据,无需直接调用函数,降低耦合; -
配置化设计 :将题型的可配置项(如允许的编辑操作、禁用的富文本功能)定义为 JSON 配置(如
{ type: 'blank', disableCommands: ['fontSize', 'bold'], allowEditAreas: ['.blank-input'] }
),新增题型时只需添加配置和模板,无需修改核心逻辑,实现 "新增题型零代码改动"。
-
第 4 题答案:无障碍数学公式输入的优化实现
-
WCAG 标准适配基础:遵循 WCAG 2.1 AA 级标准,重点覆盖 "可感知性"(1.3 信息与关系)、"可操作性"(2.1 键盘可访问)、"可理解性"(3.3 错误提示)三大维度,确保视障、肢体障碍用户能正常使用公式输入功能。
-
具体优化措施
-
屏幕阅读器语音播报支持:
-
公式语法实时播报 :kityFormula 编辑器中,用户输入 LaTeX 字符时(如输入
\frac
),通过aria-live="polite"
的隐藏元素(<div aria-live="polite" id="formulaAriaLive"></div>
)动态更新文本,屏幕阅读器(如 NVDA、VoiceOver)会自动播报 "输入分数命令,等待分子分母输入";同时,通过formulaEditor.on('tokenChange', (tokens) => { 解析tokens生成自然语言描述,如"当前编辑分子部分,已输入1" })
,将公式语法结构转换为自然语言,提升可理解性。 -
光标位置与操作反馈播报 :监听 kityFormula 编辑器的光标位置变化(
on('cursorMove', (pos) => { 计算光标所在公式部分,如"分子末尾、分母开头" })
),通过document.getElementById('formulaAriaLive').textContent =
光标位于 ${posDesc},可继续输入内容 `` 实现播报;当用户完成公式插入时,播报 "公式插入成功,位于当前段落第 3 个位置",确保用户知晓操作结果。
-
-
键盘快捷键设计
-
基础操作快捷键 :遵循系统级快捷键习惯,设计:
Tab
切换公式编辑器与富文本区域、Enter
确认插入公式、Esc
取消公式编辑、Ctrl+Z
/Ctrl+Y
撤销 / 恢复公式编辑操作;同时,通过editor.on('keydown', (e) => { if (e.altKey && e.key === 'f') { 唤起公式编辑器 } })
支持Alt+F
快速唤起公式编辑器,无需依赖鼠标。 -
公式编辑快捷键 :针对 kityFormula 的常用操作,设置专属快捷键:
Ctrl+/
插入分数(\frac{}{}
)、Ctrl+^
插入上标(^{}
)、Ctrl+_
插入下标(_{}
),并通过aria-label
为快捷键添加屏幕阅读器提示(如<button aria-label="插入分数,快捷键Ctrl+/" class="formula-tool-btn">分数</button>
),确保肢体障碍用户高效操作。
-
-
焦点管理优化
-
焦点进入逻辑 :唤起 kityFormula 编辑器时,通过
formulaEditor.getInputElement().focus()
自动将焦点聚焦到公式输入框,同时通过editor.selection.saveRange()
保存富文本中的光标位置,便于后续返回;屏幕阅读器会播报 "进入公式编辑模式,可输入 LaTeX 语法"。 -
焦点退出逻辑 :用户按下
Esc
或点击 "取消" 按钮时,通过editor.selection.restoreRange()
恢复富文本中的光标位置,同时将焦点返回富文本编辑器;若用户完成公式插入,焦点自动定位到公式末尾,确保用户可继续编辑文本,避免焦点丢失。 -
焦点可见性强化 :为公式输入框添加高对比度焦点样式(
outline: 2px solid #1E90FF; outline-offset: 2px
),适配低视力用户;同时,在焦点切换时,通过scrollIntoView({ behavior: 'smooth', block: 'nearest' })
确保当前焦点元素在视口中可见,避免焦点 "隐身"。
-
-
-
无障碍测试验证:使用 NVDA(Windows)、VoiceOver(macOS/iOS)、JAWS 等主流屏幕阅读器进行实测,确保公式输入的全流程(唤起、编辑、插入、二次编辑)均可通过语音播报清晰感知;通过键盘导航测试工具(如 Tabulator)验证所有操作均可通过键盘完成,无鼠标依赖操作。
第 5 题答案:频繁操作的性能影响与优化措施
-
核心性能影响分析
-
DOM 频繁更新导致重排重绘 :用户每输入一个 LaTeX 字符,kityFormula 会实时生成 SVG 并更新富文本中的公式预览,若直接替换 DOM 节点(如
formulaNode.innerHTML = newSvg
),会触发 2-3 次重排(布局计算)和重绘(像素渲染),当用户连续输入(如输入长公式)时,每秒可触发 10 + 次重排重绘,导致页面卡顿(帧率低于 30fps)。 -
内存占用累积:每次插入公式会创建新的 SVG 节点和关联事件监听(如点击编辑事件),若用户频繁插入 / 删除公式,未及时清理无用 DOM 节点和事件,会导致内存占用持续上升(测试中,插入 100 个公式后内存占用增加约 50MB),长期使用易引发页面崩溃。
-
富文本格式计算开销:用户调整文本格式(如选中段落修改字体)时,wangEditor 需遍历当前选区的所有 DOM 节点,计算格式应用范围,若选区内包含大量公式 SVG 节点,遍历时间会从 10ms 增加到 50ms+,导致格式调整响应延迟。
-
-
针对性优化措施
-
DOM 更新优化:虚拟 DOM 与批量更新:
-
引入轻量级虚拟 DOM 库(如 Snabbdom),将公式 SVG 的更新转换为虚拟 DOM 差异计算,仅更新变化的 SVG 元素(如修改公式中的数字时,仅替换对应
<text>
节点),减少重排重绘次数,实测重排频率降低 70%+; -
对频繁的公式编辑操作(如连续输入 LaTeX 字符)进行防抖处理,设置 100ms 防抖延迟(
debounce((latex) => { 更新公式SVG }, 100)
),避免每秒多次更新,确保帧率稳定在 50fps 以上。
-
-
公式渲染结果缓存:
-
设计二级缓存机制:内存缓存(
formulaCache = new Map()
)存储近期使用的公式(LaTeX 源码→SVG),localStorage 缓存长期使用的公式(设置 7 天过期时间);当用户再次输入相同 LaTeX 源码时,直接从缓存读取 SVG,无需重新渲染,渲染速度提升 90%+; -
缓存清理策略:当公式缓存数量超过 500 条时,采用 LRU(最近最少使用)算法删除不常用缓存;页面卸载时,通过
window.addEventListener('beforeunload', () => { 清理所有公式事件监听 })
释放内存。
-
-
富文本格式计算优化:
-
公式节点标记与跳过遍历:为公式 SVG 节点添加
data-is-formula="true"
属性,在 wangEditor 的格式计算逻辑中,通过if (node.dataset.isFormula) continue
跳过公式节点遍历,格式计算时间从 50ms 降至 8ms,响应延迟大幅降低; -
选区范围限制:通过
editor.on('selectionChange', (range) => { 若选区内包含超过100个DOM节点,提示"建议缩小选区范围" })
引导用户避免超大范围格式调整,减少计算开销。
-
-
内存泄漏防护:
-
事件监听清理:每次删除公式时,通过
removeEventListener
清理公式节点的点击编辑事件,避免僵尸事件; -
无用 DOM 节点回收:使用
MutationObserver
监听富文本容器的 DOM 变化,当检测到公式节点被删除时,立即调用node.remove()
彻底移除,同时删除缓存中对应的公式数据,实测内存占用下降 60%。
-
-
-
性能监控与调优验证
-
性能指标监控 :集成
performance
API 和Lighthouse
工具,实时监控核心性能指标:-
通过
performance.mark('formulaRenderStart')
和performance.mark('formulaRenderEnd')
标记公式渲染起始与结束时间,计算渲染耗时(performance.measure('formulaRenderTime', 'formulaRenderStart', 'formulaRenderEnd')
),要求单次渲染耗时≤50ms; -
使用
Lighthouse
定期检测页面性能得分,重点关注 "首次内容绘制(FCP)""最大内容绘制(LCP)""累积布局偏移(CLS)",确保富文本编辑场景下 CLS≤0.1(避免公式更新导致页面布局跳动)。
-
-
调优效果验证:通过模拟用户高频操作(如连续输入 100 字符公式、批量插入 20 个公式)进行压力测试,优化前后性能对比:
-
页面卡顿次数:从优化前每秒 3-5 次(帧率<30fps)降至优化后 0 次(帧率稳定 55-60fps);
-
内存占用:插入 100 个公式后,内存占用从 50MB 降至 20MB,页面卸载时内存回收率提升至 95%;
-
格式调整响应时间:从 50ms 降至 8ms,用户无明显感知延迟。
-
-
第 6 题答案:数据存储格式设计与回显方案
-
存储格式选型:"HTML+JSON 混合结构"
-
设计方案 :富文本整体内容存储为 HTML 字符串,其中公式部分采用 "HTML 注释包裹 JSON" 的格式(如
<!--formula:{"id":"f123","latex":"x^2+y^2=1","svg":"<svg width='80' height='30'>...</svg>","version":"1.0"}-->
),文本格式信息(如字体、字号)直接嵌入 HTML 标签(如<span style="font-size:16px;color:#333;">文本内容</span>
)。 -
选型优势:
-
兼容性强:HTML 是富文本标准存储格式,支持所有浏览器和后端语言解析,无需额外适配;
-
可编辑性高:保留 LaTeX 源码,支持公式二次编辑(直接提取 JSON 中的
latex
字段传入 kityFormula); -
渲染效率高:存储 SVG 可直接用于预览,无需后端重新渲染,减少网络请求和计算开销;
-
扩展性好:JSON 中可新增字段(如
version
用于版本兼容、createTime
用于数据追溯),后续功能迭代无需修改存储结构。
-
-
对比其他格式:
-
纯 HTML 格式:仅能存储 SVG,无法保留 LaTeX 源码,不支持二次编辑;
-
纯 JSON 格式:需将文本内容拆分为 "文本块 + 格式配置"(如
[{"type":"text","content":"计算:","style":{"fontSize":"16px"}},{"type":"formula","id":"f123"}]
),解析时需重组 HTML,复杂度高且渲染速度慢; -
自定义标记语言(如
[formula:f123]
):需开发专属解析器,兼容性差,后端存储和前端渲染成本高。
-
-
-
数据回显实现流程
-
步骤 1:数据解析:
-
后端返回 HTML 字符串后,通过正则表达式提取所有公式数据:
const formulaMatches = html.match(/<!--formula:(.*?)-->/g) || []
; -
遍历
formulaMatches
,解析 JSON 内容:formulaMatches.forEach(match => { const formulaData = JSON.parse(match.replace(/<!--formula:|-->/g, '')); formulaMap[formulaData.id] = formulaData; })
; -
处理 HTML 结构:将公式注释替换为 SVG 节点,生成可渲染的 HTML:
const renderHtml = html.replace(/<!--formula:(.*?)-->/g, (match, jsonStr) => { const { svg } = JSON.parse(jsonStr); return
${svg}; })
。
-
-
步骤 2:富文本回显:
-
通过
editor.txt.html(renderHtml)
将处理后的 HTML 传入 wangEditor,完成文本和公式的初始渲染; -
为公式节点绑定二次编辑事件:
document.querySelectorAll('.formula-node').forEach(node => { node.addEventListener('click', () => { const formulaId = node.dataset.formulaId; const formulaData = formulaMap[formulaId]; 唤起kityFormula并传入formulaData.latex; }) })
。
-
-
步骤 3:异常处理:
-
数据格式损坏 :若 JSON 解析失败(如
JSON.parse
抛出异常),通过try-catch
捕获错误,将损坏的公式注释替换为文本提示(如<span class="formula-error">公式数据损坏,请重新编辑</span>
),同时在控制台输出错误日志(console.error('公式解析失败:', jsonStr)
); -
公式语法错误 :回显时检测 LaTeX 源码合法性(调用 kityFormula 的
validateLatex
方法),若不合法,降级显示 LaTeX 文本(如[x^2+y^2=1(语法错误)]
),并提供 "重新编辑" 按钮; -
SVG 缺失 :若 JSON 中
svg
字段为空,自动调用 kityFormula 的render(latex)
方法重新生成 SVG,确保回显完整性,同时更新formulaMap
和本地缓存。
-
-
-
数据同步与一致性保障
-
前端本地同步 :编辑过程中,通过
editor.on('change', () => { 实时更新HTML和formulaMap,同步存储到localStorage(key:
answerCard_${cardId})
)`,防止页面刷新数据丢失; -
后端提交验证 :提交数据前,通过
formulaMap
校验所有公式的完整性(如Object.values(formulaMap).every(data => data.latex && data.svg)
),若存在缺失,提示用户 "部分公式数据不完整,请重新编辑"; -
版本兼容处理 :若后端存储的公式数据版本(
version
字段)低于前端当前版本,回显时自动调用upgradeFormulaData
方法(如补充新字段、更新 SVG 渲染规则),确保新旧数据兼容。
-
第 7 题答案:测试用例设计与测试方法
-
功能测试用例
-
公式输入完整性:
-
用例 1:输入基础 LaTeX 公式(如
x+y=5
),验证插入后 SVG 渲染正确,LaTeX 源码与输入一致; -
用例 2:输入复杂公式(如
\int_{0}^{1} x^2 dx = \frac{1}{3}
),验证积分符号、上下限、分数格式渲染无误; -
用例 3:输入特殊符号(如
\alpha
、\beta
、\sqrt{2}
),验证符号显示正常,无乱码或缺失; -
用例 4:批量输入 10 个不同公式,验证所有公式均能正确插入,无相互覆盖或位置偏移。
-
-
公式编辑准确性:
-
用例 1:点击已插入公式,验证能唤起编辑器且 LaTeX 源码正确加载;
-
用例 2:修改公式中的数字(如将
x=3
改为x=5
),验证 SVG 实时更新,且formulaMap
中数据同步修改; -
用例 3:删除公式中的部分内容(如将
\frac{1}{2}
改为\frac{1}{}
),验证错误提示正常显示,禁止插入错误公式; -
用例 4:撤销 / 恢复公式编辑(
Ctrl+Z
/Ctrl+Y
),验证操作后公式状态与历史一致,无数据丢失。
-
-
公式删除正确性:
-
用例 1:选中公式后按
Backspace
/Delete
,验证公式节点被删除,formulaMap
中对应数据同步移除; -
用例 2:删除包含公式的段落,验证公式随段落一起删除,无残留 SVG 或注释;
-
用例 3:批量删除 5 个公式,验证内存占用同步下降,无内存泄漏。
-
-
-
兼容性测试用例
-
浏览器兼容性:
-
用例 1:在 Chrome(最新版)、Firefox(最新版)、Edge(最新版)、Safari(最新版)中,验证公式渲染、编辑、插入功能正常,无样式错乱;
-
用例 2:在 IE11 中,验证基础公式渲染正常(复杂公式允许部分降级),核心功能(输入、编辑、删除)可用;
-
用例 3:在移动端浏览器(Chrome 手机版、Safari 手机版)中,验证公式编辑器适配屏幕尺寸,触摸操作(点击、输入)流畅。
-
-
设备兼容性:
-
用例 1:在 Windows 10(PC)、macOS Monterey(笔记本)中,验证快捷键(
Ctrl+K
/Cmd+K
)正常工作; -
用例 2:在 iPad(iOS 16)、Android 平板(Android 13)中,验证手写输入 LaTeX(需配合第三方输入法)时,公式能正确识别并渲染;
-
用例 3:在低配置 PC(4GB 内存、Intel i3 处理器)中,验证批量插入 20 个公式时,页面无卡顿,操作响应延迟≤100ms。
-
-
-
无障碍测试用例
-
屏幕阅读器兼容性:
-
用例 1:使用 NVDA(Windows)+Chrome,验证唤起公式编辑器时,屏幕阅读器播报 "进入公式编辑模式,可输入 LaTeX 语法";
-
用例 2:使用 VoiceOver(macOS)+Safari,验证输入
\frac
时,播报 "输入分数命令,等待分子分母输入"; -
用例 3:使用 JAWS(Windows)+Edge,验证公式插入成功后,播报 "公式插入成功,位于当前段落第 3 个位置";
-
用例 4:公式语法错误时,验证屏幕阅读器播报 "公式语法错误,请检查括号匹配",无遗漏提示。
-
-
键盘操作流畅性:
-
用例 1:仅使用键盘(无鼠标),验证能通过
Alt+F
唤起公式编辑器、Tab
切换输入框、Enter
确认插入、Esc
取消编辑; -
用例 2:编辑公式时,通过
Left
/Right
键移动光标,验证屏幕阅读器准确播报光标位置(如 "光标位于分子末尾"); -
用例 3:删除公式时,通过
Ctrl+A
选中公式后按Delete
,验证操作成功且无焦点丢失; -
用例 4:遍历所有功能按钮(工具栏、公式编辑器按钮),验证每个按钮均有焦点,且焦点顺序符合操作逻辑(从左到右、从上到下)。
-
-
-
测试工具与方法
-
功能测试工具:
-
自动化测试:使用 Cypress 框架编写 E2E 测试脚本,模拟用户操作(如输入公式、点击按钮、验证 DOM 元素),覆盖 80% 核心功能,脚本示例:
dartit('插入基础公式并验证渲染', () => { cy.visit('/answer-card'); cy.get('.toolbar-btn-formula').click(); // 点击插入公式按钮 cy.get('#formula-input').type('x+y=5'); // 输入LaTeX cy.get('.formula-confirm-btn').click(); // 确认插入 cy.get('.formula-node').should('have.length', 1); // 验证公式节点存在 cy.get('.formula-node svg').should('exist'); // 验证SVG渲染 });
- 手动测试:构建测试用例表,由测试人员逐项执行,记录异常情况(如截图、日志),重点覆盖自动化脚本未覆盖的边缘场景(如异常数据、网络中断)。
-
-
兼容性测试工具:
-
使用 BrowserStack 模拟不同浏览器(含旧版本)和设备,远程执行测试用例,节省本地环境搭建成本;
-
使用 Chrome DevTools 的 "设备工具栏" 模拟移动端设备,快速验证响应式布局和触摸操作。
-
-
无障碍测试工具:
-
使用 axe DevTools(浏览器插件)扫描页面,检测无障碍问题(如缺少
aria-label
、焦点不可见、对比度不足),生成测试报告; -
使用 WAVE(Web Accessibility Evaluation Tool)可视化展示无障碍问题(如用图标标记缺失的替代文本、错误的 ARIA 属性);
-
邀请视障用户进行真实场景测试,收集使用反馈(如语音播报是否清晰、操作是否便捷),优化用户体验。
-
-
性能测试工具:
-
使用 Chrome DevTools 的 "Performance" 面板录制用户高频操作(如连续输入公式),分析帧率、重排重绘次数、内存占用;
-
使用 Lighthouse 定期执行性能审计,生成性能得分和优化建议,确保核心指标(LCP、FID、CLS)达标;
-
使用 JMeter 模拟 100 用户同时编辑答题卡,测试后端接口的并发处理能力,确保数据提交无延迟或丢失。
-
-
第 8 题答案:"公式批量导入" 功能技术方案
-
核心技术问题分析
-
多格式文件解析:Word(.docx)、LaTeX(.tex)文件的公式存储格式差异大(Word 用 OMML 格式,LaTeX 用原生 LaTeX 语法),需开发不同解析器,确保公式数据准确提取;
-
公式冲突处理:批量导入时,公式可能与答题卡现有内容(文本、公式)重叠,需设计冲突检测和处理逻辑,避免内容覆盖;
-
大数据量性能保障:导入 100 + 公式时,可能出现页面卡顿、内存溢出、渲染延迟,需优化解析和渲染流程;
-
异常中断恢复:导入过程中若出现网络中断、页面刷新,需支持断点续传,避免重复导入或数据丢失。
-
-
技术实现思路
-
步骤 1:文件上传与格式识别
-
前端提供文件上传组件(支持多文件同时上传,单个文件大小≤10MB),通过文件后缀(
.docx
/.tex
)或 MIME 类型(application/vnd.openxmlformats-officedocument.wordprocessingml.document
/text/x-tex
)识别文件格式; -
对 Word 文件,使用
docx.js
(前端库)解析文档结构,提取包含公式的段落(OMML 格式存储在w:math
标签中);对 LaTeX 文件,使用latex-parser
库解析文本,提取\[...\]
或\(...\)
包裹的公式内容。
-
-
步骤 2:公式数据提取与转换
-
Word 文件解析:
-
使用
docx.js
的getMathContent()
方法提取 OMML 格式公式,通过omml-to-latex
库将 OMML 转换为标准 LaTeX 语法(如将 Word 中的分数格式转换为\frac{}{}
); -
验证转换后的 LaTeX 合法性(调用 kityFormula 的
validateLatex
方法),若转换失败(如复杂公式不支持),记录失败公式位置(如 "第 3 段第 2 个公式"),后续提示用户手动编辑。
-
-
LaTeX 文件解析:
-
解析
.tex
文件中的公式块(排除注释中的公式,如% \[x=1\]
),支持标准 LaTeX 环境(如equation
、align
); -
处理 LaTeX 宏定义(如
\def\abc{x+y}
),替换宏为实际内容后再提取公式,避免解析错误。
-
-
-
步骤 3:冲突检测与处理
-
冲突检测 :导入前,获取答题卡当前光标位置和内容范围,若导入区域已存在文本或公式,判定为 "位置冲突";若导入的公式 ID 与
formulaMap
中现有 ID 重复,判定为 "ID 冲突"; -
冲突处理策略:
-
位置冲突:提供 3 种选项 ------"覆盖现有内容""插入到现有内容之前""插入到现有内容之后",由用户选择;
-
ID 冲突:自动生成新 ID(如
f123_import_1
),更新公式数据中的id
字段,确保与现有数据不重复; -
格式冲突:若导入公式的 SVG 尺寸(如宽度>500px)超出答题卡编辑区域,自动缩放至适配尺寸(如最大宽度 300px),同时保留原始尺寸数据(存储在
originalSvgSize
字段),支持用户手动调整。
-
-
-
步骤 4:批量导入与进度展示
-
分批次导入 :将提取的公式列表按 20 个 / 批次拆分,使用
requestIdleCallback
或setTimeout
控制导入节奏,避免阻塞主线程;每批次导入完成后,更新formulaMap
和富文本 HTML,确保数据实时同步; -
进度展示 :在页面顶部显示导入进度条(如 "已导入 15/50 个公式"),同时通过
aria-live="polite"
区域播报进度("公式批量导入中,已完成 30%"),适配无障碍需求;导入成功后,弹出提示框("50 个公式全部导入成功,其中 2 个公式需手动调整格式"),并提供 "查看失败公式" 按钮,引导用户处理异常。
-
-
步骤 5:异常中断恢复
-
断点数据存储 :每成功导入 1 个公式,将已导入的公式 ID 列表(如
['f123_import_1', 'f123_import_2']
)和当前批次号(如2
)存储到sessionStorage
(key:batchImport_${taskId}
),taskId
为每次导入任务生成的唯一标识(基于时间戳 + 随机数); -
中断检测与恢复 :页面刷新或重新进入后,检测
sessionStorage
中是否存在未完成的导入任务,若存在,弹出提示框("检测到未完成的批量导入任务,是否继续?");用户选择 "继续" 后,从sessionStorage
读取已导入 ID 列表和批次号,跳过已导入公式,直接从下一批次开始导入; -
失败回滚机制 :若某一批次导入失败(如网络错误、公式渲染异常),自动回滚该批次所有公式(删除已插入的 DOM 节点、移除
formulaMap
中的对应数据),并在进度条上标记失败位置(如 "第 3 批次导入失败,点击重试");用户点击重试后,重新解析该批次公式并导入,避免数据残留。
-
-
-
关键技术难点与解决方案
-
难点 1:Word 文件中 OMML 格式转 LaTeX 的精度问题
-
问题描述:Word 的 OMML 格式包含大量私有属性(如自定义公式样式、特殊符号映射),部分复杂公式(如矩阵、分段函数)转换后可能出现语法错误或格式偏差;
-
解决方案:
-
基于
omml-to-latex
库进行二次开发,扩展特殊符号映射表(如将 Word 中的\u221A
映射为 LaTeX 的\sqrt{}
),覆盖 95% 以上的常用符号; -
对矩阵、分段函数等复杂结构,自定义转换规则:例如,将 OMML 中的矩阵行结构
<m:mr>
转换为 LaTeX 的\begin{pmatrix} ... \end{pmatrix}
,并自动补全缺失的分隔符(如,
); -
转换后调用 kityFormula 的
validateLatex
方法进行语法校验,若校验失败,使用 "截图 + 文本描述" 的方式降级处理(如插入公式截图,并标注 "该公式无法自动转换,建议手动输入 LaTeX")。
-
-
-
难点 2:大数据量公式导入的内存占用问题
-
问题描述:导入 100 + 公式时,每个公式的 SVG 节点(平均大小 5KB)和关联事件监听会导致内存占用快速上升,可能引发页面卡顿或崩溃;
-
解决方案:
-
采用 "虚拟渲染" 策略:导入过程中仅渲染当前可视区域的公式,非可视区域的公式暂存为
<!--formula:xxx-->
注释,当用户滚动页面时,通过IntersectionObserver
检测公式节点是否进入视口,再动态替换为 SVG; -
批量清理事件监听:导入完成后,统一为所有公式节点绑定事件委托(
document.addEventListener('click', (e) => { if (e.target.closest('.formula-node')) { 处理编辑逻辑 } })
),替代单个节点绑定事件,减少内存占用(实测 100 个公式的事件监听数量从 100 个降至 1 个); -
定期内存回收:导入完成后,调用
window.gc()
(需浏览器开启相关配置)或通过setTimeout
延迟释放解析过程中生成的临时变量(如文件解析器实例、临时 DOM 节点),降低内存峰值。
-
-
-
难点 3:跨浏览器的文件解析兼容性
-
问题描述:
docx.js
和latex-parser
在部分浏览器(如 Safari 14)中存在 API 兼容性问题(如ReadableStream
不支持),导致文件无法解析; -
解决方案:
-
使用
core-js
和regenerator-runtime
对 ES6+ API 进行 polyfill,确保ReadableStream
、Promise.allSettled
等 API 在低版本浏览器中可用; -
对无法支持前端解析的浏览器(如 IE11),提供 "后端解析" 降级方案:用户上传文件后,前端将文件通过 FormData 提交至后端,后端使用
python-docx
(解析 Word)和pyparsing
(解析 LaTeX)提取公式数据,转换为 JSON 后返回给前端,前端再执行导入逻辑; -
在文件上传组件中添加浏览器兼容性检测,若检测到不支持前端解析,自动切换至后端解析模式,并提示用户 "当前浏览器不支持本地解析,将使用服务器解析,可能耗时稍长"。
-
-
-
第 9 题答案:wangEditor 源码修改与扩展机制使用
-
项目中是否修改 wangEditor 源码?
- 结论:仅在 1 个场景下修改了 wangEditor 源码(v5.1.23 版本),其余定制化需求均通过官方扩展机制实现,核心原则是 "能通过扩展解决的,不修改源码",避免后续版本升级时的代码冲突。
-
源码修改场景与内容
-
修改场景:解决 "公式节点被选中时,wangEditor 原生格式刷功能误将公式格式应用到文本" 的问题;
-
问题根源 :wangEditor 的格式刷功能(
formatPainter
)会遍历选中区域的所有 DOM 节点,提取style
、class
等格式信息,若选中区域包含公式 SVG 节点,会误将 SVG 的width
、height
、fill
等样式提取为 "文本格式",应用到其他文本时导致样式错乱(如文本变成蓝色、固定宽度); -
修改内容:
-
找到 wangEditor 源码中
packages/core/src/module/format-painter.ts
文件的getFormatData
函数(负责提取选中区域格式); -
在函数中添加公式节点过滤逻辑:
javascript// 新增:过滤公式节点(含data-is-formula属性的节点) const isFormulaNode = (node: Node) => { return node.nodeType === 1 && (node as HTMLElement).dataset.isFormula === 'true'; }; // 修改:遍历节点时跳过公式节点 const traverseNode = (node: Node) => { if (isFormulaNode(node)) return; // 跳过公式节点 // 原有遍历逻辑... };
-
-
修改后的兼容性保障:
-
记录修改日志:在项目
docs/``wangEditor-modify-log.md
中详细记录修改文件路径、函数名称、修改原因和代码内容,便于后续版本升级时追溯; -
版本锁定与升级策略:修改后暂时锁定 wangEditor 版本为 v5.1.23,后续升级时(如升级至 v5.2.0),先在测试环境中合并源码修改(对比新版本
format-painter.ts
文件,重新添加过滤逻辑),再进行功能测试,确保修改后的功能正常; -
提交 PR 至官方:将该过滤逻辑整理为 PR(Pull Request)提交至 wangEditor GitHub 仓库,说明使用场景和问题解决效果,若官方采纳,后续版本可直接移除源码修改,降低维护成本。
-
-
-
基于官方扩展机制的定制化实现(未修改源码场景)
-
扩展机制 1:插件(Plugin)
-
使用场景:集成 kityFormula 公式输入功能;
-
实现细节:
-
基于
wangEditor-plugin-base
创建自定义插件FormulaPlugin
,实现init
、destroy
等生命周期方法; -
在
init
方法中:
-
-
-
-
注册工具栏按钮:通过
editor.ui.registerMenuButton('formula', { icon: '公式', click: () => { 唤起kityFormula编辑器 } })
; -
注册命令:通过
editor.cmd.register('insertFormula', (formulaData) => { 生成公式DOM节点并插入到光标位置 })
; -
监听编辑器事件:通过
editor.on('destroy', () => { 销毁kityFormula编辑器实例,避免内存泄漏 })
;- 优势 :插件与编辑器核心逻辑解耦,可独立开发、测试和升级,移除插件时仅需调用
editor.plugins.unregister('FormulaPlugin')
,无残留代码。
-
扩展机制 2:钩子函数(Hook)
-
使用场景:限制填空题输入框的文本长度(最多 50 字符);
-
实现细节:
-
使用
editor.hooks.on('beforeInput', (context) => {
钩子函数,在用户输入前拦截输入事件; -
在钩子函数中判断当前编辑节点是否为填空题输入框(
context.node.closest('.blank-input')
),若是,检查输入后文本长度是否超过 50 字符:inieditor.hooks.on('beforeInput', (context) => { const blankInput = context.node.closest('.blank-input'); if (blankInput) { const currentText = blankInput.value; const inputText = context.data; // 即将输入的文本 if (currentText.length + inputText.length > 50) { context.preventDefault(); // 阻止输入 // 显示错误提示 blankInput.classList.add('input-error'); setTimeout(() => blankInput.classList.remove('input-error'), 2000); } } });
-
优势:无需修改编辑器输入逻辑,通过钩子函数实现 "无侵入式" 拦截,兼容性强,升级编辑器版本时无需调整。
-
-
-
扩展机制 3:自定义节点(Custom Node)
-
使用场景 :实现 "带评分项的简答题节点"(如
<div class="question-essay" data-score="10">...</div>
),支持在编辑器中可视化编辑评分值; -
实现细节:
-
继承 wangEditor 的
ElementNode
类,创建EssayQuestionNode
,重写render
方法(生成包含评分输入框的 DOM 结构)和setValue
方法(处理评分值更新); -
注册自定义节点:通过
editor.schema.registerNode(EssayQuestionNode)
,并配置节点的 "可编辑区域"(仅允许修改评分值和文本内容,禁止删除节点容器); -
在工具栏添加 "插入简答题" 按钮,点击后通过
editor.cmd.do('insertNode', new EssayQuestionNode({ score: 10 }))
插入自定义节点;
-
-
优势:自定义节点拥有独立的渲染和交互逻辑,可复用性强,后续新增 "带难度系数的题目节点" 时,可基于相同机制快速开发。
-
- 优势 :插件与编辑器核心逻辑解耦,可独立开发、测试和升级,移除插件时仅需调用
第 10 题答案:项目架构设计与迭代管理
-
代码模块划分(基于 "高内聚、低耦合" 原则)
- 核心模块划分:
模块名称 职责范围 对外接口(API) rich-editor
wangEditor 初始化、基础配置、事件监听(如内容变化、光标移动) initEditor(config)
、destroyEditor()
、getEditorContent()
、setEditorContent(html)
formula-handler
kityFormula 集成、公式渲染、LaTeX 解析、公式数据管理(formulaMap) initFormulaEditor()
、renderFormula(latex)
、getFormulaData(id)
、saveFormulaData(data)
question-manager
题型模板管理、题型交互逻辑(如填空题输入限制、计算题公式区域控制) registerQuestionType(type, config)
、insertQuestion(type)
、getQuestionState()
accessibility
无障碍优化(屏幕阅读器支持、键盘操作、焦点管理) initA11ySupport(editor)
、updateAriaLive(text)
、setFocusableElements(elements)
data-storage
数据存储(localStorage/sessionStorage)、数据解析、回显、提交验证 saveData(key, data)
、loadData(key)
、parseContentHtml(html)
、validateSubmitData()
batch-import
公式批量导入(文件解析、进度管理、异常恢复) initBatchImport(editor)
、startImport(files)
、cancelImport(taskId)
-
模块间通信方式:
-
事件总线(Event Bus) :使用
mitt
库创建全局事件总线,模块间通过 "发布 - 订阅" 模式通信,例如:-
formula-handler
模块发布formula:inserted
事件(携带公式 ID); -
data-storage
模块订阅该事件,同步更新公式数据到本地存储; -
优势:避免模块间直接依赖,新增模块时只需订阅相关事件,无需修改现有代码;
-
-
接口调用 :核心模块对外暴露明确的 API(如
rich-editor
的getEditorContent()
),其他模块通过调用 API 获取数据或执行操作,例如question-manager
模块调用rich-editor
的setEditorContent(html)
插入题型模板; -
状态共享 :使用
Vuex
(若项目基于 Vue)或Redux
(若基于 React)管理全局状态(如当前编辑的答题卡 ID、公式批量导入进度),模块通过 "获取状态 - 修改状态" 实现数据同步,避免状态分散。
-
-
代码规范与文档建设
-
代码规范:
-
遵循
Airbnb JavaScript Style Guide
,使用ESLint
和Prettier
进行代码检查和格式化,配置强制校验规则(如禁止未声明变量、强制使用箭头函数),确保代码风格统一; -
模块命名规范:使用 "功能 + 类型" 命名(如
formula-render.ts
、question-template.js
),避免模糊命名(如utils.js
); -
注释规范:函数需添加 JSDoc 注释(说明参数、返回值、异常情况),复杂逻辑需添加行内注释(如公式解析规则、状态判断条件),示例:
php/* * 渲染LaTeX公式为SVG * @param {string} latex - LaTeX源码 * @param {object} options - 渲染配置(width: 最大宽度, height: 最大高度) * @returns {string} SVG字符串 * @throws {Error} 当LaTeX语法错误时抛出异常 */ function renderFormula(latex, options) { // 校验LaTeX语法 if (!validateLatex(latex)) { throw new Error(`LaTeX语法错误: ${latex}`); } // 渲染逻辑... }
-
-
文档建设:
-
API 文档 :使用
JSDoc
自动生成模块 API 文档(通过jsdoc
工具),托管在项目 GitHub Pages,包含每个 API 的参数说明、返回值、使用示例,例如formula-handler
模块的renderFormula
方法文档; -
开发手册 :在
docs/development.md
中记录开发流程(如环境搭建、分支管理、提交规范)、常见问题解决方案(如 wangEditor 版本升级步骤、公式渲染失败排查); -
用户手册:为最终用户(如教师)编写操作手册,包含富文本编辑、公式输入、批量导入的步骤说明,配截图和快捷键列表,降低使用门槛。
-
-
-
迭代过程中的版本管理与兼容性保障
-
版本管理:
-
采用
Semantic Versioning
(语义化版本):主版本号(X.y.z)表示不兼容的 API 变更(如修改getEditorContent()
返回格式),次版本号(x.Y.z)表示向后兼容的功能新增(如新增 "公式批量导入"),修订号(x.y.Z)表示向后兼容的问题修复(如修复公式语法错误提示 bug); -
分支管理:使用
Git Flow
工作流,main
分支保持稳定版本,develop
分支用于开发,feature/*
分支(如feature/batch-import
)用于开发新功能,hotfix/*
分支用于紧急修复线上 bug;新功能开发完成后,合并到develop
分支测试,测试通过后合并到main
分支并发布版本。
-
-
新旧功能兼容性保障:
-
向前兼容:新增功能时确保不影响旧功能,例如新增 "公式批量导入" 时,不修改现有 "单个公式插入" 的逻辑,仅新增独立模块和工具栏按钮;
-
数据兼容 :若需修改数据存储格式(如公式 JSON 新增
version
字段),需在data-storage
模块中添加数据迁移逻辑,例如:
-
-