【中国方言题库|06】HarmonyOS ArkTS 闽南语与客家话实战:统一分库页面导航与空状态
摘要两个地区分库如果分别编写导航、返回、详情和空状态短期看只是多几十行 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 验收状态正常详情 / 正常详情 / 题库缺失 补充夹具有效题库 totalCount0 - 内容空态且按钮禁用一、两个页面只有地区 ID 不同MinnanBankPage.etsimport { BankDetailContent } from ./BankDetailPage Entry Component struct MinnanBankPage { build() { Column() { BankDetailContent({ fixedBankId: b_minnan }) } .width(100%) .height(100%) } }HakkaBankPage.etsimport { 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/MinnanBankPage与pages/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 维度不要把minnan与b_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_minnan与b_hakka[b_minnan, MN_QUESTIONS.concat(/* 多组扩展数据 */)] [b_hakka, HK_QUESTIONS.concat(/* 多组扩展数据 */)]共享详情只读取转换后的Bank与Question不会把两地区原始数组合并。测试中应抽查题目的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_minnan和b_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 卡片显示完整题库名。十九、进度状态也要按两个题库隔离共享详情读取进度时使用当前题库 IDconst 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注册页面薄入口固定题库 IDBankDetailContent统一详情与缺失态TopBar统一返回再由题库 ID 贯穿内容、章节和进度。当前源码已经真实完成“题库不存在”的空状态却没有把“有效题库暂无内容”和“某章节暂无题目”分别建模getQuestions()对未知 ID 还会回退到四川话数据。这些边界不影响对现有有效入口的复核却是继续扩展地区题库时必须优先处理的工程风险。统一页面并不意味着把所有错误压成一句提示。真正可靠的复用是共享导航和布局同时保留足够精确的状态路由失配、目录缺失、内容为空、章节为空和读取失败都能被识别、解释并安全返回。做到这一点新增地区才不会让用户体验和排障成本一起失控。---AI 辅助声明本文由 AI 辅助整理现状结论基于“中国方言题库”当前MinnanBankPage、HakkaBankPage、BankCard、TopBar、BankDetailContent、MockBanks与路由清单真实源码复核独立内容空态、结果模型和严格校验均作为改进建议呈现未描述为已上线能力。