摘要:地区分库页面看起来简单,却承担着稳定身份、路由可达和共享组件复用三项关键职责。本文基于"中国方言题库"真实源码中的
SichuanBankPage.ets,沿着BankCard -> SichuanBankPage -> BankDetailContent -> PracticePage追踪b_sichuan,说明如何用薄入口组件复用题库详情、学习进度、章节列表和多设备布局,同时避免复制业务代码、路由参数污染与地区数据串库。

一、只有十几行的页面为什么值得单独分析
SichuanBankPage.ets 的全部核心代码非常短:
ts
import { BankDetailContent } from './BankDetailPage'
@Entry
@Component
struct SichuanBankPage {
build() {
Column() {
BankDetailContent({ fixedBankId: 'b_sichuan' })
}
.width('100%')
.height('100%')
}
}
它没有自己加载题目、计算进度、渲染章节,也没有再次实现随机练习和模拟考试。页面只做两件事:
- 作为 ArkUI 路由入口存在。
- 把稳定地区 ID
b_sichuan注入共享详情组件。
这种"薄入口"不是代码不足,而是有意保持职责单一。本文的唯一标记是:地区入口只负责固定题库身份 。所有可复用业务都留在 BankDetailContent,四川话差异来自目录和文化档案,而不是复制一份详情页后再修改文案。
二、完整调用链从题库卡片开始
首页和题库列表使用 BankCard。卡片根据 bank.id 决定目标页:
ts
private detailPageUrl(): string {
switch (this.bank.id) {
case 'b_sichuan':
return 'pages/SichuanBankPage'
case 'b_yue':
return 'pages/YueBankPage'
case 'b_northeast':
return 'pages/NortheastBankPage'
case 'b_shanghai':
return 'pages/ShanghaiBankPage'
case 'b_minnan':
return 'pages/MinnanBankPage'
case 'b_hakka':
return 'pages/HakkaBankPage'
default:
return 'pages/BankDetailPage'
}
}
点击四川话卡片时:
ts
router.pushUrl({
url: this.detailPageUrl(),
params: { bankId: this.bank.id }
})
虽然路由仍携带 bankId,四川话固定入口不会依赖它。BankDetailContent.aboutToAppear() 先检查 fixedBankId:
ts
if (this.fixedBankId.length > 0) {
this.bank = getBankById(this.fixedBankId)
return
}
只有未传固定 ID 时,通用 BankDetailPage 才读取 Router 参数。这条优先级保证即使页面栈中残留了其他地区参数,SichuanBankPage 仍只能展示四川话题库。
三、固定参数比页面内部常量更容易复用
一种常见写法是在详情组件内部判断当前页面名称,或在每个地区页面复制一套 UI。当前工程选择属性注入:
ts
BankDetailContent({ fixedBankId: 'b_sichuan' })
优点包括:
- 详情组件可以独立测试不同
fixedBankId。 - 地区入口无需知道详情实现。
- 通用详情页仍可支持动态
bankId。 - 六个入口结构保持一致。
- 地区身份在组件创建处一眼可见。
如果把四川 ID 隐藏在全局变量或 AppStorage 中,页面恢复时容易读到上一个地区;如果把它写进 BankDetailContent 内部,又会破坏组件复用。属性传递是最窄、最明确的依赖。
四、六个地区入口保持同构
工程中还存在:
text
YueBankPage -> b_yue
NortheastBankPage -> b_northeast
ShanghaiBankPage -> b_shanghai
MinnanBankPage -> b_minnan
HakkaBankPage -> b_hakka
每个页面都采用相同根布局与共享组件。这样做能让 AppGallery 的多设备适配、系统返回和稳定性修复一次覆盖六个分库。
同构不等于可以手工随意复制。复制入口文件后最常见的错误是文件名改了,fixedBankId 没改,导致"粤语题库"入口打开四川话内容。建议为入口建立表驱动测试:
| 页面 | 预期 ID | 预期名称 |
|---|---|---|
| SichuanBankPage | b_sichuan |
四川话题库 |
| YueBankPage | b_yue |
粤语题库 |
| NortheastBankPage | b_northeast |
东北话题库 |
| ShanghaiBankPage | b_shanghai |
上海话题库 |
| MinnanBankPage | b_minnan |
闽南语题库 |
| HakkaBankPage | b_hakka |
客家话题库 |
五、路由清单是入口可达性的第二个真源
只有 @Entry 还不够,页面必须注册在 main_pages.json:
json
{
"src": [
"pages/BankDetailPage",
"pages/SichuanBankPage",
"pages/YueBankPage",
"pages/NortheastBankPage",
"pages/ShanghaiBankPage",
"pages/MinnanBankPage",
"pages/HakkaBankPage"
]
}
页面文件、路由字符串和清单路径必须完全一致。重命名文件后只改 import,不改清单,编译可能通过部分阶段,但运行跳转会失败。发布前应自动检查:
text
BankCard 返回的每个 pages/... 路径
必须存在于 main_pages.json
且对应 .ets 文件真实存在
这是很适合写成静态脚本的规则,不需要每次靠人工逐个点击才能发现拼写错误。

六、题库目录中 b_sichuan 的真实内容
MockBanks.ets 将四川话地区与题库分开建模:
ts
{ id: 'sichuan', name: '四川话', shortName: '川', cover: ... }
{
id: 'b_sichuan',
regionId: 'sichuan',
name: '四川话题库',
totalCount: 0,
accuracy: 0,
hot: 98,
chapters: SICHUAN_CHAPTERS
}
地区 ID 是 sichuan,题库 ID 是 b_sichuan。入口传递的是题库 ID,因为详情页需要查找章节、题目与学习进度。混用两个 ID 会导致 getBankById() 返回 undefined,页面进入"未找到题库"空态。
四川话题库包含六个章节:
text
基础词汇
日常生活
人称称谓
饮食词汇
俗语歇后语
麻将与娱乐
题量并非永久写死在 Bank 模型中。目录初始化时从去重后的真实题目集合回填 totalCount 和章节总量。分库页显示的"共 N 题"因此应与练习页可用题目保持一致。
七、四川话文化差异来自 Profile,而不是页面分支
共享详情组件通过 BANK_DETAIL_PROFILES 查找四川话档案:
ts
['b_sichuan', {
subtitle: '巴适表达、茶馆闲谈和川味生活都在这里',
intro: '四川话题库偏重高频口语、称呼调侃和饮食表达,适合先从有画面感的词句入门。',
cultureNote: '会结合茶馆、火锅、麻将社交等语境,帮助理解"摆龙门阵""巴适"这类代表性说法。',
focusTags: ['高频口头词', '饮食表达', '俗语语感'],
sceneTags: ['茶馆聊天', '火锅点单', '朋友调侃']
}]
页面结构仍然是 Hero、学习进度、简介、文化提示、重点标签和章节列表。只替换档案数据,就能形成地区识别度。
文化内容需要人工校对。比如熟人之间的调侃不一定适合正式场合,同一表达在四川不同地区也可能有差异。代码能保证字段存在,却不能自动证明文化解释具有普遍性。内容维护应记录适用语境和审核来源。
八、学习进度通过题库 ID隔离
共享组件使用:
ts
UserDataManager.getProgress(this.progressList, this.bankId())
章节进度使用题库 ID 与章节 ID 联合查询:
ts
UserDataManager.getChapterProgress(
this.chapterProgressList,
this.bankId(),
chapter.id
)
这意味着 b_sichuan 不只是展示参数,也是本地学习状态的分区键。若写入时使用 sichuan,读取时使用 b_sichuan,用户完成练习后详情页仍会显示 0。
建议把 ID 类型收紧,减少任意字符串:
ts
export type BankId =
| 'b_sichuan'
| 'b_yue'
| 'b_northeast'
| 'b_shanghai'
| 'b_minnan'
| 'b_hakka'
ArkTS 工程也可以用常量对象统一引用,避免入口、目录、Profile 和测试中重复手写字符串。
九、共享详情保留多设备能力
四川话入口没有自己的断点判断,但它复用了 BankDetailContent 的适配能力。当 currentBp === 'lg' 且页面宽度不小于 700vp 时,详情页使用双栏:
- 左栏:封面、学习摘要、题库简介。
- 右栏:学习重点、常见语境、章节练习。
其他窗口使用单列滚动。底部随机练习与模拟考试按钮根据系统导航指示区计算安全距离。
这正是复用的价值:如果六个地区各自实现详情页,任何底部安全区、横屏或 2in1 修复都要改六次。现在只需修正共享组件,固定入口自然获得同一行为。
十、薄入口仍需要稳定根容器
SichuanBankPage 外层使用:
ts
Column() {
BankDetailContent({ fixedBankId: 'b_sichuan' })
}
.width('100%')
.height('100%')
根容器满宽满高,确保内部详情可以正确计算 onAreaChange 和滚动区域。若只依赖子组件自然尺寸,宽屏断点可能得到错误宽度,底部操作栏也可能无法贴合页面底部。
入口没有额外背景和 padding,避免与共享组件的背景、TopBar 和安全区重复。薄入口应尽量透明,不在外层再增加一层卡片或滚动容器。
十一、从分库进入三种练习模式
在共享详情中,四川话题库可以进入:
ts
// 章节练习
{ bankId: 'b_sichuan', chapterId, mode: 'chapter' }
// 随机练习
{ bankId: 'b_sichuan', mode: 'random' }
// 模拟考试
{ bankId: 'b_sichuan', mode: 'exam' }
参数链路中 bankId 必须保持不变。PracticePage 应先校验题库,再根据 mode 选择题目:
- chapter 只使用对应章节题目。
- random 从四川话题库真实题目中抽取。
- exam 按当前可用题量组成试卷。
不能因为入口是四川话专属页面,就让 PracticePage 依赖"上一页是什么"。目标页只应信任明确参数,这样从搜索、收藏或恢复页面进入时仍能工作。

十二、为何不直接统一成一个动态详情路由
既然通用 BankDetailPage 能读取 bankId,为什么还保留六个固定入口?当前设计可能有以下工程考虑:
- 每个地区需要稳定、可独立注册的页面地址。
- 后续可能加入地区专属资源或入口配置。
- 固定页面可防止路由参数被错误覆盖。
- 页面统计或测试可以按地区入口分别执行。
代价是路由文件和清单条目增多。若未来地区数量从 6 增长到 50,继续为每个地区创建页面会产生维护负担。那时可以改为一个类型安全的动态详情页,并在 Navigation 中统一路由契约。
当前只有六个地区,薄入口模式仍然简单可控。不要因为追求"零重复"过早引入复杂路由框架。
十三、switch 映射可以进一步集中
BankCard.detailPageUrl() 当前用 switch 将题库 ID 映射到页面。入口文件和清单新增后,还要记得修改 switch。可以用结构化 Map 减少分散配置:
ts
const BANK_DETAIL_ROUTES: Map<string, string> = new Map([
['b_sichuan', 'pages/SichuanBankPage'],
['b_yue', 'pages/YueBankPage'],
['b_northeast', 'pages/NortheastBankPage'],
['b_shanghai', 'pages/ShanghaiBankPage'],
['b_minnan', 'pages/MinnanBankPage'],
['b_hakka', 'pages/HakkaBankPage']
])
function detailRoute(bankId: string): string {
return BANK_DETAIL_ROUTES.get(bankId) || 'pages/BankDetailPage'
}
更进一步,可以把 route 加入 Bank 元数据,但要避免让纯业务模型过度依赖页面路径。若目录模型还要在非 UI 模块复用,路由映射留在导航层更合适。
十四、空态不是"不会发生"
即使固定 ID 写在源码中,仍可能因为目录重命名、题库移除或初始化失败而找不到。共享详情会显示"未找到题库",不会崩溃。
对于固定入口,这种情况属于工程配置错误,不只是普通空数据。调试构建可以记录:
text
SichuanBankPage fixedBankId=b_sichuan
getBankById result=undefined
发布构建不应展示内部路径或堆栈,但可以提供返回题库列表的操作。自动化测试应让所有固定 ID逐一通过 getBankById(),在构建前发现配置断裂。
十五、入口测试比截图更重要
四川话分库至少需要以下测试:
身份测试
- 打开 SichuanBankPage 后标题为"四川话题库"。
- Hero 封面来自四川话资源。
- 文化档案包含四川话语境,而非其他地区。
- 六个章节名称与四川目录一致。
参数测试
- 路由传入
b_yue时,固定入口仍展示b_sichuan。 - 通用 BankDetailPage 传
b_sichuan时得到相同详情内容。 - 不存在的固定 ID进入空态。
- 练习页收到的 bankId 始终为
b_sichuan。
状态测试
- 完成四川话题目后,只更新四川话进度。
- 粤语进度不会出现在四川话页。
- 完成章节后按钮与进度立即更新。
- 重启应用后本地进度可恢复。
适配测试
- 手机单列与底部操作正常。
- 平板和 2in1 双栏切换正常。
- 大字体下标签和章节标题不重叠。
- 系统导航区不会遮挡练习按钮。
十六、参数串库的典型症状和定位顺序
若用户点击四川话却看到粤语内容,按以下顺序排查:
BankCard的bank.id是否为b_sichuan。detailPageUrl()是否返回pages/SichuanBankPage。main_pages.json是否注册正确页面。SichuanBankPage是否注入b_sichuan。getBankById('b_sichuan')是否返回四川题库。BANK_DETAIL_PROFILES的 key 是否为同一 ID。- 本地进度写入是否也使用
b_sichuan。 - PracticePage 是否继续沿用传入 ID。
不要一开始就怀疑 ArkUI 状态刷新。先沿 ID 链逐层核对,通常能在很小范围内找到错配。
十七、薄入口的性能成本很低
SichuanBankPage 本身不复制数据,也不创建额外服务,只实例化共享组件并传递字符串属性。运行时主要成本仍来自详情页的图片、列表、进度计算和滚动布局。
性能优化应落在共享层:
- 题库查询使用稳定索引。
- 章节进度按 ID 快速查找。
- 封面提供适合展示尺寸的资源。
- 状态变化时避免反复遍历大量历史。
- 宽屏两栏不重复构建同一内容。
不要为了优化十几行入口而引入全局缓存;那不会解决真正的渲染成本。
十八、上架材料中的真实能力边界
根据当前源码,可以准确描述:
- 四川话拥有独立可达的题库入口。
- 入口复用统一详情组件。
- 地区身份固定为
b_sichuan。 - 页面展示四川话简介、文化提示、重点、语境和章节。
- 学习进度来自本地记录。
- 支持章节、随机和考试三种练习入口。
- 共享详情适配 phone、tablet、2in1。
不能因为有独立页面就声称四川话分库拥有独立网络服务、独立账号或独立云题库。当前差异来自包内目录、题目和文化档案。
十九、可复用的地区入口模板
新增一个地区时,最小步骤是:
text
1. 在题库目录定义唯一 Bank ID
2. 添加题目和章节并同步真实数量
3. 增加文化 Profile
4. 创建薄入口并注入固定 ID
5. 注册 main_pages.json
6. 在导航映射中增加路径
7. 验证详情、进度和三种练习模式
8. 完成 phone、tablet、2in1 适配回归
每一步都围绕同一 ID。只要 ID 在目录、Profile、入口、路由、持久化和练习页保持一致,地区扩展就不会演变成六套独立业务。
二十、总结
四川话分库最值得复用的不是一段复杂算法,而是一条清晰边界:入口固定身份,共享组件承担详情业务,目录和文化档案提供差异,练习页只依赖显式参数。SichuanBankPage 虽然只有十几行,却把地区身份稳定地接入了完整学习链路。
对于 HarmonyOS 5.0 以上的 ArkUI 工程,薄入口模式尤其适合"结构相同、内容不同"的模块。它减少重复,保留独立路由,又让多设备适配、底部安全区、进度展示和异常空态可以集中修复。真正需要严格维护的不是页面代码量,而是 b_sichuan 这条参数链在所有层级中始终一致。
AI 辅助声明: 本文由 AI 辅助整理,所有结论基于中国方言题库当前 SichuanBankPage、共享详情组件、题库目录、路由清单和练习参数真实源码复核;未将薄入口描述为独立业务或云端能力。