在这里插入图片描述

上一节解决的是“点哪台就打开哪台”的 ID 与页面状态链路。本篇继续停留在 MachineDetail,但关注点转为详情页内部:一台机台的基本属性、运行状态、关联产品、调机记录和异常信息,怎样围绕同一个 machineId 组织,而不是散落在互相独立的页面里。

机台详情不是把列表字段放大以后重新排版。列表面向快速扫读,重点是编号、名称和当前状态;详情页则要让使用者先确认自己查看的是哪台设备,再理解它的基础条件和现场关联。页面结构必须帮助用户从“这是什么机台”逐步走到“它关联了哪些业务对象”。

一、详情页的输入仍然只有一个 machineId

在这里插入图片描述

详情组件位于 entry/src/main/ets/features/machines/MachineDetail.ets。它接收机台 ID 和三个回调:返回列表、打开产品、打开调机记录。

在这里插入图片描述

@Component
export struct MachineDetail {
  machineId: string = '';
  onBack: () => void = () => {};
  onOpenProduct: (productId: string) => void = () => {};
  onOpenDebug: (recordId: string) => void = () => {};
}

页面没有再接收一整条 Machine 对象。这保证了详情页始终按当前 ID 从数据层读取对象,也使产品、调机记录等下钻入口能继续把各自的稳定 ID 交给页面容器。详情组件不用知道外层如何切换路由,只关心“给定这个机台 ID,应该展示什么”。

二、先判断对象存在,再进入完整内容

详情页先调用 exists() 检查 Repository 中是否有目标对象:

private exists(): boolean {
  return demoBusinessRepository.machineById(this.machineId) !== undefined;
}

这个判断放在整个 Scroll 外层。不存在时,页面显示“未找到机台档案”和返回按钮;存在时才渲染完整内容。这样可以避免在空对象上继续访问编号、状态或关联数据。

if (!this.exists()) {
  Column({ space: 12 }) {
    Text('未找到机台档案')
    Text('该演示机台可能已被重置,请返回机台列表重新选择。')
    Button('返回机台列表').onClick(() => this.onBack())
  }
} else {
  Scroll() {
    // 完整详情内容
  }
}

这是一条防御性界面规则,不是网络同步已经完成的证明。当前项目使用本地脱敏演示数据;未来接入远端接口时,还要区分加载中、请求失败、权限拒绝和对象已删除等状态。

三、用 currentMachine 统一读取当前对象

详情页通过 currentMachine() 获取被查看对象:

private currentMachine(): Machine {
  const found: Machine | undefined = demoBusinessRepository.machineById(this.machineId);
  return found === undefined
    ? new Machine('missing', '', '', 0, '', 'idle')
    : found;
}

exists() 决定是否进入完整页面,currentMachine() 则为后续渲染提供统一入口。代码中多次调用它,看上去像是重复查找,但表达了一个清晰边界:标题、状态、基础信息和关联查询都基于同一台机台。

如果后续数据源改为异步请求,可以把查询结果缓存为页面状态,减少重复读取;但无论怎样优化,都不应让标题来自一个对象、关联产品来自另一个 ID。详情页最重要的正确性,是所有区块的上下文一致。

四、顶部先给出返回入口和状态标签

详情内容的第一行放置返回按钮和状态标签:

Row() {
  Button('返回')
    .onClick(() => this.onBack())
  Blank()
  Text(this.currentMachine().statusLabel())
    .fontColor(this.statusColor(this.currentMachine()))
}

Blank() 把返回和状态推到两端。用户刚进入详情时,最先需要确认两个信息:是否能安全回到上一层,以及当前机台的运行状态。把状态标签放在标题区附近,能避免用户滚动到页面中部才发现设备处于维护中。

状态颜色由 statusColor() 统一判断:运行中使用成功色,维护中使用警示色,待机使用另一种提示色。颜色只是辅助,状态文字仍然保留;不能只依赖颜色传达“运行中”或“维护中”。

五、标题区展示识别信息,不重复堆叠字段

标题区使用机台编号和名称:

Column({ space: 6 }) {
  Text(this.currentMachine().code)
    .fontSize(24)
    .fontWeight(FontWeight.Bold)
  Text(this.currentMachine().name)
    .fontSize(14)
    .fontColor(ThemeTokens.textSecondary)
}

编号是现场识别设备的主要线索,因此使用更大的字号和粗体;名称解释编号代表的设备用途,使用较弱的视觉层级。这个安排与列表卡片一致,但详情页不再需要把全部摘要字段挤在一行,而是留出空间建立清楚的身份锚点。

如果把位置、锁模力和状态都放进标题区,用户会在进入页面的第一秒接收过多同层信息。当前写法先回答“我看的是什么”,再在下一张信息卡中回答“它在哪里、能力如何、当前怎样”。

六、基础信息卡描述机台的稳定属性

基础信息区块集中展示锁模力、位置和状态:

Column({ space: 10 }) {
  Text('基础信息')
  Text(`锁模力:${this.currentMachine().tonnage}T`)
  Text(`位置:${this.currentMachine().location}`)
  Text(`当前状态:${this.currentMachine().statusLabel()}`)
}

这三个字段分别回答能力、物理位置和运行状态。Machine 构造函数会把锁模力限制为不小于零的值,避免演示模型出现负数能力值;状态文本则集中由 statusLabel() 生成,列表与详情使用同一套中文标签。

需要区分的是,锁模力和位置是当前模型中的基础属性,状态是当前演示数据的页面状态。它们在详情页并排展示,并不意味着已经完成实时设备采集或工业协议接入。

七、关联产品区块从 machineId 查询

机台详情接下来展示关联产品:

if (demoBusinessRepository.productsForMachine(this.currentMachine().id).length === 0) {
  Text('暂无关联产品')
} else {
  ForEach(demoBusinessRepository.productsForMachine(this.currentMachine().id), (product: Product) => {
    Button(`${product.code} · ${product.name}`)
      .onClick(() => this.onOpenProduct(product.id))
  }, (product: Product) => product.id)
}

这里的重点不是按钮样式,而是查询参数始终是 currentMachine().id。Repository 的 productsForMachine(machineId) 会筛选产品模型中的 machineIds 集合,因此产品区块与当前详情对象天然绑定。

空集合也有明确文案。没有关联产品不一定是错误,可能是设备尚未配置产品,也可能是当前演示数据没有覆盖该关系。把“暂无关联产品”写出来,比留下一块空白更能帮助使用者判断下一步应该检查配置还是返回列表。

八、现场关联把调机记录和异常放在同一语境

“现场关联”区块先统计调机记录,再显示异常计数:

private debugRecords(): DebugRecord[] {
  return demoBusinessRepository.debugForMachine(this.currentMachine().id);
}

Text(`最近调机记录:${this.debugRecords().length}`)
Text(`待处理异常:${demoBusinessRepository.openExceptionsForMachine(this.currentMachine().id).length}`)

调机记录和异常都属于“这台机台正在发生什么”的上下文。把它们放在基础信息之后,读者先知道设备能力和位置,再看到与现场处理有关的对象,信息顺序更接近工程师的实际判断路径。

有调机记录时,ForEach 为每条记录提供可点击入口,并把 record.id 交给 onOpenDebug。没有记录时则显示“暂无关联调机记录”。异常数量用强调色提示,但页面仍显示具体文字和数量,避免颜色成为唯一线索。

九、为什么关联查询不在列表页提前完成

一种看似省事的做法,是在机台列表加载时就把产品、调机记录和异常全部拼到每张卡片里。这会让列表承担大量不必要的数据组织工作,也会让滚动列表变得难以阅读。

当前实现把卡片保持为摘要,把关联查询放到详情页。用户主动打开一台设备时,才需要读取与它相关的产品和现场记录。即使当前 Repository 是内存数据,这个职责边界仍有价值;将来数据量扩大时,它也能自然演进为按详情 ID 请求关联信息。

十、可复核的检查路径

可以使用一台有完整关联数据的机台进行检查:

  1. 从机台列表打开 IM-120T-11
  2. 核对标题区的编号、名称和状态标签。
  3. 核对基础信息区的锁模力、位置和当前状态。
  4. 查看关联产品区是否出现产品条目。
  5. 查看现场关联区的调机记录数量、记录入口和待处理异常数量。
  6. 点击返回,确认回到机台列表;点击关联对象时,则应交给页面容器继续打开相应详情。

这些检查验证的是当前本地演示数据下的详情组织和 ID 关联。它们不能推导出真实设备数据已经同步,也不能替代生产环境中的权限、刷新和异常处理验证。

十一、常见错误与排查方法

1. 标题和基础信息显示的不是同一台设备

先检查是否所有字段都通过 currentMachine() 获取,再检查传入的 machineId 是否被中途改写为展示编号。详情页应只有一个当前对象来源。

2. 关联产品区一直为空

检查 productsForMachine() 的入参是否为机台 ID,并确认产品模型的 machineIds 中包含该 ID。不要用产品名称或机台编号去比较稳定键。

3. 调机记录数量正确,但点击后没有打开记录

检查 onOpenDebug(record.id) 是否仍把记录 ID 交给页面容器;若把 machineId 误传给调机详情,后续组件会找不到对应记录。

4. 维护中状态只显示颜色,没有文字

检查状态标签是否保留 statusLabel() 返回值。颜色用于加快识别,状态文本才是可访问、可复制和可明确沟通的信息。

十二、小结

本文 让机台详情围绕一个 machineId 展开:顶部确认返回和状态,标题区确认设备身份,基础信息说明能力与位置,关联产品与现场关联则从同一 ID 查询并继续下钻。

附录:工程配置与版本说明

为了便于读者复现本文中的代码片段和运行现象,这里把当前文章系列对应的工程基线单独列出。本文所说的“当前工程”,指 e_notebook 项目的 HarmonyOS ArkTS 客户端,应用名称为“注塑工程师助手”,主要用于脱敏演示机台档案、产品档案、调机记录、参数模板、异常闭环、生产批次和看板报表等业务路径。

1. 应用与模块配置

  • 应用包名:com.atan.enotebook
  • 应用版本:versionName1.0.0versionCode1000000
  • 工程模型:ArkTS / ArkUI Stage 模型。
  • 主模块:entry,模块类型为 entry
  • 入口 Ability:EntryAbility,入口文件为 entry/src/main/ets/entryability/EntryAbility.ets
  • 主页面配置:模块通过 pages: "$profile:main_pages" 读取页面列表。
  • 设备类型:当前模块声明支持 phonetablet2in1
  • 安装方式:deliveryWithInstalltrueinstallationFreefalse,属于随应用安装的普通 entry 模块。

2. SDK 与 API 版本

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • DevEco Studio 版本:DevEco Studio Beta 26.0.0.461
  • 编译 SDK:HarmonyOS SDK API 26 Beta1,SDK 包版本为 26.0.0.23
  • SDK 平台信息:apiVersion26platformVersion26.0.0releaseType / stageBeta1
  • targetSdkVersion26.0.0
  • compatibleSdkVersion6.1.1(24)
  • API 口径说明:文章系列以 API 24 作为兼容目标进行表述;当前工程实际由 API 26 Beta SDK 编译,并在 API 24 模拟器上做过安装、启动和交互观察。因此,文中的“API 24 运行观察”表示兼容目标环境下的模拟器验证结果,不等同于使用 API 24 SDK 重新完成编译验证。

3. 构建与运行工具

  • 开发工具 IDE:DevEco Studio Beta,安装目录指向 D:/Program Files/Huawei/DevEco Studio Beta
  • SDK 路径:D:/Program Files/Huawei/DevEco Studio Beta/sdk
  • 构建系统:Hvigor,工程入口 hvigorfile.ts 使用 @ohos/hvigor-ohos-pluginappTasks
  • 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.json5module.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. 为什么应用级配置放在 AppScope

AppScope 负责应用整体身份,而不是某个页面的业务逻辑。app.json5 中的 bundleNameversionNameversionCode、应用图标和应用标签,会影响安装包身份、桌面展示和版本识别。把这类配置放在应用级目录,可以避免业务页面为了改一个标题或图标而混入应用发布配置。

在当前工程中,AppScope 更像“应用身份证”。它回答的是“这个 App 是谁、版本是多少、展示什么图标”,而不是“机台列表怎么筛选、详情页怎么返回”。

2. 为什么业务代码集中在 entry/src/main/ets

entry 是当前工程的主业务模块,src/main/ets 是 ArkTS 源码根目录。截图里打开的 MachineDetail.ets 就位于 features/machines 下面,说明机台详情页被归入“机台业务域”,而不是随意放在全局页面目录中。

这种组织方式的好处是定位明确:机台问题优先看 features/machines,产品问题优先看 features/products,生产批次问题优先看 features/production。当文章里讨论某个业务链路时,读者也能从目录直接反推代码位置。

3. componentsfeaturespages 的边界

components 放的是可复用组件,例如底部导航、加载/空态/失败态面板。它们不应该直接知道“当前打开的是哪台机台”,而是通过参数和回调服务于不同页面。

features 放的是业务域页面。每个子目录都围绕一个业务主题组织,例如 machines 负责机台档案,templates 负责参数模板,exceptions 负责异常闭环。业务页面可以组合组件,也可以读取模型和仓储,但应尽量把本业务域的显示和交互留在本目录内。

pages 更偏页面容器和入口装配。当前 Index.ets 承担主页面状态切换、底部导航和详情路径分发等职责。它不应该塞满所有业务细节,而是负责把用户当前所在位置、打开对象和页面分支组织起来。

4. modelsrepositoriesstores 分别解决什么问题

models 定义数据形状,例如机台、产品、调机记录、生产批次等对象有哪些字段。它让页面和仓储使用同一套类型语言,避免每个页面临时拼对象。

repositories 定义数据来源和查询边界。当前工程使用脱敏演示数据和本地持久化快照,因此仓储层负责“从哪里取数据、按什么 ID 查询、怎样重置演示数据”。页面不直接关心数据是内置数组、Preferences 快照,还是后续真实接口。

stores 定义页面级或应用级状态规则,例如当前导航项、路由分支、打开详情的类型和 ID。把状态规则从具体组件中抽出来,可以减少“列表、详情、导航互相覆盖状态”的问题。

5. 为什么资源放在 resources/base

resources/base/element 管字符串、颜色等声明,resources/base/media 管图标和图片,resources/base/profile 管页面 profile。它们和 ArkTS 页面代码分开,是为了让“界面逻辑”和“静态资源”各自清晰。

如果页面显示异常,先判断是布局代码问题还是资源引用问题。比如图标不显示,应优先检查 media 和资源引用;页面无法进入,应检查 profile/main_pages.jsonmodule.json5 的页面声明;颜色或字符串不符合预期,则回到 element 下核对。

6. 构建目录和生成目录不要手工维护

.hvigorentry/build 和部分中间产物目录由构建系统生成,主要用于缓存、编译记录、HAP 输出和临时文件。它们可以帮助排查构建结果,但不应该作为手写业务代码维护。

当前调试 HAP 位于 entry/build/default/outputs/default/entry-default-unsigned.hap。这个路径说明构建已经产出安装包,但它仍是 unsigned 调试产物;正式发布前应回到 DevEco Studio 的签名配置和发布流程,而不是直接修改 build 目录里的文件。

在这里插入图片描述

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐