拾階教育 App:一個 x² 如何難倒三個終端——數學公式的顯示、輸入與評分
一个 x² 难倒了三个终端:数学公式的显示、输入和判分
拾階教育 App 以分端渲染、符號鍵和答案格式歸一化,處理數學公式的顯示、輸入與判分問題。新規則在 Unicode 規範化前處理上下標,並以本地題庫 1.4 萬道填空題、1 萬種答案寫法測試,合併 464 組寫法;逐組核對後,未發現不同答案被判為相同。
2026-10-25 11
版权
版权声明:
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议》和 《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
简介: 本文详解拾阶教育App在数学公式处理上的三大难题与解法:显示(三端差异化渲染)、输入(手机符号键盘设计)、判分(Unicode与LaTeX归一化比对),兼顾准确性、体验与工程可行性。(239字)

一道填空题,标准答案是 k^2。学生在手机上写了 k²,系统判 0 分。
学生没写错,系统也没有 bug,至少每一行代码单独看都没有。题库里的答案是 LaTeX 写法,学生手机键盘上能打出的是 Unicode 上标。比较之前,系统会先做一次 Unicode 规范化(NFKC),它把 k² 折成了 k2。于是 k^2 对 k2,不相等。
做学习类产品,数学公式是绕不开的。它在三个地方都会出问题:显示,学生看不看得懂题;输入,学生能不能把答案写出来;判分,写对了能不能被认出来。拾阶有网页、App、微信小程序三个终端,同一个公式在这三处各有各的坑。这篇把我们的解法摊开讲。
一、显示:同一个 LaTeX,三个终端三种画法

我们的内容约定很简单:公式一律写 LaTeX($...$),排版一律 Markdown。约定只有一份,三个终端各自想办法把它画出来。
网页最省事,KaTeX 直接排。
App 是 React Native,没有现成的公式排版。摆在面前的路有两条:自己写一个 LaTeX 排版器,或者把 KaTeX 放进 WebView。
自己写排版器的问题不是做不出来,而是做不全。分式、根号、上下标好办;矩阵、化学方程式箭头上的条件、多行对齐不好办。做一半的排版器会让一部分题目显示成乱码,这比统一用 WebView 慢一点糟得多。所以 App 的讲解、题干、解析这类长文本走 WebView + KaTeX;列表里的短文本不值得为它开一个 WebView,转成近似的纯文本显示。KaTeX 加载失败也不白屏,公式原样显示成文本,简单的式子照样读得懂。
小程序最麻烦。小程序的富文本组件不认 SVG 标签,把 KaTeX 的输出放进去,根号和伸缩括号会消失;在小程序里跑 MathJax,又会撑爆 2MB 的主包。
最后的做法是让服务端画。网页那边加了一个接口,用 MathJax 把单个公式渲染成自包含的 SVG,长缓存;小程序把公式当图片插进富文本。图片在富文本里必须给出尺寸才显示,所以还有第二个接口:一页里的所有公式一次性批量询问宽、高和基线位置。MathJax 给的尺寸单位是 ex,换算成 em 再插进去,公式图片就和周围的文字一样大、基线对齐。
这里有个容易忽略的体验细节:尺寸回来之前显示什么?
最初显示的是 LaTeX 源码。网慢的时候,学生盯着题干上的「在 $-2$,$+3$,$0$ 中」看好几秒,像一串乱码。后来改成先显示纯文本近似(-2、x²、1/2),尺寸回来再无缝换成公式图片。
二、一个专门的坑:框起来的一整句中文
讲解里有一种写法很常见:
{
mathJaxContainer[0]}
用公式框标出重点,排出来是一个带边框的结论。已发布的知识点里,九成都用到了它。
问题是 KaTeX 把它当成一整块公式,不会换行。网页上屏幕够宽,没事;App 里手机屏宽放不下,右半句直接被切掉;小程序里它是一张公式图片,缩到屏幕宽度以后字小得看不清。
这个问题单看代码是发现不了的:渲染没报错,数据也没问题,只有在真机屏幕上看才知道右半句没了。
解法是承认它的本质:这种框里装的是一句话,不是公式。所以当整条公式只有一个 \boxed、里面又有中文时,改写成一个能换行的文字框,中文照常排版,夹在中间的公式片段(比如 180^{\circ})仍按行内公式排。纯公式的框(\boxed{140^{\circ}})不动。
三、列表里的公式:只求认得出
错题本、成绩页的折叠卡片只显示两行题干。最早的做法是把公式一律换成「…」,结果数学题成了这样:
若关于 … 的方程 … 的两根均为正数,则 … 的取值范围是
一屏全是省略号,学生认不出是哪道题。每题都开一个公式渲染又太重。
所以写了一个转换函数,把公式转成能读的纯文本:x^2-3x+2=0 → x²-3x+2=0,\frac{1}{2} → 1/2,\angle → ∠。上下标只在每个字符都有对应的上标字形时才转,否则写成 ^(…);分子分母不止一个字的时候自动加括号。只求认得出,不求排版精确,点开以后仍是完整的公式渲染。
这个函数 App 和小程序是同一份,文件头写着「改一处要同步另一处」。
四、输入:一排符号键
显示解决了,接下来是学生怎么写答案。
手机键盘打不出 x²、√、π、≥。学生只好写「x的平方」「根号3」,在手机浏览器上还得切三四次键盘找符号。
我们的办法很朴素:在正在输入的框下面放一排符号键。
// 上下标单独一个字形太小看不清,键面画成 x²、x₁,插进去的只有上标本身
export const MATH_KEYS = [
...["²", "³", "^", "₁", "₂"].map((c) => ({
label: c === "^" ? "xⁿ" : `x${
c}`, insert: c })),
...["√", "π", "/", "(", ")", "°", "±", "×", "÷", "≥", "≤", "≠", "∠", "△", "⊥", "∥", "∞", "≈", "∈"]
.map((c) => ({
label: c, insert: c })),
];
做起来有几个细节,每一个都是踩过才知道的:
- 插进去的是 Unicode 字形,不是 LaTeX。 学生看到的就是
x²+1,而不是x^{2}+1。 - 按下按键时不能让输入框失焦。 失焦了符号键就收起来,点击落空。所以按键在
pointerdown时阻止默认行为。 - 光标要放回符号后面。 受控输入框重新渲染后光标会跳到末尾,要等这一帧画完再把光标放回去。否则学生在中间插一个 √,光标就飞到最后了。
- 只在需要的时候出现。 题干里有公式或数字才给符号键,语文、英语的填空用不上,平白占一行。
三个终端用的是同一套键位。同一道题,不能 App 上能打 x²、小程序上打不了。
五、判分:把不同写法折成同一种

回到开头那道题。学生写 k²,题库存 k^2。
以前全靠出题时把每种写法都列进答案,漏列一种,学生写对了也判错。题库里有上万道填空题,指望每道都列全是不现实的。
现在的做法是:两边都折成同一种线性写法再比。
x² / x^2 / x^{2} → x^2
\sqrt{19} / √19 / √(19) → √19
\dfrac{a^2}{4b^4} / a²/(4b⁴) → a^2/(4b^4)
180^\circ / 180° → 180°
有一个顺序问题必须注意:上下标字形要在 Unicode 规范化之前处理。NFKC 会把 ² 折成普通的 2,等它折完再想转成 ^2 已经来不及了。开头那个 0 分就是这么来的。
另外两条原则:
- 只会更宽,不会更严。 原来的比较照旧,新规则只在原来判错时再补一次。原来判对的,现在一定还判对。
- 能按数值比的就按数值比。
0.5、1/2、50%本来就按数值比;-\dfrac37原本解析不成数字,折成-3/7以后也能参与数值比较了。
规则上线前,我们用本地题库里 1.4 万道填空题、1 万种不同的答案写法跑了一遍。新规则一共把 464 组写法合并成了相同,逐组看过,全部是同一个式子的不同写法,没有把两个不同的答案判成相同。
最后这一步最不能省。判分规则放宽是有风险的:放得太宽,错的答案也会被判对。「只会更宽」保证了不会冤枉学生,而那 464 组的人工核对保证了不会放过错误。
如果只带走几句
- 公式内容只存一种格式,显示交给每个终端各自解决,不要为某个终端改内容。
- 画不了就让服务端画。 小程序的限制很硬,绕不过去就换个地方做。
- 加载中的样子也是体验。 先显示纯文本近似,比显示源码好得多。
- 框里装的是什么,决定它该怎么排。 一句话就按一句话排,能换行。
- 判分放宽要有数据兜底。 改之前拿全量题库跑一遍,逐组看合并结果。
附:这些在产品里长什么样
网页版答题页,填空题下面那排符号键。图里的 -1/8 是用「/」键输入的。

拾阶网页版:填空题的数学符号键
小程序里的知识点讲解,公式是服务端画好的图片;「对顶角相等」那几个带边框的结论,就是改写后能换行的文字框。

拾阶小程序:知识点讲解里的公式与重点框
App 的错题本,折叠卡片里的题干是公式转出来的纯文本:x²+x+1=0、x²-√5x+1=0,一眼认得出是哪道题。

拾阶 App:错题本的题干预览
下一篇讲我们怎么发现这类「代码没错、页面不对」的问题:让 AI 当测试员,对着正式环境把 App 和小程序的每个页面点一遍。
來源:阿里雲開發者社區 AI · developer.aliyun.com