Appearance
错题本开发遇到的麻烦
起因
小孩做作业,错题不少。老师要求整理错题本,把做错的题目抄下来,分析错误原因,定期复习巩固。
听起来是个好习惯,但实际操作起来问题一大堆:
1、抄题太费劲——一道数学大题抄下来要十几分钟,小孩嫌累不愿意写
2、拍照打印——用手机拍下来再打印,图片模糊、排版混乱,效果不好
3、电子版整理——用 Word 或笔记软件录入,公式输入困难,效率极低
于是想着做一个在线错题本工具:拍照或截图上传 → AI 识别题目内容 → 自动生成错题记录 → 支持分类标签和复习提醒。
想法很美好,开发过程却踩了一堆坑。
麻烦一:题目识别准确率低
最初方案是用 OCR 文字识别提取题目文字。实际测试发现:
- 手写字体识别差:小孩的作业本上字迹潦草,OCR 识别率不到 60%
- 数学公式无法处理:分数、根号、上下标等符号,OCR 全部识别成乱码
- 图文混排噩梦:题目中穿插几何图形,OCR 要么漏掉要么位置错乱
后来改用多模态 AI(视觉语言模型)直接理解图片内容,效果好了很多,但成本也上来了。单次识别成本约 0.02~0.05 元,如果用户频繁使用,费用不可忽视。
最终做了个折中:优先尝试轻量级 OCR,识别置信度低于阈值时才调用 AI 模型兜底。
麦烦二:公式渲染是个深坑
识别出的题目包含大量数学公式,需要在前端正确渲染展示。这块坑特别多:
LaTeX 解析问题
AI 返回的内容常包含 LaTeX 格式的公式,如 \frac{1}{2}x^2 + \sqrt{3}。前端渲染 LaTeX 有几个主流方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| MathJax | 成熟稳定 | 体积大(~300KB),首次加载慢 |
| KaTeX | 轻量快速 | 部分复杂语法不支持 |
| MathML | 原生支持 | 兼容性差,Safari 渲染异常 |
选了 KaTeX,结果遇到各种边界情况:
- AI 生成的 LaTeX 不规范,缺少闭合标签导致渲染失败
- 分数嵌套过深时溢出容器
- 中文字符与公式混排时间距异常
最终写了一大堆正则做预处理清洗,又加了容错机制:渲染失败时降级为纯文本显示。
移动端适配
手机屏幕窄,长公式自动换行后可读性很差:
原式:(a + b + c + d)(e + f + g + h) = ae + af + ag + ah + be + bf + bg + bh + ...
换行后变成一堆碎片,完全看不懂解决方案是检测公式长度,超长公式启用横向滚动容器,并添加「展开/收起」按钮。
麻烦三:数据存储的纠结
错题本的核心价值在于数据积累和长期使用,存储方案必须慎重选择。
本地存储 vs 云存储
| 维度 | 本地存储(IndexedDB) | 云存储 |
|---|---|---|
| 隐私安全 | 数据不出浏览器 | 需要用户信任服务端 |
| 多端同步 | 不支持 | 天然支持 |
| 开发成本 | 低 | 需要用户系统、鉴权、数据库 |
| 数据风险 | 清浏览器数据就没了 | 可靠但依赖服务器 |
考虑到目标用户是学生家长,对隐私敏感度较高,最终选择了 本地优先 策略:
- 默认使用 IndexedDB 存储在浏览器本地
- 提供可选的云同步功能(需注册登录)
- 支持导出 JSON / PDF 备份
IndexedDB 的坑
IndexedDB 用起来比 localStorage 复杂得多:
- 异步 API:所有操作都是异步的,代码容易陷入回调地狱
- 版本迁移:数据库结构变更时需要处理 upgrade 逻辑,处理不好会丢数据
- 存储限额:浏览器通常限制几十到几百 MB,存多了会被清理
- 无查询能力:没有 SQL 那样的查询语法,筛选排序得自己实现
封装了一个简易的 ORM 层才勉强能用的舒服点。
麻烦四:图片处理性能问题
用户上传的错题图片通常是手机拍摄的照片,分辨率高、文件大(3~8MB)。
直接上传和处理会遇到几个问题:
1、预览卡顿:大图在 canvas 上渲染时内存飙升,页面明显卡顿 2、上传慢:原图上传到 AI 服务耗时久,用户体验差 3、存储膨胀:IndexedDB 存太多大图很快触及限额
优化方案:
- 上传前先压缩(Canvas 重绘,质量调至 0.7,最大边长限制 2048px)
- 生成缩略图用于列表展示,点击后才加载原图
- 使用 Web Worker 做压缩处理,避免阻塞主线程
javascript
// 压缩示例(简化版)
async function compressImage(file, maxWidth = 2048, quality = 0.7) {
const bitmap = await createImageBitmap(file)
const scale = Math.min(1, maxWidth / bitmap.width)
const canvas = new OffscreenCanvas(bitmap.width * scale, bitmap.height * scale)
const ctx = canvas.getContext('2d')
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height)
return canvas.convertToBlob({ type: 'image/jpeg', quality })
}麻烦五:复习功能的复杂性
错题本不只是记录,更重要的是复习。这块逻辑比想象中复杂:
艾宾浩斯遗忘曲线
按照遗忘曲线安排复习时间:
- 第 1 次复习:学习后 20 分钟
- 第 2 次复习:1 天后
- 第 3 次复习:2 天后
- 第 4 次复习:4 天后
- 第 5 次复习:7 天后
- 第 6 次复习:15 天后
每道题需要独立追踪复习状态和时间节点,状态机设计如下:
新录入 → 待复习(第1次) → 已掌握 / 待复习(第2次) → ... → 已归档
↓
答错 → 回退到上一轮复习模式交互
复习时需要遮住答案让用户作答,答完后再显示正确答案对比。涉及:
- 答案区域的显示/隐藏切换
- 用户作答结果的记录(正确/错误/跳过)
- 错误题目的自动回退重练
- 复习统计数据的实时更新
这块前后端状态同步容易出 bug,特别是用户快速连续操作时。
总结
一个看起来简单的错题本工具,实际开发中遇到了这些主要挑战:
| 问题领域 | 核心难点 | 解决思路 |
|---|---|---|
| 题目识别 | 手写体 + 公式 + 图文混排 | OCR + 多模态 AI 分级策略 |
| 公式渲染 | LaTeX 规范性差 + 移动端适配 | 预处理清洗 + 容错降级 |
| 数据存储 | 隐私 vs 同步 vs 成本 | 本地优先 + 可选云同步 |
| 图片性能 | 大图处理导致卡顿 | 前端压缩 + 缩略图 + Web Worker |
| 复习算法 | 遗忘曲线 + 状态管理 | 状态机设计 + 防抖节流 |
每个问题单独看都不算太难,但组合在一起就让开发周期远超预期。好在一步步踩坑过来,工具总算能用了。
工欲善其事,必先利其器。好的工具能让开发工作事半功倍。
如果你有其他好用的开发工具推荐,欢迎在评论区分享!