HarmonyOS ArkTS API 24 实战:从零搭建注塑机调试记录页面(表单录入 + 列表刷新)
开始之前:本篇在系列中的位置
本文是“HarmonyOS ArkTS 工业 App 实战”系列的第 001 篇,不要求具备注塑行业经验,也不要求提前掌握复杂的状态管理。开始前只需准备一个可以正常打开的 HarmonyOS 工程,并能够找到 ArkTS 页面文件。
本篇将完成注塑机调试记录的基础业务闭环:用户填写工艺参数,页面校验并转换输入数据,随后创建调试记录,通过 @State 更新记录数组,最终驱动列表重新渲染。完成后,你应该能够独立追踪“输入 → 校验 → 对象创建 → 状态更新 → 列表刷新”的完整过程,并定位保存后列表没有变化的常见原因。
当前记录仅保存在页面内存中,应用退出后不会保留数据。后续文章可以在这个数据模型上继续增加本地持久化,以及机台、产品和模具档案关联;正式选题尚未确认前,本文不提供虚构的标题或链接。
前言
本文从"注塑工程师助手"App 的核心功能模块切入——注塑机调试记录。
在注塑现场,工程师需要记录机台、产品、模具、材料、温度、压力和周期。若数据散落在纸张或 Excel 中,后续复盘会很困难。
同一产品再次投产时,工程师需要快速找回设备、温度、压力与缺陷记录。缺少结构化历史数据,换班或换料后就可能重复调试。
移动 App 可以把调机过程变成结构化字段,并为查询与统计提供数据基础。这个场景也能串联 ArkTS 数据模型、表单、状态和列表渲染。
本文将基于 HarmonyOS ArkTS API 24,实现一个完整的调机记录页面,重点覆盖以下三方面内容:
- 使用 ArkTS 定义调机记录数据模型
- 基于 ArkUI 构建工艺参数录入表单
- 通过
@State装饰器驱动记录列表自动刷新
本文不追求一次性完成整个 App,而是先跑通一个真实业务模块。后续文章将陆续扩展机台档案、产品档案、生产记录、异常记录及报表统计等功能。
阅读时建议关注两条主线:业务线——一条调机记录包含哪些核心信息;代码线——用户输入如何从表单流入状态变量,再从状态变量驱动列表渲染。打通这两条链路后,后续构建更复杂的生产记录模块将事半功倍。
效果预览
下面用演示截图确认页面结构,所有机台与工艺参数均为示例值。
截图 1:页面总览。先观察统计区与记录列表的上下关系。
截图 2:调机参数表单。工程师可录入机台、模具、产品、材料、温度、压力及成型周期等完整参数。
截图 3:保存后的记录列表。新增记录自动置顶,按时间倒序排列便于查阅。
截图 4:错误反馈。输入不能转换为正数时,保存流程会停在校验阶段。
开发环境
先确认本文使用的版本基线:
HarmonyOS:6.1.1
API Version:24
开发语言:ArkTS
开发工具:DevEco Studio 6.1.1
功能目标
本页面需实现的功能如下表所示:
| 功能 | 说明 |
|---|---|
| 基础信息录入 | 机台编号、产品名称、模具编号、材料 |
| 工艺参数录入 | 料筒温度、模具温度、注射压力、保压压力、成型周期 |
| 调机记录保存 | 点击按钮生成一条新的调试记录 |
| 记录列表展示 | 最新记录自动显示在列表顶部 |
| 状态驱动刷新 | records 数据变更后,页面自动重新渲染 |
一、项目结构与文件规划
为了让教程更贴近真实项目,我们先明确代码组织结构。
本篇示例建议独立为一个页面文件:
entry/src/main/ets/pages/InjectionDebugPage.ets
若后续继续扩展"注塑工程师助手"App,可按以下模块拆分页面:
系列按页面职责拆分,文件名对应业务边界:
InjectionDebugPage.ets:调机记录闭环。InjectionMachinePage.ets:机台档案。ProductArchivePage.ets:产品档案。ProductionRecordPage.ets:生产批次。ExceptionRecordPage.ets:异常处理。
本篇只修改第一个页面,其余留待扩展。
模块化拆分有两点好处:
- 页面与文件一一对应,读者一目了然
- 系列文章每篇都有明确的代码落点,内容体系更清晰
二、数据模型设计
首先定义一条调机记录所需的字段结构:
完整模型代码统一放在“十四、完整代码”,这里先理解字段职责。
以上代码定义了一条完整的调试记录。首版字段设计保持精简,重点关注页面逻辑与状态流转。
可以按现场使用顺序理解字段:
- 先用
machineNo确认记录属于哪台设备。 - 再用
moldNo定位本次安装的模具。 productName说明这次试制的产品。barrelTemp保存料筒温度,单位为摄氏度。injectionPressure保存注射压力,单位为 MPa。holdingPressure保存保压压力,单位同为 MPa。moldTemp保存模具温度,单位为摄氏度。cycleTime保存单次成型周期,单位为秒。result记录正常、缩水或飞边等现场结论。
后续若构建完整生产系统,可继续扩展班次、批次、操作员、图片附件、异常分类等字段。
三、页面整体布局架构
页面分为三大区域,布局结构如下:
Scroll
└── Column
├── 顶部标题区
├── 调机参数表单区
└── 最近调试记录列表
外层使用 Scroll,内部嵌套 Column,让多字段表单可以上下滚动。先搭骨架,再填充组件细节。
四、逐段代码解析
下面按代码顺序逐步拆解,帮助你不仅知道"代码能跑",更理解每一段在页面中承担的职责。
1. InjectionDebugRecord:定义业务数据模型
字段声明可在篇末完整代码中逐项对照。
这段代码是整个页面的数据基石。页面上每一张"调试记录卡片",背后都对应一个 InjectionDebugRecord 实例。
字段设计遵循一个原则:优先保留工程师复盘时最核心的信息。machineNo 用于定位机台,productName 和 moldNo 用于定位产品与模具,material 记录原材料,温度、压力、周期则是调机过程的关键工艺参数。
数据模型不必一次覆盖所有需求。第一版先完成核心闭环,附件、班次、操作员和异常分类可在需求明确后扩展。
2. constructor:确保记录对象可完整实例化
构造方法的完整参数与赋值顺序保留在篇末,阅读时重点核对一一对应关系。
构造方法负责将传入参数赋值给对象属性。
用户点击"保存调机记录"时,程序从表单中取出机台编号、产品名称、温度、压力等数据,通过 new InjectionDebugRecord(...) 创建一条新记录。构造方法相当于一个装配入口,确保每条记录都按照统一的字段顺序生成。
注意一个细节:id 放在参数列表第一位。后续列表渲染时,ForEach 需要稳定的唯一标识来区分每一项,id 正承担此角色。缺少唯一 key 可能导致列表刷新时项与项的对应关系错乱。
3. @Entry 与 @Component:声明页面组件
@Entry
@Component
struct InjectionDebugPage {
}
@Component 表示这是一个 ArkUI 组件,内部可定义状态变量、业务方法及 build() 渲染函数。
@Entry 表示该组件可作为入口页面独立运行。两个装饰器共同向框架声明:这个 struct 并非普通结构体,而是一个可渲染到屏幕上的完整页面组件。
页面命名为 InjectionDebugPage,语义清晰。真实项目中,建议页面命名尽量与业务模块对应,便于后期维护。
4. @State:表单输入与页面状态的绑定
@State machineNo: string = 'IM-120T-01'
@State productName: string = '外壳件 A01'
@State moldNo: string = 'MOLD-A01'
@State material: string = 'ABS'
@State barrelTemp: string = '230'
@State moldTemp: string = '65'
@State injectionPressure: string = '85'
@State holdingPressure: string = '58'
@State cycleTime: string = '32'
@State result: string = '调试正常'
@State errorMessage: string = ''
这些 @State 变量用于存储表单的当前输入值。
为什么温度、压力等数值型参数先用 string 保存? 因为它们来自 TextInput 组件,输入框天然返回文本类型。输入阶段用字符串存储更便捷,待点击保存时再统一转为数字,表单处理逻辑更简洁。
以 barrelTemp 为例:页面初始化时默认值为 230,用户在输入框中修改为 235 后,barrelTemp 的值会同步更新。点击保存时,再将其转换为 number 类型写入调试记录。
errorMessage 是校验提示状态变量。当用户输入的温度或压力不合法时,通过它显示错误信息。该字段不参与记录保存,仅用于用户反馈。
表单页面的典型数据流如下:
输入框变化 → 更新 @State → 点击保存 → 生成业务对象 → 刷新列表
5. records:记录列表的数据源
@State records: InjectionDebugRecord[] = [
new InjectionDebugRecord(
1,
'IM-120T-01',
'外壳件 A01',
'MOLD-A01',
'ABS',
230,
65,
85,
58,
32,
'调试正常,产品表面无明显缺陷',
'2026-07-20 09:30'
)
]
records 是调试记录列表的数据源,是一个 InjectionDebugRecord 类型的数组。
此处预置了一条演示数据,确保页面打开即可看到列表效果。教程写作中这一点很重要——空白页面不利于读者理解最终呈现形态。
真实项目中,这些记录可能来自本地数据库、后端接口或设备调试历史文件。本文使用内存数组演示,旨在将注意力集中在 ArkTS 页面状态管理与 ArkUI 渲染逻辑上。
6. validateForm 与 toNumber:保存前的校验与转换
校验、正数判断和转换的完整实现见篇末,本节集中说明三者的调用顺序。
这部分代码分为两步:先校验,再转换。
validateForm() 负责判断表单是否满足保存条件。料筒温度、模具温度、注射压力、保压压力、成型周期等关键参数,若为空、非数字或小于等于 0,均不应继续保存。
isPositiveNumber() 专注一件事:判断输入值是否为有效正数。将校验逻辑抽离为独立方法,避免散落在 addRecord() 中,后续新增参数校验时也可直接复用。
toNumber() 则只负责类型转换。这里遵循一个设计原则:转换失败时不静默返回 0。对注塑工艺参数而言,0℃、0MPa 都会造成记录失真。更稳妥的做法是先拦截错误输入,确认数据有效后再执行转换。
这段代码体现了一个重要的工程习惯:表单提交不等于流程结束,关键生产参数必须先校验、再落库。
7. addRecord:点击按钮后的记录生成逻辑
addRecord() 的完整代码保留在篇末,下面按校验、装配和数组更新三步解释。
这是整篇文章中最核心的业务逻辑。
第一步:表单校验。代码先执行 validateForm(),若关键参数不合法则直接 return,不生成错误记录。这个判断至关重要——调机参数属于生产数据,宁可提示用户重填,也不能将错误值写入记录。
第二步:创建记录对象。通过 new InjectionDebugRecord(...) 实例化新记录,前几个字段直接取自表单状态,温度、压力、周期等字段经 toNumber() 转换为数字类型。
第三步:更新列表数据:
this.records = [record, ...this.records]
将新记录置于数组最前端,旧记录依次拼接在后。这样用户刚保存的记录自动出现在列表顶部,符合"最近记录优先"的阅读习惯。
此处未使用 push(),而是重新赋值整个数组——写法更直观,也更易触发页面刷新。这是 ArkTS 状态驱动开发中的常用模式。
8. build:Scroll 包裹完整页面结构
build() {
Scroll() {
Column() {
this.Header()
this.FormArea()
this.RecordList()
}
.width('100%')
.padding(16)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F7FA')
}
build() 是页面的渲染入口,页面所有可见元素都从这里开始组织。
外层使用 Scroll,原因在于调机表单字段较多。手机屏幕高度有限,若仅用 Column,底部记录列表可能被挤出可视区域。加上 Scroll 后页面可上下滚动,体验更接近真实 App。
Column 按用户操作顺序组合页面:
- 用户先从
Header()确认页面用途。 - 用户再到
FormArea()填写工艺数据。 - 保存后由
RecordList()展示最新结果。
因此阅读 build() 时,可以直接还原用户的操作路径。
9. Header:顶部标题区
@Builder
Header() {
Column() {
Text('注塑机调试记录')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#1F2937')
Text('记录每一次调机参数,方便后续复盘和复用')
.fontSize(14)
.fontColor('#6B7280')
.margin({ top: 6 })
}
}
Header() 是一个自定义构建函数,负责渲染页面标题区域。
大标题明确告知用户当前页面为"注塑机调试记录",副标题补充说明页面用途——记录调机参数,便于复盘与复用。
工业类 App 的页面文案宜简洁直接。工程师打开页面是为了高效完成记录与查询,标题清晰、说明到位即可。
10. FormArea:集中管理调机参数表单
@Builder
FormArea() {
Column() {
Text('本次调试参数')
this.InputItem('机台编号', this.machineNo, (value: string) => {
this.machineNo = value
})
this.InputItem('产品名称', this.productName, (value: string) => {
this.productName = value
})
Button('保存调机记录')
.onClick(() => {
this.addRecord()
})
}
}
完整示例中表单区代码较长,以上仅截取核心结构。
表单区的设计思路:每个输入项均调用 InputItem(),传入标签、当前值和变更回调。以机台编号为例,显示 this.machineNo,用户修改后执行:
this.machineNo = value
由此实现输入框与状态变量的双向联动。
表单字段与状态变量的对应关系如下表:
输入沿着“界面值 → 状态 → 记录字段”移动:
- 机台编号写入
machineNo。 - 产品名称写入
productName。 - 模具编号写入
moldNo。 - 材料名称写入
material。 - 料筒温度校验后进入
barrelTemp。 - 模具温度校验后进入
moldTemp。 - 注射压力进入
injectionPressure。 - 保压压力进入
holdingPressure。 - 成型周期进入
cycleTime。 - 调试结论进入
result,当前不做枚举限制。
所有映射在addRecord()中汇合。
底部按钮绑定 onClick() 事件,用户点击时执行 this.addRecord(),将表单内容存入 records 数组。
这也是业务表单页面的基础范式:多个输入项采集数据,单个提交按钮触发保存。
11. InputItem:抽取重复输入框为统一组件
@Builder
InputItem(label: string, value: string, onChange: (value: string) => void) {
Column() {
Text(label)
.fontSize(13)
.fontColor('#4B5563')
.margin({ bottom: 6 })
TextInput({ text: value, placeholder: `请输入${label}` })
.height(40)
.fontSize(14)
.backgroundColor('#F9FAFB')
.borderRadius(6)
.onChange((inputValue: string) => {
onChange(inputValue)
})
}
}
InputItem() 的封装非常关键,它大幅减少了表单代码的重复量。
如果不做封装,每个输入框都要重复编写 Text、TextInput、高度、字号、背景色、圆角及 onChange() 逻辑。字段少时问题不大,字段增多后代码维护成本会急剧上升。
抽离为 InputItem() 后,表单区只需关注业务字段本身:
this.InputItem('材料', this.material, (value: string) => {
this.material = value
})
一行代码同时表达了三件事:标签名称、当前值来源、变更后更新目标。
这就是封装的价值——不是为了炫技,而是让业务代码更聚焦、更易读。
12. RecordList:根据 records 渲染记录列表
@Builder
RecordList() {
Column() {
Row() {
Text('最近调试记录')
Blank()
Text(`${this.records.length} 条`)
}
ForEach(this.records, (item: InjectionDebugRecord) => {
this.RecordCard(item)
}, (item: InjectionDebugRecord) => item.id.toString())
}
}
RecordList() 负责渲染最近调试记录区域。
顶部 Row 左侧为标题,右侧为记录总数。Blank() 组件自动填充中间剩余空间,使标题与数量自然分布于两侧。
下方 ForEach 遍历 records 数组,有几条记录就渲染几张 RecordCard 卡片。
第三个参数:
(item: InjectionDebugRecord) => item.id.toString()
用于提供唯一 key。列表渲染时,唯一 key 帮助框架精准识别每一项,减少不必要的重渲染,也避免数据更新后列表项对应关系错乱。
13. RecordCard:单条调试记录的卡片展示
@Builder
RecordCard(item: InjectionDebugRecord) {
Column() {
Row() {
Text(item.machineNo)
Blank()
Text(item.createdAt)
}
Text(`${item.productName} / ${item.moldNo} / ${item.material}`)
Text(`料筒 ${item.barrelTemp}℃,模温 ${item.moldTemp}℃,注射 ${item.injectionPressure}MPa,保压 ${item.holdingPressure}MPa,周期 ${item.cycleTime}s`)
Text(item.result)
}
}
RecordCard() 接收一条调试记录,将其渲染为信息卡片。
- 第一行:机台编号 + 记录时间。工程师可快速定位记录来源与时间点。
- 第二行:产品 / 模具 / 材料。三者经常联动查阅——同一机台可能生产不同产品,同一产品也可能搭配不同模具和材料。
- 第三行:核心工艺参数汇总。料温、模温、注射压力、保压压力、成型周期集中展示,便于快速扫读。
若后续参数继续增加,不建议全部挤在一行。可改为两行展示,或使用 Grid 布局做成参数宫格,兼顾扫读效率与卡片整洁度。
- 最后一行:调试结果,如"调试正常"“轻微缩水”"飞边待调整"等。后续可扩展为颜色标签:正常用绿色、待调整用橙色、异常用红色。
14. 整体数据流梳理
至此,完整的代码流程已经清晰:
用户填写 TextInput
↓
onChange 更新 @State 变量
↓
点击保存按钮
↓
addRecord 创建 InjectionDebugRecord
↓
records 数组重新赋值
↓
ForEach 重新渲染列表
↓
RecordCard 展示最新调机记录
这是 ArkTS 页面开发中极为典型的一条数据链路。掌握这条链路后,后续开发生产记录、异常记录、产品档案等模块,思路都是相通的:先设计数据模型,再用状态承接输入,最后将状态映射为界面。
五、UI 细节与工业场景适配
工业 App 用户通常带着明确任务进入页面,因此界面应清晰、直接、少干扰。
本页面的 UI 设计遵循以下原则:
- 标题区明确:第一时间告知用户当前页面功能
- 分层视觉设计:表单区采用白底卡片,与浅灰背景形成明确分层
- 卡片式列表:每条记录独立成卡,便于逐条浏览
- 参数集中展示:数值类内容汇总呈现,支持快速扫读
- 结果字段突出:调试结果单独展示,便于后续扩展颜色标签
后续可继续优化的方向包括:异常结果颜色标签、空状态提示、表单分组标题、记录卡片的编辑与详情按钮等。
页面采用浅灰背景、白色卡片、蓝色主按钮和深色文字。重点是参数准确与保存反馈,而不是装饰效果。
六、效率对比:数字化记录 vs 传统方式
这个页面之所以值得专门成文,不仅在于功能可运行,更在于它能切实提升现场记录效率。
| 记录方式 | 记录速度 | 复盘难度 | 历史参数查询 |
|---|---|---|---|
| 纸质记录 | 慢 | 高 | 困难 |
| Excel 表格 | 中等 | 中等 | 一般 |
| App 数字化记录 | 快 | 低 | 便捷 |
数字化记录的优势体现在:
- 输入项固定,减少漏填错填
- 历史记录自动沉淀,随时可查
- 后续可扩展筛选、搜索与统计功能
- 多人按统一规范记录,数据格式一致
七、常见问题 FAQ
Q1:点击保存后列表没有变化怎么办?
先检查保存方法中是否真正修改了 records 数组。推荐写法:
this.records = [record, ...this.records]
如果仅创建了 record 对象但未放入数组,页面当然不会刷新。
Q2:输入的数字为什么先用 string 类型存储?
TextInput 接收的是文本输入,因此页面输入阶段用 string 存储更便捷。
但生产参数不能始终以字符串处理。保存前需先校验、再转数字。本文通过 validateForm() 校验关键参数,通过 toNumber() 执行最终转换——兼顾输入处理的灵活性与数据写入的准确性。
Q3:真实项目中这些数据应该存储在哪里?
本文先放在页面状态中,便于理解 UI 交互与状态刷新机制。
生产版本需要继续补齐数据能力:
- 先把页面数据写入可靠存储。
- 再提供按设备检索记录的入口。
- 产品与模具可关联已有档案。
- 报表导出应建立在可信数据上。
这些方向会复用本篇的数据模型。
八、把数据链路真正吃透
前面的代码已经能够形成完整页面,这一节不再增加功能,而是用一次具体操作追踪数据如何移动。假设工程师把料筒温度改为 235,TextInput 接收到的仍是字符串;它触发 onChange,再把文本写入由 @State 修饰的 barrelTemp。ArkUI 会跟踪界面对该状态的依赖,因此输入框能够显示新值,但此时还没有创建调试记录。
用户点击保存后,事件进入 addRecord()。方法不会立即执行数字转换,而是先调用 validateForm() 检查五个数值字段;任一字段不合法,流程都会提前返回,并通过 errorMessage 显示反馈。只有校验全部通过,toNumber() 才把字符串转换为数字,随后由 new InjectionDebugRecord(...) 组装业务对象。
构造参数的顺序必须与字段定义保持一致。顺序错位不一定立即产生语法错误,却会让温度、压力或周期进入错误字段,因此调整模型时要同时核对构造方法与调用位置。对象创建完成后,页面仍不会自动出现新卡片,真正触发业务列表变化的是 this.records = [record, ...this.records]。
这次赋值创建了一个新数组,把新记录放在首位,再把旧记录依次接到后面。RecordList() 读取更新后的 records,ForEach 按数组顺序生成 RecordCard,一次保存操作至此才形成“表单输入 → 记录对象 → 列表展示”的完整闭环。
排查保存问题时,可以按下面六个检查点从输入端逐步向列表端确认:
- 检查点一:输入框是否触发
onChange。 - 检查点二:目标
@State是否收到新值。 - 检查点三:
validateForm()是否允许继续。 - 检查点四:字符串是否正确转为数字。
- 检查点五:新对象字段是否装配正确。
- 检查点六:
records是否完成重新赋值。
不要一开始就把问题归因于 ForEach。如果状态从未更新、校验提前返回,或者对象创建后没有写入 records,列表都不会出现新数据;按链路逐段验证,比同时修改多个方法更容易找到根因。
九、字段设计为什么这样分组
理解字段用途比记住字段顺序更重要。当前模型可以分为设备定位、产品模具、材料、工艺参数、结果时间和唯一标识六组,每组都回答现场复盘中的一类问题。
1. 设备定位字段
machineNo 标识本次使用的注塑机,回答“这次调试发生在哪台设备上”。演示值 IM-120T-01 便于直接观察页面;生产环境更适合从机台档案中选择,避免同一设备因为人工输入而出现多种编号写法。本文尚未实现机台选择器,这属于后续档案模块的职责。
2. 产品与模具字段
productName 说明本次加工对象,moldNo 说明采用哪套模具。两者不能简单合并,因为同一产品可能使用不同模具版本,而同一模具也可能经历维修或改版。当前示例使用名称和编号保持直观,生产系统则可以进一步保存产品 ID 与模具档案 ID。
3. 材料字段
material 记录原材料名称,因为材料变化会直接影响温度、压力和周期的组合。教程使用 ABS 作为演示值;真实系统还可能需要牌号、供应商和批次,但这些扩展不会改变本篇“输入、校验、创建记录、刷新列表”的数据链路。
4. 工艺数值字段
barrelTemp、moldTemp、injectionPressure、holdingPressure 和 cycleTime 构成核心工艺参数。它们在表单阶段以字符串保存,写入记录前才转换为数字,这样既适配 TextInput,又便于后续排序、统计和范围校验。
当前校验只要求数值大于零,并不代表参数一定符合工艺要求。不同机台、材料和模具的合理范围可能不同,生产规则应来自设备能力或工艺配置,而不是把一套固定上下限写死在页面中。
5. 结果与时间字段
result 保存本次调试结论。自由文本适合快速记录缩水、飞边等现场现象,但不利于稳定统计;生产系统可以使用“结果枚举 + 补充备注”的组合,在保留表达空间的同时获得结构化数据。
createdAt 用于显示记录时间,示例新增记录写入“刚刚”,适合即时反馈,却不适合作为长期存储值。持久化时应保存可排序的标准时间,再由显示层转换为友好文本。
6. 唯一标识字段
id 用于区分不同记录,ForEach 再把它转换为稳定的字符串 key。示例使用 Date.now() 生成 ID,适合单机低频演示;高并发或跨设备写入时应改用数据库主键或 UUID。后者只是生产化建议,当前代码并未实现。
十、状态管理与刷新机制
1. @State 解决的问题
普通字段可以保存数据,但它的变化不一定驱动界面更新。@State 把字段纳入组件状态管理:组件渲染时读取状态,框架据此建立状态与 UI 的依赖,状态变化后再更新相关界面。开发者不需要逐个查找并修改 Text,这正是声明式 UI 的基本思路。
2. 表单状态与业务状态
machineNo 等字段属于表单状态,描述用户当前正在编辑的内容;records 属于业务列表状态,保存已经提交的记录;errorMessage 属于反馈状态,说明最近一次校验为什么失败。三类状态职责不同,本篇分别声明,便于初学者从界面输入一路追踪到业务结果。
3. 为什么重新赋值数组
当前代码没有调用 push(),而是创建一个同时包含新旧数据的数组引用。这样既能直观看到最新记录位于开头,也让状态变化更容易观察。若改成原地修改数组,需要结合项目所用状态管理机制确认观察规则;本文只解释当前源码采用的重新赋值方式。
4. 列表 key 的作用
ForEach 的第三个参数根据记录 id 生成 key。key 应在列表生命周期内保持稳定,而且不能在两条记录之间重复;否则框架可能复用错误节点,表现为内容错位或更新异常。排查列表问题时,除了检查数组长度,也要确认 ID 与 key 是否可靠。
5. 状态更新的调试方法
调试状态链路时,先观察输入框是否显示新值,再检查保存前字段、validateForm() 返回值、新对象内容和 records.length。每次只验证一个环节,比同时修改多处代码更可靠。验证结束后应删除临时输出,也不要把敏感生产参数写入公开日志。
十一、表单校验的设计边界
1. 空字符串
trim() 用于排除纯空格输入,否则空格可能被误当成有效内容。当前正数校验已经覆盖空数值,但文本字段尚未执行同等校验,因此机台编号为空时仍可能创建记录。生产版本应为必要文本字段增加明确规则。
2. 非数字文本
Number(value) 尝试把文本转换为数字,失败时返回 NaN,再由 Number.isNaN(result) 识别。NaN 不能进入工艺记录,否则后续比较、统计和范围判断都可能产生错误结果。
3. 零和负数
当前规则要求数值大于零,可以挡住零值和负数,却不能证明参数符合工艺要求。例如过高温度仍会通过当前校验,生产系统需要按字段配置范围,并允许规则随材料、机台或模具变化。
4. 小数输入
Number() 可以解析合法小数,因此 32.5 能够转换为数字。是否允许小数以及保留几位精度,应由具体工艺规则决定;本文没有限制小数位数,生产系统可在保存前统一精度。
5. 错误提示顺序
validateForm() 按字段顺序检查,并且一次只显示第一个错误。这样的实现直接、提示清晰,但用户可能需要多次提交才能修正全部问题。另一种方案是收集所有错误后统一展示,不过它会增加状态结构和界面复杂度,本篇暂时采用单错误提示。
6. 校验通过后的状态
校验成功时必须清空 errorMessage,否则旧错误会继续留在页面。扩展校验分支时既要检查失败提示,也要确认成功路径仍然能够恢复正常状态。
十二、逐步定位常见故障
故障一:输入框无法修改
- 检查
TextInput是否绑定当前状态。 - 检查
onChange是否接收新值。 - 检查回调是否写回正确字段。
- 检查是否误写到其他同名变量。
故障二:点击保存没有反应
- 检查按钮是否绑定
onClick。 - 检查事件中是否调用
addRecord()。 - 检查
validateForm()是否提前返回。 - 检查页面是否显示
errorMessage。
故障三:数值转换后异常
- 检查输入中是否包含单位文本。
- 输入框只应保存数字字符串。
- 单位应由界面固定展示。
- 检查小数点是否符合解析格式。
- 检查是否绕过了正数校验。
故障四:记录生成但列表不更新
- 检查
records.length是否增加。 - 检查是否重新赋值给
records。 - 检查
records是否带有@State。 - 检查
RecordList()是否读取同一数组。 - 检查新记录是否位于数组首项。
故障五:列表内容错位
- 检查每条记录的
id是否唯一。 - 检查 key 是否来自稳定字段。
- 检查构造参数顺序是否错位。
- 检查卡片读取的字段是否正确。
故障六:重新打开应用后数据消失
- 当前记录只存在页面内存中。
- 页面示例没有执行持久化。
- 退出应用后丢失属于预期边界。
- 下一阶段可接入本地存储。
- 接入前应先设计数据版本迁移。
故障七:错误信息一直存在
- 检查成功校验是否清空错误。
- 检查新增分支是否遗漏清理。
- 检查输入变化时是否需要即时清除。
- 本文选择保存成功后统一清除。
十三、从演示页面走向生产模块
当前页面适合学习基础数据链路,但它还不是可以直接投入生产的完整模块。实际落地前,需要继续补齐存储、档案、校验、身份、时间、附件、审计、性能和安全等能力。
1. 数据持久化
页面内存不能承担长期记录,需要根据部署方式选择本地数据库或后端服务。离线车间还要设计同步策略,并在写入失败时明确反馈,不能先显示保存成功再悄悄丢失数据。
2. 档案关联
机台、产品和模具最好来自对应档案,而不是完全依赖自由文本。业务记录保存稳定的关联 ID,界面继续展示名称和编号,这样既便于用户阅读,也更适合后续统计。
3. 参数范围
不同机台具有不同能力边界,不同材料也有各自的加工窗口,因此统一正数校验只能挡住明显错误。生产规则应支持动态范围,并根据风险把越界结果区分为提示、警告或禁止保存。
4. 操作身份
调试记录通常需要操作人员、班次,某些流程还需要审核人。身份信息不应由用户随意填写,而应来自登录状态、组织信息或排班数据。
5. 时间与时区
文本“刚刚”只适合即时展示,持久化时应保存标准时间值。涉及多设备或跨地区同步时还要统一时区策略,再由显示层生成友好的本地时间文本。
6. 异常与附件
结果字段可以扩展异常分类,并关联现场照片或检测报告。附件上传需要处理失败、重试和断点场景,敏感生产资料还必须配置访问控制。
7. 编辑与审计
生产记录可以允许更正,但不应覆盖历史痕迹。系统可记录修改人、时间、原因与变更前内容,具体审计强度取决于企业管理制度。
8. 性能与分页
少量内存记录可以直接渲染,长期积累后则需要分页或分段加载。筛选应尽量在数据层完成,避免把全部历史记录一次性放入页面。
9. 安全与隐私
日志不应输出完整生产参数,接口需要鉴权与传输保护,导出文件也要限制访问范围。本文没有实现这些生产能力,因此不能把演示页面直接视为生产方案。
十四、完整代码
以下是该页面的完整示例代码:
class InjectionDebugRecord {
id: number
machineNo: string
productName: string
moldNo: string
material: string
barrelTemp: number
moldTemp: number
injectionPressure: number
holdingPressure: number
cycleTime: number
result: string
createdAt: string
constructor(
id: number,
machineNo: string,
productName: string,
moldNo: string,
material: string,
barrelTemp: number,
moldTemp: number,
injectionPressure: number,
holdingPressure: number,
cycleTime: number,
result: string,
createdAt: string
) {
this.id = id
this.machineNo = machineNo
this.productName = productName
this.moldNo = moldNo
this.material = material
this.barrelTemp = barrelTemp
this.moldTemp = moldTemp
this.injectionPressure = injectionPressure
this.holdingPressure = holdingPressure
this.cycleTime = cycleTime
this.result = result
this.createdAt = createdAt
}
}
@Entry
@Component
struct InjectionDebugPage {
@State machineNo: string = 'IM-120T-01'
@State productName: string = '外壳件 A01'
@State moldNo: string = 'MOLD-A01'
@State material: string = 'ABS'
@State barrelTemp: string = '230'
@State moldTemp: string = '65'
@State injectionPressure: string = '85'
@State holdingPressure: string = '58'
@State cycleTime: string = '32'
@State result: string = '调试正常'
@State errorMessage: string = ''
@State records: InjectionDebugRecord[] = [
new InjectionDebugRecord(
1,
'IM-120T-01',
'外壳件 A01',
'MOLD-A01',
'ABS',
230,
65,
85,
58,
32,
'调试正常,产品表面无明显缺陷',
'2026-07-20 09:30'
)
]
private validateForm(): boolean {
if (!this.isPositiveNumber(this.barrelTemp)) {
this.errorMessage = '料筒温度需要填写有效数字'
return false
}
if (!this.isPositiveNumber(this.moldTemp)) {
this.errorMessage = '模具温度需要填写有效数字'
return false
}
if (!this.isPositiveNumber(this.injectionPressure)) {
this.errorMessage = '注射压力需要填写有效数字'
return false
}
if (!this.isPositiveNumber(this.holdingPressure)) {
this.errorMessage = '保压压力需要填写有效数字'
return false
}
if (!this.isPositiveNumber(this.cycleTime)) {
this.errorMessage = '成型周期需要填写有效数字'
return false
}
this.errorMessage = ''
return true
}
private isPositiveNumber(value: string): boolean {
const result = Number(value)
return value.trim().length > 0 && !Number.isNaN(result) && result > 0
}
private toNumber(value: string): number {
return Number(value)
}
private addRecord(): void {
if (!this.validateForm()) {
return
}
const record = new InjectionDebugRecord(
Date.now(),
this.machineNo,
this.productName,
this.moldNo,
this.material,
this.toNumber(this.barrelTemp),
this.toNumber(this.moldTemp),
this.toNumber(this.injectionPressure),
this.toNumber(this.holdingPressure),
this.toNumber(this.cycleTime),
this.result,
'刚刚'
)
this.records = [record, ...this.records]
}
build() {
Scroll() {
Column() {
this.Header()
this.FormArea()
this.RecordList()
}
.width('100%')
.padding(16)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F7FA')
}
@Builder
Header() {
Column() {
Text('注塑机调试记录')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#1F2937')
Text('记录每一次调机参数,方便后续复盘和复用')
.fontSize(14)
.fontColor('#6B7280')
.margin({ top: 6 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 16 })
}
@Builder
FormArea() {
Column() {
Text('本次调试参数')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#111827')
.margin({ bottom: 12 })
this.InputItem('机台编号', this.machineNo, (value: string) => {
this.machineNo = value
})
this.InputItem('产品名称', this.productName, (value: string) => {
this.productName = value
})
this.InputItem('模具编号', this.moldNo, (value: string) => {
this.moldNo = value
})
this.InputItem('材料', this.material, (value: string) => {
this.material = value
})
this.InputItem('料筒温度', this.barrelTemp, (value: string) => {
this.barrelTemp = value
})
this.InputItem('模具温度', this.moldTemp, (value: string) => {
this.moldTemp = value
})
this.InputItem('注射压力', this.injectionPressure, (value: string) => {
this.injectionPressure = value
})
this.InputItem('保压压力', this.holdingPressure, (value: string) => {
this.holdingPressure = value
})
this.InputItem('成型周期', this.cycleTime, (value: string) => {
this.cycleTime = value
})
this.InputItem('调试结果', this.result, (value: string) => {
this.result = value
})
if (this.errorMessage.length > 0) {
Text(this.errorMessage)
.fontSize(13)
.fontColor('#DC2626')
.margin({ top: 4, bottom: 4 })
}
Button('保存调机记录')
.width('100%')
.height(44)
.margin({ top: 12 })
.backgroundColor('#2563EB')
.fontColor(Color.White)
.onClick(() => {
this.addRecord()
})
}
.width('100%')
.padding(16)
.borderRadius(8)
.backgroundColor(Color.White)
.margin({ bottom: 16 })
}
@Builder
InputItem(label: string, value: string, onChange: (value: string) => void) {
Column() {
Text(label)
.fontSize(13)
.fontColor('#4B5563')
.margin({ bottom: 6 })
TextInput({ text: value, placeholder: `请输入${label}` })
.height(40)
.fontSize(14)
.backgroundColor('#F9FAFB')
.borderRadius(6)
.onChange((inputValue: string) => {
onChange(inputValue)
})
}
.width('100%')
.margin({ bottom: 10 })
}
@Builder
RecordList() {
Column() {
Row() {
Text('最近调试记录')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor('#111827')
Blank()
Text(`${this.records.length} 条`)
.fontSize(13)
.fontColor('#2563EB')
}
.width('100%')
.margin({ bottom: 12 })
ForEach(this.records, (item: InjectionDebugRecord) => {
this.RecordCard(item)
}, (item: InjectionDebugRecord) => item.id.toString())
}
.width('100%')
}
@Builder
RecordCard(item: InjectionDebugRecord) {
Column() {
Row() {
Text(item.machineNo)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#111827')
Blank()
Text(item.createdAt)
.fontSize(12)
.fontColor('#6B7280')
}
.width('100%')
Text(`${item.productName} / ${item.moldNo} / ${item.material}`)
.fontSize(13)
.fontColor('#4B5563')
.margin({ top: 6 })
Text(`料筒 ${item.barrelTemp}℃,模温 ${item.moldTemp}℃,注射 ${item.injectionPressure}MPa,保压 ${item.holdingPressure}MPa,周期 ${item.cycleTime}s`)
.fontSize(13)
.fontColor('#374151')
.margin({ top: 8 })
Text(item.result)
.fontSize(13)
.fontColor('#059669')
.margin({ top: 8 })
}
.width('100%')
.padding(14)
.borderRadius(8)
.backgroundColor(Color.White)
.margin({ bottom: 10 })
}
}
十五、本篇小结与系列衔接
本文完成了"注塑工程师助手"的第一个核心页面——注塑机调试记录。
通过本篇学习,你已掌握:
- 如何定义调机记录的数据模型
- 如何用 ArkUI 搭建工艺参数录入表单
- 如何用
@State管理页面输入状态 - 如何通过按钮点击生成新记录
- 如何用
ForEach渲染调试记录列表
这个页面虽然功能精简,但已具备真实业务 App 的基本形态。下一篇我们将继续扩展"注塑机档案列表",管理机台编号、吨位、品牌、车间位置及运行状态,为后续生产记录与调机历史功能打下基础。
更多推荐


所有评论(0)