HarmonyOS ArkTS API 24+ 实战:从机台卡片进入详情——稳定 ID、回调与页面状态
机台列表做出来以后最容易出现的下一步需求就是“点哪台就看哪台”。表面上只是给按钮加一个点击事件实际却要先回答一个边界问题卡片应该把整条机台数据交给页面容器还是只交给一个稳定的标识本篇使用注塑工程师助手中的机台档案模块梳理从MachineCard到MachineDetail的完整链路。它承接上一节的列表筛选但不重复搜索和状态条件重点放在machine.id、回调函数、页面容器状态以及找不到对象时的降级界面。一、列表页面不该自己决定详情页长什么样机台列表位于entry/src/main/ets/features/machines/MachineArchive.ets。其中的MachineCard负责把编号、名称、状态、锁模力和位置组织成一张可扫读的卡片也提供“查看详情”入口。如果卡片在点击后直接拼装详情页列表组件就要知道详情页的布局、返回动作和后续跳转。卡片职责会从“展示一条机台摘要”膨胀为“管理页面导航”以后在产品页、报表页或异常页复用相同入口时也会重复同一段跳转逻辑。当前实现让卡片只发送一个机台 ID。页面容器决定要不要打开详情、打开哪一类详情以及用户返回后如何回到原来的主页面。这种拆分让列表、路由状态和详情展示各自只有一个主要责任。二、先认识机台模型中的稳定标识机台模型位于entry/src/main/ets/models/Machine.ets。它同时保存展示字段和业务标识exportclassMachine{id:string;code:string;name:string;tonnage:number;location:string;status:MachineStatus;}code是用户在页面上容易识别的设备编号例如IM-120T-11name用来描述设备用途。它们适合展示和搜索但并不适合承担页面之间的唯一关联职责。编号规则可能调整名称也可能修改详情页若只依赖展示文案就会把显示规则和关联规则绑在一起。id的作用不同。它是数据层用于定位单个对象的稳定键。当前演示数据中第一台机台的 ID 是machine-001详情页据此回到 Repository 查询完整对象。页面可以继续显示编号和名称而关联链路不需要依赖这些可见文本。三、卡片只上报 ID不搬运整条对象MachineCard对外暴露的回调参数是一个字符串Componentstruct MachineCard{machine:MachinenewMachine(empty,,,0,,idle);onOpen:(machineId:string)void(){};}按钮点击时卡片把当前对象的id交给回调Button(查看详情).width(100%).height(34).onClick((){this.onOpen(this.machine.id);})这里没有把machine直接保存在详情页也没有让卡片去修改外层页面状态。卡片只说明“用户希望打开 ID 为某值的机台”至于怎样渲染、能否查到数据和返回到哪里都留给拥有页面状态的上层组件处理。这种选择还有一个实用好处卡片里的对象可能来自筛选结果。上一节的关键词和状态条件会改变“当前显示哪些卡片”却不应该改变“详情页根据 ID 查询哪台机台”的规则。传递稳定 ID 能让筛选结果与详情查询保持解耦。四、列表组件把回调继续交给页面容器MachineArchive本身不保存当前详情页状态而是接收onOpenMachineComponentexportstruct MachineArchive{onOpenMachine:(machineId:string)void(){};onOpenProducts:()void(){};}在渲染列表时它把这个回调传给每张卡片ForEach(this.filteredMachines(),(machine:Machine){MachineCard({machine:machine,onOpen:this.onOpenMachine});},(machine:Machine)machine.id)这段代码有两层含义。第一ForEach使用machine.id作为稳定键框架能据此区分不同卡片。第二卡片点击后调用的仍然是外层提供的onOpenMachine列表组件没有额外改写 ID也不会偷换成编号或数组下标。不要把数组下标当成详情参数。筛选、排序或刷新后同一个下标可能对应另一台设备详情页看上去能打开却可能展示错误对象。ID 与下标的区别在列表比较简单时不明显在过滤条件增加后就会变成难以追踪的问题。五、页面容器保存“打开什么”和“打开谁”应用入口页面位于entry/src/main/ets/pages/Index.ets。它维护两个状态detailKind表示正在显示哪类详情detailId表示这类详情要查询的对象。StatedetailKind:DetailKindnone;StatedetailId:string;privateopenDetail(kind:DetailKind,id:string):void{this.detailKindkind;this.detailIdid;}privatecloseDetail():void{this.detailKindnone;this.detailId;}机台列表注入回调时只需指定详情类型为machineMachineArchive({onOpenMachine:(machineId:string)this.openDetail(machine,machineId),onOpenProducts:()this.openDetail(productArchive,)})这就是点击事件进入页面状态的关键一步。按钮发出machine-001openDetail把页面切换到machine类型同时把detailId保存为machine-001。因为这两个字段都属于响应式状态页面会重新计算应该显示的组件。六、详情组件只在对应状态下挂载页面容器根据detailKind选择内容区域if(this.detailKindmachine){MachineDetail({machineId:this.detailId,onBack:()this.closeDetail(),onOpenProduct:(productId:string)this.openDetail(product,productId),onOpenDebug:(recordId:string)this.openDetail(debug,recordId)}).layoutWeight(1)}MachineDetail收到的是 ID不是列表卡片的实例。这样详情页可以独立查询机台数据也能继续把产品 ID、调机记录 ID 等关联对象交回同一个页面容器。页面切换规则集中在Index.ets不同详情页不需要彼此直接依赖。从用户角度看点击“查看详情”后机台列表被详情内容替换底部导航仍留在页面中。运行观察中选择IM-120T-11后详情页显示相同的编号与名称并展示锁模力、位置、状态、关联产品、调机记录和待处理异常。这些内容来自详情页按 ID 再次查询后的对象而不是卡片临时拼接的文本。七、详情页通过 Repository 重新定位对象详情组件位于entry/src/main/ets/features/machines/MachineDetail.ets。它先检查 ID 是否存在再取得当前机台privateexists():boolean{returndemoBusinessRepository.machineById(this.machineId)!undefined;}privatecurrentMachine():Machine{constfound:Machine|undefineddemoBusinessRepository.machineById(this.machineId);returnfoundundefined?newMachine(missing,,,0,,idle):found;}Repository 中的machineById使用同一个稳定键查找machineById(id:string):Machine|undefined{returnthis.machines.find((item:Machine)item.idid);}这条查询链路的好处是明确。列表负责发送 ID页面容器负责保存 ID详情页负责使用 ID 查询。读者沿着machineId搜索就能从点击入口追到具体数据来源而不用在多个组件里猜测对象何时被复制、何时失效。八、找不到对象时不要继续渲染空数据页面状态不一定永远有效。例如列表刷新后演示数据被恢复、未来接入数据同步后对象被删除或者调用方传入了错误 ID。若详情页仍直接访问不存在对象的字段用户通常看到空白内容或运行异常而不是可以理解的提示。当前组件先用exists()分支保护界面if(!this.exists()){Column({space:12}){Text(未找到机台档案)Text(该演示机台可能已被重置请返回机台列表重新选择。)Button(返回机台列表).onClick(()this.onBack())}}else{// 渲染完整机台详情}这是代码可直接推导出的降级路径不存在的 ID 不会进入完整详情渲染而会显示返回入口。当前普通列表只能传入现有机台 ID因此缺失分支需要在受控状态下单独验证不能把它误说成用户已通过正常点击必然看到的界面。九、关联信息也从同一个 ID 出发详情页中的关联产品、调机记录和异常计数都通过当前机台的 ID 查询demoBusinessRepository.productsForMachine(this.currentMachine().id)this.debugRecords()demoBusinessRepository.openExceptionsForMachine(this.currentMachine().id)这说明machine.id不只是“打开详情的参数”。它还是产品适用机台、调机记录、异常记录等关联关系的连接点。详情页不需要从列表卡片携带一份关联数据副本而是围绕同一个 ID 按需读取各类数据。需要注意的是当前 Repository 使用本地脱敏演示数组。这能证明组件间的 ID 传递和查询边界但不能证明已经完成服务端鉴权、远程同步、并发更新或真实产线设备接入。以后替换为网络数据源时仍可保留 ID 回调和缺失态保护同时补充加载、失败、请求竞态和权限处理。十、四个常见错误与排查顺序1. 点击卡片后详情始终是同一台机台先检查按钮是否调用了this.onOpen(this.machine.id)。若误传固定字符串、第一项数组下标或展示编号详情页拿到的就不是当前卡片的稳定 ID。然后检查MachineArchive是否把onOpenMachine原样传给卡片。2. 点击后页面没有变化检查Index.ets中的回调是否调用openDetail(machine, machineId)以及openDetail是否同时写入detailKind和detailId。只保存 ID 而不切换类型页面仍会渲染原来的主内容只切换类型而没有 ID则详情页会进入缺失保护分支。3. 返回后列表状态丢失或显示异常检查返回动作是否统一走closeDetail()。当前实现会把详情类型恢复为none、清空详情 ID并让主内容区域回到所选 Tab。不要让详情组件直接修改列表内部的筛选数组否则返回路径会和列表状态耦合。4. 详情页出现空标题或关联信息不对检查machineById的入参是否仍是模型的id并确认关联查询也使用currentMachine().id。如果在某处把code当作 ID列表看似能正常显示但 Repository 查找和关联数据都会失配。十一、可复核的运行观察可以按下面的顺序检查这条链路进入机台页面确认列表中可见IM-120T-11卡片和“查看详情”按钮。点击该按钮确认页面标题变为IM-120T-11并显示“精密外壳注塑机”。核对基础信息中的锁模力、位置和运行状态是否与该卡片一致。继续核对关联产品、最近调机记录与待处理异常是否出现确认详情页使用的是同一台机台的 ID。点击“返回”确认回到机台列表而不是跳转到其他详情类型。这些步骤验证的是当前本地演示数据下的页面状态链路。它们不等同于真实设备控制、生产数据同步或服务端权限校验。十二、小结从机台卡片进入详情关键不在于把一整个对象塞进点击事件而在于让machine.id穿过清晰的边界卡片上报 ID列表转交回调页面容器保存详情类型与 ID详情页再由 Repository 查询完整对象。这条链路让筛选、列表、详情和关联数据各自保持职责边界也给找不到对象时的降级界面留出了位置。下一篇将继续停留在机台详情模块拆解基础信息、维护状态与关联对象怎样组织成可读的详情页面。附录工程配置与版本说明为了便于复现本文中的代码片段和运行现象这里把当前文章系列对应的工程基线单独列出。本文所说的“当前工程”指e_notebook项目的 HarmonyOS ArkTS 客户端应用名称为“注塑工程师助手”主要用于脱敏演示机台档案、产品档案、调机记录、参数模板、异常闭环、生产批次和看板报表等业务路径。1. 应用与模块配置应用包名com.atan.enotebook。应用版本versionName为1.0.0versionCode为1000000。工程模型ArkTS / ArkUI Stage 模型。主模块entry模块类型为entry。入口 AbilityEntryAbility入口文件为entry/src/main/ets/entryability/EntryAbility.ets。主页面配置模块通过pages: $profile:main_pages读取页面列表。设备类型当前模块声明支持phone、tablet和2in1。安装方式deliveryWithInstall为trueinstallationFree为false属于随应用安装的普通 entry 模块。2. SDK 与 API 版本DevEco Studio 版本DevEco Studio Beta26.0.0.461。编译 SDKHarmonyOS SDK API 26 Beta1SDK 包版本为26.0.0.23。SDK 平台信息apiVersion为26platformVersion为26.0.0releaseType/stage为Beta1。targetSdkVersion26.0.0。compatibleSdkVersion6.1.1(24)。API 口径说明文章系列以 API 24 作为兼容目标进行表述当前工程实际由 API 26 Beta SDK 编译并在 API 24 模拟器上做过安装、启动和交互观察。因此文中的“API 24 运行观察”表示兼容目标环境下的模拟器验证结果不等同于使用 API 24 SDK 重新完成编译验证。3. 构建与运行工具开发工具 IDEDevEco Studio Beta安装目录指向D:/Program Files/Huawei/DevEco Studio Beta。SDK 路径D:/Program Files/Huawei/DevEco Studio Beta/sdk。构建系统Hvigor工程入口hvigorfile.ts使用ohos/hvigor-ohos-plugin的appTasks。Hvigor 执行配置开启 daemon、incremental、parallel 和 typeCheck日志级别为info。构建脚本本地build.ps1优先使用 DevEco Studio 自带的 JBR、Node.js、SDK 与 Hvigor避免系统环境变量中的 Java 或 Node.js 版本干扰构建结果。调试产物未配置签名时本地构建生成entry/build/default/outputs/default/entry-default-unsigned.hap。这类 unsigned HAP 只用于本地调试和模拟器验证正式发布前需要在 DevEco Studio 中补充签名配置。4. 本系列文章的验证边界本系列代码以脱敏演示数据为主Repository、Store、页面状态和组件边界都围绕本地演示闭环展开。已观察过的运行现象以文中对应截图、布局树和人工核对记录为准没有重新核对的页面不在单篇文章中扩大为完整结论。如果读者使用更新的 DevEco Studio、HarmonyOS SDK 或真机系统版本复现API 差异、控件行为和签名流程可能会发生变化。遇到差异时建议优先核对build-profile.json5、module.json5、SDK Manager 中安装的 API 版本以及当前设备或模拟器的系统 API 等级。附录 2项目目录结构与设计意图下面这份目录说明对应当前 DevEco Studio 中打开的harmonyos-app工程。截图里能看到的目录并不只是文件摆放习惯它反映了一个 ArkTS Stage 工程的分层方式应用级配置、业务模块、页面源码、资源文件、构建配置和过程归档分别放在不同位置方便后续排查问题时先判断“问题属于配置、页面、数据、状态、资源还是构建产物”。harmonyos-app/ ├── AppScope/ # 应用级配置与全局资源入口 │ ├── app.json5 # bundleName、版本号、图标、应用标签等应用级元信息 │ └── resources/ # 应用级图标、字符串和基础资源 ├── entry/ # 主业务模块当前 App 的主要页面和业务代码都在这里 │ ├── src/main/ets/ # ArkTS 源码根目录 │ │ ├── components/ # 可复用 ArkUI 组件如底部导航、数据状态面板 │ │ ├── entryability/ # Stage 模型入口 Ability负责应用启动入口 │ │ ├── features/ # 按业务域拆分的功能页面 │ │ │ ├── debug/ # 调机记录相关页面 │ │ │ ├── exceptions/ # 异常处置与闭环相关页面 │ │ │ ├── home/ # 首页看板与概览入口 │ │ │ ├── machines/ # 机台档案列表、详情和机台相关交互 │ │ │ ├── production/ # 生产批次、报工和结案门禁相关页面 │ │ │ ├── products/ # 产品档案、产品详情和关联信息 │ │ │ ├── reports/ # 周报、月报、班次报表和下钻入口 │ │ │ └── templates/ # 参数模板列表与详情 │ │ ├── models/ # 业务对象的数据结构如 Machine、Product、DebugRecord │ │ ├── pages/ # 页面容器与导航装配如 Index.ets │ │ ├── repositories/ # 脱敏演示数据、查询方法、快照持久化和数据重置边界 │ │ ├── stores/ # 页面路由、导航选择和共享状态规则 │ │ └── utils/ # 主题令牌、校验函数等通用工具 │ ├── src/main/resources/base/ # 模块级资源目录 │ │ ├── element/ # 字符串、颜色等基础资源声明 │ │ ├── media/ # 图标、启动图等媒体资源 │ │ └── profile/ # 页面 profile 配置如 main_pages.json │ ├── src/main/module.json5 # entry 模块配置声明 EntryAbility、设备类型和页面入口 │ ├── build-profile.json5 # 模块级构建目标、混淆和 target 配置 │ └── oh-package.json5 # entry 模块包信息与依赖声明 ├── hvigor/ # Hvigor 构建系统配置 │ └── hvigor-config.json5 # 构建执行参数如增量、并行和类型检查 ├── build-profile.json5 # 工程级 SDK、targetSdkVersion、compatibleSdkVersion 配置 ├── hvigorfile.ts # 工程级构建任务入口接入 appTasks ├── local.properties # 本机 SDK 路径配置 ├── oh-package.json5 # 工程级包信息与依赖声明 ├── build.ps1 # 本地构建脚本固定使用 DevEco Studio 自带工具链 ├── document_claude/ # 开发过程归档、测试记录和验证材料 ├── .hvigor/ # Hvigor 生成的缓存和构建记录不作为手写源码维护 ├── .idea/ # DevEco Studio / IntelliJ 工程配置不承载业务逻辑 └── entry/build/ # 构建输出目录HAP 和中间产物由构建流程生成1. 为什么应用级配置放在AppScopeAppScope负责应用整体身份而不是某个页面的业务逻辑。app.json5中的bundleName、versionName、versionCode、应用图标和应用标签会影响安装包身份、桌面展示和版本识别。把这类配置放在应用级目录可以避免业务页面为了改一个标题或图标而混入应用发布配置。在当前工程中AppScope更像“应用身份证”。它回答的是“这个 App 是谁、版本是多少、展示什么图标”而不是“机台列表怎么筛选、详情页怎么返回”。2. 为什么业务代码集中在entry/src/main/etsentry是当前工程的主业务模块src/main/ets是 ArkTS 源码根目录。截图里打开的MachineDetail.ets就位于features/machines下面说明机台详情页被归入“机台业务域”而不是随意放在全局页面目录中。这种组织方式的好处是定位明确机台问题优先看features/machines产品问题优先看features/products生产批次问题优先看features/production。当文章里讨论某个业务链路时读者也能从目录直接反推代码位置。3.components、features和pages的边界components放的是可复用组件例如底部导航、加载/空态/失败态面板。它们不应该直接知道“当前打开的是哪台机台”而是通过参数和回调服务于不同页面。features放的是业务域页面。每个子目录都围绕一个业务主题组织例如machines负责机台档案templates负责参数模板exceptions负责异常闭环。业务页面可以组合组件也可以读取模型和仓储但应尽量把本业务域的显示和交互留在本目录内。pages更偏页面容器和入口装配。当前Index.ets承担主页面状态切换、底部导航和详情路径分发等职责。它不应该塞满所有业务细节而是负责把用户当前所在位置、打开对象和页面分支组织起来。4.models、repositories和stores分别解决什么问题models定义数据形状例如机台、产品、调机记录、生产批次等对象有哪些字段。它让页面和仓储使用同一套类型语言避免每个页面临时拼对象。repositories定义数据来源和查询边界。当前工程使用脱敏演示数据和本地持久化快照因此仓储层负责“从哪里取数据、按什么 ID 查询、怎样重置演示数据”。页面不直接关心数据是内置数组、Preferences 快照还是后续真实接口。stores定义页面级或应用级状态规则例如当前导航项、路由分支、打开详情的类型和 ID。把状态规则从具体组件中抽出来可以减少“列表、详情、导航互相覆盖状态”的问题。5. 为什么资源放在resources/baseresources/base/element管字符串、颜色等声明resources/base/media管图标和图片resources/base/profile管页面 profile。它们和 ArkTS 页面代码分开是为了让“界面逻辑”和“静态资源”各自清晰。如果页面显示异常先判断是布局代码问题还是资源引用问题。比如图标不显示应优先检查media和资源引用页面无法进入应检查profile/main_pages.json和module.json5的页面声明颜色或字符串不符合预期则回到element下核对。6. 构建目录和生成目录不要手工维护.hvigor、entry/build和部分中间产物目录由构建系统生成主要用于缓存、编译记录、HAP 输出和临时文件。它们可以帮助排查构建结果但不应该作为手写业务代码维护。当前调试 HAP 位于entry/build/default/outputs/default/entry-default-unsigned.hap。这个路径说明构建已经产出安装包但它仍是 unsigned 调试产物正式发布前应回到 DevEco Studio 的签名配置和发布流程而不是直接修改build目录里的文件。