欢迎光临
我们一直在努力

【笔下生辉|05】HarmonyOS ArkTS 练习结果实战:汇总正确率、薄弱项和继续学习动作

练习结果页不是一个简单的“显示分数”页面。题库应用里,用户完成一次限时挑战后,需要马上知道三件事:这次表现如何、错在哪里、下一步怎么继续练。结果页如果只给一个大分数,用户无法定位薄弱题;如果只列题目细节,页面又会变得冗长;如果结果页直接重新判题,还容易和练习页的记录口径不一致。

笔下生辉的 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()
    })

    结果页通常在考试结束后出现,底部操作是核心路径。如果按钮被系统导航条遮住,用户就无法继续解析或重练。这个安全区处理是结果页可用性的一部分。

    十二、验证清单

    基于源码可以按下面步骤验证结果页:

  • 从 PracticePage 的 exam 模式交卷,确认跳转到 ExamResultPage。
  • 检查路由参数包含 bankId、score、total、correct、durationSec 和 records。
  • 检查顶部大卡片显示分数和对应印章。
  • 检查指标区正确率、用时、错题数和传参一致。
  • 检查答题网格中正确题、错误题、未答题颜色不同。
  • 点击“错题解析”,确认进入 PracticePage 且 mode='wrongAnalysis'。
  • 点击“再来一次”,确认 replaceUrl() 打开新的 exam 模式。
  • 从错题解析返回结果页,确认 examHistory 不重复写入同一场考试。
  • 十三、常见问题排查

    问题优先检查
    分数显示正确但网格为空 records 参数是否为空或 JSON 解析失败
    正确率异常 correct 和 total 是否来自同一轮考试
    历史记录重复 historySaved 是否在本页生命周期内保持为 true
    错题解析没有题 records 中是否存在 correct=false 项
    再来一次后返回路径混乱 是否误用 pushUrl() 而不是 replaceUrl()
    底部按钮被挡住 navigationIndicatorHeightPx 和 bottomSafePadding() 是否生效

    十四、总结

    笔下生辉的练习结果页把一次考试的闭环拆得很清楚:PracticePage 负责判题和传参,ExamResultPage 负责恢复结果、展示分数、正确率、用时、错题网格、保存挑战历史,并提供错题解析和再来一次。它没有把长期统计、智能薄弱项诊断和逐题解析全部塞进结果页,而是通过路由把职责交给更合适的页面。

    对 HarmonyOS 5.0+ ArkTS 应用来说,这种结构值得复用:结果页应以“可信结果展示 + 后续动作”为核心,不重新判题,不复制练习页 UI,不伪造诊断能力。只要输入参数、结果展示、历史保存和继续学习动作四个边界清楚,结果页就能稳定承接练习流程,并为后续统计页或错题页留下清晰扩展空间。

    部分内容由AI辅助生成。

    赞(0)
    未经允许不得转载:171主机测评 » 【笔下生辉|05】HarmonyOS ArkTS 练习结果实战:汇总正确率、薄弱项和继续学习动作
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址