当前位置: 首页 > news >正文

足球口袋教练 HarmonyOS 规划(22):训练目录 Service 与本地课程模型边界

一、先定义目录规划要解决的冲突

训练目录同时面对三种变化:课程条目会持续增加,首页只需要少量摘要,详情页却需要步骤、器材和场地等完整信息。如果把数组、筛选条件、收藏标记和详情跳转全部放在一个页面里,新增分类会迫使多个视图一起改动,数据异常也只能退化成空白列表。

这份规划把目录能力限定为“本地课程读模型”:负责提供稳定的分类、列表和详情查询,不包含云端内容管理、付费课程、账号同步与训练计时。页面只消费查询结果,不直接遍历原始常量。这样的边界既能承接现有本地课程,也为以后增加远端数据源保留替换点。

使用场景Service 输入页面得到的结果不在本期范围
首页推荐日期、画像摘要一条可进入的课程摘要自动生成完整训练计划
分类浏览模块标识、关键词排序后的课程卡片云端搜索与推荐算法
课程详情课程标识完整步骤与安全提示视频下载与版权管理
计划回看计划中的课程标识可恢复的课程摘要多账号跨端同步

二、用读模型隔离页面和原始数据

目录模型需要区分“可存储的领域数据”和“直接展示的派生字段”。颜色、选中态、是否为今日任务都属于页面派生结果,不应混入课程主数据;课程时长、难度、步骤和所属模块则需要稳定标识,才能被计划与训练记录引用。

export interface TrainingCourse { id: string moduleId: string title: string subtitle: string durationMinutes: number difficulty: '入门' | '进阶' | '挑战' focus: string steps: string[] scenario?: string equipment?: string venue?: string version: number } export interface CourseSummary { id: string moduleId: string title: string durationMinutes: number difficulty: string matchedKeyword: boolean }

稳定的id是计划、收藏和记录之间的连接点。标题可以修改,模块可以重新排序,但历史记录不能因为文案变化而失去关联。version用于本地目录升级:旧计划引用已下架课程时,可以按版本映射到替代课程,而不是直接删除用户的计划项。

三、Service 契约只暴露业务查询

页面不应该知道课程来自常量、JSON 资源还是本地数据库。Service 对外只提供查询语义,并统一处理空关键词、未知模块和失效课程标识。接口返回只读数组或新对象,避免页面修改共享数据后影响其他页面。

export interface CatalogQuery { moduleId?: string keyword?: string limit?: number } export interface TrainingCatalogService { listModules(): Promise<ReadonlyArray<TrainingModule>> queryCourses(query: CatalogQuery): Promise<ReadonlyArray<CourseSummary>> getCourse(courseId: string): Promise<TrainingCourse | null> resolveCourse(courseId: string): Promise<TrainingCourse> } export class CourseNotFoundError extends Error { constructor(public courseId: string) { super(`Course unavailable: ${courseId}`) } }

getCourse()允许详情页展示“课程已不可用”的温和状态;resolveCourse()则适合训练启动前的强校验,找不到课程就中止进入计时页。两个入口看似重复,却能避免所有调用方用同一种异常策略。

四、本地仓库负责校验、索引与版本迁移

本地仓库在启动时建立courseId -> course索引,同时验证重复标识、空步骤和非法时长。校验失败时保留上一版有效快照,并把错误转换成可记录的诊断结果。页面不会看到半份目录,也不会因为一条坏数据导致全部课程不可用。

export class LocalCourseRepository { private courses: Map<string, TrainingCourse> = new Map() load(items: TrainingCourse[]): CatalogLoadResult { const next = new Map<string, TrainingCourse>() const errors: string[] = [] for (const item of items) { if (!item.id || next.has(item.id)) { errors.push(`duplicate id: ${item.id}`) continue } if (item.durationMinutes < 5 || item.steps.length === 0) { errors.push(`invalid course: ${item.id}`) continue } next.set(item.id, { ...item, steps: item.steps.slice() }) } if (next.size > 0) { this.courses = next } return { accepted: next.size, errors } } }

版本迁移不应在页面渲染时临时判断。计划仓库读取旧任务后先执行标识映射,再交给目录 Service 解析。映射失败的任务保留标题与时长,并标记为“待替换”,这样用户仍能理解原计划发生了什么。

五、页面状态只保留一次查询的生命周期

目录页需要明确区分首次加载、已有数据刷新、空结果和加载失败。把它们压缩成一个items.length === 0会混淆“没有匹配课程”和“数据源不可用”。ViewModel 保存查询条件与请求序号,较早请求返回时不能覆盖较新的关键词结果。

export type CatalogPhase = 'idle' | 'loading' | 'ready' | 'empty' | 'error' export class CatalogViewModel { phase: CatalogPhase = 'idle' items: CourseSummary[] = [] message: string = '' private requestToken: number = 0 async search(service: TrainingCatalogService, query: CatalogQuery): Promise<void> { const token = ++this.requestToken this.phase = 'loading' try { const result = await service.queryCourses(query) if (token !== this.requestToken) return this.items = result.slice() this.phase = result.length === 0 ? 'empty' : 'ready' } catch (error) { if (token !== this.requestToken) return this.phase = 'error' this.message = '目录暂不可用,请稍后重试' } } }

ArkUI 组件绑定phaseitems和查询条件,点击课程后只传递courseId。详情页重新调用 Service 获取完整模型,避免把一个可能过期的大对象塞进路由参数。状态管理的基础约束可参考华为开发者文档。

六、失败和降级路径要先于远端扩展

目录设计至少覆盖四类失败:资源文件损坏、课程标识重复、计划引用失效、查询期间页面退出。前三类由仓库与 Service 转换为稳定结果,最后一类由请求令牌或页面生命周期取消处理。任何错误都不应触发删除收藏、重置计划或清空训练记录。

失败条件默认行为用户可见反馈恢复动作
新目录校验失败继续使用上一版快照顶部提示内容更新失败修复资源后重新加载
未知模块返回空结果展示“暂无此分类”回到全部分类
课程标识失效保留计划占位项标记课程待替换选择同模块替代课
查询被新输入覆盖丢弃旧响应不显示额外错误展示最新关键词结果
页面退出停止写入页面状态无提示重新进入后新建查询

七、实施顺序从抽模型开始

第一步把课程与模块接口移出页面,保持原有数据和 UI 不变;第二步建立本地仓库与索引校验;第三步让列表、详情、今日推荐逐一改用 Service;第四步加入失效标识和版本迁移;最后才考虑 JSON 资源或远端数据源。每一步都必须保持旧入口可用,不能一次性同时改数据、路由和视觉。

迁移期间可以保留兼容适配器,把旧数组包装成TrainingCatalogService。当列表、详情和计划三个消费者都不再直接读取旧数组后,再删除适配器。这样回归范围清楚,出现问题也能回滚到上一个边界。

八、验收条件与后续证据计划

静态验收包括:所有课程标识唯一、时长和步骤合法、Service 不返回共享可变数组、页面不再直接读取原始目录。交互验收包括:切换模块、连续输入关键词、从计划进入详情、打开失效课程、返回后恢复筛选条件。异常验收需要注入损坏资源、重复标识和空目录。

后续证据应包含四组材料:构建通过记录;目录页、空结果页和失效课程页的真机截图;连续搜索时旧请求不覆盖新结果的日志;升级前后计划标识迁移对照。只有这些证据齐全后,才适合把该主题改写为实现或实战文章。

九、总结

训练目录模块化的关键不是新增一个名为 Service 的文件,而是让课程标识、查询契约、错误策略和页面状态各有明确归属。以本地读模型为起点,可以在不承诺云端能力的前提下先解决可维护性,并让未来的数据源替换、版本迁移与异常验收都有稳定接口可依赖。

http://www.jsqmd.com/news/1258079/

相关文章:

  • 茂名本地防水补漏精选TOP5推荐:正规漏水检测维修公司上门师傅推荐:厕所/棚顶/屋面/飘窗/阳台/地下室/厨房渗漏水精准测漏维修(2026最新) - 即刻修防水
  • HarmonyOS NEXT ArkUI Row 布局完全指南:水平布局原理、属性详解与实战案例
  • 技术合规的警示:PyWxDump项目被律师函叫停的完整解析
  • 工业视觉检测中二维码可读性优化方案
  • AI编程时代:从焦虑到务实,程序员如何用AI提升效率而非被替代
  • OpenCV与YOLO实战:从零搭建机器人视觉感知系统
  • Google Flow免费额度政策解析:每日50次调用与8月31日截止
  • 2026年家用电蒸锅哪个牌子好?一文教你挑出心仪的品牌! - 品牌排行榜
  • 推理时引导技术:提升LLM跨语言事实一致性的原理与实践
  • AI日报周报自动化不是工具问题,而是流程熵增问题(附Gartner认证的RPA-AI协同框架)
  • 2026年优选:朝阳街周边诚信可靠的少儿民族舞实力学校解析 - 装修教育财税推荐2026
  • Linux系统思维:从命令熟练到问题解决的关键跃迁
  • 【AI大模型应用开发】【项目实战】14.RAG智慧问答项目-(二)项目工具之OllamaLangChainMilvus向量数据库
  • 从OpenAI基建实践到实战:打造AI研发效率倍增的自动化实验平台
  • 大模型推理优化:量化、注意力与动态批处理实战
  • 2026年新疆乌鲁木齐的代理记账公司大盘点 - 品牌排行榜
  • 基于WebLLM的本地大语言模型浏览器扩展开发实践
  • 课堂人脸分析系统实战:从场景定义到工程落地的完整指南
  • Open WebUI 部署指南:为本地大模型打造 ChatGPT 级 Web 界面
  • 2026年短剧广告植入哪家公司做得好?看完就明白 - 品牌排行榜
  • AI行业两极分化:从云依赖到自主可控的技术转型策略
  • 3D涂装进阶:从贴图到独立模型的结构重塑与实战
  • Beyond Compare 5 授权失效问题:深度分析软件授权验证机制与3种实用解决方案
  • 《Java纪录片》:讲述Java三十年的兴衰与重生
  • 2026年广州水泥沙包配送厂家哪家强? - 品牌排行榜
  • DDPG算法优化永磁同步电机位置控制研究
  • TensorRT-LLM大模型推理加速实战指南
  • Mistral Connectors:企业AI应用集成开发的安全可控新范式
  • Gemini与Flash技术结合:快速构建自定义AI工具开发指南
  • 计算机毕业设计之基于springboot的理财管理系统设计与实现