【中国方言题库|06】HarmonyOS ArkTS 闽南语与客家话实战:统一分库页面导航与空状态
2026/8/22 13:09:44 网站建设 项目流程

摘要:两个地区分库如果分别编写导航、返回、详情和空状态,短期看只是多几十行 ArkTS,长期却会产生行为漂移。“中国方言题库”的闽南语与客家话入口都复用BankDetailContent,由BankCard统一选择路由,main_pages.json注册页面,TopBar统一返回,题库 ID 再决定目录、Profile、题目与进度。本文基于 HarmonyOS 5.0+ 当前真实源码,复核两条分库链路,重点分析“题库不存在”和“题库存在但暂无内容”为什么不能混成一个空状态。

版本范围与可直接落地的最小改造

本文核验范围明确为HarmonyOS 5.0 及以上版本、Stage 模型、ArkTS/ArkUI Router 页面栈。针对源码中“题库缺失”和“内容为空”尚未分离的问题,可以先在共享详情组件落地一个不改变路由结构的最小改造。

type BankDetailState = 'ready' | 'bankMissing' | 'contentEmpty' private detailState(): BankDetailState { if (this.bank === undefined) return 'bankMissing' if (this.bank.totalCount <= 0) return 'contentEmpty' return 'ready' } private canStartPractice(): boolean { return this.detailState() === 'ready' }

渲染时让bankMissing显示“未找到题库”,让contentEmpty显示“暂无可练题目”,并用canStartPractice()禁用随机练习与模拟考试。该改造只触及共享详情状态判断,闽南语与客家话入口、路由清单、卡片映射和现有进度键无需变化。

验收输入:b_minnan / b_hakka / b_unknown 验收状态:正常详情 / 正常详情 / 题库缺失 补充夹具:有效题库 totalCount=0 -> 内容空态且按钮禁用

一、两个页面只有地区 ID 不同

MinnanBankPage.ets

import { BankDetailContent } from './BankDetailPage' @Entry @Component struct MinnanBankPage { build() { Column() { BankDetailContent({ fixedBankId: 'b_minnan' }) } .width('100%') .height('100%') } }

HakkaBankPage.ets

import { BankDetailContent } from './BankDetailPage' @Entry @Component struct HakkaBankPage { build() { Column() { BankDetailContent({ fixedBankId: 'b_hakka' }) } .width('100%') .height('100%') } }

两个入口都没有复制标题栏、封面卡片、章节列表、进度条或练习按钮。唯一差别是fixedBankId

本文的唯一标记是:空状态必须区分路由失配与内容为空。前者是配置或参数问题,后者是有效题库暂时没有可练内容,两者的恢复动作完全不同。

二、完整导航链路有四个环节

从题库卡片进入地区分库,不是调用一次router.pushUrl()就结束。完整链路包括:

BankCard 选择目标页面 -> main_pages.json 注册页面 -> 地区入口注入 fixedBankId -> BankDetailContent 查询题库并渲染

其中任一环节不一致,都会形成不同故障:

  • 卡片映射错误:进入另一个地区。
  • 页面未注册:导航失败。
  • 固定 ID 拼错:显示“未找到题库”。
  • 目录缺少题库:入口存在但无法加载。

因此,统一导航首先要统一这些环节之间的契约,而不是只让两个页面看起来相似。

三、BankCard是地区路由分发点

当前卡片组件按题库 ID 选择页面:

private detailPageUrl(): string { switch (this.bank.id) { case 'b_minnan': return 'pages/MinnanBankPage' case 'b_hakka': return 'pages/HakkaBankPage' default: return 'pages/BankDetailPage' } }

实际源码还包含四川话、粤语、东北话和上海话分支。未知题库会回退到通用详情页。

这个设计的优点是地区卡片可以落到稳定的独立页面;风险是switch、页面文件和路由清单需要同步更新。新增题库时漏改其中一处,编译未必立即指出业务映射错误。

可以通过自动化测试验证:

expect(detailPageUrl('b_minnan')).toBe('pages/MinnanBankPage') expect(detailPageUrl('b_hakka')).toBe('pages/HakkaBankPage')

这里是测试思路,不表示项目当前已经接入该测试框架。

四、路由清单必须包含两个入口

main_pages.json当前真实注册:

{ "src": [ "pages/BankDetailPage", "pages/MinnanBankPage", "pages/HakkaBankPage", "pages/PracticePage", "pages/ExamResultPage" ] }

这使pages/MinnanBankPagepages/HakkaBankPage能被 Router 找到。文件存在但未进入清单,不能算可导航页面。

发布前至少做三组检查:

卡片映射 URL == main_pages.json 中的字符串 main_pages.json 条目 == 实际 ets 页面路径 页面 fixedBankId == BANKS 中的题库 ID

字符串路径应保持完全一致,包括大小写。开发机文件系统可能容忍大小写差异,构建或设备环境不一定容忍。

五、固定 ID 比外部参数优先

共享详情初始化逻辑为:

aboutToAppear(): void { if (this.fixedBankId.length > 0) { this.bank = getBankById(this.fixedBankId) return } const params = router.getParams() as BankDetailParams | undefined if (params && params.bankId) { this.bank = getBankById(params.bankId) } }

闽南语页面始终查询b_minnan,客家话页面始终查询b_hakka。地区专属入口不会被调用方附带的其他bankId覆盖。

这是导航一致性的第二层保护。第一层由BankCard选择页面,第二层由入口固定地区。即使调用方参数残留,详情仍不会串区。

六、题库 ID 串起目录、内容与进度

目录中的两项为:

{ id: 'b_minnan', regionId: 'minnan', name: '闽南语题库', cover: $r('app.media.img_bank_cover_minnan'), hot: 84, chapters: MN_CHAPTERS } { id: 'b_hakka', regionId: 'hakka', name: '客家话题库', cover: $r('app.media.img_bank_cover_hakka'), hot: 80, chapters: HK_CHAPTERS }

同一 ID 还用于:

RAW_MAP 选择题目集合 QUESTIONS_CACHE 选择缓存 PracticePage 选择练习题 UserDataManager 读取题库进度 章节进度的 bankId 维度

不要把minnanb_minnan当成可互换别名。前者是地区 ID,后者是题库 ID;章节又使用minnan_c1形式。

七、两套章节配置保持相同结构

闽南语章节:

const MN_CHAPTERS = makeChapters('minnan', [ '基础发音', '日常用语', '海洋词汇', '南洋文化', '闽南俗语', '歌仔戏词' ])

客家话章节:

const HK_CHAPTERS = makeChapters('hakka', [ '基础发音', '客家家训', '农耕词汇', '迁徙故事', '土楼生活', '客家俗语' ])

两者都由makeChapters()产生六章,详情组件因此能使用同一套ForEach、进度条和“开始/继续/完成”状态。

不过当前题目展开时按索引对六取模分配章节,不代表每道题已经按语义人工标注。章节 UI 一致与内容分类准确是两个问题,文章不能把前者包装成后者。

八、地区特色由 Profile 配置,不由页面复制

闽南语 Profile:

{ subtitle: '从问候到俗语,感受闽南语的生活温度', intro: '闽南语题库兼顾基础发音、常用问候和民间俗语,适合从熟悉的日常场景建立记忆。', cultureNote: '内容会结合歌仔戏、南洋迁徙和民间饮食文化,让词句背后有更完整的来历。', focusTags: ['基础问候', '俗语记忆', '文化理解'], sceneTags: ['家常寒暄', '市场交流', '戏曲文化'] }

客家话 Profile:

{ subtitle: '从家族称呼到土楼生活,练出客家话的辨识度', intro: '客家话题库适合从称谓、家训和迁徙文化切入,先抓住有根脉感的表达,再做题型强化。', cultureNote: '会把土楼、祭祖、农耕和客家家风放进练习语境里,让语言和文化一起记住。', focusTags: ['称谓表达', '家风词汇', '文化背景'], sceneTags: ['家族交流', '土楼生活', '节俗礼仪'] }

共享页面不等于共享文案。正确做法是把地区差异收敛成数据配置,而不是复制整棵 ArkUI 组件树。

九、真实题目内容也完全分开

闽南语题目包含:

逐家、好势、啥物、拍谢、囡仔、厝边、佗位 中秋博饼、红砖古厝、妈祖信俗、南洋迁徙

客家话题目包含:

屋下、阿公、食朝、转屋下、细人仔、硬颈 围屋、晴耕雨读、客家山歌、酿豆腐、历史迁徙

它们分别映射到b_minnanb_hakka

['b_minnan', MN_QUESTIONS.concat(/* 多组扩展数据 */)] ['b_hakka', HK_QUESTIONS.concat(/* 多组扩展数据 */)]

共享详情只读取转换后的BankQuestion,不会把两地区原始数组合并。测试中应抽查题目的bankId,而不是只看页面标题。

十、顶部返回由同一个TopBar实现

详情页统一使用:

TopBar({ title: this.bank ? this.bank.name : '题库详情' })

TopBar的返回点击为:

.onClick(() => { router.back() })

并通过状态栏避让高度计算顶部空间:

private topSafePadding(): number { return Math.max( 0, this.getUIContext().px2vp(this.topAvoidAreaHeightPx) ) }

闽南语与客家话无需分别维护返回图标、触控区域和状态栏内边距。46vp的返回容器也比只让24vp图标响应点击更容易操作。

十一、返回语义依赖正确的入栈方式

地区卡片进入详情通常使用router.pushUrl(),详情页再执行router.back()。这条组合能回到来源页面,并保留来源列表的滚动与状态。

需要避免这些错误组合:

入口使用 replaceUrl,详情却期待回到题库列表 详情返回时硬编码跳到首页,丢失真实来源 练习完成后重复 push 详情,形成多层相同页面 卡片连续点击造成重复入栈

当前TopBar采用标准返回,不硬编码目的地。若产品允许从搜索、统计或收藏进入同一分库,这种返回语义更自然。

十二、现有空状态只覆盖“题库不存在”

getBankById()返回undefined时,共享详情显示:

if (this.bank === undefined) { Column({ space: 12 }) { Image($r('app.media.img_empty_default')) .width(120) .height(120) .objectFit(ImageFit.Contain) .opacity(0.6) Text('未找到题库') } .layoutWeight(1) .width('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) }

这是真实存在的空态,适用于:

  • fixedBankId拼写错误。
  • 目录删除了对应题库。
  • 通用详情页收到未知bankId
  • 页面与数据版本不一致。

它不是“题目为空”提示。题库对象存在但题量为零时,bank仍然有效,页面会进入正常详情分支。

十三、路由失配与内容为空必须分开

建议把页面状态显式建模:

type DetailState = | 'loading' | 'ready' | 'bankMissing' | 'contentEmpty' | 'loadError'

对应提示与动作应不同:

bankMissing:题库入口无效,返回上一页 contentEmpty:题库已存在,暂时没有可练题目 loadError:读取失败,可重试 ready:正常展示详情和练习入口

若把所有情况都显示“未找到题库”,用户无法判断是数据尚未上线还是页面坏了;开发日志也失去定位价值。

十四、内容为空时不应保留可点击练习按钮

当前底部动作直接导航:

router.pushUrl({ url: 'pages/PracticePage', params: { bankId: this.bank!.id, mode: 'random' } })

如果题库目录存在、最终题量却为零,用户仍可能点击随机练习或模拟考试。更完整的详情状态应根据totalCount控制:

private canStartPractice(): boolean { return this.bank !== undefined && this.bank.totalCount > 0 }

然后禁用按钮并显示原因:

该分库暂无可练题目 内容准备完成后即可开始

这段是改进建议。当前源码已经有“未找到题库”,但没有独立的“内容为空”详情状态,不能声称两种空态均已完成。

十五、getQuestions()的默认回退需要警惕

题目查询中存在:

const source = RAW_MAP.get(bankId) || SICHUAN_QUESTIONS

对于有效的b_minnanb_hakka,它们都能命中RAW_MAP,不会触发回退。但如果错误 ID 绕过详情进入练习,函数会使用四川话原始题目,再把展开后的bankId标成错误 ID。

这会把“配置错误”伪装成“有题可做”,比明确空态更难发现。更安全的接口应返回空数组或结果对象:

interface QuestionLoadResult { ok: boolean bankId: string questions: Question[] reason?: 'bank-not-found' | 'content-empty' }

在可验证内容系统中,宁可明确失败,也不要静默借用另一个地区的数据。

十六、章节空态还需要第三层判断

即使题库总题量大于零,某个章节也可能没有题。当前章节筛选为:

export function getQuestionsByChapter( bankId: string, chapterId: string ): Question[] { return getQuestions(bankId).filter( (question: Question) => question.chapterId === chapterId ) }

练习页检测章节结果为空后,会再次读取整个题库。这能避免空白练习页,却可能让用户点“歌仔戏词”后做到了全题库题目。

更清晰的策略是:

章节有题:进入章节练习 章节无题:留在详情并提示“本章暂无题目” 题库无题:显示内容空态,禁用所有练习入口 题库不存在:显示路由/目录失配空态

空态越接近错误发生的位置,用户越不容易被错误范围误导。

十七、错误状态要保留返回能力

共享详情即使bank === undefined,顶部仍然存在:

TopBar({ title: this.bank ? this.bank.name : '题库详情' })

所以“未找到题库”页面仍有返回按钮。这一点很重要:空状态不能变成无法退出的死路。

还应验证系统返回手势与顶部按钮一致:

点击顶部返回:回到来源页 系统侧滑/返回键:回到来源页 连续进入两个分库后返回:按真实栈顺序退出 空态返回:不重复创建首页

对于 2in1 设备,还要验证鼠标点击和键盘导航能触达返回控件。

十八、标题过长与左右占位保持对称

TopBar左右都保留46vp区域,中间标题使用:

Text(this.title) .layoutWeight(1) .textAlign(TextAlign.Center) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis })

因此“闽南语题库”和“客家话题库”都能保持视觉居中。右侧没有功能时仍使用Blank().width(46),避免标题因左侧返回按钮而偏移。

如果以后标题增加地区全称,不要通过缩小到不可读字号解决;应保留单行省略,并在正文 Hero 卡片显示完整题库名。

十九、进度状态也要按两个题库隔离

共享详情读取进度时使用当前题库 ID:

const p = UserDataManager.getProgress( this.progressList, this.bankId() )

闽南语与客家话分别使用:

b_minnan b_hakka

章节进度还组合bankId + chapterId。导航正确但进度键错误,仍会出现“进入闽南语,看到客家话进度”的问题。

回归测试应执行真实流程:

完成一组闽南语题 -> 返回详情 -> 闽南语进度增加 进入客家话详情 -> 客家话进度不随之增加 完成客家话某章 -> 仅对应 hakka_cN 更新

二十、两类内容的真实性边界

可以准确描述当前能力:

  • 闽南语与客家话均有独立入口。
  • 两个入口均复用共享题库详情。
  • 卡片组件按题库 ID 选择入口。
  • 页面已注册在main_pages.json
  • 顶部导航统一使用router.back()
  • 题库不存在时显示“未找到题库”。
  • 两套本地题目与 Profile 分别配置。
  • 支持章节、随机和考试入口。

当前不应描述:

  • 已有“内容为空”的独立详情状态。
  • 所有章节都有人工语义标注。
  • 所有历史听音题都有真实音频。
  • 无效题库 ID 一定返回空数组。
  • 方言内容已由权威机构逐条认证。
  • 闽南语或客家话内部不存在地区差异。

二十一、导航与空状态测试清单

闽南语入口

BankCard(b_minnan) -> pages/MinnanBankPage 页面注入 b_minnan 标题显示闽南语题库 题目 bankId 均为 b_minnan 返回恢复原列表位置

客家话入口

BankCard(b_hakka) -> pages/HakkaBankPage 页面注入 b_hakka 标题显示客家话题库 题目 bankId 均为 b_hakka 返回恢复原列表位置

异常路径

未知 bankId 显示“未找到题库” 错误 fixedBankId 不展示其他地区 空态仍可返回 未注册页面在构建/自动化阶段被发现 题量为零时不应进入无意义练习 空章节不应静默扩展为全题库

多设备

phone 单列可完整滚动 tablet/2in1 宽屏布局不遮挡返回 状态栏避让正确 底部按钮避开系统导航区域 大字体下标题、空态和按钮不重叠

二十二、总结

闽南语与客家话分库展示了一种可持续的地区导航方式:BankCard决定目标路由,main_pages.json注册页面,薄入口固定题库 ID,BankDetailContent统一详情与缺失态,TopBar统一返回,再由题库 ID 贯穿内容、章节和进度。

当前源码已经真实完成“题库不存在”的空状态,却没有把“有效题库暂无内容”和“某章节暂无题目”分别建模;getQuestions()对未知 ID 还会回退到四川话数据。这些边界不影响对现有有效入口的复核,却是继续扩展地区题库时必须优先处理的工程风险。

统一页面并不意味着把所有错误压成一句提示。真正可靠的复用,是共享导航和布局,同时保留足够精确的状态:路由失配、目录缺失、内容为空、章节为空和读取失败都能被识别、解释并安全返回。做到这一点,新增地区才不会让用户体验和排障成本一起失控。

---

AI 辅助声明:本文由 AI 辅助整理,现状结论基于“中国方言题库”当前MinnanBankPageHakkaBankPageBankCardTopBarBankDetailContentMockBanks与路由清单真实源码复核;独立内容空态、结果模型和严格校验均作为改进建议呈现,未描述为已上线能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询