基于HarmonyOS API 24的ArkTS声明式UI深度实战:HarmonyOS 6.1.1多模态交互架构下的社区团购次日自提电商应用全链路解析
一、鸿蒙技术栈全景:从HarmonyOS 6.1.1到ArkTS API 24的声明式UI范式演进
HarmonyOS 6.1.1作为华为全场景分布式操作系统的最新迭代版本,在前端UI框架层面带来了显著的架构升级与能力增强。其核心UI开发语言ArkTS基于TypeScript超集扩展,融合了声明式编程范式与响应式状态管理机制,为开发者提供了一套类型安全、编译期校验完备的高性能UI开发体系。在HarmonyOS ArkTS API 24中,ArkUI框架的组件模型、状态管理装饰器体系、布局引擎以及动画子系统均经历了深度优化,使得复杂交互场景下的渲染性能与开发体验达到了全新的高度。
ArkTS语言的核心设计哲学在于"声明式描述UI结构,响应式驱动状态更新"。与传统的命令式UI编程不同,开发者不再需要手动操作DOM节点或调用增量更新接口,而是通过@Component装饰器标注自定义组件,在build()方法中以声明式语法描述UI的当前状态。当@State、@Prop、@Link等状态装饰器修饰的变量发生变化时,ArkUI框架的差分渲染引擎会自动计算出最小的UI更新补丁,并批量应用到组件树上。这种"状态驱动视图"的模式从根源上消除了状态与视图之间的同步不一致风险。
在HarmonyOS 6.1.1中,ArkUI框架引入了多项关键改进。首先是布局引擎的性能优化,Column、Row、Stack等基础容器组件的测量与布局流程经过了原生代码级的加速,在处理大规模列表和嵌套容器场景时帧率更加稳定。其次是动画系统的完善,animation属性修饰器和animateTo全局API提供了更丰富的动画曲线与回调机制。此外,API 24还增强了ForEach渲染控制组件的键值生成策略和diff算法效率,在大数据量场景下的列表更新性能有显著提升。
声明式UI范式在ArkTS中的实现依赖于编译期的代码转换。ArkTS编译器会将build()方法中声明的DSL语法链式调用转换为高效的组件创建指令。例如,Text('hello').fontSize(14)在编译期会被转换为创建Text组件实例并设置fontSize属性的字节码。这种编译期转换策略避免了运行时解析的开销,使得ArkUI的首次渲染速度和后续更新速度都具备了接近原生代码的执行效率。
ArkTS的类型系统在TypeScript基础上进一步收紧。在ArkUI上下文中,所有组件属性方法均接受强类型参数。例如fontSize()方法接受number或Length类型,fontColor()接受ResourceColor类型(即Color枚举值或字符串)。这种严格的类型约束在编译期即可捕获绝大多数的类型错误,大幅降低了运行时崩溃的风险。同时,ArkTS对interface和class的使用也施加了特定约束,例如接口属性不能使用!非空断言修饰符,且对象字面量必须符合接口定义的结构类型。
状态管理是ArkUI架构的核心支柱。@State装饰器为组件创建响应式状态变量,当变量被重新赋值时触发组件的重新渲染。@Prop用于父到子的单向同步,@Link用于父子双向同步。在HarmonyOS 6.1.1中,这些装饰器的实现底层依赖于ObservedObject的代理机制:框架会拦截被装饰变量的getter/setter,在setter被调用时通知所有依赖该变量的UI节点进行更新。这种细粒度的依赖追踪机制确保了状态更新时只有真正受影响的UI片段会重新渲染。
本文将以一个完整的社区团购电商应用——"多多买菜"为案例,深入剖析HarmonyOS ArkTS API 24在实际业务场景中的架构设计、状态管理策略、组件复用模式以及复杂交互的实现方案。该应用模拟了拼多多旗下"多多买菜"社区团购业务的核心功能,包含首页瀑布流商品展示、分类分栏浏览、购物袋管理、订单全生命周期追踪、个人中心数据可视化等模块,并集成了6种不同类型的弹窗交互、柱状图数据可视化、进度条动画等丰富的UI效果。
二、应用业务背景:社区团购次日自提电商模型
社区团购是一种以社区为单位、以自提点为履约终端的电商模式。用户在前一天下单,商品在次日清晨配送至社区附近的指定自提点,用户凭借提货码到自提点取货。这种模式省去了"最后一公里"的配送成本,通过集中采购和统仓统配实现了极致的价格优势。
多多买菜应用完整模拟了这一业务流程。应用采用底部双排8 Tab的导航架构:上排为运营位(签到、领券、客服),下排为主功能位(首页、分类、购物袋、订单、我的)。这种设计借鉴了拼多多成熟的运营策略,通过运营位引导用户参与营销活动,通过主功能位完成核心交易闭环。
应用包含18个SKU商品,覆盖时令水果、新鲜蔬菜、肉禽蛋品、海鲜水产、乳品烘焙、粮油调味、休闲零食、日用百货等8大品类。每个商品携带价格、原价、销量、标签、分类、单位等完整属性。此外还有6个秒杀商品、6个购物袋商品、8条订单记录、3个自提点,构成了一个完整的电商数据生态。
在交互层面,应用实现了6种弹窗:规格选择底部抽屉(含数量步进器)、提货码中央弹窗、新增备注表单、编辑自提点单选列表、删除订单警示框、缺货赔付明细卡。这些弹窗覆盖了电商场景中绝大多数的模态交互模式,每一种都采用了不同的定位策略和布局方案。
三、架构层次总览
应用的整体架构清晰分为三层。最顶层是入口组件DuoDuoGroceryApp,它持有activeTab状态变量控制当前显示的页面,同时管理cartCount购物袋数量角标。中间层是内容区域contentArea,通过条件渲染根据activeTab的值动态切换5个页面组件。最底层是底部导航栏,包含运营排和主排两个Row容器。
入口组件采用@Entry装饰器标注,表示这是应用的根入口。@Component装饰器将其声明为ArkUI自定义组件。@State activeTab和@State cartCount两个状态变量分别管理当前激活的Tab索引和购物袋商品数量。当用户点击底部Tab时,activeTab被更新,触发contentArea的条件渲染分支切换,从而显示对应的页面内容。
四、类型定义深度解析
ArkTS的类型系统是其区别于普通JavaScript的核心特征之一。应用首先定义了5个interface接口,为整个应用的数据层提供了严格的类型契约。
4.1 商品类型GoodsItem
interface GoodsItem {
id: number
name: string
icon: string
price: number
original: number
sold: string
tag: string
category: string
unit: string
}

GoodsItem接口定义了商品的核心数据结构。id为数字类型的唯一标识符,用于ForEach渲染时的键值生成和商品查找。name和icon分别存储商品名称和Emoji图标——在真实业务中icon通常指向图片资源URL,此处使用Emoji是为了简化演示。price和original为数字类型的现价与原价,类型选择number而非string是因为后续需要进行toFixed(2)格式化和金额计算。
sold字段定义为string类型而非number,这是一个值得注意的设计决策。在电商场景中,销量数字经常以"2.3万+“这样的格式展示,纯数字类型无法直接表达这种"万级+模糊后缀"的语义。因此将sold定义为字符串,直接存储展示文本,避免了在渲染时进行数字到文本的格式化转换。tag存储营销标签文本如"新人价”“热销”,category用于分类筛选,unit存储计量单位如"份"“箱”“盒”。
在ArkTS的接口约束下,任何声明为GoodsItem类型的对象字面量必须包含以上所有属性,且每个属性的类型必须与定义一致。编译器会在编译期检查所有对象字面量的结构合规性。如果某个商品缺少unit属性,或price被赋值为字符串,编译器会报错并阻止构建。
4.2 购物袋类型CartItem
interface CartItem {
id: number
name: string
icon: string
price: number
count: number
unit: string
stock: number
}
CartItem接口描述购物袋中的商品条目。与GoodsItem相比,它去掉了original、sold、tag、category字段——因为购物袋中不需要展示原价、销量、标签和分类信息。新增了count(购买数量)和stock(库存数量)两个字段。stock为0表示该商品暂时缺货,这一状态在UI层会触发"缺货赔付"弹窗的展示逻辑。
这种"按场景定制接口"的设计方式体现了ArkTS类型系统的灵活性。不同页面关注的数据维度不同,使用不同的接口定义可以避免携带冗余字段,同时让类型约束更加精准。
4.3 订单类型OrderItem
interface OrderItem {
id: number
no: string
goodsText: string
icon: string
amount: number
count: number
status: string
time: string
station: string
}
OrderItem接口定义了订单的数据结构。no为订单编号字符串如"DD20260826001",采用字符串类型而非数字,因为订单编号通常包含日期信息和前缀,不适合用数字类型。goodsText是商品摘要文本如"蓝莓2盒、土鸡蛋20枚、鲜牛奶3组 等5件",将多商品信息压缩为一条可读字符串,这是电商订单列表的常见展示策略。
amount为订单总金额,count为商品件数,status为订单状态字符串。status字段使用字符串而非枚举,值为"待提货"“已提货”"退款中"等。在更严格的架构中,应使用联合类型或枚举来约束status的取值范围,但当前设计通过运行时条件判断实现状态路由,在可读性和类型安全性之间取得了平衡。time存储下单时间文本,station存储自提点名称。
4.4 秒杀商品类型SecKillItem
interface SecKillItem {
id: number
name: string
icon: string
price: number
original: number
progress: number
}
SecKillItem接口专为秒杀商品设计。progress字段存储抢购进度百分比(0-100),用于在UI层渲染进度条。这是秒杀场景特有的数据维度——通过展示"已抢78%"这样的信息制造紧迫感,驱动用户快速决策。
4.5 自提点类型StationItem
interface StationItem {
id: number
name: string
distance: string
address: string
hours: string
}

StationItem接口定义了自提点的数据结构。distance存储距离文本如"350m",使用字符串类型而非数字,原因与sold字段类似——距离值可能携带"m"或"km"等不同单位后缀,统一存储为字符串避免了单位转换逻辑。hours存储营业时间如"06:00 - 22:00"。
五、静态数据层架构
应用的数据层完全由文件顶部的const常量定义,采用静态数据池的模式。这种模式在原型开发和功能演示中非常高效——所有数据在编译期即已确定,无需异步网络请求,UI可以同步渲染。
GOODS_ALL是一个包含18条GoodsItem记录的数组常量,覆盖了8大品类。每条记录的icon使用Emoji字符如’🫐’‘🍌’‘🍎’,在真实应用中这些会被替换为网络图片URL。商品价格从1.99元到49.9元不等,原价设置在现价的1.4-2倍区间,符合社区团购的折扣定价策略。
CATEGORY_LIST是一个字符串数组,包含9个分类项:8个品类名称加上1个"全部商品"选项。在ArkTS中,数组字面量['时令水果', '新鲜蔬菜', ...]的类型会被推断为string[],与const声明结合后成为一个不可变引用的字符串数组。注意ArkTS中const修饰的是引用不可变性,即变量不能重新赋值,但数组内容理论上仍可修改(不过在当前代码中没有任何地方修改这些常量数组的内容)。
5.1 分类筛选纯函数getGoodsByCategory
function getGoodsByCategory(cat: string): GoodsItem[] {
const result: GoodsItem[] = []
for (let i = 0; i < GOODS_ALL.length; i++) {
if (cat === '全部商品' || GOODS_ALL[i].category === cat) {
result.push(GOODS_ALL[i])
}
}
return result
}

getGoodsByCategory是一个全局纯函数,接受分类名称字符串,返回该分类下的商品数组。函数内部使用传统的for循环遍历GOODS_ALL数组,通过条件判断筛选匹配分类的商品。当传入"全部商品"时,由于短路或运算符||的特性,条件直接为true,返回所有商品。
这个函数定义在组件外部,是ArkTS允许的顶层函数声明。在ArkTS中,function声明的函数可以在任何作用域定义,包括文件顶层。全局函数不依赖于组件实例,可以在任意组件的build()方法中直接调用。这种"数据逻辑与UI逻辑分离"的设计模式使得代码结构更加清晰,数据筛选逻辑可以独立测试和复用。
值得注意的是,该函数每次调用都会创建一个新的result数组。在ArkUI的响应式系统中,由于函数在build()方法内被调用时,其返回值直接参与UI渲染,每次重新渲染都会重新执行筛选。对于18条数据来说,这个开销可以忽略,但在大数据量场景下应考虑缓存优化。
六、入口组件与底部双排Tab导航深度剖析
6.1 枚举定义GroceryTab
enum GroceryTab {
HOME = 0,
CATEGORY = 1,
CART = 2,
ORDER = 3,
MINE = 4
}

ArkTS支持TypeScript风格的enum枚举类型。GroceryTab枚举定义了5个主Tab的索引值,分别为首页(0)、分类(1)、购物袋(2)、订单(3)、我的(4)。使用枚举而非魔法数字(如直接用0、1、2)的好处在于:代码可读性大幅提升,activeTab === GroceryTab.HOME比activeTab === 0语义清晰得多;同时枚举值在编译期即被替换为数字常量,运行时无额外开销。
6.2 入口组件结构
@Entry
@Component
struct DuoDuoGroceryApp {
@State activeTab: GroceryTab = GroceryTab.HOME
@State cartCount: number = 10
// ...
}
@Entry装饰器标记DuoDuoGroceryApp为应用的入口组件,整个页面树的根节点。@Component装饰器声明它是一个ArkUI自定义组件。这两个装饰器通常配合使用:@Entry确保该组件在应用启动时被自动渲染为页面根节点,@Component使其具备了自定义组件的全部能力(如build()方法、@Builder方法、状态装饰器等)。
@State activeTab: GroceryTab = GroceryTab.HOME声明了一个响应式状态变量,初始值为首页。当activeTab的值被修改(例如用户点击Tab后执行this.activeTab = tab),ArkUI框架会检测到变化并重新执行依赖此变量的UI分支——即contentArea中的条件渲染逻辑。
@State cartCount: number = 10管理购物袋商品数量角标。初始值为10,表示购物袋中已有10件商品。这个值会影响底部Tab栏购物袋图标的角标显示:当cartCount > 0时,购物袋图标右上角显示红色数字角标。
6.3 内容区域条件渲染contentArea
@Builder contentArea() {
Column() {
if (this.activeTab === GroceryTab.HOME) {
GroceryHomeContent()
} else if (this.activeTab === GroceryTab.CATEGORY) {
CategorySplitContent()
} else if (this.activeTab === GroceryTab.CART) {
ShoppingCartContent()
} else if (this.activeTab === GroceryTab.ORDER) {
GroceryOrderContent()
} else {
GroceryMineContent()
}
}
.layoutWeight(1)
}
@Builder装饰器是ArkUI中用于声明可复用UI构建方法的关键装饰器。被@Builder标注的方法不会创建新的组件实例,而是将其中的UI描述内联到调用处。这与@Component标注的自定义组件不同——@Component组件是独立的可复用单元,拥有自己的状态和生命周期;而@Builder方法更像是UI片段的"宏展开"。
contentArea方法内部使用if-else if-else条件语句根据activeTab的值渲染不同的页面组件。ArkTS支持在build()方法和@Builder方法中使用条件语句(if)和循环语句(for、while)来控制UI的结构。这与React JSX中的条件渲染类似,但ArkTS的条件渲染是在ArkUI框架的DSL层面实现的,编译器会将if分支转换为条件节点。
当activeTab变化时,ArkUI的差分渲染引擎会比较新旧组件树的结构差异。例如从GroceryHomeContent()切换到CategorySplitContent()时,引擎会卸载首页组件树并挂载分类页组件树。Column().layoutWeight(1)确保内容区域占据底部Tab栏之外的所有剩余空间。layoutWeight(1)是ArkUI Flex布局的弹性权重属性,值为1表示该组件在主轴方向上尽可能扩展以填充父容器的剩余空间。
关于@Builder装饰器的技术要点:@Builder方法可以接受参数,参数可以是基本类型、对象或函数(回调)。在本例中,contentArea不接受参数,直接访问组件的this.activeTab状态变量。@Builder方法内部的UI描述与build()方法完全一致的语法,可以使用所有ArkUI组件和属性方法。
6.4 运营位Tab项promoTabItem
@Builder promoTabItem(icon: string, label: string) {
Column() {
Text(icon).fontSize(15)
Text(label).fontSize(9).fontColor('#2E7D32').margin({ top: 1 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
.padding({ top: 4, bottom: 4 })
.backgroundColor('#E8F5E9')
.onClick(() => { this.activeTab = this.activeTab })
}

promoTabItem是一个参数化的@Builder方法,接受icon和label两个字符串参数,分别作为Tab的图标和文字。方法内部构建了一个Column容器,垂直排列图标Text和文字Text。
Column是ArkUI中最基础的布局容器之一,其作用是将子组件在垂直方向(从上到下)依次排列。Column的常用属性包括:alignItems设置子组件在水平方向的对齐方式(HorizontalAlign.Start居左、HorizontalAlign.Center居中、HorizontalAlign.End居右);justifyContent设置子组件在垂直方向的分布方式(FlexAlign.Start顶部对齐、FlexAlign.Center居中、FlexAlign.SpaceBetween两端对齐等);layoutWeight设置弹性权重。
此处Column().alignItems(HorizontalAlign.Center)使图标和文字水平居中对齐。padding({ top: 4, bottom: 4 })设置了上下4vp的内边距。backgroundColor('#E8F5E9')设置浅绿色背景。layoutWeight(1)使该Tab项在父Row中均分宽度——3个运营位Tab各占1/3宽度。
onClick(() => { this.activeTab = this.activeTab })是一个"空操作"——将activeTab赋值给它自身。这实际上是运营位Tab的占位逻辑,表示签到、领券、客服这些功能暂未实现页面跳转。在真实应用中,这里应替换为打开对应功能页面的导航逻辑。
6.5 主Tab项bottomTabItem与角标
@Builder bottomTabItem(icon: string, label: string, tab: GroceryTab) {
Column() {
Stack() {
Column() {
Text(icon).fontSize(19).opacity(this.activeTab === tab ? 1.0 : 0.42)
}
.alignItems(HorizontalAlign.Center)
if (tab === GroceryTab.CART && this.cartCount > 0) {
Text(this.cartCount.toString()).fontSize(8).fontColor('#FFFFFF')
.backgroundColor('#E53935').borderRadius(8)
.padding({ left: 4, right: 4, top: 1, bottom: 1 })
.position({ x: 22, y: -4 })
}
}
.height(24)
Text(label).fontSize(9)
.fontColor(this.activeTab === tab ? '#07C160' : '#999999')
.fontWeight(this.activeTab === tab ? FontWeight.Bold : FontWeight.Normal)
.margin({ top: 1 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
.padding({ top: 5, bottom: 5 })
.onClick(() => { this.activeTab = tab })
}

bottomTabItem是主Tab项的构建方法,比运营位Tab更复杂。它接受3个参数:icon图标、label文字、tab对应的枚举值。方法内部使用了Stack容器来叠加图标和数量角标。
Stack是ArkUI的层叠布局容器,其作用是将子组件堆叠在一起,后声明的子组件覆盖在先声明的子组件之上。Stack的常用属性包括:alignContent设置子组件的对齐方式(默认为Alignment.Center居中);可以通过子组件的position属性实现绝对定位。在当前代码中,Stack内放置了图标Column和(条件渲染的)角标Text,角标通过position({ x: 22, y: -4 })绝对定位到图标右上方,实现了红点角标效果。
opacity(this.activeTab === tab ? 1.0 : 0.42)通过三元表达式动态控制图标透明度。当前激活的Tab图标完全不透明(1.0),非激活Tab图标半透明(0.42),实现了选中态的视觉区分。opacity属性接受0到1之间的浮点数,0完全透明、1完全不透明。
角标的条件渲染逻辑if (tab === GroceryTab.CART && this.cartCount > 0)只有在当前Tab是购物袋且购物数量大于0时才显示角标。this.cartCount.toString()将数字转为字符串显示。backgroundColor('#E53935')设置红色背景,borderRadius(8)使角标呈圆角矩形。padding({ left: 4, right: 4, top: 1, bottom: 1 })设置内边距使文字与边缘有适当间距。position({ x: 22, y: -4 })将角标相对于Stack左上角偏移到右上方位置,y: -4为负值表示超出Stack上边界,营造角标"悬浮"在图标右上角的视觉效果。
文字标签的颜色和字重也通过三元表达式动态切换:激活态为绿色#07C160加粗FontWeight.Bold,非激活态为灰色#999999常规FontWeight.Normal。FontWeight是ArkUI内置的字体粗细枚举,包含Normal、Bold、Medium、Light等值。
onClick(() => { this.activeTab = tab })是Tab切换的核心逻辑。用户点击Tab后,将activeTab赋值为该Tab对应的枚举值,触发contentArea的条件渲染更新和所有bottomTabItem的选中态样式更新。这是ArkUI响应式状态管理的经典应用场景:一次状态变更同时驱动内容区域和导航栏的UI更新。
6.6 build方法的整体布局
build() {
Column() {
this.contentArea()
Column() {
Row() {
this.promoTabItem('📅', '签到')
this.promoTabItem('🎟️', '领券')
this.promoTabItem('💬', '客服')
}
.width('100%').backgroundColor('#E8F5E9')
Row() {
this.bottomTabItem('🏠', '首页', GroceryTab.HOME)
this.bottomTabItem('🗂️', '分类', GroceryTab.CATEGORY)
this.bottomTabItem('🛒', '购物袋', GroceryTab.CART)
this.bottomTabItem('📦', '订单', GroceryTab.ORDER)
this.bottomTabItem('👤', '我的', GroceryTab.MINE)
}
.width('100%').backgroundColor('#FFFFFF')
.padding({ top: 3, bottom: 6 })
.shadow({ radius: 10, color: '#14000000', offsetY: -3 })
}
.width('100%')
}
.width('100%').height('100%')
.backgroundColor('#F5FFF7')
}
build()方法是每个ArkUI自定义组件必须实现的核心方法,返回该组件的UI描述。入口组件的build()方法构建了一个最外层Column,内部依次包含内容区域和底部导航栏。
Row是ArkUI的另一个基础布局容器,与Column相对,其作用是将子组件在水平方向(从左到右)依次排列。Row的常用属性包括:alignItems设置子组件在垂直方向的对齐方式(VerticalAlign.Top顶对齐、VerticalAlign.Center居中、VerticalAlign.Bottom底对齐);justifyContent设置子组件在水平方向的分布方式。
底部导航栏由两个Row组成:上方的运营排和下方的主排。运营排Row使用浅绿色背景#E8F5E9,内含3个promoTabItem。主排Row使用白色背景,内含5个bottomTabItem。
shadow({ radius: 10, color: '#14000000', offsetY: -3 })为主排Tab栏添加阴影效果。radius控制模糊半径(值越大阴影越模糊),color使用8位十六进制颜色#14000000——前两位14为alpha通道(约8%不透明度),后六位000000为黑色。offsetY: -3使阴影向上偏移3vp,模拟光线从下方照射的效果,使Tab栏产生"悬浮"在内容上方的视觉层次感。
外层Column设置width('100%').height('100%')使其占满整个屏幕。backgroundColor('#F5FFF7')设置极浅绿色背景作为应用底色。width和height属性接受字符串(百分比或带单位的长度值)或数字(默认单位vp)。'100%'表示占满父容器。
七、首页模块深度剖析
7.1 首页组件状态定义
@Component
struct GroceryHomeContent {
@State showSpec: boolean = false
@State specCount: number = 1
@State selectedGoods: GoodsItem = GOODS_ALL[0]
@State specUnit: string = '1份'
// ...
}
首页组件GroceryHomeContent使用@Component装饰器声明为自定义组件(注意没有@Entry,因为它不是入口组件,而是被contentArea调用的子组件)。它管理4个响应式状态变量。
@State showSpec: boolean = false控制规格选择弹窗的显示/隐藏。boolean类型的初始值为false表示弹窗默认不显示。当用户点击商品卡片的"+"按钮时,showSpec被设为true,弹窗渲染显示。
@State specCount: number = 1管理规格弹窗中数量步进器的当前值,初始为1。用户在弹窗中点击"+"或"−"按钮时修改此值,弹窗中的数量显示文本同步更新。
@State selectedGoods: GoodsItem = GOODS_ALL[0]存储当前选中的商品对象。初始值为GOODS_ALL数组的第一个元素。当用户点击不同商品时,selectedGoods被重新赋值,弹窗中的商品名称、价格、销量等信息同步更新。这里体现了ArkTS状态管理的重要特性:@State修饰的引用类型变量,当整个引用被替换时(this.selectedGoods = g),框架检测到引用变化并触发重新渲染。
@State specUnit: string = '1份'管理规格弹窗中选中的规格单位文本。用户可以在"1份"“2份装”"家庭装"三个选项间切换,选中项的背景色变为绿色,文字变为白色。
7.2 遮罩层Builder modalOverlay
@Builder modalOverlay(onClose: () => void) {
Column()
.width('100%').height('100%')
.backgroundColor('rgba(10,50,20,0.55)')
.onClick(onClose)
}
modalOverlay是一个参数化的@Builder方法,接受一个onClose回调函数作为参数。它构建一个全屏半透明遮罩层Column。backgroundColor('rgba(10,50,20,0.55)')使用RGBA颜色格式,最后一个参数0.55表示55%不透明度,实现半透明遮罩效果。颜色值(10,50,20)偏向深绿色,与应用的绿色主题协调。
onClick(onClose)将点击事件直接绑定到传入的回调函数。这个设计实现了遮罩层的通用化——任何弹窗都可以复用这个遮罩层,只需传入自己的关闭逻辑。点击遮罩层时调用onClose回调,通常将对应的show*状态变量设为false来关闭弹窗。
在ArkTS中,@Builder方法接受函数类型的参数是合法的。onClose: () => void表示一个无参数无返回值的回调函数。这种"将行为作为参数传入"的设计模式使得UI组件的可复用性大幅提升。
7.3 规格选择弹窗specModal
规格选择弹窗是应用中最复杂的弹窗之一,采用底部抽屉式设计,包含商品信息、规格选择、数量步进和操作按钮四个区域。
@Builder specModal() {
Column() {
this.modalOverlay(() => { this.showSpec = false })
Column() {
Row() {
Column() {
Text(this.selectedGoods.icon).fontSize(40)
}
.width(84).height(84).backgroundColor('#E8F5E9').borderRadius(12)
.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)
Column() {
Text('¥' + this.selectedGoods.price.toFixed(2)).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#E53935')
Text('已选:' + this.specUnit + ' × ' + this.specCount.toString()).fontSize(11).fontColor('#666666').margin({ top: 3 })
Text('库存 ' + this.selectedGoods.sold + '人已买').fontSize(10).fontColor('#BDBDBD').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Start).margin({ left: 12 })
Text('✕').fontSize(16).fontColor('#999999')
.onClick(() => { this.showSpec = false })
}
.width('100%').padding(16)
// ...规格选择和数量步进...
}
.width('100%').backgroundColor('#FFFFFF')
.borderRadius({ topLeft: 22, topRight: 22 })
.position({ x: 0, y: '55%' })
}
.width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(999)
}
弹窗的最外层是一个Column,包含两部分:遮罩层modalOverlay和实际内容Column。外层Column设置position({ x: 0, y: 0 })使其脱离正常文档流,从屏幕左上角开始定位。zIndex(999)设置层级为999,确保弹窗覆盖在所有正常流组件之上。zIndex属性在ArkUI中用于控制兄弟组件的渲染顺序,值越大的组件越晚渲染(即在视觉上覆盖在上方)。
内容Column使用position({ x: 0, y: '55%' })将其固定在屏幕55%高度处,即从屏幕中偏下位置开始向上展开。这种底部抽屉定位方式是移动端弹窗的经典设计模式。borderRadius({ topLeft: 22, topRight: 22 })只设置左上和右上的圆角,营造从底部"滑出"的视觉效果。
商品信息行使用Row水平排列:左侧84×84的商品图标方块,中间的价格和库存信息,右侧的关闭按钮。Column内的商品图标使用justifyContent(FlexAlign.Center)实现图标在垂直方向的居中。FlexAlign是ArkUI Flex布局的对齐枚举,Center表示居中对齐。
价格文本Text('¥' + this.selectedGoods.price.toFixed(2))使用字符串拼接和toFixed(2)方法将价格格式化为两位小数。toFixed()是JavaScript/TypeScript的Number原型方法,在ArkTS中同样可用,返回保留指定小数位数的字符串表示。fontSize(20).fontWeight(FontWeight.Bold).fontColor('#E53935')设置20vp大字号、加粗、红色(电商价格标配色)。
规格选择区域
Column() {
Text('规格').fontSize(12).fontColor('#666666').width('100%')
Row() {
Text('1份').fontSize(12)
.fontColor(this.specUnit === '1份' ? '#FFFFFF' : '#333333')
.backgroundColor(this.specUnit === '1份' ? '#07C160' : '#F5F5F5')
.borderRadius(8).padding({ left: 16, right: 16, top: 6, bottom: 6 }).margin({ right: 8 })
.onClick(() => { this.specUnit = '1份' })
Text('2份装').fontSize(12)
.fontColor(this.specUnit === '2份装' ? '#FFFFFF' : '#333333')
.backgroundColor(this.specUnit === '2份装' ? '#07C160' : '#F5F5F5')
.borderRadius(8).padding({ left: 16, right: 16, top: 6, bottom: 6 }).margin({ right: 8 })
.onClick(() => { this.specUnit = '2份装' })
Text('家庭装').fontSize(12)
.fontColor(this.specUnit === '家庭装' ? '#FFFFFF' : '#333333')
.backgroundColor(this.specUnit === '家庭装' ? '#07C160' : '#F5F5F5')
.borderRadius(8).padding({ left: 16, right: 16, top: 6, bottom: 6 })
.onClick(() => { this.specUnit = '家庭装' })
}
.margin({ top: 8 })
}
规格选择区域展示了ArkUI中"按钮组"的常见实现方式。三个Text组件作为规格选项,每个选项的样式通过三元表达式动态切换:选中时白字绿底(#FFFFFF文字 + #07C160背景),未选中时深字灰底(#333333文字 + #F5F5F5背景)。borderRadius(8)使选项呈圆角矩形。padding和margin的组合控制选项的内部留白和选项间距。
每个选项的onClick回调将该选项的规格文本赋值给specUnit状态变量。当specUnit变化时,三个选项的样式条件重新求值,选中态自动切换到新选中的选项。这是ArkUI响应式系统在交互场景中的典型应用——通过状态变量驱动UI样式,无需手动操作DOM或调用样式API。
数量步进器
Row() {
Text('数量').fontSize(13).fontColor('#333333').layoutWeight(1)
Row() {
Text('−').fontSize(18).fontColor('#666666')
.width(30).height(30).backgroundColor('#F5F5F5').borderRadius(15)
.textAlign(TextAlign.Center)
.onClick(() => {
if (this.specCount > 1) { this.specCount = this.specCount - 1 }
})
Text(this.specCount.toString()).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#333333')
.width(44).textAlign(TextAlign.Center)
.scale({ x: 1.0, y: 1.0 })
Text('+').fontSize(18).fontColor('#FFFFFF')
.width(30).height(30).backgroundColor('#07C160').borderRadius(15)
.textAlign(TextAlign.Center)
.onClick(() => { this.specCount = this.specCount + 1 })
}
}
数量步进器由减号按钮、数字显示和加号按钮三部分组成。减号按钮的onClick逻辑包含边界检查if (this.specCount > 1),确保数量不会降到1以下。加号按钮无上限检查(在真实应用中应增加库存上限校验)。
TextAlign.Center是ArkUI的文本对齐枚举,使文字在组件范围内居中。减号按钮使用灰色背景#F5F5F5表示"次要操作",加号按钮使用绿色背景#07C160表示"主要操作",这种视觉区分符合电商场景中鼓励用户增加购买量的设计意图。
scale({ x: 1.0, y: 1.0 })是ArkUI的变换属性,可以缩放组件。当前值为1.0表示无缩放。在更高级的实现中,可以通过animateTo在数量变化时触发缩放动画,实现"点击+数字弹跳"的微交互效果。
底部操作按钮
Row() {
Text('加入购物袋').fontSize(14).fontColor('#07C160')
.layoutWeight(1).textAlign(TextAlign.Center)
.backgroundColor('#E8F5E9').borderRadius(20)
.padding({ top: 11, bottom: 11 })
.onClick(() => { this.showSpec = false })
Text('立即下单').fontSize(14).fontColor('#FFFFFF')
.layoutWeight(1).textAlign(TextAlign.Center)
.backgroundColor('#07C160').borderRadius(20)
.padding({ top: 11, bottom: 11 }).margin({ left: 10 })
.onClick(() => { this.showSpec = false })
}
底部两个按钮使用layoutWeight(1)均分宽度,中间用margin({ left: 10 })留出间距。"加入购物袋"使用浅绿底绿字(次要操作),"立即下单"使用实心绿底白字(主要操作)。两个按钮的onClick都只是关闭弹窗(在真实应用中应执行加入购物车或下单的业务逻辑)。
7.4 商品瀑布卡goodsCard
@Builder goodsCard(g: GoodsItem) {
Column() {
Column() {
Text(g.icon).fontSize(46)
}
.width('100%').height(90).backgroundColor('#F1F8E9')
.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center)
Text(g.name).fontSize(12).fontColor('#333333').maxLines(2)
.width('100%').margin({ top: 6 })
Row() {
Text(g.tag).fontSize(8).fontColor('#E53935').backgroundColor('#FFEBEE')
.padding({ left: 4, right: 4, top: 1, bottom: 1 }).borderRadius(4)
Text(g.sold + '人付款').fontSize(9).fontColor('#BDBDBD').margin({ left: 4 })
}
.width('100%').margin({ top: 4 })
Row() {
Column() {
Row() {
Text('¥').fontSize(10).fontColor('#E53935')
Text(g.price.toFixed(2)).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#E53935')
}
Text('¥' + g.original.toFixed(2)).fontSize(9).fontColor('#CCCCCC')
.decoration({ type: TextDecorationType.LineThrough })
}
.alignItems(HorizontalAlign.Start).layoutWeight(1)
Column() {
Text('+').fontSize(14).fontColor('#FFFFFF')
.width(24).height(24).borderRadius(12)
.backgroundColor('#07C160').textAlign(TextAlign.Center)
}
.onClick(() => {
this.selectedGoods = g
this.specCount = 1
this.specUnit = '1份'
this.showSpec = true
})
}
.width('100%').alignItems(VerticalAlign.Bottom).margin({ top: 6 })
}
.width('48%').backgroundColor('#FFFFFF').borderRadius(12).padding(8)
.margin({ left: 4, right: 4, top: 8 })
.onC
goodsText: string
icon: string
amount: number
count: number
if (this.showStation) { this.stationModal() }
}
.width('100%').height('100%')
}
}

19.10 总结:
应用实现了6种弹窗交互、步进器交互、Tab切换交互、列表选择交互、文本输入交互等多种交互模式。所有交互都遵循ArkUI的"事件触发→状态更新→UI重渲染"三步范式:用户操作触发onClick/onChange事件回调,回调中修改@State变量,框架自动重新渲染受影响的UI。这种统一的交互处理范式使得不同类型的交互(点击、输入、选择)在代码层面具有一致的结构,降低了理解和维护成本。
综上所述,本应用全面展示了HarmonyOS ArkTS API 24在电商场景中的技术实践能力。从声明式UI范式到响应式状态管理,从布局容器体系到弹窗架构设计,从数据可视化到动画特效,HarmonyOS 6.11的ArkUI框架提供了一套完整、高效、类型安全的移动应用开发解决方案。开发者通过掌握@State/@Builder/@Component装饰器体系、Column/Row/Stack布局容器、ForEach/if渲染控制、linearGradient/shadow/animation视觉属性等核心技术,即可构建出功能丰富、交互流畅、视觉精美的移动应用。
更多推荐


所有评论(0)