HarmonyOS ArkTS API 24+ 实战:完成调机记录保存动作
前言:保存不是把对象丢进数组就结束
前面文章 介绍了表单如何生成 DebugRecord, 又整理了提交错误反馈。真正让页面产生业务结果的代码在 DemoBusinessRepository.saveDebugRecord():它需要判断当前记录是新增还是编辑,更新内存集合,并在提交复核时推进关联异常状态。
如果保存逻辑放在表单里,表单就必须知道数组位置、异常状态和后续通知。这样一来,列表、异常详情和其他未来入口都可能各自实现一份保存规则。本篇把 Repository 方法拆开解释,重点说明稳定 ID、更新分支、提交状态和变更通知之间的关系。


一、保存方法的真实代码
DemoBusinessRepository.ets 中的核心实现是:
saveDebugRecord(record: DebugRecord): void {
const index: number = this.debugRecords.findIndex(
(item: DebugRecord) => item.id === record.id
);
if (index >= 0) {
this.debugRecords[index] = record;
} else {
this.debugRecords.push(record);
}
const exception: ExceptionRecord | undefined =
this.exceptionById(record.exceptionId);
if (exception !== undefined && record.status === 'submitted') {
exception.status = 'verifying';
}
this.notifyStateChanged();
}
方法内部有三段职责:先按 ID 更新或追加记录,再根据提交状态处理关联异常,最后通知状态变化。它没有直接操作页面组件,因此列表、表单和异常详情可以共享同一条保存规则。
二、为什么用 id 判断更新还是新增
findIndex() 比较的是 DebugRecord.id:
const index: number = this.debugRecords.findIndex(
(item: DebugRecord) => item.id === record.id
);
找到索引表示集合里已有同一条记录,保存动作应该替换原对象;索引为 -1 表示没有找到,保存动作应该追加新对象。
不能用异常 ID、机台 ID 或数组下标代替记录 ID。一个异常可能在流程中产生多次调机记录,同一台机也会有多条历史记录。数组下标还会随着新增、筛选或排序改变,不能稳定表示对象身份。
当前演示表单新建时使用 DBG-NEW-001,编辑时继续使用原记录 ID。这样重复打开编辑页会走更新分支,不会因为每次保存都生成一条相同内容的新记录。
三、更新分支如何保持列表历史唯一
已有记录的保存路径是:
if (index >= 0) {
this.debugRecords[index] = record;
}
这行代码用新的对象替换原对象。表单可能只修改了参数和试样字段,但构造出的 DebugRecord 仍然带有原来的关联 ID,因此记录仍然属于同一条业务对象。
替换对象而不是逐字段修改,能够让保存边界更清楚:表单提供一个完整的候选对象,Repository 决定是否接受并放入集合。生产实现中还需要加入权限、并发版本和服务端响应判断;当前内存演示只验证集合更新逻辑。
四、新增分支如何追加记录
新建记录没有匹配 ID 时走:
else {
this.debugRecords.push(record);
}
push() 将记录加入集合尾部。之后 DebugRecordList.filteredRecords() 再次调用 loadDebugRecords() 时,就可以看到新的对象。当前列表没有单独的排序字段,因此新增记录的展示位置遵循数组顺序;如果未来按更新时间倒序,需要把排序规则明确放在查询或列表层。
固定的 DBG-NEW-001 适合演示更新分支,但不适合生产。真实系统需要由服务端或统一 ID 生成机制保证唯一性,否则不同用户同时新增记录可能相互覆盖。
五、提交状态会推进异常状态
保存记录后,方法查询关联异常:
const exception: ExceptionRecord | undefined =
this.exceptionById(record.exceptionId);
if (exception !== undefined && record.status === 'submitted') {
exception.status = 'verifying';
}
只有记录状态为 submitted 时才推进异常。草稿保存不会触发异常进入复核阶段,因为草稿还没有完成提交。记录状态为 approved 的已有数据也不会在本方法中重新推进异常,这个动作只对应“提交复核”这个业务事件。
关联查询可能返回 undefined,所以代码先判断异常是否存在。当前演示数据中 DBG-241 关联 EX-241,但模型边界仍然要允许记录没有可找到的异常,不能因为演示数据完整就省略空值处理。
六、为什么保存后要通知状态变化
方法最后调用:
this.notifyStateChanged();
通知的作用不是直接刷新某个列表,而是告诉状态持久化层:Repository 中的数据已经发生变化。DemoStatePersistence.initialize() 会注册监听器,变化后保存快照并递增 demoStateVersion。
这种设计把“数据变了”和“谁需要响应”分开。Repository 只发出变化通知,持久化层负责保存,入口页和 ArkUI 状态负责重新计算页面。未来替换为数据库或网络服务时,也可以保留这个业务方法边界。
七、表单、Repository 和页面的完整链路
一次提交可以沿着以下路径追踪:
- 用户在
DebugRecordForm修改@State字符串。 input()将字符串转换为DebugRecordInput。Validation.ets判断提交是否通过。- 表单创建状态为
submitted的DebugRecord。 - 表单调用
demoBusinessRepository.saveDebugRecord(record)。 - Repository 通过 ID 更新或追加记录。
- Repository 找到关联异常后将其推进到
verifying。 - Repository 调用
notifyStateChanged()。 - 持久化层保存快照,页面重新读取同一个 Repository 实例。
如果保存后列表没有变化,优先确认第 5 步是否执行;如果列表有记录但异常状态不变,检查记录的 exceptionId 和 status;如果重启应用后数据丢失,检查持久化初始化、快照写入和恢复路径。
八、当前保存边界不是数据库事务
DemoBusinessRepository 使用内存数组和演示快照。saveDebugRecord() 的更新、异常状态推进和通知在同一个同步方法中执行,但这不等于具备数据库事务、服务端幂等或多端并发能力。
文章可以据此解释当前 App 的数据流,但不能写成已经完成数据库写入。生产实现还需要定义保存失败、网络重试、状态冲突、权限检查和审计记录。尤其是调机记录一旦进入复核流程,状态变化通常需要可追溯,不能只依赖客户端内存。
九、验证新增和更新两条路径
可以使用两组动作验证方法行为:
新增路径
进入新增调机记录,修改至少一项参数,填写试样数量、观察时长和结论,提交后观察列表。预期是产生一条新的记录,状态显示为“待复核”,关联异常进入复核中。
更新路径
打开已有的 DBG-241,修改参数或结论后保存。预期是原记录被更新,而不是列表中出现两条相同 ID 的记录。再次打开同一记录时,应能看到最新字段值。
这些结论针对当前脱敏演示 Repository 和页面路径。当前运行事实是 API 26 Beta SDK 构建并在 API 24 模拟器上观察,不扩大为 API 24 SDK 编译结论。
十、常见误区
1. 每次保存都 push
编辑已有记录会不断产生重复历史。应先用稳定 ID 查找,再决定替换还是追加。
2. 草稿保存也推进异常状态
草稿代表过程未完成,不能直接把异常推进到复核状态。当前代码只对 submitted 做状态推进。
3. 让表单直接操作 debugRecords 数组
这样会让页面知道数据结构和关联流程。应由 Repository 暴露保存方法,统一处理对象集合、异常状态和变更通知。
4. 把 notifyStateChanged 当成网络同步
它当前只触发本地持久化监听和状态版本变化,不代表数据已经提交到服务器。
十一、总结
本篇完成了调机记录的保存动作:
- 用
DebugRecord.id区分更新和新增。 - 用替换对象保证编辑不会重复追加。
- 用
submitted状态触发关联异常进入verifying。 - 用
notifyStateChanged()把数据变化交给持久化层。 - 明确内存演示保存与生产事务、网络同步之间的边界。
下一篇将观察保存成功后返回列表的刷新路径,重点说明 demoStateVersion、快照保存和列表重新读取之间的关系。
附录:工程配置与版本说明
为了便于读者复现本文中的代码片段和运行现象,这里把当前文章系列对应的工程基线单独列出。本文所说的“当前工程”,指 e_notebook 项目的 HarmonyOS ArkTS 客户端,应用名称为“注塑工程师助手”,主要用于脱敏演示机台档案、产品档案、调机记录、参数模板、异常闭环、生产批次和看板报表等业务路径。
1. 应用与模块配置
- 应用包名:
com.atan.enotebook。 - 应用版本:
versionName为1.0.0,versionCode为1000000。 - 工程模型:ArkTS / ArkUI Stage 模型。
- 主模块:
entry,模块类型为entry。 - 入口 Ability:
EntryAbility,入口文件为entry/src/main/ets/entryability/EntryAbility.ets。 - 主页面配置:模块通过
pages: "$profile:main_pages"读取页面列表。 - 设备类型:当前模块声明支持
phone、tablet和2in1。 - 安装方式:
deliveryWithInstall为true,installationFree为false,属于随应用安装的普通 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 平台信息:
apiVersion为26,platformVersion为26.0.0,releaseType/stage为Beta1。 targetSdkVersion:26.0.0。compatibleSdkVersion:6.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-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. 为什么应用级配置放在 AppScope
AppScope 负责应用整体身份,而不是某个页面的业务逻辑。app.json5 中的 bundleName、versionName、versionCode、应用图标和应用标签,会影响安装包身份、桌面展示和版本识别。把这类配置放在应用级目录,可以避免业务页面为了改一个标题或图标而混入应用发布配置。
在当前工程中,AppScope 更像“应用身份证”。它回答的是“这个 App 是谁、版本是多少、展示什么图标”,而不是“机台列表怎么筛选、详情页怎么返回”。
2. 为什么业务代码集中在 entry/src/main/ets
entry 是当前工程的主业务模块,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/base
resources/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 目录里的文件。

更多推荐



所有评论(0)