HarmonyOS 「校园二手交易商城」App应用实战 39 底部操作栏:ProductUtil 加购与购买
39 底部操作栏:ProductUtil 加购与购买
系列四:商品详情与列表 · 第 9 篇
对应工程:
features/multishoppingproduct/src/main/ets/components/ProductUtil.ets
引言
底部操作栏是详情页的"转化出口":加购、购买、客服、购物袋,所有促成下单的按钮都集中在这里。ProductUtil 组件把这一排操作浓缩成一条 48vp 高的圆角胶囊,用 SymbolGlyph 系统图标 + 双按钮组合,按断点切换"通栏左对齐"与"悬浮靠右"两种形态,购买按钮则通过 @Event 上抛事件,由页面层用 bindSheet 拉起支付半屏。本文拆解它的全部细节。
操作栏组成:图标组与双按钮
ProductUtil 的 build 是一条 Row,依次是三个 SymbolGlyph 图标(首页/购物袋/客服)与两个按钮(加购/购买):
Row() {
SymbolGlyph($r('sys.symbol.house')) // 首页
.fontSize(24)
.fontColor([$r('app.color.text_primary')])
.margin({ right: this.widthBp === WidthBreakpoint.WIDTH_SM ? 0 : 12 })
SymbolGlyph($r('sys.symbol.bag')) // 购物袋
.fontSize(24)
.fontColor([$r('app.color.text_primary')])
.margin({ right: this.widthBp === WidthBreakpoint.WIDTH_SM ? 0 : 12 })
SymbolGlyph($r('sys.symbol.message')) // 客服
.fontSize(24)
.fontColor([$r('app.color.text_primary')])
.margin({ right: this.widthBp === WidthBreakpoint.WIDTH_SM ? 0 : 12 })
if (this.widthBp !== WidthBreakpoint.WIDTH_SM) {
Blank()
.layoutWeight(1)
.displayPriority(1)
}
Button(this.labelState.addCartLabel) // 加购
.fontSize(16)
.fontColor('#CF0A2C')
.height(40)
.borderRadius(20)
.backgroundColor($r('app.color.buy_outline_background'))
.backgroundBlurStyle(BlurStyle.Thin, { colorMode: ThemeColorMode.LIGHT, scale: 1.0 })
.width(new WidthBreakpointType(128, 188, 140, 140).getValue(this.widthBp))
Button(this.labelState.buyLabel) // 购买
.fontSize(16)
.fontColor($r('sys.color.font_on_primary'))
.height(40)
.width(new WidthBreakpointType(64, 188, 140, 140).getValue(this.widthBp))
.borderRadius(20)
.backgroundColor('#CF0A2C')
.onClick(() => {
if (this.isLivePage || this.isSplitMode) {
return;
}
this.onBuyClick();
})
}
.layoutWeight(1)
.justifyContent(this.widthBp !== WidthBreakpoint.WIDTH_SM ? FlexAlign.Start : FlexAlign.SpaceBetween)
.backgroundBlurStyle(BlurStyle.Thin, { colorMode: ThemeColorMode.SYSTEM, adaptiveColor: AdaptiveColor.DEFAULT, scale: 1.0 })
.height(48)
.borderRadius(24)
.width('100%')设计要点:
SymbolGlyph系统符号:用$r('sys.symbol.house/bag/message')直接引用系统 SF 风格符号,替代本地 SVG 图标,天然适配多尺寸与深浅色,且零资源体积。Blank + displayPriority:非窄屏下插入Blank()占位,把按钮组推到最右;displayPriority(1)保证空间不足时 Blank 优先被压缩而不是按钮。窄屏下justifyContent(SpaceBetween)让图标与按钮两端分布,充分利用宽度。- 双按钮胶囊:加购为描边风格(
buy_outline_background+ 红色文字),购买为实心红(#CF0A2C+ 白色文字),主次分明;两按钮宽度随断点变化(窄屏 128/64,中屏 188/188,宽屏 140/140)。 backgroundBlurStyle毛玻璃:整条操作栏叠加BlurStyle.Thin系统模糊,滚动时内容从毛玻璃下透出,视觉更高级;ThemeColorMode.SYSTEM让模糊底色跟随系统深浅色。
断点驱动的布局差异
ProductUtil 接收父容器传入的 @Param widthBp,内部按断点做三处差异化:
- 图标间距:窄屏(SM)图标间距 0,宽屏 12vp——窄屏靠
SpaceBetween拉伸,宽屏靠显式 margin。 - 按钮宽度:
WidthBreakpointType(128, 188, 140, 140),中屏(MD)两按钮都加宽到 188vp 适配折叠屏展开态。 - 整体对齐:
justifyContent在窄屏SpaceBetween、宽屏Start,配合父容器alignRules(窄屏通栏、宽屏悬浮右栏)完成形态切换。
点击购买拉起 PayCard 半屏
购买按钮不直接弹窗,而是通过 @Event onBuyClick 上抛意图。ProductUtil 的 @Event 声明与守卫逻辑:
@Event private onBuyClick: () => void = () => {
};
// ...
Button(this.labelState.buyLabel)
.onClick(() => {
if (this.isLivePage || this.isSplitMode) {
return; // 直播页/分屏下禁用购买跳转
}
this.onBuyClick();
})页面层 ProductDetail 在 onBuyClick 回调里把 detailType 置为 'detailPayCard'、isDialogOpen 置 true,从而触发 bindSheet 打开 PayCard 半屏弹窗(620vp 高、居中、不可拖拽、点外不穿透)。PayCard 是支付模块 multishoppingpay 的组件,内部包含收货地址、规格选择、支付方式与支付按钮,属于"支付领域"的独立模块——操作栏与支付模块之间只隔一个 @Event,零 import 依赖。
需要说明的是,本项目的加购按钮与购物袋图标在演示工程中未绑定完整业务(购物袋完整逻辑见系列六 ShopBag/ShopList),但结构上它们都指向同一个目标:把用户操作转成状态变更,由页面层决定下一步动作。购物袋入口接入时,只需在 bag 图标的 onClick 里上抛另一个 @Event onBagClick,由页面层拉起 detailType = 'ShopBag' 的弹窗即可。
安全区与多端适配
操作栏的安全区适配由父容器 ProductDetailContent 完成:RelativeContainer 的 alignRules 让操作栏永远锚定容器底部,配合 expandSafeArea 沉浸式策略,系统手势区不会遮挡按钮;窄屏下宽度 calc(100% - 32vp) 自带左右 16vp 留白,视觉上"悬浮贴底",与 iPhone 风格 Home Indicator 类似但完全由 ArkUI 原生实现。
注意事项与总结
@Event上抛而非直接改父状态:ProductUtil不持有isDialogOpen,避免组件与页面状态强耦合;复用操作栏到直播页时只需换一个onBuyClick实现。- 守卫条件:
isLivePage || isSplitMode时禁用购买,避免在直播/分屏场景误触拉起支付弹窗;接入真实业务时建议把守卫逻辑下沉到页面层,操作栏保持"纯展示 + 上抛"。 displayPriority善用:悬浮栏空间紧张时优先压缩Blank而非图标,保证核心按钮永远可见。- 毛玻璃开销:
backgroundBlurStyle有 GPU 成本,滚动频繁的页面建议评估性能;必要时降级为半透明纯色背景。
ProductUtil 用 110 行实现了"图标入口 + 双按钮 + 断点形态 + 事件上抛"的完整操作栏,是 @Event 组件通信模式的最佳示范。系列四的收官篇,我们把镜头从详情页切回列表页,看入口 MobilePhoneList 如何用栅格与懒加载撑起商品列表。
更多推荐



所有评论(0)