HarmonyOS5 购物商城app(二):购物车与支付(附代码)

218 阅读7分钟

鸿蒙应用购物车模块开发实践:从代码解析到功能实现

一、引言

在鸿蒙(HarmonyOS)应用生态中,购物车功能是电商类应用的核心组成部分,它承担着整合用户选购商品、计算总价、支撑支付流程等关键职责。本文将深入剖析一段鸿蒙应用购物车功能的实现代码,详细解读从商品数据管理、界面交互到支付流程衔接的完整逻辑,为鸿蒙应用开发者提供实践参考。

二、数据模型搭建:构建购物车基石

(一)商品类 ShopClass 定义

class ShopClass {
  img: string = ''
  name: string = ''
  attributes: string = ''
  price: number = 0
  id: number = 0
  quantity: number = 0

  constructor(img: string, name: string, attributes: string, price: number, id: number, quantity: number) {
    this.img = img
    this.name = name
    this.attributes = attributes
    this.price = price
    this.id = id
    this.quantity = quantity
  }
}

ShopClass 作为购物车商品的数据载体,清晰定义了商品图片路径、名称、属性描述、价格、唯一标识及数量等核心属性 。通过构造函数,能便捷地初始化商品信息,为后续购物车功能实现提供基础数据模型,让每个商品在购物车流程中都有明确的“身份”与“特征”。

(二)支付信息接口 pays 设计

interface pays{
  pay: number
  shopDel: Array<ShopClass>
}

pays 接口用于规范购物车结算时传递的支付相关数据结构,其中 pay 存储结算总金额,shopDel 存放已选中待支付的商品集合,确保购物车页面与支付页面间数据传递的准确性与规范性,是衔接购物车结算和支付流程的关键数据桥梁。

三、主页面 Index:购物车核心交互载体

(一)状态与数据管理

@Entry
@Component
struct Index {
  @Provide  ('shopArray') ShopArray: Array<ShopClass> = [
    new ShopClass('app.media.shop_01', '华为家居小户科技布', '蓝色 尺寸L', 3600, 1, 1),
    new ShopClass('app.media.shop_02', '简约现代餐桌椅组合', '白色 一套', 8999, 2, 1),
    new ShopClass('app.media.shop_03', '智能无线吸尘器', '星空灰', 1499, 3, 1)
  ]
  @State ShopDel: Array<ShopClass> = []
  @State flag: boolean = false
  @State priceSum: number = 0
  @Provide('mainStarIndex') mainStarIndex: NavPathStack = new NavPathStack();
  //...
}
  • @Provide @State 装饰器@Provide('shopArray') 共享商品列表数据,方便子组件访问;@State 分别管理已选中商品集合 ShopDel、全选状态 flag 以及总价 priceSum ,实现组件内部状态的响应式更新。
  • NavPathStack:借助其实现页面导航功能,搭建购物车主页与支付页的跳转逻辑,让用户在购物流程中能顺畅切换页面。

(二)界面构建与交互逻辑

1. 标题栏设计
Row() {
  Row({ space: 10 }) {
    Image($r('app.media.shopcar')) 
     .width(50)
     .height(50)
    Text('购物车')
     .fontSize(24)
     .fontWeight(FontWeight.Bold)
  }
 .width('100%')
 .justifyContent(FlexAlign.SpaceBetween)
 .alignItems(VerticalAlign.Center)
 .padding({ top: 10, bottom: 15 })
}

通过 Row 组件搭建购物车标题栏,整合购物车图标与标题文本。设置布局属性,让标题栏在界面上美观且布局合理,为用户提供清晰的功能入口标识。

2. 商品列表展示
Scroll() {
  Column() {
    List() {
      ForEach(this.ShopArray, (item: ShopClass, index) => {
        ListItem() {
          shops({
            item: item,
            ShopDel: this.ShopDel,
            priceSum: this.priceSum,
            ShopArray: this.ShopArray
          })
        }
       .margin({ top: 10 })
      })
    }
   .lanes(1, 10)
  }
 .height('100%')
 .justifyContent(FlexAlign.Start)
 .backgroundColor('#fff5f5f5')
}
.layoutWeight(1)
.width('100%')
.padding(10)

利用 ScrollListForEach 等组件构建商品滚动列表。shops 子组件负责渲染单个商品条目,实现商品展示、选中状态切换及数量调整等交互功能,让用户能直观浏览和操作购物车中的商品。

3. 结算栏交互
Row({ space: 8 }) {
  Checkbox()
   .onChange((val) => {
      this.flag = val
      if (val) {
        this.ShopArray.forEach((item) => {
          if (!this.ShopDel.some(delItem => delItem.id === item.id)) {
            this.ShopDel.push(item);
          }
        })
      } else {
        this.ShopDel = []
      }
      this.updateTotalPrice()
    })
  Text('全选')
   .layoutWeight(1)
  Text('合计:')
  Text(`¥${this.priceSum}`)
  Button('结算')
   .backgroundColor(Color.Orange)
   .onClick(() => {
      const pay:pays = { pay: this.priceSum, shopDel: this.ShopDel }
      this.mainStarIndex.pushPathByName('pay', pay)
    })
}
.height(60)
.width('100%')
.padding(16)
  • 全选功能:复选框 Checkbox 结合 onChange 事件,实现全选或取消全选逻辑。全选时遍历商品列表,将未选中商品加入 ShopDel;取消全选则清空 ShopDel,并调用 updateTotalPrice 更新总价。
  • 结算流程:结算按钮点击时,构建支付数据 pays,通过 NavPathStackpushPathByName 方法跳转至支付页面,开启支付流程。

四、商品条目 shops 组件:精细化商品交互

@Reusable
@Component
struct shops {
  @Prop item: ShopClass
  @Link ShopDel: Array<ShopClass>
  @Link priceSum: number
  @Link ShopArray: Array<ShopClass>
  @State checkboxif: boolean = false 

  build() {
    Row({ space: 10 }) {
      Checkbox()
       .onChange((value) => {
          if (value) {
            if (!this.ShopDel.some(delItem => delItem.id === this.item.id)) {
              this.checkboxif = true
              this.ShopDel.push(this.item);
            }
          } else {
            this.ShopDel = this.ShopDel.filter((delItem) => delItem.id !== this.item.id);
            this.checkboxif = false
          }
          this.updateTotalPrice()
          this.checkboxif = this.ShopDel.length === this.ShopArray.length
        })
       .width(20)
      // 商品图片、信息列、数量控制区域等构建逻辑
    }
   .width('100%')
   .height(80)
   .backgroundColor(Color.White)
   .borderRadius(8) 
   .shadow({ color: '#1de9b6', radius: 4 }) 
  }

  updateTotalPrice(): void {
    let total = 0
    this.ShopDel.forEach((shopItem) => {
      total += shopItem.price * shopItem.quantity
    })
    this.priceSum = total
  }
}
  • 交互逻辑:作为商品列表子组件,通过 @Prop@Link 接收数据并实现双向绑定。复选框实现商品选中/取消选中功能,同步更新 ShopDel、总价及全选状态;数量控制区域的 “-”“+” 按钮,调整商品数量并实时更新总价,让用户能精细化操作单个商品。
  • 界面美化:设置组件背景色、圆角及阴影效果,提升商品条目的视觉呈现,优化用户体验。

五、支付页面 pay 组件:完成购物闭环

@Component
struct pay {
  @Consume('mainStarIndex') mainStarIndex: NavPathStack;
  @Consume  ('shopArray') shopArray: Array<ShopClass>
  @Prop pay: pays

  build() {
    NavDestination(){
      Column() {
        // 金额展示、支付方式选择、按钮等构建逻辑
      }
     .width('100%')
     .height('100%')
     .justifyContent(FlexAlign.Start)
     .alignItems(HorizontalAlign.Center)
    }
  }

  onClickPay() {
    AlertDialog.show({
      message: '是否支付!',
      buttons: [
        {
          value: '确定',
          action: () => {
            this.shopArray = this.shopArray.filter((item) => !this.pay.shopDel.some(delItem => delItem.id === item.id))
            this.mainStarIndex.pop(); 
            prompt.showToast({ message: '支付成功!' });
          }
        },
        {
          value: '取消',
          action: () => {
          }
        }
      ]
    });
  }
}
  • 支付流程:接收支付数据与商品列表,展示支付金额及支付宝、微信等支付方式选项。确认支付按钮点击后,弹出对话框询问用户,确认则从商品列表移除已结算商品,通过 mainStarIndex.pop() 返回购物车主页并提示支付成功;取消则终止支付流程,完成购物闭环的最后一环。
  • 用户反馈:利用 prompt.showToast 提供支付结果反馈,增强用户对操作的感知。

六、附:代码

import { prompt } from '@kit.ArkUI'

// 商品类定义
class ShopClass {
  img: string = ''
  name: string = ''
  attributes: string = ''
  price: number = 0
  id: number = 0
  quantity: number = 0

  constructor(img: string, name: string, attributes: string, price: number, id: number, quantity: number) {
    this.img = img
    this.name = name
    this.attributes = attributes
    this.price = price
    this.id = id
    this.quantity = quantity
  }
}
interface pays{
  pay: number
  shopDel: Array<ShopClass>
}

@Entry
@Component
struct Index {
  // 购物车商品列表数据源
  @Provide  ('shopArray') ShopArray: Array<ShopClass> = [
    new ShopClass('app.media.shop_01', '华为家居小户科技布', '蓝色 尺寸L', 3600, 1, 1),
    new ShopClass('app.media.shop_02', '简约现代餐桌椅组合', '白色 一套', 8999, 2, 1),
    new ShopClass('app.media.shop_03', '智能无线吸尘器', '星空灰', 1499, 3, 1)
  ]

  // 已选中商品集合
  @State ShopDel: Array<ShopClass> = []

  // 全选状态标志
  @State flag: boolean = false

  // 总价
  @State priceSum: number = 0

  @Provide('mainStarIndex') mainStarIndex: NavPathStack = new NavPathStack();

  @Builder
  shopPage(name: string, params: pays) {
    if (name === 'pay') {
      pay({
        pay:  params,
      })
    }
  }

  build() {
    Navigation(this.mainStarIndex){
      Column() {
        // 购物车标题栏
        Row() {
          Row({ space: 10 }) {
            Image($r('app.media.shopcar')) // 使用合适的购物车图标资源
              .width(50)
              .height(50)
            Text('购物车')
              .fontSize(24)
              .fontWeight(FontWeight.Bold)
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .alignItems(VerticalAlign.Center)
          .padding({ top: 10, bottom: 15 })
        }
        Divider()
        // 商品滚动列表
        Scroll() {
          Column() {
            // 商品列表容器
            List() {
              ForEach(this.ShopArray, (item: ShopClass, index) => {
                ListItem() {
                  shops({
                    item: item,
                    ShopDel: this.ShopDel,
                    priceSum: this.priceSum,
                    ShopArray: this.ShopArray
                  })
                }
                .margin({ top: 10 })
              })
            }
            .lanes(1, 10)
          }
          .height('100%')
          .justifyContent(FlexAlign.Start)
          .backgroundColor('#fff5f5f5')
        }
        .layoutWeight(1)
        .width('100%')
        .padding(10)

        Divider()
        // 结算栏
        Row({ space: 8 }) {
          Checkbox()
            .onChange((val) => {
              // 全选/取消全选逻辑
              this.flag = val
              if (val) {
                this.ShopArray.forEach((item) => {
                  if (!this.ShopDel.some(delItem => delItem.id === item.id)) {
                    this.ShopDel.push(item);
                  }
                })
              } else {
                // 取消全选时清空ShopDel
                this.ShopDel = []
              }
              // 更新总价
              this.updateTotalPrice()
            })
          Text('全选')
            .layoutWeight(1)
          // 计算总价
          Text('合计:')
          Text(`¥${this.priceSum}`)
          Button('结算')
            .backgroundColor(Color.Orange)
            .onClick(() => {
              // 模拟结算操作
              const pay:pays = { pay: this.priceSum, shopDel: this.ShopDel }
              this.mainStarIndex.pushPathByName('pay', pay)
            })
        }
        .height(60)
        .width('100%')
        .padding(16)
      }
      .width('100%')
      .height('100%')
    }
    .hideTitleBar(true)
    .mode(NavigationMode.Stack)
    .navDestination(this.shopPage)
  }

  // 单独提取更新总价的函数,便于维护和调用
  updateTotalPrice(): void {
    this.priceSum = this.ShopDel.reduce((sum, item) => sum + item.price * item.quantity, 0)
  }
}

// 商品项构建器
@Reusable
@Component
struct shops {
  @Prop item: ShopClass
  @Link ShopDel: Array<ShopClass>
  @Link priceSum: number
  @Link ShopArray: Array<ShopClass>
  @State checkboxif: boolean = false // shop状态

  build() {
    Row({ space: 10 }) {
      // 复选框 - 用于选择商品
      Checkbox()
        .onChange((value) => {
          if (value) {
            // 如果 value 为 true,将 item 添加到 this.ShopDel 中
            if (!this.ShopDel.some(delItem => delItem.id === this.item.id)) {
              this.checkboxif = true
              this.ShopDel.push(this.item);
            }
          } else {
            // 如果 value 为 false,从 this.ShopDel 中移除当前的 item
            this.ShopDel = this.ShopDel.filter((delItem) => delItem.id !== this.item.id);
            this.checkboxif = false
          }

          // 更新总价
          this.updateTotalPrice()

          // 更新全选状态
          this.checkboxif = this.ShopDel.length === this.ShopArray.length
        })
        .width(20)

      // 商品图片
      Image($r(this.item.img))
        .width(80)

      // 商品信息列
      Column() {
        // 商品名称
        Text(this.item.name)
          .width('100%')
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })

        // 商品属性
        Text(this.item.attributes)
          .width('100%')

        // 商品价格
        Text(`¥${this.item.price}`)
          .width('100%')
          .fontColor('#ff527fb') // 修正颜色值格式
      }
      .width(100)

      // 数量控制区域
      Column({ space: 5 }) {
        Text('数量:')
          .fontSize(12)
          .textAlign(TextAlign.Center)

        Row({ space: 5 }) {
          Text('-')
            .width(25)
            .height(25)
            .onClick(() => {
              if (this.item.quantity > 1) {
                this.item.quantity--
                this.updateTotalPrice()
              }
            })

          Text(`${this.item.quantity}`)
            .width(25)
            .textAlign(TextAlign.Center)

          Text('+')
            .width(25)
            .height(25)
            .onClick(() => {
              this.item.quantity++
              this.updateTotalPrice()
            })
        }
        .justifyContent(FlexAlign.SpaceAround)
      }
      .width(80)

      // 详情箭头图标
      Image($r('app.media.black'))
        .width(10)
        .onClick(() => {
          // TODO 模拟跳转详情页
        })
    }
    .width('100%')
    .height(80)
    .backgroundColor(Color.White)
    .borderRadius(8) // 添加圆角效果
    .shadow({ color: '#1de9b6', radius: 4 }) // 添加阴影效果
  }

  // 提取公共方法来更新总价
  updateTotalPrice(): void {
    let total = 0
    this.ShopDel.forEach((shopItem) => {
      total += shopItem.price * shopItem.quantity
    })
    this.priceSum = total
  }
}

@Component
struct pay {
  @Consume('mainStarIndex') mainStarIndex: NavPathStack;
  @Consume  ('shopArray') shopArray: Array<ShopClass>
  @Prop pay: pays
  build() {
    NavDestination(){
      Column() {
        Row(){
          Text('合计:')
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
          Text(`¥${this.pay.pay}`)
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
        }
        // 支付方式标题
        Text('选择支付方式')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .padding({ top: 20, bottom: 10 })

        Divider()

        // 支付宝支付选项
        Row({ space: 15 }) {
          Image($r('app.media.alipay')) // 假设已有支付宝图标资源
            .width(30)
            .height(30)
          Text('支付宝')
            .fontSize(16)
          Checkbox()
            .onChange((value) => {
              if (value) {
                // 可以添加相关逻辑,如记录用户选择了支付宝支付
                console.log("用户选择了支付宝支付")
              }
            })
        }
        .width('90%')
        .padding({ left: 20, right: 20 })
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)
        .margin({ top: 10 })

        Divider()

        // 微信支付选项
        Row({ space: 15 }) {
          Image($r('app.media.wechatpay')) // 假设已有微信支付图标资源
            .width(30)
            .height(30)
          Text('微信')
            .fontSize(16)
          Checkbox()
            .onChange((value) => {
              if (value) {
                // 可以添加相关逻辑,如记录用户选择了微信支付
                console.log("用户选择了微信支付")
              }
            })
        }
        .width('90%')
        .padding({ left: 20, right: 20 })
        .justifyContent(FlexAlign.SpaceBetween)
        .alignItems(VerticalAlign.Center)
        .margin({ top: 10 })

        Divider()

        // 提交支付按钮
        Button('确认支付')
          .onClick(() => {
            // 跳转·到支付页面
            this.onClickPay()
          })
          .backgroundColor(Color.Orange)
          .width('80%')
          .margin({ top: 30 })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Start)
      .alignItems(HorizontalAlign.Center)
    }
  }

  
  // 支付点击事件处理函数
  onClickPay() {
    // 模拟支付成功提示
    AlertDialog.show({
      message: '是否支付!',
      buttons: [
        {
          value: '确定',
          action: () => {
            // 移除已选商品并返回首页
            // this.pay.ShopDel = this.ShopDel.filter((delItem) => delItem.id !== item.id);
            this.shopArray = this.shopArray.filter((item) => !this.pay.shopDel.some(delItem => delItem.id === item.id));
            this.mainStarIndex.pop(); // 返回首页
            // 提示支付成功
            prompt.showToast({ message: '支付成功!' });
          }
        },
        {
          value: '取消',
          action: () => {
            // 取消支付操作
          }
        }
      ]
    });
  }
}