测验页面的价值不止是给出分数。对于学习者,知道“这一题错了”还不够,还要能看到正确选项和解释;完成一组题后,结果页还应给出本次成绩与历史最佳成绩。篮球口袋教练把逐题反馈和结果汇总分为两个阶段,避免在答题过程中丢失题目上下文。

一、单题反馈保存题目上下文
用户选择选项后,页面根据当前题的正确答案计算对错,并展示正确选项与解释文本。解释来自题目模型,不是页面临时拼接的通用提示,因此不同知识点可以保留各自的学习原因。
const correct = question.options[selectedIndex] === question.answer
this.feedback = correct ? '回答正确' : '答案不正确'
this.explanation = question.explanation
题目、用户选择、正确答案和解释要在当前题切换前一起保留。否则页面一进入下一题,用户只能看到结果颜色,无法知道自己在哪个概念上出现偏差。
二、完成结果以模块为单位归档
答完后,服务记录模块 id、得分、题目总数与时间。历史最佳成绩从同一模块的结果集合中计算,因此体能训练的成绩不会覆盖战术策略的成绩。
static bestScore(moduleId: string): number {
return QuizService.allResults()
.filter(item => item.moduleId === moduleId)
.reduce((best, item) => Math.max(best, item.score), 0)
}
| 作答中 | 当前题、所选答案、解释 | 立即理解对错原因 |
| 提交后 | 得分、总题数、正确率 | 了解本次完成度 |
| 再次进入 | 模块历史最佳 | 判断是否超过过去表现 |
三、运行回读结果
模拟器中逐题选择答案后,页面回读到正确选项、对错反馈和当前题解释。完成体能训练模块 6 道题后,结果页显示 6/6、100%、“满分”和“历史最高:6/6”。这证明解释反馈与结果汇总都在真实答题动作链中出现。
四、解释型反馈的边界
解释型反馈不等于给用户直接公布所有题目的答案。正确的顺序是先接受用户选择,再显示该题的判定与解释;结果页只汇总已经完成的题目。若题目集合为空,页面应阻止进入作答并提示暂无内容,而不是生成 0 分的伪结果。
五、后续扩展的切入点
当前模型已经以模块 id 分隔成绩,后续可以在不改变题目反馈逻辑的前提下增加错题回练、按知识点统计和阶段性复习。关键是继续让题目解释、作答过程和成绩持久化各自承担单一职责,避免把所有规则堆进结果页。
分数之外为什么还要保留错因
解释型反馈的关键是把题目、用户选择、正确答案和 explanation 在切题前保留下来。只有颜色变化不能帮助学习者建立概念;例如挡拆、空间或退防的题目需要把正确选择与原因连在一起,才能把一次答错转成下一次训练的依据。
提交阶段保存 moduleId、score、total 和时间,历史最佳从同模块记录中归约得到。这个范围限制很重要:不同主题的题目数量与难度不同,不能让一次体能小测的分数覆盖战术模块的学习轨迹。
export class QuizService {
static submitResult(moduleId: string, score: number, total: number): void {
const results: QuizResult[] = QuizService.allResults();
results.push({ moduleId, score, total, ts: Date.now() } as QuizResult);
AppStorage.setOrCreate<string>(QUIZ_KEY, JSON.stringify(results));
}
static allResults(): QuizResult[] {
const raw: string = AppStorage.get<string>(QUIZ_KEY) ?? '[]';
try {
return JSON.parse(raw) as QuizResult[];
} catch (e) {
return [];
}
}
static bestScore(moduleId: string): number {
const results: QuizResult[] = QuizService.allResults();
let best: number = 0;
for (const r of results) {
if (r.moduleId === moduleId && r.score > best) {
best = r.score;
}
}
return best;
}
static hasCompleted(moduleId: string): boolean {
return QuizService.allResults().some(r => r.moduleId === moduleId);
}
static completedModuleCount(): number {
const done: Set<string> = new Set<string>();
for (const r of QuizService.allResults()) {
done.add(r.moduleId);
提交结果如何按模块归档
结果页的保存应有幂等边界。页面重新显示时可以回读已保存结果和最佳分,但不应因为组件再次出现又插入一条相同记录;空题库则应终止提交并给出返回路径,而不是写入 0/0 的假成绩。
}
static bestScore(moduleId: string): number {
const results: QuizResult[] = QuizService.allResults();
let best: number = 0;
for (const r of results) {
if (r.moduleId === moduleId && r.score > best) {
best = r.score;
}
}
return best;
}
static hasCompleted(moduleId: string): boolean {
return QuizService.allResults().some(r => r.moduleId === moduleId);
}
static completedModuleCount(): number {
const done: Set<string> = new Set<string>();
for (const r of QuizService.allResults()) {
done.add(r.moduleId);
}
return done.size;
}
static hasPerfect(): boolean {
return QuizService.allResults().some(r => r.score === r.total);
}
}
| 正常路径 | 题目模型保留正确答案与 explanation,QuizService 按 moduleId 保存结果并在同一模块集合中计算 bestScore。 | 显示与真实数据一致的结果 |
| 边界条件 | 结果页重建、重复进入、空题库和不同模块切换不能重复写分或混淆历史最佳。 | 不给出假成功,保留可恢复入口 |
| 回读验证 | 完成一个模块测验、回读解释与本次得分,重进结果页核对历史最佳,再切换另一模块确认成绩独立。 | 跨页面或重进后结果一致 |
历史最佳怎样避免跨模块串分
验收要覆盖完成、重进和切模块三个时刻:本次结果页显示解释,重进后最佳分保持,另一个模块的最佳分不受影响。这样验证的是结果模型和数据范围,而不是一次偶然的按钮文案。
if (params !== undefined && params !== null) {
this.moduleId = params.moduleId;
this.score = params.score;
this.total = params.total;
const m: LearnModule | undefined = findModule(this.moduleId);
this.moduleTitle = m !== undefined ? m.title : '测验';
if (!this.saved) {
QuizService.submitResult(this.moduleId, this.score, this.total);
this.saved = true;
}
}
}
build() {
Column() {
TopBar({ title: '测验结果' });
Scroll() {
Column({ space: AppSizes.s5 }) {
Column().height(AppSizes.s5);
// 分数圆环
Stack({ alignContent: Alignment.Center }) {
Column()
.width(160).height(160)
.borderRadius(80)
.border({ width: 8, color: this.palette.bgElevated });
Column()
.width(160).height(160)
.borderRadius(80)
.border({ width: 8, color: this.ringColor() })
.opacity(1);
Column({ space: AppSizes.s1 }) {
上述代码片段来自当前实现的连续调用点:它们分别回答“谁提供事实”“谁消费结果”“异常时在哪里停止”。读者排查同类问题时,应先验证这三个边界,而不是只根据按钮颜色判断业务是否完成。
结果页的重进与重复提交
完成一个模块测验、回读解释与本次得分,重进结果页核对历史最佳,再切换另一模块确认成绩独立。
定位这类问题时,第一步应回到实际风险:只显示本次分数会让学习者不知道错在哪;若结果未按模块归档,战术测验可能覆盖体能测验的历史最佳。。先确认输入是否已经被模型或服务拒绝,再检查页面是否把该结果展示出来;如果先从视觉现象倒推,往往会把偶然残留的控件状态误判成业务完成。
当前实现选择的是:题目模型保留正确答案与 explanation,QuizService 按 moduleId 保存结果并在同一模块集合中计算 bestScore。。这意味着每个页面不必重复实现同一份判断,却也要求任何新增入口都调用相同的服务或模型;绕过该入口虽然能暂时缩短页面代码,却会让后续回读失去一致性。
需要单独保住的边界是:结果页重建、重复进入、空题库和不同模块切换不能重复写分或混淆历史最佳。。边界出现时,页面应当保留原因和下一步操作,而不是把错误状态压成空白或成功提示。读者据此可以区分“没有数据”“资源不可用”和“动作尚未完成”。
把这条边界写进文章还有一个维护价值:当课程内容、页面布局或资源形式变化时,验收仍可以围绕同一个事实来源进行,而不必依赖某张旧截图或某个控件曾经显示过的文字。这样得到的结论能被下一次修改复查。
| 输入 | 课程、记录或题目是否有稳定标识 | 从页面文本推断业务对象 |
| 服务 | 判断或写入是否经过唯一入口 | 在多个页面复制同一段临时逻辑 |
| 回读 | 重进后是否从持久化或模型得到同一结果 | 只凭一次按钮变化判定成功 |
ArkTS 响应式状态的基础机制可参考 HarmonyOS ArkTS 状态管理文档。这里的重点不是堆叠状态字段,而是让页面在每次进入时都从同一业务事实重新得到可见结果。
当需求继续扩展时,应把新增条件加入现有服务或模型的明确入口,并为该条件补充可观察的回读动作。这样课程内容、页面交互和持久化数据仍可沿同一条链路解释,而不会在不同入口形成互相矛盾的结论。
网硕互联帮助中心








评论前必须登录!
注册