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

乒乓球口袋教练 HarmonyOS 学习应用(01):动作课程模型与专项内容组织

一、专项内容为何先落在课程模型

乒乓球课程不是只靠标题排列的海报集合。发球、接发、相持和战术训练既需要面向用户的标题,也需要稳定的 id、模块归属、子分类和顺序。把这些字段收敛到课程模型,分类页、搜索页和详情页才会对同一门课给出相同结论。

export interface Course { id: string; moduleId: string; subCategory: string; subCategoryLabel: string; title: string; desc: string; thumbKey: string; videoSrc?: string; durationSec: number; order: number; intro: Block[]; keyPoints: Block[]; mistakes: Block[]; training: Block[]; keywords: string[]; externalVideoRefs?: ExternalVideoRef[]; } export interface SubCategory { id: string; label: string; } export class ExternalVideoRef { platform: string = ''; collectionTitle: string = ''; sectionTitle: string = ''; bvid: string = ''; cid: number = 0; page: number = 0; part: string = ''; url: string = ''; durationSec: number = 0; }

二、模块与子分类如何限制列表范围

coursesByModule 先按 moduleId 过滤,再按 order 排序;分类页在此基础上叠加 subCategoryId。这样“全部”与某一专项不是两份手写列表,新增课程只需补齐模型字段。页面不应通过标题包含某个词来猜测它属于发球还是接发。

事实唯一来源页面职责
正常结果模型或服务的回读渲染可观察状态
边界结果明确的缺项或失败分支说明原因并保留入口
重进结果持久化或模型再次解析不复用旧页面变量
} export function coursesByModule(moduleId: string): Course[] { return COURSES.filter(c => c.moduleId === moduleId).sort((a, b) => a.order - b.order); } export function findCourse(id: string): Course | undefined { return COURSES.find(c => c.id === id); } export function nextCourse(id: string): Course | undefined { const cur: Course | undefined = findCourse(id); if (cur === undefined) return undefined; const list: Course[] = coursesByModule(cur.moduleId); const idx: number = list.findIndex(c => c.id === id); return idx >= 0 && idx < list.length - 1 ? list[idx + 1] : undefined; } export function prevCourse(id: string): Course | undefined { const cur: Course | undefined = findCourse(id); if (cur === undefined) return undefined; const list: Course[] = coursesByModule(cur.moduleId); const idx: number = list.findIndex(c => c.id === id); return idx > 0 ? list[idx - 1] : undefined; }

三、详情页怎样避免标题和课程事实脱节

详情页从路由参数取得 courseId 后重新查找课程,并据此构造标题、章节和视频入口。这个做法让路由只携带稳定标识;页面重建时能重取最新课程,而不是复用上一张卡片遗留的文案或图片。

.backgroundColor(this.palette.bg); } private filtered(): Course[] { const all: Course[] = coursesByModule(this.moduleId); if (this.subCategoryId === 'all') return all; return all.filter(c => c.subCategory === this.subCategoryId); } }

四、数据缺项时应该停止在哪里

模型缺项要显式处理。未知 moduleId、找不到 courseId 或没有子分类时,页面应返回空状态或禁用无效入口,而不是用列表第一项作为默认课程。默认回退会让用户进入错误专项,也会让后续学习记录归属错误。

风险不能采用的做法当前链路的处理
数据不存在用默认对象继续渲染停在可解释的空或失败状态
页面重进复用上一次内存字段重新查询模型或服务
重复动作追加同一业务事实通过稳定 id 或进入标记收敛
import { AppSizes, AppText, ColorPalette, LightPalette, CategoryTabs, TabItem, BlockRenderer, PlaceholderImage, AppToast, TopBar, Routes, DetailParams, PlayerParams, Course, Block, findCourse, nextCourse, prevCourse, coursesByModule, FavoriteService, ProgressService, formatDuration, courseCover, courseVideoSrc, CourseVideoService } from 'library1'; const TAB_INTRO: string = 'intro'; const TAB_KEY: string = 'key'; const TAB_MISTAKE: string = 'mistake'; const TAB_TRAIN: string = 'train'; const TXT_DETAIL: string = '\u8bfe\u7a0b\u8be6\u60c5'; const TXT_NOT_FOUND: string = '\u8bfe\u7a0b\u4e0d\u5b58\u5728'; const TXT_SHARE: string = '\u5206\u4eab'; const TXT_SAVED: string = '\u5df2\u6536\u85cf'; const TXT_SAVE: string = '\u6536\u85cf'; const TXT_COPY_OK: string = '\u5df2\u590d\u5236\u5230\u526a\u8d34\u677f'; const TXT_SAVE_OK: string = '\u5df2\u52a0\u5165\u6536\u85cf'; const TXT_UNSAVE_OK: string = '\u5df2\u53d6\u6d88\u6536\u85cf'; const TXT_PLAY_LOCAL: string = '\u70b9\u51fb\u64ad\u653e'; const TXT_NO_VIDEO_PLACEHOLDER: string = '\u672c\u8bfe\u7a0b\u4ee5\u56fe\u6587\u5b66\u4e60\u4e3a\u4e3b\uff0c\u6682\u65e0\u914d\u5957\u89c6\u9891'; const TXT_DOWNLOAD_LOCAL: string = '\u4e0b\u8f7d\u5230\u672c\u5730'; const TXT_DOWNLOADED_LOCAL: string = '\u5df2\u4e0b\u8f7d\u5230\u672c\u5730'; const TXT_DOWNLOADING_LOCAL: string = '\u6b63\u5728\u4fdd\u5b58'; const TXT_DOWNLOAD_OK: string = '\u5df2\u4fdd\u5b58\u5230\u672c\u5730\u5e76\u540c\u6b65\u5230\u76f8\u518c'; const TXT_DOWNLOAD_LOCAL_OK: string = '\u5df2\u4fdd\u5b58\u5230\u672c\u5730';

五、从首页到详情的验收路径

验收从首页选择一个模块开始,依次切换子分类、打开一门课程并返回列表。需要观察模块标题、课程数量、选中分类与详情标题是否都来自同一 id;再用一个不存在的 id 验证页面不会展示无关课程。

验收阶段操作应观察到的结果
建立事实完成一次与主题直接对应的动作服务或模型能回读该事实
页面回读进入目标页或结果页标题、数据和状态相互一致
重进验证返回、重启或切换模块后再次进入结果不依赖上一页残留

六、取舍与后续维护

当前实现把业务事实集中到模型与服务,页面只负责呈现和触发操作。这样做会多出类型、查询和回读代码,却能避免同一份统计或记录在多个页面分叉。需要扩展新课程、新资源或新题目时,应先补齐稳定标识与服务合同,再补页面入口。

ArkTS 的响应式状态机制可参考 HarmonyOS ArkTS 状态管理文档。本文的代码和验收围绕同一个原则:可见结果必须能回到对应模型、服务或持久化事实,不以邻近页面和控件文字代替主题结果。

七、围绕事实来源继续演进

乒乓球口袋教练 HarmonyOS 学习应用(01):动作课程模型与专项内容组织 关注的不是给页面再增加一层显示逻辑,而是让“专项内容为何先落在课程模型”始终能回到唯一的业务事实。模型字段、服务返回值和页面展示应当保持单向关系:事实先发生,页面随后回读;页面不保存第二份能够被误认为真实数据的临时副本。

处理“模块与子分类如何限制列表范围”时,新增条件必须先归属到课程模型、资源服务、日志事实或测验结果之一。把条件散落在多个组件里,短期看起来省事,后续就会出现入口不同、重进不同、统计不同的问题。把规则放回稳定入口,页面只根据结果改变可见状态,能让同一结论在不同入口被复查。

“详情页怎样避免标题和课程事实脱节”说明当前实现选择了页面重建后重新查询,而不是把对象和统计快照长期塞进路由参数。读取会增加少量成本,却避免课程改名、资源替换、日志编辑或题库切换后继续显示旧内容。学习应用的结果需要能解释,不能只追求一次渲染恰好看起来正确。

异常分支也应保留语义:模型缺项要显式处理。未知 moduleId、找不到 courseId 或没有子分类时,页面应返回空状态或禁用无效入口,而不是用列表第一项作为默认课程。默认回退会让用户进入错误专项,也会让后续学习记录归属错误。 这要求页面区分数据不存在、动作未完成和资源不可用,并提供返回选择、补充输入、重新下载或再次答题等对应操作;不能把不同原因压缩成同一条模糊的成功或失败提示。

最后执行“从首页到详情的验收路径”时,至少观察一次主题动作、一次目标页回读和一次重进或切换后的回读。三次观察都指向相同模型或服务结果,才说明实现链路没有依赖旧页面残留。 最后执行“从首页到详情的验收路径”时,至少观察一次主题动作、一次目标页回读和一次重进或切换后的回读。三次观察都指向相同模型或服务结果,才说明实现链路没有依赖旧页面残留。

面向维护的检查可以沿三个问题展开:输入对象的稳定标识是什么,哪个服务或模型决定结果,页面如何把该结果显示出来。用模块、子分类、课程顺序与课程 id 组织发球、接发、相持和战术内容,让首页、分类页与详情页共享同一份课程事实。 这三个问题分别对应数据合同、调用边界和可见反馈;只要其中一层被绕过,新增入口就可能产生和原入口不同的事实。

对失败路径也要保留同样的追踪能力。先让服务返回清晰的不可用、缺项或未完成状态,再由页面提供与原因匹配的操作。这样日志、课程、资源和题目发生变化时,回归测试能够看到具体的分歧点,而不是只能面对一张没有信息的空白页面。

这种组织不会承诺所有后续需求都零成本。新增字段需要同步更新模型、服务与验收;但它把变更范围限制在可枚举的位置,避免把同一判断复制到首页、详情、个人页和结果页后再分别修补。

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

相关文章:

  • WebAssembly for Proxies (Go SDK)核心功能解析:打造高性能代理扩展
  • 终极音乐解锁指南:3分钟掌握浏览器端无损解密技术
  • CD19靶向抗体药物偶联物能否改善复发难治性弥漫大B细胞淋巴瘤的临床结局?
  • 北京离婚谈判律师推荐:希望协商解决财产与子女问题怎么选律师 - 品牌深度评测
  • 深度解析dupeGuru:Python驱动的智能重复文件检测架构设计
  • 【单片机毕业设计】 基于嵌入式单片机的粉尘超标声光报警系统设计,基于 STM32 的自动 / 手动双模式环境通风控制器实现(010301)
  • 2026年小区健身步道施工:专业服务公司选择与行业解析 - 优企名品
  • 6000万美元合作两年后,“美国贴吧”Reddit为何后悔向谷歌提供内容训练AI?
  • 2026年谷歌推广服务商深度测评:技术驱动下的海外获客新范式 - 品牌前沿专家
  • Yuzu模拟器终极优化指南:三步解决卡顿闪退问题
  • 戴尔笔记本风扇控制新方案:如何通过开源工具精准管理散热与噪音
  • 终极免费指南:Firefox浏览器快速下载Sketchfab完整3D模型
  • 【爱马仕】Hermes AI 智能工具快速搭建教程,Windows 整合包一键落地
  • 2026年甄选:江苏地区具备综合实力的潜水搅拌机供应企业深度洞察 - 企业推荐官【官方】
  • 仓库里堆的不是货,是真金白银 有色金属仓储:一个容错率为零的战场
  • 【单片机毕业设计】基于 STM32 的流量数据 OLED 显示与阈值控制系统,基于嵌入式开发的流量手动 / 自动双模控制装置设计(010401)
  • 如何快速精通猫抓浏览器扩展:媒体资源嗅探与智能下载的完整实用指南
  • 如何让经典Windows 7重获新生:终极Windows 7 SP2完整更新包使用指南
  • 小红书数据采集终极指南:5个简单步骤掌握Python爬虫技术
  • 终极指南:如何在macOS上运行Windows程序?Whisky让跨平台工作变得简单
  • 自运维 + 自答疑:AI 时代软件的自我修养
  • Altium Designer 2024 PCB高级功能:BGA封装的制作
  • 大模型提示词效能跃迁指南(多角色协同推理技术白皮书)
  • DyberPet:零代码创建个性化桌面宠物的终极解决方案
  • 2026年 江苏QJB潜水搅拌机厂家推荐:高效混合/节能环保/坚固耐用的行业优选 - 企业推荐官【官方】
  • QQ空间数据备份终极指南:如何一键导出你的青春记忆
  • FireSharp认证管理:用户创建、密码重置与权限控制最佳实践
  • Meep FDTD电磁仿真软件完全指南:从基础理论到光子学应用
  • 杰理蓝牙读取txt文件更新蓝牙名字
  • 留学生技术面被问 API 限流算法?用令牌桶与漏桶实现毫秒级防护「蒸汽求职分享」