鸿蒙 ArkTS 实战:香料库存管理的状态驱动界面与业务逻辑
鸿蒙 ArkTS 实战:香料库存管理的状态驱动界面与业务逻辑
前言
香料名称、当前克数、补货阈值和香料数组,是一个使用 ArkTS 与 ArkUI 实现的鸿蒙单页工具。
本文从 entry/src/main/ets/pages/Index.ets 出发,结合真实代码说明状态字段、派生函数、输入控件、数组更新和结果展示。
页面输入改变后,结果立即刷新,是声明式状态管理最直观的表现。
业务规则独立于组件树,能够让界面结构和计算逻辑同时保持清晰。
ArkTS 负责业务表达,ArkUI 负责声明式界面,@State 连接输入与结果,成员函数 承载计算规则,边界保护 保证输出稳定。
图示说明:上图展示 DevEco Studio 开发环境,本文代码均来自当前项目的 Index.ets 页面入口。
一、项目定位
1.1 使用场景
香料库存管理把日常场景中的记录、计算和状态判断集中到一个页面。
用户通过滑块、文本框或按钮修改参数,页面根据当前状态给出反馈。
1.2 功能闭环
- 读取默认状态。
- 修改业务参数。
- 执行计算或数组操作。
- 查看结果与状态文字。
1.3 当前边界
当前功能位于 Index 组件中。
@State 保存交互数据,成员函数处理规则,build 组织 UI。
文章只分析代码已经实现的功能。
1.4 分析维度
| 层次 | 关注内容 | 页面体现 |
|---|---|---|
| 数据 | 状态和接口 | 输入与记录 |
| 规则 | 函数和阈值 | 计算与判断 |
| 交互 | 控件和回调 | 状态变化 |
| 展示 | ArkUI 组件 | 结果与列表 |
二、入口组件
2.1 页面定义
interface SpiceItem{id:number;name:string;qty:number;threshold:number}
@Entry
@Entry 标识页面入口。
@Component 声明可渲染组件。
Index 集中承载状态、方法与 build。
2.2 状态入口
@Component
struct Index{
页面初始值决定首次打开时的结果。
数值落在控件范围内,数组数据用于展示列表初始状态。
2.3 构建函数
@State nameText:string='黑胡椒'
@State qtyText:string='120'
build 使用 Column、Row、Text、Slider、TextInput、Button、Scroll 和 ForEach 组织页面。
2.4 布局顺序
结果区位于输入区之前。
用户打开页面就能看到当前状态,再向下调整参数。
三、状态字段
3.1 字段清单
| 状态字段 | 页面含义 | 业务作用 |
|---|---|---|
| nameText | 页面状态 | 参与香料库存管理 |
| qtyText | 页面状态 | 参与香料库存管理 |
| thresholdText | 页面状态 | 参与香料库存管理 |
| spices | 页面状态 | 参与香料库存管理 |
| 这些字段同时连接控件、函数与展示区域。 |
3.2 状态职责
nameText、qtyText、thresholdText、spices 分别承担输入、模式、记录集合或过程状态。
字段名称使用业务语义,阅读代码时容易定位。
3.3 响应式更新
@State 改变后,读取该字段的组件会重新计算。
页面不需要手动刷新 Text 节点。
3.4 初始状态代码
@State thresholdText:string='30'
@State spices:SpiceItem[]=[{id:1,name:'黑胡椒',qty:120,threshold:30},{id:2,name:'孜然',qty:20,threshold:25}]
初始状态不仅提供示例,也用于验证布局和公式。
四、核心规则
4.1 规则说明
新增克数不小于 0,阈值至少为 1;数量小于等于阈值时计入补货;每次使用减少 10 克且不低于 0。
4.2 函数分工
项目中的关键函数包括:parseValue、addSpice、useSpice、lowCount。
这些函数分别处理转换、计算、状态映射或数组更新。
4.3 真实实现
parseValue(t:string,f:number):number{const v=Number.parseFloat(t);return Number.isFinite(v)?v:f}
addSpice():void{const n=this.nameText.trim();if(n.length===0){return};const id=this.spices.length===0?1:this.spices[this.spices.length-1].id+1;this.spices=[{id:id,name:n,qty:Math.max(0,this.parseValue(this.qtyText,0)),threshold:Math.max(1,this.parseValue(this.thresholdText,1))},...this.spices];this.nameText=''}
函数直接读取当前状态,并把结果返回给 UI。
4.4 第二段规则
useSpice(id:number):void{this.spices=this.spices.map((item:SpiceItem)=>{if(item.id===id){item.qty=Math.max(0,item.qty-10)};return item})}
lowCount():number{let n=0;this.spices.forEach((item:SpiceItem)=>{if(item.qty<=item.threshold){n+=1}});return n}
复杂判断被留在成员函数中,build 只负责调用和展示。
五、边界处理
5.1 数字输入
文本数字进入业务逻辑前通过 parseInt 或 parseFloat 转换。
转换失败时使用兜底值或阻止操作。
5.2 范围保护
数量、剩余值和进度不应出现非法负数。
比例和阈值通过 Math.max、Math.min 或条件分支保护。
5.3 空值保护
名称写入数组前使用 trim。
空名称和无效数字不会创建记录。
5.4 边界代码
build(){Column(){Column(){Text('香料库存管理').fontSize(28).fontWeight(FontWeight.Bold);Text('需要补货 '+this.lowCount().toString()+' 种').fontSize(16).fontColor('#DC2626').margin({top:6})}.width('100%').padding(20).backgroundColor('#FEF3C7');Scroll(){Column({space:14}){Column(){Row({space:8}){TextInput({text:this.nameText,placeholder:'香料名称'}).layoutWeight(1).onChange((v:string)=>{this.nameText=v});TextInput({text:this.qtyText,placeholder:'克数'}).width(88).onChange((v:string)=>{this.qtyText=v});TextInput({text:this.thresholdText,placeholder:'阈值'}).width(88).onChange((v:string)=>{this.thresholdText=v})};Button('加入香料柜').width('100%').margin({top:12}).backgroundColor('#D97706').onClick(()=>{this.addSpice()})}.padding(18).backgroundColor('#FFFFFF').borderRadius(10);ForEach(this.spices,(item:SpiceItem)=>{Row(){Column(){Text(item.name).fontSize(18).fontWeight(FontWeight.Bold);Text(item.qty.toFixed(0)+'g · 阈值 '+item.threshold.toFixed(0)+'g').fontSize(13).fontColor('#64748B').margin({top:4})}.layoutWeight(1);Text(item.qty<=item.threshold?'补货':'充足').fontWeight(FontWeight.Bold).fontColor(item.qty<=item.threshold?'#DC2626':'#166534');Button('用10g').margin({left:8}).backgroundColor('#FEF3C7').fontColor('#92400E').onClick(()=>{this.useSpice(item.id)})}.padding(14).backgroundColor('#FFFFFF').borderRadius(10)},(item:SpiceItem)=>item.id.toString())}.padding(18)}.scrollBar(BarState.Off)}.width('100%').height('100%').backgroundColor('#FFFBEB')}
}
这段真实代码体现了当前页面对输入或结果范围的处理。
六、控件设计
6.1 Slider
Slider 处理时间、数量、重量、尺寸、里程或等级。
min、max 和 step 将输入限制在业务区间。
6.2 TextInput
TextInput 保存名称或数字文本。
onChange 只负责写回状态,转换工作交给业务函数。
6.3 Button
Button 用于新增、切换、消耗、完成或重新生成。
按钮文案和颜色可以从状态派生。
6.4 控件代码
@State nameText:string='黑胡椒'
控件回调更新 @State 后,结果和状态文字同步变化。
七、数组与列表
7.1 记录结构
库存、事务和座位页面通过接口描述记录。
稳定的 id 用于定位和 ForEach key。
7.2 新增记录
新记录通常插入数组首位,让最新内容优先显示。
新增前会检查名称和数字。
7.3 更新记录
map 适合修改单条记录的数量、完成状态或占用状态。
filter 适合移除已经耗尽的记录。
7.4 二维数组
教室座位页面使用 SeatItem[][]。
外层数组代表行,内层数组代表每行座位。
八、布局分析
8.1 顶部结果
核心数字使用更大字号。
状态文案提供自然语言解释。
8.2 输入区域
参数控件集中在白色内容区域中。
统一内边距让标签与控件保持清晰关系。
8.3 状态颜色
| 状态 | 视觉表达 | 页面含义 |
|---|---|---|
| 正常或充足 | 绿色、青色 | 当前无需处理 |
| 中间状态 | 灰色、黄色 | 继续关注 |
| 临期、补货或超限 | 红色、深色 | 需要及时行动 |
8.4 滚动内容
Scroll 承载增长中的记录和网格。
滚动条可以隐藏,但纵向浏览能力保留。
九、交互流程
9.1 项目操作
新增香料并设置阈值;使用 10 克后库存与补货状态同步更新。
9.2 状态闭环
- 用户改变参数。
- 回调写回 @State。
- 派生函数重新执行。
- 结果文字和统计刷新。
- 数组型页面重新渲染列表。
9.3 操作反馈
| 操作 | 状态变化 | 页面反馈 |
|---|---|---|
| 拖动滑块 | 数值更新 | 结果变化 |
| 输入文本 | 文本更新 | 可提交内容变化 |
| 点击切换 | 布尔值或索引变化 | 文案和颜色变化 |
| 操作记录 | 数组元素变化 | 列表与统计变化 |
9.4 事件职责
回调保持短小。
业务判断放在具名函数中。
build 因此继续保持声明式结构。
十、ArkUI 组件职责
10.1 Text
Text 展示标题、单位、数值和状态。
字体层级区分主结果与辅助信息。
10.2 Row 与 Column
Column 负责纵向分区。
Row 负责并排展示统计和按钮。
layoutWeight 分配剩余宽度。
10.3 ForEach
ForEach 读取数组并生成记录项。
稳定 key 有利于列表更新。
10.4 Scroll
Scroll 让列表在移动端保持可浏览。
固定输入区和可增长列表形成清晰结构。
十一、声明式更新
11.1 单一数据来源
输入只保存在 @State。
结果由函数派生。
UI 不重复维护同一份计算结果。
11.2 计算与展示
新增克数不小于 0,阈值至少为 1;数量小于等于阈值时计入补货;每次使用减少 10 克且不低于 0。
公式位于函数中,Text 只读取返回值。
11.3 数组更新
数组重新赋值后,ForEach 读取最新集合。
统计函数也基于同一数组重新计算。
11.4 状态文案
状态函数把数字转换成临期、充足、需要保养、马上休息或已完成等文字。
这比只展示一个数值更容易理解。
十二、运行表现
12.1 首次打开
默认值立即生成结果。
用户无需先提交一次操作。
12.2 参数变化
滑块和按钮触发回调。
多个读取同一状态的区域同时刷新。
12.3 列表变化
新增、map 更新和 filter 移除形成完整记录链路。
顶部统计始终对应当前数组。
12.4 当前范围
当前代码聚焦单页交互和内存状态。
重新启动后的持久化、远程同步和多人协作不属于已实现范围。
十三、公式逐步推演
13.1 输入量拆分
香料库存管理的核心输入可以分成基础量、修正量和状态量。
基础量决定计算起点,修正量改变最终结果,状态量负责页面反馈。
当前示例输入分别是:黑胡椒 120g、阈值 30g、孜然 20g、阈值 25g。
这些值都直接来自页面的 @State 字段,不存在隐藏配置。
13.2 计算顺序
第一步读取所有输入,确认数值已经落在 Slider 或文本转换允许的范围内。
第二步执行基础计算,得到未应用修正条件之前的中间值。
第三步应用项目中的条件分支,对周期、得分、数量或状态进行修正。
第四步执行 Math.max、Math.min、Math.round 或取整操作。
第五步把最终结果交给 Text 和状态函数展示。
本项目的计算主线是:当前克数与阈值共同决定补货状态。
13.3 示例结果
使用默认状态进行计算时,关键中间结果为:孜然进入补货状态。
页面最终展示为:使用一次减少 10g。
这个结果可以从源代码中的状态初值和成员函数逐项复算。
因此顶部结果不是固定文案,而是当前输入的派生值。
13.4 公式特征
| 计算阶段 | 输入来源 | 输出作用 |
|---|---|---|
| 基础值 | 主状态字段 | 建立计算起点 |
| 条件修正 | 布尔值、索引或阈值 | 改变周期或结果 |
| 范围保护 | Math.max / Math.min | 阻止非法结果 |
| 格式处理 | round / floor / toFixed | 生成稳定显示 |
十四、边界场景验证
14.1 最小输入
最小输入用于确认页面不会出现负数、除零或不可读结果。
当前项目的最小值由 Slider 的 min 或业务函数中的 Math.max 共同约束。
当输入接近下限时,结果区仍然会显示明确数字和状态文字。
14.2 最大输入
最大输入用于观察结果是否超出界面承载范围。
Slider 的 max 限制了用户可以提交的最大数值。
结果使用整数、百分比、克数、分钟或状态词展示,不会无限增长。
14.3 条件边界
本项目最关键的条件边界是:库存不会被扣成负数。
测试边界时需要同时观察函数返回值和 UI 文案。
等于阈值、刚低于阈值、刚高于阈值应分别验证。
这三个点可以发现大多数大于号和大于等于号错误。
14.4 状态恢复
项目中的恢复行为是:数量等于阈值时已经属于补货范围。
恢复操作会修改源状态,派生结果随后自动更新。
它不是单纯改变按钮文字,而是重新建立下一轮记录的起点。
十五、状态迁移分析
15.1 输入状态
页面打开时先使用代码中的默认值。
用户拖动滑块、输入文本或点击模式按钮后,回调写回 @State。
ArkUI 根据依赖关系刷新读取这些字段的组件。
15.2 派生状态
派生状态不单独存储,而是由成员函数实时计算。
这种方式避免输入字段和结果字段出现不同步。
同一函数可以同时服务顶部结果、说明文字和统计卡片。
15.3 操作状态
操作前、操作中和操作后的状态可以表示为:
| 阶段 | 数据变化 | 页面反馈 |
|---|---|---|
| 初始 | 使用默认值 | 展示默认结果 |
| 调整 | 一个或多个字段变化 | 结果实时刷新 |
| 确认 | 执行业务方法 | 状态被记录或重置 |
| 再次调整 | 新状态继续变化 | 进入下一轮计算 |
15.4 误操作修正
对于可切换按钮,再次点击可以恢复另一种状态。
对于重置按钮,状态回到明确起点。
对于列表记录,稳定 id 保证只修改目标条目。
十六、控件与业务字段映射
16.1 Slider 映射
每个 Slider 都绑定一个 number 状态。
value 负责显示当前位置,min 和 max 定义业务边界,step 定义输入粒度。
onChange 中的 Math.round 用于保持整数语义。
16.2 Button 映射
Button 用于模式切换、状态确认或重置。
按钮文字读取当前状态,因此操作后能够立即反映新状态。
背景色承担选中、完成或风险提示,不参与业务计算。
16.3 Text 映射
Text 展示输入标签、计算结果和状态解释。
顶部结果使用较大字号,辅助说明使用较小字号和次要颜色。
动态文本只读取函数返回值,不在组件树中重复实现公式。
16.4 控件链路代码
build(){Column(){Column(){Text('洗车周期计算').fontSize(28).fontWeight(FontWeight.Bold);Text(this.statusText()).fontSize(17).fontWeight(FontWeight.Bold).fontColor('#0369A1').margin({top:8})}.width('100%').padding(20).backgroundColor('#E0F2FE');Column({space:16}){Column(){Text('距离上次洗车 '+this.daysSince.toString()+' 天').fontSize(18).fontWeight(FontWeight.Bold);Slider({value:this.daysSince,min:0,max:60,step:1}).onChange((v:number)=>{this.daysSince=Math.round(v)});Text('期间雨天 '+this.rainDays.toString()+' 天').fontSize(18).fontWeight(FontWeight.Bold).margin({top:12});Slider({value:this.rainDays,min:0,max:20,step:1}).onChange((v:number)=>{this.rainDays=Math.round(v)});Text('行驶 '+this.mileageKm.toString()+' km').fontSize(18).fontWeight(FontWeight.Bold).margin({top:12});Slider({value:this.mileageKm,min:0,max:3000,step:50}).onChange((v:number)=>{this.mileageKm=Math.round(v)})}.padding(18).backgroundColor('#FFFFFF').borderRadius(10);Button(this.outdoorPark?'露天停车':'室内停车').width('100%').height(48).backgroundColor('#0284C7').onClick(()=>{this.outdoorPark=!this.outdoorPark});Text('建议周期约 '+this.intervalDays().toString()+' 天。').fontSize(18).fontWeight(FontWeight.Bold).padding(18).backgroundColor('#F0F9FF').borderRadius(10);Button('标记已洗车').width('100%').height(50).backgroundColor('#075985').onClick(()=>{this.washNow()})}.padding(18)}.width('100%').height('100%').backgroundColor('#F0F9FF')}
Text(this.resultText())
这段链路对应“输入改变、状态写回、函数重算、文本刷新”四个阶段。
十七、运行案例
17.1 默认案例
默认案例采用 黑胡椒 120g、阈值 30g、孜然 20g、阈值 25g。
按照页面函数顺序计算后得到 孜然进入补货状态。
最终界面显示 使用一次减少 10g。
17.2 临界案例
把关键输入调整到阈值附近,可以观察状态文字的切换。
阈值前一个单位、阈值本身和阈值后一个单位应分别操作。
页面应只在代码规定的条件成立时改变状态。
17.3 恢复案例
执行确认、完成、清洁、重置或切换操作后,检查所有相关字段。
预期恢复行为为:数量等于阈值时已经属于补货范围。
随后再次改变输入,页面应继续正常计算。
17.4 连续操作
连续拖动 Slider 时,结果区会多次重算。
成员函数没有写入额外状态,因此不会累积上一次计算的副作用。
这保证了相同输入始终得到相同结果。
十八、实现细节深读
18.1 为什么使用普通成员函数
普通成员函数适合表达纯计算和轻量状态判断。
它可以直接读取 this 上的 @State 字段。
函数名让 build 中的调用具有业务语义。
18.2 为什么不保存重复结果
如果同时保存输入和计算结果,每次输入变化都必须手动同步两份数据。
当前代码让结果由函数派生,减少遗漏更新的可能。
这也是声明式页面保持一致性的关键。
18.3 为什么限制输入范围
范围限制既保护公式,也保护页面展示。
用户无法通过正常控件输入极端无效值。
业务函数仍保留兜底逻辑,形成第二层保护。
18.4 项目核心结论
当前克数与阈值共同决定补货状态。
默认案例证明当前公式能够从状态得到稳定结果。
边界案例证明阈值判断和恢复操作具有明确行为。
十九、调试定位路径
19.1 结果不正确
先记录所有 @State 当前值。
再单独调用核心函数复算中间结果。
最后检查格式化和状态文案是否读取了同一个函数。
19.2 控件不刷新
检查 value 是否绑定正确字段。
检查 onChange 是否真正给 @State 赋值。
检查 Text 是否读取了该字段或派生函数。
19.3 列表不刷新
确认数组操作完成后重新赋值。
确认 ForEach 使用稳定 id。
确认目标记录 id 与按钮回调传入值一致。
19.4 展示异常
检查动态文字是否过长。
检查 Row 中是否使用 layoutWeight。
检查按钮高度、卡片内边距和结果字号是否稳定。
二十、完整验证过程
20.1 验证准备
使用默认值打开页面,记录首屏结果。
逐个改变状态字段,每次只改变一个变量。
对照公式判断结果变化方向是否正确。
20.2 验证步骤
- 验证默认状态与手工计算一致。
- 验证最小值不会产生非法结果。
- 验证最大值仍可正常显示。
- 验证阈值两侧文案切换准确。
- 验证恢复操作修改了全部相关状态。
20.3 验证结果
默认输入可以得到 使用一次减少 10g。
关键边界规则为:库存不会被扣成负数。
恢复规则为:数量等于阈值时已经属于补货范围。
三类结果与当前源代码保持一致。
20.4 技术结论
该页面形成了完整的状态驱动闭环。
输入控件负责采集数据,成员函数负责业务计算,ArkUI 负责结果渲染。
项目规模虽小,但包含状态管理、边界保护、条件分支和交互反馈等完整要素。
二十一、字段级数据字典
21.1 字段语义
nameText 保存香料名,qtyText 保存数量文本,thresholdText 保存阈值,spices 保存库存记录。
这些字段不是为了凑齐页面控件,而是分别承担输入、计算、状态和展示职责。
字段类型在 ArkTS 中保持明确,数值使用 number,文本使用 string,开关使用 boolean,记录集合使用接口数组。
明确的数据字典能够让业务公式与 UI 控件一一对应。
21.2 字段写入位置
Slider 的 onChange 写入连续数值。
TextInput 的 onChange 写入原始文本。
Button 的 onClick 写入布尔值、索引或调用操作方法。
数组型状态通过重新赋值触发 ForEach 更新。
21.3 字段读取位置
核心计算函数读取业务输入。
状态函数读取计算结果并返回自然语言。
build 中的 Text 读取结果函数。
按钮样式读取布尔值或索引以显示当前状态。
21.4 字段依赖关系
香料库存管理的依赖链可以概括为:输入字段 -> 浮点转换、每次扣减 10g、低库存统计 -> 结果文本。
任何一个输入字段变化,都只影响依赖它的结果区域。
没有被读取的字段不会无意义地触发业务变化。
这种依赖关系让代码行为可以从字段反向追踪到界面。
二十二、默认值逐项复算
22.1 默认值作用
默认值让页面打开后立即形成完整业务结果。
它同时覆盖主要控件、核心函数和状态文案。
用户无需先输入一遍数据才能理解页面用途。
22.2 复算步骤
先从 @State 声明读取所有默认值。
再按照核心函数中的表达式顺序计算。
遇到条件分支时,明确当前默认值进入哪一个分支。
最后应用取整、上下限和格式化。
22.3 展示核对
核对顶部主结果是否与手工复算一致。
核对辅助统计是否使用同一批输入。
核对状态文案对应的阈值区间。
核对按钮选中样式是否符合默认布尔值或索引。
22.4 默认案例价值
默认案例不是测试数据堆砌,而是项目最短的可运行说明。
它能证明 浮点转换、每次扣减 10g、低库存统计 已经连通。
当默认结果错误时,可以沿输入、函数、展示三个层次定位。
二十三、条件分支深度分析
23.1 关键边界集合
本项目需要重点观察:数量等于阈值、数量低于 10g、库存归零。
每个边界都应测试边界前、边界值和边界后。
这样可以确认代码使用大于、大于等于、小于或小于等于是否符合实际语义。
23.2 条件优先级
多个条件同时成立时,代码从上到下决定优先结果。
风险、超限或完成状态通常比普通状态拥有更高优先级。
状态函数的分支顺序因此也是业务规则的一部分。
23.3 数值保护
Math.max 防止剩余量、库存、分钟或得分出现负数。
Math.min 防止比例或得分超过上限。
Math.round 与 Math.floor 分别表达四舍五入和向下取整。
格式化方法只改变展示,不改变原始状态。
23.4 布尔状态
布尔字段适合表示完成、清洁、预约、模式或开关。
按钮点击后直接取反,界面文案与颜色随之刷新。
重置方法则把布尔状态和相关数值一起恢复。
二十四、文章技术结论
24.1 业务完整性
香料库存管理已经具备输入、计算、边界处理和反馈四个环节。
页面没有依赖网络,也能形成完整的本地交互闭环。
24.2 代码完整性
@Entry、@Component、@State、成员函数和 build 组成清晰入口。
控件回调短小,业务规则集中,结果来源单一。
24.3 交互完整性
用户每次操作都有可见反馈。
数值变化更新结果,状态变化更新文案,数组变化更新列表。
24.4 最终结论
浮点转换、每次扣减 10g、低库存统计是本项目最核心的实现主线。
字段边界、条件顺序和恢复操作共同保证结果稳定。
这套实现展示了 ArkTS 在轻量生活工具中的状态驱动开发方式。
总结
香料库存管理展示了鸿蒙 ArkTS 工具页面的典型组织方式:
- @State 保存输入和记录。
- 成员函数承载规则和状态判断。
- Slider、TextInput 与 Button 修改状态。
- ForEach、map 和 filter 管理列表。
- Text、Row、Column、Scroll 组织界面。
香料名称、当前克数、补货阈值和香料数组这一场景通过明确的状态边界形成完整交互。
数据、规则、控件和反馈保持单向关系后,页面既容易操作,也容易阅读。
相关资源: - ArkTS 语言基础
- ArkUI 声明式开发
更多推荐



所有评论(0)