在这里插入图片描述
在这里插入图片描述

实例:错题本(Mistake)|技术:10 道错题、四科覆盖、轮次梯度

一、种子数据的设计目标

实例 19 的种子数据要撑起「错题列表 + 复习队列 + 错因统计 + 总览」,设计目标:

  1. 10 道错题:列表内容充实——多屏可滚;
  2. 四科覆盖:数学 4 / 英语 3 / 物理 2 / 化学 1——学科筛选有结果;
  3. 轮次梯度:0~4 次——复习进度有层次(0 次急待复习、4 次快掌握);
  4. 掌握状态:已掌握 2 / 未掌握 8——「已掌握」tab 有内容;
  5. 错因覆盖:计算失误 3 / 概念不清 4 / 思路错误 2 / 粗心大意 1——错因统计条四色;
  6. 知识点多样:函数变换/绝对值不等式/数列求和/三角函数/被动语态/虚拟语气/倒装句/自由落体/圆周运动/氧化还原——知识点搜索有结果。

二、10 道错题种子数据

# 学科 题目 正确答案 我的答案 知识点 错因 轮次 掌握 录入
1 数学 已知 f(x)=x²+2x,求 f(x+1) 展开式 x²+4x+3 x²+2x+2 函数变换 计算失误 2 未掌握 -20天
2 数学 解不等式 |x-3|>5 x<-2 或 x>8 x>8 绝对值不等式 思路错误 1 未掌握 -18天
3 数学 等差数列 a₁=2,d=3,求 S₁₀ 155 152 数列求和 计算失误 3 未掌握 -15天
4 数学 sin 75° 的值 (√6+√2)/4 (√6-√2)/4 三角函数 概念不清 4 已掌握 -30天
5 英语 The book ___ (write) by him was written written 被动语态 概念不清 2 未掌握 -12天
6 英语 I suggest he ___ (go) home early go goes 虚拟语气 概念不清 1 未掌握 -10天
7 英语 翻译:他不仅学习好,还乐于助人 Not only does he… He not only study… 倒装句 粗心大意 2 未掌握 -8天
8 物理 自由落体 3s 内下落高度(g=10) 45m 30m 自由落体 计算失误 1 未掌握 -6天
9 物理 匀速圆周运动向心力方向 始终指向圆心 指向切线方向 圆周运动 概念不清 4 已掌握 -25天
10 化学 配平:C+O₂→CO 2C+O₂=2CO C+O₂=CO 氧化还原 思路错误 0 未掌握 -3天

设计要点

  • 轮次梯度 0~4:化学配平 0 次(最急)、多数 1~3 次、三角函数/圆周运动 4 次且已掌握——复习进度有层次
  • 掌握分布 8/2:未掌握 8(复习主力)、已掌握 2(sin75°、向心力——复习满 5 次前手动标记的示例)——「已掌握」tab 有内容;
  • 错因分布:概念不清 4(最多——「没学懂」是主要矛盾)、计算失误 3、思路错误 2、粗心大意 1——错因统计条「概念不清 4」排第一(cnt DESC);
  • 录入时间 -30 天 ~ -3 天:错题时间跨度——列表「新录入在前」(created_time DESC 化学配平最新最顶);
  • 复习队列mastered=0 AND rounds<5 命中 8 道(未掌握全在队列),ORDER BY review_time ASC(=created_time)——化学配平(-3 天)最旧优先?不——review_time 小 = 录入早 = 排前:sin75° 和向心力已掌握不在队列,队列里 review_time 最小的是 -20 天的 f(x+1)(最久没复习)排最前。

三、代码实现

const now = Date.now();
const day = 86400000;
const seed: MistakeSeed[] = [ /* 10 道,见第二节表格 */ ];
for (const s of seed) {
  const values: relationalStore.ValuesBucket = {
    subject: s.subject, question: s.question, answer: s.answer, my_answer: s.myAnswer,
    knowledge: s.knowledge, reason: s.reason, rounds: s.rounds,
    mastered: s.mastered, created_time: s.createdTime, review_time: s.createdTime,
  };
  await store.insert(MistakeDao.TABLE, values);
}
hilog.info(DOMAIN, TAG, '已填充 10 道错题种子数据');

review_time: s.createdTime:上次复习时间初始化为录入时间(刚录入 = 刚看过)——复习队列的初始语义幂等保护:查 mistake COUNT,非空即返回。

四、注入后的错题本效果预测

区块 预期效果
标题栏 「共 10 道 · 待复习 8」
错因统计条 ●概念不清 4 / ●计算失误 3 / ●思路错误 2 / ●粗心大意 1
全部列表 未掌握 8 在前(轮次少在前):化学配平(0次) → 不等式(1) → 自由落体(1) → 虚拟语气(1) → … → 已掌握 2 在后
待复习 tab 8 道未掌握,按 review_time 升序(f(x+1) 最久没复习排最前)
已掌握 tab 2 道(sin75°、向心力,✅ 已掌握)
学科筛选 数学 4 / 英语 3 / 物理 2 / 化学 1

演示脚本:指着统计条说「我的错题主要集中在概念不清,4 道——说明不是粗心是没学懂」→ 切「待复习」→ 8 道,「f(x+1) 这个最久没复习,排第一」→ 点化学配平「复习」→ Toast「✅ 复习第 1 次」→ 复习标签变 1/5 → 连点 5 次(模拟多轮)→ 第 5 次 Toast「🎉 复习 5 次,已标记掌握!」→ 该题变「✅ 已掌握」、待复习 8→7。

五、验证方法

  1. hilog 日志:搜 MistakeDao,看到「已填充 10 道错题种子数据」;
  2. 页面显示:标题栏「10 道 · 待复习 8」、统计条 4 色、列表 10 卡;
  3. 数据库直查
    SELECT COUNT(*) FROM mistake;  -- 10
    SELECT subject, COUNT(*) FROM mistake GROUP BY subject;  -- 数学4 英语3 物理2 化学1
    SELECT reason, COUNT(*) FROM mistake GROUP BY reason ORDER BY COUNT(*) DESC;
    -- 概念不清4 计算失误3 思路错误2 粗心大意1
    SELECT COUNT(*) FROM mistake WHERE mastered=0 AND rounds<5;  -- 8(待复习)
    SELECT question FROM mistake WHERE mastered=0 AND rounds<5 ORDER BY review_time ASC LIMIT 3;
    -- f(x+1)(-20天最旧)最前
    
  4. 交互验证:复习(轮次+1)、标记掌握/取消、学科筛选、已掌握 tab、删除。

六、FAQ

Q1:为什么已掌握 2 道是「复习 4 次」而不是 5 次?
A:种子里 sin75° 和向心力 rounds=4 且 mastered=1——模拟「手动标记掌握」的结果(4 次后手动标掌握,不必等满 5 次)——展示「手动干预」通道。**「轮次 4 + 手动掌握」**体现两种掌握路径。

Q2:错因为什么「概念不清」最多?
A:真实学情——「没学懂」是错题主因(计算失误是偶发、概念不清是系统性)。统计条「概念不清 4」排第一让诊断有说服力(演示「我的问题在哪」)。

Q3:复习队列里 f(x+1) 为什么排最前?
A:ORDER BY review_time ASC——f(x+1) 录入最早(-20 天),review_time=created_time 最小 → 排最前——「最久没复习的优先」。化学配平虽然轮次 0(最急),但录入最晚(-3 天)排最后——「队列按时间排序不按轮次」(轮次急迫性由 tab 切换体现)。

Q4:10 道题够演示复习闭环吗?
A:够——点化学配平复习 5 次(0→5 次完整演示「自动掌握」)+ 手动标记/取消掌握 + 队列变化(8→7)——复习闭环全路径可演示

Q5:题目里的数学符号(√、²、|)能正常显示吗?
A:UTF-8 文本正常显示(√6、x²、|x-3|)——ArkUI Text 支持 Unicode。「题目是纯文本」(不渲染 LaTeX)——Demo 简化,真实错题应用可内嵌公式图片或富文本。

Q6:知识点搜索「函数」会命中什么?
A:LIKE '%函数%' 命中「函数变换」(第 1 题)——知识点标签的模糊搜索演示。「按知识点聚合复习」:搜同一知识点找出所有相关错题一起练。

七、文章小结

本篇文章展示了实例 19 的种子数据:10 道错题四科覆盖(数学 4/英语 3/物理 2/化学 1 + 轮次梯度 0~4 + 掌握 8/2 + 错因四类 + 知识点十种)。核心是「复习队列与诊断的种子设计」——轮次梯度让复习进度有层次、错因分布让统计条「概念不清 4」有诊断说服力、review_time=created_time 让队列「最久没复习优先」成立。错题本一开屏就有复习队列与错因画像。

下一篇(19-5)是实例 19 的收官文章,展示 MistakePage 全量代码与运行效果。

附录:initSeedData 完整代码与种子设计复盘

第三节的插入循环里写着 /* 10 道,见第二节表格 */——那是占位符,真正的 10 个 MistakeSeed 对象在这里完整亮相。对照第二节表格逐行核对,数据 100% 一致。

8.1 种子数组完整定义(10 道)

interface MistakeSeed {
  subject: string;      // 学科
  question: string;     // 题目
  answer: string;       // 正确答案
  myAnswer: string;     // 我的错误答案
  knowledge: string;    // 知识点
  reason: string;       // 错因
  rounds: number;       // 复习轮次
  mastered: number;     // 0 未掌握 / 1 已掌握
  createdTime: number;  // 录入时间(毫秒时间戳)
}

const day = 86400000;   // 一天 = 24 * 60 * 60 * 1000 ms
const now = Date.now();

const seed: MistakeSeed[] = [
  // 数学 4 道
  { subject: '数学', question: '已知 f(x)=x²+2x,求 f(x+1) 展开式', answer: 'x²+4x+3', myAnswer: 'x²+2x+2', knowledge: '函数变换', reason: '计算失误', rounds: 2, mastered: 0, createdTime: now - 20 * day },
  { subject: '数学', question: '解不等式 |x-3|>5', answer: 'x<-2 或 x>8', myAnswer: 'x>8', knowledge: '绝对值不等式', reason: '思路错误', rounds: 1, mastered: 0, createdTime: now - 18 * day },
  { subject: '数学', question: '等差数列 a₁=2,d=3,求 S₁₀', answer: '155', myAnswer: '152', knowledge: '数列求和', reason: '计算失误', rounds: 3, mastered: 0, createdTime: now - 15 * day },
  { subject: '数学', question: 'sin 75° 的值', answer: '(√6+√2)/4', myAnswer: '(√6-√2)/4', knowledge: '三角函数', reason: '概念不清', rounds: 4, mastered: 1, createdTime: now - 30 * day },
  // 英语 3 道
  { subject: '英语', question: 'The book ___ (write) by him', answer: 'was written', myAnswer: 'written', knowledge: '被动语态', reason: '概念不清', rounds: 2, mastered: 0, createdTime: now - 12 * day },
  { subject: '英语', question: 'I suggest he ___ (go) home early', answer: 'go', myAnswer: 'goes', knowledge: '虚拟语气', reason: '概念不清', rounds: 1, mastered: 0, createdTime: now - 10 * day },
  { subject: '英语', question: '翻译:他不仅学习好,还乐于助人', answer: 'Not only does he…', myAnswer: 'He not only study…', knowledge: '倒装句', reason: '粗心大意', rounds: 2, mastered: 0, createdTime: now - 8 * day },
  // 物理 2 道
  { subject: '物理', question: '自由落体 3s 内下落高度(g=10)', answer: '45m', myAnswer: '30m', knowledge: '自由落体', reason: '计算失误', rounds: 1, mastered: 0, createdTime: now - 6 * day },
  { subject: '物理', question: '匀速圆周运动向心力方向', answer: '始终指向圆心', myAnswer: '指向切线方向', knowledge: '圆周运动', reason: '概念不清', rounds: 4, mastered: 1, createdTime: now - 25 * day },
  // 化学 1 道
  { subject: '化学', question: '配平:C+O₂→CO', answer: '2C+O₂=2CO', myAnswer: 'C+O₂=CO', knowledge: '氧化还原', reason: '思路错误', rounds: 0, mastered: 0, createdTime: now - 3 * day },
];

补全核对表(数组 ↔ 第二节表格):

维度 设计值 数组实际
总题数 10 10(数学 4 / 英语 3 / 物理 2 / 化学 1)
轮次梯度 0~4 0 次×1、1 次×3、2 次×3、3 次×1、4 次×2
掌握分布 已掌握 2 rounds=4 且 mastered=1 的 sin75° 与向心力
错因分布 概念不清 4 最多 计算失误 3 / 思路错误 2 / 粗心大意 1
录入时间 -30 ~ -3 天 now - N*day,跨度 27 天

为什么用 now - N*day 而不是写死时间戳:种子要在任何时间首次启动都呈现「30 天前到 3 天前」的错题跨度——写死时间戳会在几个月后变成「半年前的旧错题」,createdTime 相对 now 计算永远新鲜

8.2 initSeedData 完整方法(含幂等保护)

static async initSeedData(context: common.Context): Promise<void> {
  const store = await MistakeDao.getStore(context);
  // 幂等保护:已有数据则直接返回,避免每次启动重复插入
  const count = await store.count(MistakeDao.TABLE);
  if (count > 0) {
    hilog.info(DOMAIN, TAG, '错题本已有 %{public}d 条数据,跳过种子填充', count);
    return;
  }
  for (const s of seed) {
    const values: relationalStore.ValuesBucket = {
      subject: s.subject, question: s.question, answer: s.answer, my_answer: s.myAnswer,
      knowledge: s.knowledge, reason: s.reason, rounds: s.rounds,
      mastered: s.mastered, created_time: s.createdTime, review_time: s.createdTime,
    };
    await store.insert(MistakeDao.TABLE, values);
  }
  hilog.info(DOMAIN, TAG, '已填充 10 道错题种子数据');
}

两个容易被忽略的细节:

  1. review_time: s.createdTime:上次复习时间 = 录入时间——刚录入的题「刚看过」,复习队列 ORDER BY review_time ASC 才能把最久没复习的排最前(见第五节 SQL);
  2. 幂等保护在 insert 之前store.count 非 0 即 return——错题本支持用户删除/新增真实数据,绝不能在用户已有数据时再灌种子(否则用户删掉的题会「复活」)。

8.3 注入后的错题本效果预测(SQL 自检)

用四个 SQL 直查数据库,验证种子注入是否正确:

-- ① 总数与学科分布:10 / 数学4 英语3 物理2 化学1
SELECT COUNT(*) FROM mistake;
SELECT subject, COUNT(*) FROM mistake GROUP BY subject;

-- ② 轮次梯度:0 到 4 都有(复习进度有层次)
SELECT rounds, COUNT(*) FROM mistake GROUP BY rounds ORDER BY rounds;

-- ③ 错因分布:概念不清 4 排第一(统计条主色)
SELECT reason, COUNT(*) FROM mistake GROUP BY reason ORDER BY COUNT(*) DESC;

-- ④ 复习队列命中:mastered=0 AND rounds<5 → 8 道
SELECT question FROM mistake WHERE mastered=0 AND rounds<5 ORDER BY review_time ASC;

预期结果:① 10 条、② 轮次 0/1/2/3/4 各 1/3/3/1/2、③ 概念不清 4、④ 队列 8 条且 f(x+1)(-20 天)排最前。任何一项不符,先查 initSeedData 是否在入口被调用、表结构字段名是否与 ValuesBucket 键名一致。

8.4 FAQ(种子数据专项)

Q1:now - 20 * day 每次启动都在变,两天后 f(x+1) 变成 -22 天,会影响队列顺序吗?
A:不会——所有题目的 createdTime 同时右移,相对顺序不变(f(x+1) 永远比化学配平早 17 天)。复习队列按 review_time 相对排序,绝对时间戳漂移无影响。

Q2:已掌握 2 道(rounds=4)为什么还留在表里?
A:错题本不删题——掌握 ≠ 删除。「已掌握 tab」展示它们(✅ 标记),随时可「取消掌握」重新进入复习队列——掌握状态可逆,符合真实学习(旧错题可能再错)。

Q3:幂等保护只查 count,如果用户把题删到 0 条会怎样?
A:count=0 会再次灌种子——这是特性不是 bug:全删后回到「干净示例状态」,方便再次演示。生产环境可加 seed_done 标志表区分「从未初始化」与「用户清空」。

Q4:种子时间戳为什么用毫秒而不是秒?
A:Date.now() 返回毫秒(13 位),created_time/review_time 字段类型 INTEGER 直接存毫秒——与 reviewOncereview_time = Date.now() 同一单位,排序/比较无需换算。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐