练习结果页不是一个简单的“显示分数”页面。题库应用里,用户完成一次限时挑战后,需要马上知道三件事:这次表现如何、错在哪里、下一步怎么继续练。结果页如果只给一个大分数,用户无法定位薄弱题;如果只列题目细节,页面又会变得冗长;如果结果页直接重新判题,还容易和练习页的记录口径不一致。
笔下生辉的 ExamResultPage.ets 采用了更稳的闭环:PracticePage 在考试结束时把 bankId、score、total、correct、durationSec 和 records 作为路由参数传给结果页;结果页只负责接收、展示、保存历史,并提供两个继续动作:进入错题解析,或重新开始一次限时挑战。这样结果页不重新判题,避免统计口径分裂。
本文基于本地工程 D:\\huawei\\one17-11 的真实源码编写,包名标记为 com.jiaweikang.one17。这里需要明确边界:队列标题里的“薄弱项”在当前源码中不是智能诊断算法,而是由答题网格、错题数量和“错题解析”入口承接;源码没有按知识点聚类、AI 诊断或自动生成补弱建议。

本文重点解决五个工程问题:


一、结果页接收的是结算结果,不是原始题库
结果页的输入参数很明确。
interface ExamResultParams {
bankId: string
score: number
total: number
correct: number
durationSec: number
records: string
}
这些参数来自 PracticePage。考试结束时,练习页已经完成了判题、计分和答题记录序列化。
router.replaceUrl({
url: 'pages/ExamResultPage',
params: {
bankId: this.bankId,
score: score,
total: this.questions.length,
correct: correctCount,
durationSec: this.timerSec,
records: JSON.stringify(this.records),
}
})
这条边界很重要:结果页不是判题页。它不需要重新读取题目并再次计算对错,只要按照练习页传来的结果展示。这样能避免两处代码分别算分导致不一致。
二、aboutToAppear 负责恢复结果状态
ExamResultPage 进入时读取路由参数,把分数、题量、正确数、耗时和答题记录恢复到页面状态。
aboutToAppear(): void {
const params = router.getParams() as ExamResultParams | undefined
if (params) {
this.bankId = params.bankId
this.score = params.score
this.total = params.total
this.correct = params.correct
this.durationSec = params.durationSec
try {
this.records = JSON.parse(params.records) as AnswerRecord[]
} catch (_) {}
}
this.rankInfo = getRankByScore(this.score)
}
records 是字符串传参,因此解析失败时不能让页面崩溃。源码用 try/catch 兜底,失败时保留空数组。结果页仍能显示分数、正确率和用时,只是答题网格无法还原每题状态。
三、分数等级由 ThemeConstants 统一映射
结果页不是直接在页面里写“90 分优秀、75 分良好”。它通过 getRankByScore() 获取等级、印章和颜色。
export function getRankByScore(score: number): RankInfo {
if (score >= 90) return {
label: '优秀',
stamp: $r('app.media.img_exam_stamp_excellent'),
color: Colors.ERROR
}
if (score >= 75) return {
label: '良好',
stamp: $r('app.media.img_exam_stamp_good'),
color: Colors.ACCENT
}
if (score >= 60) return {
label: '及格',
stamp: $r('app.media.img_exam_stamp_pass'),
color: Colors.SUCCESS
}
return {
label: '继续努力',
stamp: $r('app.media.img_exam_stamp_fail'),
color: Colors.TEXT_HINT
}
}
这样做的收益是视觉规则集中。以后如果产品要调整等级阈值、印章素材或等级颜色,不需要改结果页布局,只改常量函数。
四、核心指标只展示三项
结果页的指标区展示正确率、用时和错题数。
Row() {
this.MetricItem(`${Math.round(this.correct / Math.max(this.total, 1) * 100)}%`, '正确率')
this.MetricDivider()
this.MetricItem(this.formatDuration(this.durationSec), '用时')
this.MetricDivider()
this.MetricItem(`${Math.max(this.total – this.correct, 0)}`, '错题')
}
这里有两个防御点:
- Math.max(this.total, 1) 避免 total=0 时除零。
- Math.max(this.total – this.correct, 0) 避免异常参数导致错题数为负。
指标选择也很克制。分数已经在顶部大卡片展示,下面就不再重复堆“总题数、正确数、错误数、排名”等过多数据,而是把用户最关心的正确率、耗时和错题数放在一行。
五、答题网格承接薄弱项入口
当前源码没有知识点维度的薄弱项算法,但有答题网格。它可以让用户快速看到哪些题答错、哪些题未答。
private makeAnswerGrid(): GridItem[] {
const result: GridItem[] = []
for (let i = 0; i < this.total; i++) {
let state: string = 'empty'
if (i < this.records.length) {
state = this.records[i].correct ? 'correct' : 'wrong'
}
result.push({ index: i + 1, state })
}
return result
}
颜色映射同样独立成方法。
private stateColor(state: string): string {
if (state === 'correct') return Colors.SUCCESS
if (state === 'wrong') return Colors.ERROR
return Colors.DIVIDER
}
这就是当前源码可复核的“薄弱项”边界:结果页通过错题数和红色网格提示薄弱题位,真正的逐题复盘交给“错题解析”动作。
六、图例降低网格理解成本
结果页没有假设用户天然理解颜色含义,而是提供图例。
Row({ space: 16 }) {
this.Legend(Colors.SUCCESS, '正确')
this.Legend(Colors.ERROR, '错误')
this.Legend(Colors.DIVIDER, '未答')
}
Legend 是一个很小的 Builder。
@Builder
Legend(color: string, label: string) {
Row({ space: 4 }) {
Column()
.width(12)
.height(12)
.borderRadius(3)
.backgroundColor(color)
Text(label)
.fontSize(Sizes.SMALL_FONT)
.fontColor(Colors.TEXT_HINT)
}
}
对结果页来说,图例比长文字解释更有效。用户扫一眼就能理解红、绿、灰分别代表什么。
七、挑战历史只写一次
结果页会把当前考试写入 examHistory。
@State historySaved: boolean = false
@StorageLink('examHistory') examHistory: ExamHistory[] = []
if (!this.historySaved && this.total > 0) {
this.examHistory = UserDataManager.addExamHistory(
this.examHistory,
this.bankId,
this.score,
this.total,
this.correct,
this.durationSec
)
this.historySaved = true
}
historySaved 的注释说明了真实问题:从错题解析返回结果页时,aboutToAppear() 可能再次触发。如果不加保护,同一场考试会被重复写入历史。
持久化方法在 UserDataManager 中。
static addExamHistory(records: ExamHistory[], bankId: string, score: number, total: number,
correct: number, durationSec: number): ExamHistory[] {
const result: ExamHistory[] = [{
bankId, score, total, correct, durationSec, finishedAt: nowStr()
}, …records]
UserDataManager.persist(UserDataManager.K_EXAM, result)
return result
}
这里使用新记录前置,方便历史列表按最近一次优先展示。
八、错题解析动作回到 PracticePage
结果页底部有两个动作。第一个是错题解析,它不会在结果页内展开所有错题,而是跳回 PracticePage 的 wrongAnalysis 模式。
GreenButton({
text: '错题解析',
isOutline: true,
onTap: () => {
router.pushUrl({
url: 'pages/PracticePage',
params: {
bankId: this.bankId,
mode: 'wrongAnalysis',
records: JSON.stringify(this.records)
}
})
}
})
这个动作保持了职责清晰:结果页负责总结,练习页负责逐题解析。错题解析仍然复用练习页已有的题目展示、选项状态和解析卡片,不需要结果页再实现一份类似 UI。
九、再来一次使用 replaceUrl
第二个动作是重新开始一次考试。
GreenButton({
text: '再来一次',
onTap: () => {
router.replaceUrl({
url: 'pages/PracticePage',
params: { bankId: this.bankId, mode: 'exam' }
})
}
})
这里使用 replaceUrl() 而不是 pushUrl(),可以避免页面栈里累积多个结果页。用户点击再来一次后,新考试成为当前任务,返回路径也更干净。
十、结果页不是学习统计页
源码里还有 LearningStatsPage、ExamTab 等页面会展示长期统计,ExamResultPage 只服务于一次考试结束后的闭环。它保存历史,但不承担长期趋势分析。
这种边界能避免结果页变胖。一次考试结束时,用户只需要:
| 查看本次分数 | 负责 |
| 查看正确率、用时、错题数 | 负责 |
| 查看每题状态 | 负责 |
| 进入错题解析 | 提供入口 |
| 再来一次 | 提供入口 |
| 统计近 5 次均分 | 不负责 |
| 展示长期题库进度 | 不负责 |
长期数据应该放到统计页或挑战记录页,避免结果页成为所有学习数据的堆放区。
十一、结果页的安全区处理
底部按钮区域使用 navigationIndicatorHeightPx 计算安全 padding。
private bottomSafePadding(): number {
return Math.max(
Sizes.BOTTOM_NAV_MIN_PADDING,
this.getUIContext().px2vp(this.navigationIndicatorHeightPx)
)
}
底部按钮区使用这个 padding:
.padding({
left: Sizes.PADDING_LARGE,
right: Sizes.PADDING_LARGE,
top: 10,
bottom: 16 + this.bottomSafePadding()
})
结果页通常在考试结束后出现,底部操作是核心路径。如果按钮被系统导航条遮住,用户就无法继续解析或重练。这个安全区处理是结果页可用性的一部分。
十二、验证清单
基于源码可以按下面步骤验证结果页:
十三、常见问题排查
| 分数显示正确但网格为空 | records 参数是否为空或 JSON 解析失败 |
| 正确率异常 | correct 和 total 是否来自同一轮考试 |
| 历史记录重复 | historySaved 是否在本页生命周期内保持为 true |
| 错题解析没有题 | records 中是否存在 correct=false 项 |
| 再来一次后返回路径混乱 | 是否误用 pushUrl() 而不是 replaceUrl() |
| 底部按钮被挡住 | navigationIndicatorHeightPx 和 bottomSafePadding() 是否生效 |
十四、总结
笔下生辉的练习结果页把一次考试的闭环拆得很清楚:PracticePage 负责判题和传参,ExamResultPage 负责恢复结果、展示分数、正确率、用时、错题网格、保存挑战历史,并提供错题解析和再来一次。它没有把长期统计、智能薄弱项诊断和逐题解析全部塞进结果页,而是通过路由把职责交给更合适的页面。
对 HarmonyOS 5.0+ ArkTS 应用来说,这种结构值得复用:结果页应以“可信结果展示 + 后续动作”为核心,不重新判题,不复制练习页 UI,不伪造诊断能力。只要输入参数、结果展示、历史保存和继续学习动作四个边界清楚,结果页就能稳定承接练习流程,并为后续统计页或错题页留下清晰扩展空间。
部分内容由AI辅助生成。






