48 直播与购物袋联动

系列五:直播与画中画 · 第 8 篇
对应工程:features/multishoppinglive + features/multishoppingpayLiveUtil.etsLiveBroadCastPage.etsShopBag.etsPayCard.ets

引言

直播带货的闭环是"看 → 点 → 买"。前几篇把"看"做完了,这篇打通"点"和"买":直播页底部购物袋入口 LiveUtil → 打开购物袋面板 ShopBag → 展示直播商品(live_bag_product_1-4)→ 点击「立即购买」拉起支付卡 PayCard。整个联动横跨 multishoppinglivemultishoppingpay 两个特性模块,靠 LiveBroadCastPage@Event 回调和 panelType 状态机串成一条完整的购买链路。

入口:LiveUtil 的购物袋按钮

LiveUtil 是直播页底部操作栏(评论输入 + 分享/点赞/购物袋)。购物袋按钮是红色圆钮,点击通过 @Event 上报父级:

Row() {
  SymbolGlyph($r('sys.symbol.bag_fill'))
    .fontSize(24)
    .fontColor([$r('sys.color.font_on_primary')])
}
.height(40)
.width(40)
.borderRadius(20)
.backgroundColor('#CF0A2C')
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.onClick(() => {
  this.onOpenShopBag();
})

@Event public onOpenShopBag: () => void 是 V2 组件的事件出口:组件只负责"我点了购物袋",至于打开面板还是跳页面,由父级决定。LiveBroadCast 把它一路转发,最终 LiveBroadCastPage 接住并打开面板。

打开购物袋:panelType 状态机

LiveBroadCastPage 用三个 @Local 字段管理面板状态:

@Local public isDialogOpen: boolean = false;   // 面板是否显示
@Local public isMoreDetail: boolean = true;    // 是否展开更多详情
@Local public panelType: string = 'ShopBag';   // 当前面板类型

购物袋入口统一走到同一段逻辑:

LiveBroadCast({
  onOpenShopBag: () => {
    this.panelType = 'ShopBag';
    this.isMoreDetail = true;
    this.isDialogOpen = true;
  }
})

这里做了三件事:切换到"购物袋"面板、重置为"更多详情"态、打开面板。panelType 是面板内容的状态机——只有两个值:'ShopBag'(购物袋)与 'detailPayCard'(支付卡)。面板内容区按 panelTypevisibility 互斥显示:

Column() {
  ShopBag({ ... })
    .visibility(this.panelType === 'ShopBag' ? Visibility.Visible : Visibility.None)
  PayCard({ ... })
    .visibility(this.panelType === 'detailPayCard' ? Visibility.Visible : Visibility.None)
}

面板呈现:bindSheet 与侧栏

同样的面板内容,在不同断点下用不同容器承载:

  • 小屏(XS/SM)Column 上挂 bindSheet($$this.isDialogOpen, this.panelContent(), { height: '620vp', preferType: SheetType.CENTER, ... }),620vp 的半屏居中弹窗,$$ 双向绑定让面板收起时自动同步 isDialogOpen = false
  • 大屏(MD/LG/XL)if (this.isDialogOpen) { this.sidePanel() },把面板作为右侧栏(宽 37.5%)直接嵌入页面,左侧直播舞台压缩到 62.5%。

小屏的 bindSheet 还配了 enableOutsideInteractive: false(禁止点击外部交互,保证直播画面不被误操作)与 showClose: false(用面板内部的关闭按钮)。

购物袋列表:ShopBag 与 ShopList

ShopBag 是购物袋容器:头部 ShopBagHeader + Tabs 分类 + ShopList 商品列表。

头部图标来自 ShopBagModel.getHeaderIcons,正是资源文件 icon_bag.svg(购物袋)、icon_search.svg(搜索)、icon_order.svg(订单):

static getHeaderIcons(context: Context): ShopBagHeaderIconConfig[] {
  return [
    { icon: $r('app.media.icon_bag'), name: ... },
    { icon: $r('app.media.icon_search'), name: ... },
    { icon: $r('app.media.icon_order'), name: ... }
  ];
}

ShopList 渲染购物袋商品,图片正是直播专属的 live_bag_product_1-4.png(与直播商品的 live_product_1-8 是两套资源,购物袋是"已加购"的精选集合):

{
  image: $r('app.media.live_bag_product_1'),
  name: 'iPad Air 5',
  remark: g($r('app.string.SHOP_BAG_P1_REMARK')),
  discounts: [g($r('app.string.SHOP_BAG_DISCOUNT_POINTS')), g($r('app.string.SHOP_BAG_DISCOUNT_FLAGSHIP'))],
  price: 2199
}

每件商品卡片尾部是"立即购买"按钮——它来自 ShopBag 向下注入的 @BuilderParam payCardButton

@BuilderParam public payCardButton: () => void = this.payCardBuilder;

// ShopList 内
Row() {
  Text(this.currencyPrefix + JSON.stringify(item.getPrice())) ...
  Blank()
  this.payCardButton()     // 渲染父级注入的按钮
}

@BuilderParam 让"按钮"成为可注入的插槽:LiveBroadCastPage 注入的正是 payCardButton 构建器——点击后 panelType = 'detailPayCard'、打开支付卡面板:

@Builder
private payCardButton() {
  Text($r('app.string.LIVE_PAGE_BUY_NOW'))
    .height(28)
    .borderRadius(14)
    .fontSize(12)
    .backgroundColor('#CF0A2C')
    .onClick(() => {
      this.panelType = 'detailPayCard';
      this.isDialogOpen = true;
    })
}

购买流程:从直播发起

"立即购买"后链路变为:ShopBagPayCardPayCard 是支付卡组件,通过两个 @Event 与页面双向同步:

PayCard({
  isOpen: this.isDialogOpen,
  detailType: this.panelType,
  onIsOpenChange: (value: boolean) => { this.isDialogOpen = value; },
  onDetailTypeChange: (value: string) => {
    this.panelType = value;
    if (value === 'ShopBag') {
      this.isDialogOpen = true;   // 从支付卡返回购物袋
    }
  }
})

PayCard 内部用 SelectItem/SelectProjectItem 提供支付方式选择(getPayCardSelectList),头部有"关闭/返回"按钮:detailType === 'detailPayCard' 时关闭面板,否则 onDetailTypeChange('ShopBag') 退回购物袋。至此从直播页发起的"看 → 点 → 买"闭环完成。

注意事项与总结

  • 事件只传意图onOpenShopBag 是纯事件出口,打开什么面板、怎么呈现由页面根据断点决定——小屏弹窗、大屏侧栏,同一事件两套承载。
  • panelType 互斥渲染ShopBagPayCardvisibility 互斥显示而非条件渲染,两个组件常驻内存、切换零重建;真实项目组件重时可改为 if/else
  • BuilderParam 注入按钮payCardButton 作为插槽从页面注入 ShopBag 再注入 ShopList,按钮的样式与行为可以完全自定义,组件不感知。
  • **双向绑定∗∗:‘bindSheet( 双向绑定**:`bindSheet(双向绑定bindSheet(this.isDialogOpen, …)让面板关闭事件自动回写状态,不用手动监听onDisappear` 之外的回调。

购买链路在直播页内部闭环:入口按钮 → 购物袋 → 支付卡。接下来两篇回到视频本身:播放控制与异常处理、以及 rawfile 资源管理。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐