img

一、案例介绍

本案例将展示如何使用SpanStepper组件实现一个购物流程的分步操作界面,包括购物车确认、收货地址选择、支付方式选择等步骤。

二、代码实现

@Entry
@Component
struct ShoppingStepperExample {
  @State currentStep: number = 0
  @State cartItems: Array<{
    name: string,
    price: number,
    quantity: number
  }> = [
    { name: '商品1', price: 99.9, quantity: 1 },
    { name: '商品2', price: 199.9, quantity: 2 }
  ]
  @State selectedAddress: string = ''
  @State paymentMethod: string = ''

  // 计算总价
  getTotalPrice(): number {
    return this.cartItems.reduce((total, item) => {
      return total + (item.price * item.quantity)
    }, 0)
  }

  build() {
    Column() {
      // 步骤指示器
      SpanStepper() {
        StepperItem() {
          Column() {
            Text('购物车确认').fontSize(16).fontWeight(FontWeight.Bold)
            Text('确认商品和数量').fontSize(14).opacity(0.6)
          }
        }
        .status(this.currentStep === 0 ? ItemState.CURRENT :
               this.currentStep > 0 ? ItemState.COMPLETED : ItemState.WAITING)

        StepperItem() {
          Column() {
            Text('收货地址').fontSize(16).fontWeight(FontWeight.Bold)
            Text('选择收货地址').fontSize(14).opacity(0.6)
          }
        }
        .status(this.currentStep === 1 ? ItemState.CURRENT :
               this.currentStep > 1 ? ItemState.COMPLETED : ItemState.WAITING)

        StepperItem() {
          Column() {
            Text('支付方式').fontSize(16).fontWeight(FontWeight.Bold)
            Text('选择支付方式').fontSize(14).opacity(0.6)
          }
        }
        .status(this.currentStep === 2 ? ItemState.CURRENT : ItemState.WAITING)
      }
      .width('100%')
      .padding(16)

      // 表单内容
      Column() {
        if (this.currentStep === 0) {
          // 购物车商品列表
          List() {
            ForEach(this.cartItems, (item) => {
              ListItem() {
                Row() {
                  Text(item.name)
                    .fontSize(16)
                  Column() {
                    Text(`¥${item.price.toFixed(2)}`)
                      .fontSize(14)
                    Text(`数量:${item.quantity}`)
                      .fontSize(14)
                      .margin({ top: 4 })
                  }.alignItems(HorizontalAlign.End)
                }
                .width('100%')
                .justifyContent(FlexAlign.SpaceBetween)
                .padding(8)
              }
            })
          }
          .width('100%')

          // 总价
          Text(`总计:¥${this.getTotalPrice().toFixed(2)}`)
            .fontSize(18)
            .fontWeight(FontWeight.Bold)
            .margin({ top: 16 })

        } else if (this.currentStep === 1) {
          // 地址选择
          Column() {
            Radio({ value: 'address1', group: 'address' })
              .onChange((isChecked: boolean) => {
                if (isChecked) {
                  this.selectedAddress = '地址1:北京市海淀区XX街道XX号'
                }
              })
            Text('北京市海淀区XX街道XX号')
              .fontSize(14)
              .margin({ left: 8 })
          }.margin({ top: 16 })

          Column() {
            Radio({ value: 'address2', group: 'address' })
              .onChange((isChecked: boolean) => {
                if (isChecked) {
                  this.selectedAddress = '地址2:上海市浦东新区XX路XX号'
                }
              })
            Text('上海市浦东新区XX路XX号')
              .fontSize(14)
              .margin({ left: 8 })
          }.margin({ top: 16 })

        } else {
          // 支付方式选择
          Column() {
            Radio({ value: 'alipay', group: 'payment' })
              .onChange((isChecked: boolean) => {
                if (isChecked) {
                  this.paymentMethod = '支付宝'
                }
              })
            Text('支付宝支付')
              .fontSize(14)
              .margin({ left: 8 })
          }.margin({ top: 16 })

          Column() {
            Radio({ value: 'wechat', group: 'payment' })
              .onChange((isChecked: boolean) => {
                if (isChecked) {
                  this.paymentMethod = '微信支付'
                }
              })
            Text('微信支付')
              .fontSize(14)
              .margin({ left: 8 })
          }.margin({ top: 16 })

          // 订单信息确认
          Column() {
            Text('订单信息确认').fontSize(16).fontWeight(FontWeight.Bold)
            Text(`总金额:¥${this.getTotalPrice().toFixed(2)}`)
              .margin({ top: 8 })
            Text(`收货地址:${this.selectedAddress}`)
              .margin({ top: 8 })
            Text(`支付方式:${this.paymentMethod}`)
              .margin({ top: 8 })
          }
          .width('100%')
          .padding(16)
          .margin({ top: 20 })
        }
      }.width('100%').padding({ left: 16, right: 16 })

      // 操作按钮
      Row() {
        Button('上一步')
          .enabled(this.currentStep > 0)
          .opacity(this.currentStep > 0 ? 1 : 0.5)
          .onClick(() => {
            if (this.currentStep > 0) {
              this.currentStep--
            }
          })

        Button(this.currentStep === 2 ? '提交订单' : '下一步')
          .enabled(this.currentStep < 3)
          .onClick(() => {
            if (this.currentStep < 2) {
              this.currentStep++
            } else {
              // 在实际应用中,这里可以处理订单提交逻辑
              console.info('订单提交成功')
            }
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)
      .padding(16)
      .margin({ top: 20 })
    }
  }
}

三、代码解析

1. 状态定义

@State currentStep: number = 0  // 当前步骤索引
@State cartItems: Array<{  // 购物车商品列表
  name: string,
  price: number,
  quantity: number
}>
@State selectedAddress: string = ''  // 选择的收货地址
@State paymentMethod: string = ''  // 选择的支付方式
  • 使用@State装饰器定义组件的状态变量
  • cartItems数组存储购物车商品信息
  • selectedAddresspaymentMethod记录用户选择

2. 商品列表渲染

List() {
  ForEach(this.cartItems, (item) => {
    ListItem() {
      Row() {
        Text(item.name)
        Column() {
          Text(`¥${item.price.toFixed(2)}`)
          Text(`数量:${item.quantity}`)
        }
      }
    }
  })
}
  • 使用ListForEach组件循环渲染商品列表
  • 展示商品名称、价格和数量信息
  • 使用toFixed(2)格式化价格显示

3. 地址和支付方式选择

Radio({ value: 'address1', group: 'address' })
  .onChange((isChecked: boolean) => {
    if (isChecked) {
      this.selectedAddress = '地址1:北京市海淀区XX街道XX号'
    }
  })
  • 使用Radio组件实现单选功能
  • 通过onChange事件更新选择状态
  • 使用group属性分组单选按钮

4. 订单信息确认

Column() {
  Text('订单信息确认')
  Text(`总金额:¥${this.getTotalPrice().toFixed(2)}`)
  Text(`收货地址:${this.selectedAddress}`)
  Text(`支付方式:${this.paymentMethod}`)
}
  • 在最后一步显示订单汇总信息
  • 展示总金额、收货地址和支付方式
  • 使用模板字符串格式化显示内容

四、总结

通过本案例,我们学习了:

  1. SpanStepper组件在购物流程中的应用
  2. 如何实现商品列表的渲染和总价计算
  3. 使用Radio组件实现选择功能
  4. 订单信息的收集和展示

这些知识点将帮助你在实际项目中实现更复杂的购物流程界面。

Logo

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

更多推荐