错题本的学习仓库:ArkTS 为鸿蒙错题应用设计复习字段


实例:错题本(Mistake)|技术:复习轮次、掌握状态、错因分类
一、业务需求分析:错题本的数据形态
错题本是**「学习记忆」应用**——每道错题记录题目/答案/错因,并通过复习轮次推进掌握(类似间隔重复)。数据层核心是复习状态机(轮次 + 掌握度)与多维筛选(学科/知识点/错因)。
核心业务需求:
- 错题记录:学科、题目、正确答案、我的错误答案、知识点、错因;
- 复习机制:每次复习轮次 +1,复习 5 次自动标记掌握——CASE WHEN 条件更新;
- 掌握状态:未掌握/已掌握切换;
- 复习队列:未掌握且轮次 < 5,按上次复习时间升序(最久没看的优先)——优先队列查询;
- 统计:错因统计、学科统计、总览(总数/未掌握/已掌握/待复习)——多维度聚合。
二、字段设计:学习驱动错题
错题表 mistake
| 字段名 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT | 自增主键 |
| subject | TEXT | NOT NULL | 学科(数学/英语/物理…) |
| question | TEXT | NOT NULL | 题目内容 |
| answer | TEXT | DEFAULT ‘’ | 正确答案 |
| my_answer | TEXT | DEFAULT ‘’ | 我的错误答案 |
| knowledge | TEXT | DEFAULT ‘’ | 知识点标签 |
| reason | TEXT | DEFAULT ‘粗心大意’ | 错因(计算失误/概念不清/粗心/思路错误) |
| rounds | INTEGER | NOT NULL DEFAULT 0 | 复习轮次 0~5 |
| mastered | INTEGER | NOT NULL DEFAULT 0 | 0 未掌握 / 1 已掌握 |
| created_time | INTEGER | NOT NULL | 录入时间戳 |
| review_time | INTEGER | NOT NULL DEFAULT 0 | 上次复习时间戳 |
设计要点拆解:
1. rounds(复习轮次)是核心字段。0~5 的整数——每次复习 +1,达到 5 次自动掌握。「轮次 = 复习进度的量化」:轮次少 → 需多复习(列表排前),轮次 5 → 已掌握。这是简化版间隔重复(真实 Anki 有难度分级 + 间隔天数,Demo 用轮次线性推进)。
2. mastered(掌握度)二态。0 未掌握 / 1 已掌握——状态与轮次联动:轮次达到 5 自动置 1,也可手动切换(「标记掌握」/「取消掌握」)。**「轮次驱动 + 手动干预」**双通道。
3. reason(错因)文本分类。计算失误/概念不清/粗心大意/思路错误——错因统计(GROUP BY reason)让用户看到「我的错题集中在哪类错因」。错因是诊断维度(学习分析的核心)。
4. knowledge(知识点)标签。函数变换/绝对值不等式/被动语态…——知识点是复习的关联线索(同类知识点一起复习),LIKE 模糊搜索知识点(queryByKnowledge)。
5. my_answer vs answer 对照。错误答案与正确答案并存——「错题的价值 = 对照」:复习时先看自己错在哪、再看正确答案。
6. review_time(上次复习时间):驱动复习队列排序(最久没复习的优先)——ORDER BY review_time ASC 把「好几天没看的」排最前。**「时间越久越优先」**是间隔重复的核心排序。
三、建表 SQL 与索引
CREATE TABLE IF NOT EXISTS mistake (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subject TEXT NOT NULL,
question TEXT NOT NULL,
answer TEXT DEFAULT '',
my_answer TEXT DEFAULT '',
knowledge TEXT DEFAULT '',
reason TEXT DEFAULT '粗心大意',
rounds INTEGER NOT NULL DEFAULT 0,
mastered INTEGER NOT NULL DEFAULT 0,
created_time INTEGER NOT NULL,
review_time INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_mistake_subject ON mistake (subject);
CREATE INDEX IF NOT EXISTS idx_mistake_knowledge ON mistake (knowledge);
idx_mistake_subject:学科筛选(WHERE subject = '数学')走索引。idx_mistake_knowledge:知识点 LIKE 搜索的前缀索引(knowledge LIKE '函数%' 可走,%函数% 不行——教学索引示例)。
四、MistakeDao 封装:复习机制是亮点
数据层核心 MistakeDao,本实例最独特的是reviewOnce(复习一次的条件更新):
/** 复习一次:轮次 +1,刷新复习时间;轮次达到 5 自动标记已掌握 */
static async reviewOnce(context: common.Context, id: number): Promise<void> {
const store = await MistakeDao.getStore(context);
const now = Date.now();
await store.executeSql(
`UPDATE ${MistakeDao.TABLE}
SET rounds = rounds + 1,
review_time = ${now},
mastered = CASE WHEN rounds + 1 >= 5 THEN 1 ELSE mastered END
WHERE id = ${id}`
);
}
UPDATE mistake
SET rounds = rounds + 1,
review_time = 1720869600000,
mastered = CASE WHEN rounds + 1 >= 5 THEN 1 ELSE mastered END
WHERE id = 3;
逐行拆解:
1. 自增更新 rounds = rounds + 1:复习轮次在原值上加 1——SQL 的自增更新(不需要先查再算,一条 UPDATE 完成)。
2. CASE WHEN 条件赋值 mastered = CASE WHEN rounds + 1 >= 5 THEN 1 ELSE mastered END:复习后轮次达到 5 → 自动标记掌握(1),否则保持原值——「根据新轮次条件决定新状态」的 CASE 表达式。这是本实例最亮眼的 SQL 技巧:在 UPDATE 里做条件分支(比先查再改少一次往返)。
3. 为什么用 executeSql 而不用 update(values, predicates)? RdbPredicates 的 update 只能设固定值({ rounds: ? } 不知道当前值);rounds = rounds + 1 与 CASE WHEN 是SQL 表达式(引用旧值)——「需要引用旧值的更新必须用 SQL 表达式」,executeSql 才能写完整 UPDATE。
4. 单条复习按 id:WHERE id = ${id}——精确单条。
markMastered(手动掌握切换):
static async markMastered(context: common.Context, id: number, mastered: number): Promise<number> {
const store = await MistakeDao.getStore(context);
const values: relationalStore.ValuesBucket = { mastered: mastered, review_time: Date.now() };
const predicates = new relationalStore.RdbPredicates(MistakeDao.TABLE);
predicates.equalTo('id', id);
return await store.update(values, predicates);
}
手动设置固定值:{ mastered: mastered, review_time: Date.now() }——**「设固定值用 update(values, predicates),自增/条件用 executeSql」**的 API 选择准则。
queryReviewQueue(复习队列):
static async queryReviewQueue(context: common.Context): Promise<Mistake[]> {
const store = await MistakeDao.getStore(context);
const predicates = new relationalStore.RdbPredicates(MistakeDao.TABLE);
predicates.equalTo('mastered', 0).lessThan('rounds', 5).orderByAsc('review_time');
const result = await store.query(predicates);
return MistakeDao.collect(result);
}
「待复习」的数学定义:mastered=0(未掌握)AND rounds < 5(还没复习够)——双条件交集;排序 review_time ASC:最久没复习的排最前(间隔重复的优先级)。**「条件筛选 + 时间升序」**是待办队列的通用模式(最旧优先)。
五、统计三件套
overview(总览,一条 SQL 四个统计):
static async overview(context: common.Context): Promise<MistakeOverview> {
const result = await store.querySql(
`SELECT COUNT(*) AS total,
SUM(CASE WHEN mastered=0 THEN 1 ELSE 0 END) AS pending,
SUM(CASE WHEN mastered=1 THEN 1 ELSE 0 END) AS mastered,
SUM(CASE WHEN mastered=0 AND rounds<5 THEN 1 ELSE 0 END) AS to_review
FROM ${MistakeDao.TABLE}`
);
// 解析四列
}
SUM(CASE WHEN … THEN 1 ELSE 0 END) 条件计数:一行 SQL 算出四个统计(总数/未掌握/已掌握/待复习)——**「CASE WHEN 配合 SUM 做条件计数」**是本实例的第二个 SQL 亮点(不用四次 COUNT + WHERE)。一次聚合多统计的极致写法。
reasonStats / subjectStats(错因/学科分组统计):
static async reasonStats(context: common.Context): Promise<ReasonStat[]> {
// SELECT reason, COUNT(*) AS cnt FROM mistake GROUP BY reason ORDER BY cnt DESC
}
static async subjectStats(context: common.Context): Promise<SubjectStat[]> {
// SELECT subject, COUNT(*) AS cnt FROM mistake GROUP BY subject ORDER BY cnt DESC
}
GROUP BY + COUNT + DESC——与实例 16/18 同构(按维度分组计数)。错因统计的意义:计算失误 4 / 概念不清 4 / 思路错误 2——用户一眼看到「我错题集中在哪」——学习诊断。
六、技术要点对照表
| 技术点 | 实现方式 | 生产价值 |
|---|---|---|
| 轮次自增 | rounds = rounds + 1 | 复习计数 |
| 条件赋值 | CASE WHEN 自动掌握 | 无往返状态流转 |
| 复习队列 | mastered=0 + rounds<5 + review ASC | 最久优先 |
| 条件计数 | SUM(CASE WHEN) 四统计一 SQL | 总览一次拿 |
| 分组统计 | GROUP BY reason/subject | 诊断维度 |
| 知识点搜索 | LIKE %kw% | 关联复习 |
七、文章小结
错题本的数据层核心是**「复习轮次 + 条件更新」:rounds 轮次量化复习进度(0~5)、reviewOnce 用 SQL 表达式(rounds + 1 + CASE WHEN)一条 UPDATE 完成「轮次自增 + 时间刷新 + 满 5 自动掌握」、queryReviewQueue 用「未掌握 + 轮次未满 + 最久优先」定义复习队列、overview 用 SUM(CASE WHEN) 一次 SQL 算出四个总览统计。「SQL 表达式更新」与「条件计数」**是两个可复用的高级技巧。这是「学习复习类应用」的通用数据模型。
下一篇(19-2)展示横线笔记本风 UI——错因统计条 + 学科筛选 + 横线纸题目卡。
八、字段联动深入:复习状态机的三种流转
rounds 与 mastered 的联动状态机
复习状态由 rounds(0~5)与 mastered(0/1)共同决定,共三种合法组合:
| 状态 | rounds | mastered | 含义 | 复习队列 |
|---|---|---|---|---|
| 待复习 | 0~4 | 0 | 轮次未满且未掌握 | ✅ 进队列 |
| 已掌握(自动) | 5 | 1 | 复习满 5 次自动标记 | ❌ 出队列 |
| 已掌握(手动) | 0~4 | 1 | 手动「标记掌握」 | ❌ 出队列 |
关键点:「已掌握」的判定是 mastered 而非 rounds——所以 queryReviewQueue 只查 mastered=0 AND rounds<5,手动掌握项不会被误拉进队列;而 reviewOnce 的 CASE WHEN rounds+1>=5 THEN 1 保证自动通道在轮次满 5 时兜底。自动 + 手动两条通道汇合到同一个状态位,是这套状态机的设计精髓:
// 状态机判定:复习队列只看 mastered,自动掌握看 rounds
const inQueue = item.mastered === 0 && item.rounds < 5;
const autoMastered = item.rounds >= 5; // reviewOnce 里由 CASE WHEN 保证
const manualMastered = item.mastered === 1; // markMastered 手动置位
reason 错因的诊断维度
reason 不只是文本标签,它是学习诊断的切面:GROUP BY reason 让错因分布可视化——「计算失误多 → 刷题练计算;概念不清多 → 回去看书」。四种错因对应四种补救动作:
| 错因 | 诊断含义 | 复习建议 |
|---|---|---|
| 计算失误 | 会做但算错 | 限时练习 + 验算习惯 |
| 概念不清 | 知识点没懂 | 回课本重学该知识点 |
| 粗心大意 | 审题/抄写失误 | 慢读题 + 关键词圈画 |
| 思路错误 | 方法不对 | 看解析对比思路 |
knowledge 知识点标签
knowledge 是跨错题的关联线索——queryByKnowledge 用 LIKE 把同知识点的错题捞出来一起复习(函数变换的几道题连刷,形成记忆组块)。
九、my_answer / answer 对照的字段意义
错题本区别于普通题库的关键:错题的价值在「错」。my_answer 存现场错误答案,answer 存正确答案,两者对照产生三个用途:
- 复习时先遮答案:看题目 → 默写 → 对 answer——「错误记忆被重新激活」正是遗忘曲线上的最佳复习点;
- 统计错因的依据:对比 my_answer 与 answer 的偏差类型(算错/概念错/思路错),人工归类到 reason;
- UI 对照展示:横线纸卡上「我的答案(红)/ 正确答案(绿)」并排,一眼看出差距:
// 列表卡片对照展示:红我的答案 / 绿正确答案
Text('我的答案:' + item.my_answer).fontColor('#E5484D');
Text('正确答案:' + item.answer).fontColor('#30A46C');
十、复习队列思路预览
queryReviewQueue 的核心是**「最久未复习优先」**——ORDER BY review_time ASC 让 3 天没看的排在 1 天前看过的前面。为什么时间升序就是间隔重复?因为 review_time 越小 = 距上次复习越久 = 记忆越模糊,最该先复习。真正的 Anki 按「到期日」排,Demo 用 review_time 做线性简化。19-3 将深入队列查询与条件计数的 SQL 细节。
十一、FAQ
Q1:为什么 rounds 到 5 自动掌握,还要手动 mastered 字段?
自动通道只管「复习满 5 次」,但用户可能想提前「标记掌握」(这道题其实早会了),或复习时发现忘了再「取消掌握」。mastered 是用户可干预的最终状态,rounds 是机器自动的进度计数——两个字段各司其职。
Q2:手动标记掌握后,rounds 还继续涨吗?
会。markMastered 只改 mastered 不动 rounds;之后 reviewOnce 仍会 rounds+1,但 mastered 已是 1,队列不再拉取。这是「状态与进度解耦」的刻意设计。
Q3:为什么 my_answer 用 DEFAULT ‘’ 而不是 NOT NULL?
录入错题时可能只记题目忘了填错误答案,DEFAULT '' 保证 INSERT 不报错,UI 端对空串显示「未记录」兜底——**「可空即默认值」**比 NOT NULL 更适合用户录入场景。
Q4:knowledge LIKE ‘%函数%’ 能走索引吗?
不能。idx_mistake_knowledge 是普通 B-tree 索引,%kw% 前导通配无法命中,只有 函数% 前缀才能走索引。Demo 数据量小无所谓,生产可考虑 FTS5 全文检索。
Q5:rounds 满 5 后这道题会怎样?
mastered 置 1 后自动退出复习队列(mastered=0 AND rounds<5 双条件),列表进入「已掌握」tab。想重刷时手动取消掌握即可重新入队——队列的进出完全由状态机控制。
更多推荐




所有评论(0)