足球口袋教练 HarmonyOS 设计(21):训练大页面拆分的模块边界规划
一、先把这篇文章的定位说清楚本文是一份尚待落地的模块化设计方案讨论对象是足球口袋教练中的训练大页面。文中的接口、类型与伪代码用于约束后续实现不代表这些拆分已经进入当前版本。这样定位的原因很直接当训练入口、动作详情、计时、计划和记录都由一个页面同时管理时任何一次小改动都会碰到共享状态、生命周期和持久化问题但在真实拆分完成、构建通过并获得运行证据之前不能把目标架构描述成既成事实。规划要解决的是“边界如何定”而不是简单追求文件数量。拆成五个页面文件却继续共享十几个可变字段仍然只是把复杂度搬家。真正有效的拆分需要回答四个问题领域模型由谁定义计时与记录由谁保存跨页面状态由谁持有页面退出后哪些任务必须继续。HarmonyOS 的状态管理与 MVVM 思路可以参阅华为开发者文档。本文只引用这一份平台资料重点放在训练业务自己的契约上。二、先冻结范围再讨论目录本轮规划包含训练目录、动作详情、训练计时、今日计划和训练记录五个视图。它们共享动作编号、训练时长和完成结果却有不同的生命周期。训练目录适合短生命周期查询计时任务可能跨越页面切换训练记录必须能够持久化统计视图只消费稳定结果不应直接控制计时器。暂不纳入本轮的内容包括云端账号同步、跨设备接续、语音播报和手表协同。这些能力都需要额外的平台配置与运行证据不能因为目录里预留了接口就宣称已经支持。当前规划只保证边界能够容纳未来适配不提前承诺具体能力。模块本轮职责明确不负责TrainingHome分类、筛选、进入动作详情计时器生命周期与记录写入TrainingDetail展示动作步骤并发起训练直接修改统计数据TrainingActive呈现计时状态与操作命令自行决定持久化格式TrainingPlan编排今日任务与完成顺序维护动作素材原始数据TrainingStats聚合已完成记录并展示趋势控制训练流程边界确定后目录只是边界的物理表达。页面依赖状态层状态层依赖服务接口服务层依赖模型与存储抽象反向依赖则通过命令和结果对象返回避免视图直接读写全局变量。三、领域模型先于页面组件第一步应当把训练动作、计划任务和训练记录定义成稳定模型。模型不引用 ArkUI 组件也不携带弹窗、颜色或路由对象。这样才能在单元测试、后台任务和不同设备页面中复用同一份业务语义。下面是设计阶段的类型草案。字段命名刻意区分“计划时长”“实际用时”和“完成时间”避免一个duration在不同页面表示不同含义。export type TrainingLevel beginner | intermediate | advanced export interface TrainingItem { id: string title: string categoryId: string level: TrainingLevel plannedSeconds: number stepIds: string[] enabled: boolean } export interface PlanTask { taskId: string trainingId: string order: number targetSets: number completedSets: number planDate: string } export interface TrainingRecord { recordId: string trainingId: string taskId?: string startedAt: number finishedAt: number elapsedSeconds: number completedSets: number finishReason: completed | cancelled | interrupted }模型还需要满足两个约束。其一持久化字段必须可序列化不把计时器句柄或 UI 上下文塞入记录其二业务标识不能用数组下标代替否则排序、筛选之后会把记录关联到错误动作。四、服务层以接口隔离变化训练数据的来源、计时方式和统计口径变化速度不同因此不适合放进一个万能服务。规划中拆成TrainingCatalogService、TrainingTimerService与TrainingReportService三组接口目录服务只负责查询计时服务维护会话报告服务保存记录并生成聚合结果。export interface TrainingCatalogService { list(categoryId?: string): PromiseTrainingItem[] getById(trainingId: string): PromiseTrainingItem | undefined } export interface TrainingTimerSnapshot { sessionId: string trainingId: string phase: idle | running | paused | finished startedAt?: number elapsedSeconds: number } export interface TrainingTimerService { start(trainingId: string): PromiseTrainingTimerSnapshot pause(sessionId: string): PromiseTrainingTimerSnapshot resume(sessionId: string): PromiseTrainingTimerSnapshot finish(sessionId: string): PromiseTrainingTimerSnapshot restore(): PromiseTrainingTimerSnapshot | undefined } export interface TrainingReportService { save(record: TrainingRecord): Promisevoid listByDate(from: string, to: string): PromiseTrainingRecord[] summarize(records: TrainingRecord[]): TrainingSummary }接口拆开以后本地静态训练目录可以先落地计时服务随后替换为可恢复实现统计页面也可以用固定记录验证聚合逻辑。任何一层尚未完成时都能通过明确的空结果或不可用状态降级而不是让页面猜测底层发生了什么。五、状态层只暴露视图真正需要的状态状态层的核心不是把所有字段集中到一个类而是把业务状态变成有限状态集合。规划中的TrainingViewModel负责把目录查询、计时快照和记录保存转换为页面可消费的状态同时提供唯一命令入口。export interface TrainingUiState { loading: boolean items: TrainingItem[] selected?: TrainingItem timer?: TrainingTimerSnapshot planTasks: PlanTask[] message?: string recoverable: boolean } export class TrainingViewModel { Trace state: TrainingUiState { loading: false, items: [], planTasks: [], recoverable: true } constructor( private catalog: TrainingCatalogService, private timer: TrainingTimerService, private report: TrainingReportService ) {} async load(categoryId?: string): Promisevoid { this.patch({ loading: true, message: undefined }) try { const items await this.catalog.list(categoryId) this.patch({ items, loading: false }) } catch (error) { this.patch({ loading: false, message: 训练内容暂时不可用 }) } } select(item: TrainingItem): void { this.patch({ selected: item, message: undefined }) } private patch(next: PartialTrainingUiState): void { this.state { ...this.state, ...next } } }页面不直接持有 Repository也不自行拼装错误文案。视图只观察loading、items、timer、message等稳定字段并通过load、select、start、pause、finish命令驱动变化。这样才能明确判断一次点击究竟触发了哪条业务路径。六、视图层的拆分与编排方式五个视图组件不应互相调用内部方法。顶层训练路由负责选择当前场景组件接收只读状态与回调。动作详情只发送“开始训练”意图活动训练页只发送计时命令完成后由状态层生成记录并决定去向。Component export struct TrainingScene { Param state: TrainingUiState Event onSelect: (item: TrainingItem) void Event onStart: (trainingId: string) void Event onPause: () void Event onFinish: () void build() { Column() { if (this.state.timer?.phase running) { TrainingActiveView({ snapshot: this.state.timer, onPause: this.onPause, onFinish: this.onFinish }) } else if (this.state.selected) { TrainingDetailView({ item: this.state.selected, onStart: this.onStart }) } else { TrainingHomeView({ items: this.state.items, loading: this.state.loading, onSelect: this.onSelect }) } } } }状态归属建议持有者离开页面后的处理筛选词、折叠项当前视图可以丢弃或按体验需要保存已选动作TrainingViewModel返回训练入口时可恢复计时会话TrainingTimerService页面离开后仍能查询快照今日计划PlanRepository重启后按日期恢复完成记录ReportRepository写入成功后才参与统计这套编排不会自动带来后台计时能力。后续若要支持切到后台仍准确计时应以开始时间和暂停区间计算时长并补充后台、锁屏、进程回收后的运行验证不能只依赖页面上的每秒回调。七、迁移顺序要让每一步都可回退拆分不适合一次性改完。建议先记录现有训练入口的可见行为再按“模型、纯服务、状态层、叶子视图、顶层编排”的顺序迁移。每一步都保留清晰的输入输出出现回归时可以定位到最近一层。1. 冻结动作列表、详情、开始、暂停、结束和记录查询的行为清单。 2. 提取领域类型并为旧数据增加显式转换函数。 3. 建立目录、计时、报告接口先用内存实现跑通契约测试。 4. 引入状态层让旧页面逐步改为消费统一状态。 5. 先拆无副作用的详情与统计视图再拆活动训练页。 6. 顶层页面最终只保留路由选择、组件装配和生命周期转发。 7. 替换正式持久化实现完成迁移、异常与恢复测试。迁移期间可以设置兼容适配器把旧页面已有数据转换成新模型。适配器是过渡边界不应长期承载新业务。等新旧行为对照通过后再删除旧字段和重复分支。八、失败、降级与验收证据设计方案只有覆盖失败路径才具备实施价值。目录读取失败时显示可重试空态素材缺失时仍展示文字步骤记录保存失败时保留待写入记录并提示用户路由恢复找不到动作时回到训练入口计时会话无法恢复时明确结束原会话不能静默生成一条完整记录。失败场景降级动作后续验收证据训练目录为空或解析失败空态加重试不渲染无效详情错误注入测试与页面截图计时中切到后台以时间戳重算不依赖 UI 回调次数前后台切换录屏与时长对照记录写入失败保留待写入对象统计不提前增加存储失败日志与重试结果路由参数失效返回训练入口并提示内容已变化无效参数自动化用例动作图片缺失使用占位图并保留文字步骤缺图构建包运行截图实施完成的验收条件也要事先约定页面文件不再保存计时器句柄计时服务能恢复运行、暂停和完成快照记录只有持久化成功后才进入统计五个视图能独立预览或测试拆分前后的核心操作行为一致构建通过并获得与能力标题直接对应的运行证据。在这些证据齐备前本文仍保持设计方案定位。后续实现文章应引用真实类型、构建结果和运行画面清楚区分“已完成”“正在迁移”和“待验证”。九、总结训练大页面拆分的关键不是把代码分散到更多文件而是建立单向、可验证的依赖模型定义业务语义服务封装变化状态层统一命令视图只负责呈现和交互。计时、计划与记录拥有不同生命周期必须分别确定状态归属和恢复策略。这份规划给出了后续开发可以逐项执行的边界、接口、迁移顺序、失败处理与验收条件。等模型、服务和组件真实落地并通过构建与运行验证后再把设计稿升级为实战复盘才能让标题、正文和工程事实始终一致。

相关新闻

为什么ChatBI是CEO押注的战略优先:三个价值层的组织重估

为什么ChatBI是CEO押注的战略优先:三个价值层的组织重估

导语 今天我想聊一个反直觉的结论:绝大多数企业都把ChatBI归为「AI技术在BI领域的工具升级」,仅此而已,但很少有CEO会将其定义为影响组织整体效率的战略级选择。这恰恰是当前很多企业数字化转型陷入瓶颈的核心原因。 我们都能感知到这个行业矛…

2026/10/22 16:04:00 阅读更多 →
校园二手交易平台设计与实现

校园二手交易平台设计与实现

摘 要 在高校校园内,学生们的学习与生活需求不断变化,校园二手交易平台的诞生恰逢其时。一方面,学生常面临学业更迭,像毕业、升学等,会产生大量闲置物品亟待处理。另一方面,大家消费观念日趋理性&#x…

2026/10/22 16:03:56 阅读更多 →
2026年GEO优化全解析:生成式引擎优化如何重构企业流量与品牌护城河

2026年GEO优化全解析:生成式引擎优化如何重构企业流量与品牌护城河

搜索范式转移,GEO成企业必选项用户行为正在从“Search(搜索)”向“Ask(提问)”迁移。据相关数据显示,超60%的用户开始习惯通过AI对话获取决策信息。在这一背景下,GEO(Generative Eng…

2026/10/21 10:23:04 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →