182.[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局进阶篇:打造高级交互与视觉体验

2025-06-30 23:01:43
104次阅读
0个评论

[HarmonyOS NEXT 实战案例九:Grid] 电商网格布局进阶篇:打造高级交互与视觉体验

项目已开源,开源地址: https://gitcode.com/nutpi/HarmonyosNextCaseStudyTutorial , 欢迎fork & star

效果演示

image.png

1. 电商网格布局进阶概述

在基础篇中,我们学习了如何使用 HarmonyOS NEXT 的 Grid 和 GridItem 组件创建基本的电商商品展示页面。在本篇教程中,我们将深入探讨更高级的电商网格布局技术,包括高级交互设计、动态布局调整、高级样式与视觉效果等,帮助开发者打造出更具吸引力和交互性的电商应用。

1.1 进阶特性概览

特性 描述
高级交互设计 商品详情对话框、购物车浮层、手势交互
动态布局调整 响应式列数、动态卡片大小
高级样式与视觉效果 卡片样式变体、动画效果
高级数据处理 多维度筛选、复杂排序算法
购物车管理 购物车浮层、数量调整、总价计算

2. 高级交互设计

2.1 商品详情对话框

商品详情对话框是电商应用中的核心交互元素,它允许用户在不离开当前页面的情况下查看商品的详细信息。

@Builder
ProductDetailDialog() {
  if (this.selectedProduct) {
    Column() {
      // 商品图片
      Image(this.selectedProduct.image)
        .width('100%')
        .height(300)
        .objectFit(ImageFit.Cover)
        .borderRadius({ topLeft: 16, topRight: 16 })

      // 商品信息
      Column() {
        // 价格和标签
        Row() {
          Column() {
            Row() {
              Text(`¥${this.selectedProduct.price}`)
                .fontSize(24)
                .fontWeight(FontWeight.Bold)
                .fontColor('#FF3B30')

              if (this.selectedProduct.originalPrice) {
                Text(`¥${this.selectedProduct.originalPrice}`)
                  .fontSize(14)
                  .fontColor('#999999')
                  .decoration({ type: TextDecorationType.LineThrough })
                  .margin({ left: 8 })
              }

              if (this.selectedProduct.discount) {
                Text(`${this.selectedProduct.discount}折`)
                  .fontSize(12)
                  .fontColor('#FFFFFF')
                  .backgroundColor('#FF3B30')
                  .borderRadius(4)
                  .padding({ left: 4, right: 4, top: 2, bottom: 2 })
                  .margin({ left: 8 })
              }
            }

            if (this.selectedProduct.isFreeShipping) {
              Text('包邮')
                .fontSize(12)
                .fontColor('#007AFF')
                .border({ width: 1, color: '#007AFF' })
                .borderRadius(4)
                .padding({ left: 4, right: 4, top: 1, bottom: 1 })
                .margin({ top: 4 })
            }
          }
          .alignItems(HorizontalAlign.Start)

          Blank()

          Column() {
            if (this.selectedProduct.isHot) {
              Text('热销')
                .fontSize(12)
                .fontColor('#FFFFFF')
                .backgroundColor('#FF9500')
                .borderRadius(4)
                .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                .margin({ bottom: 4 })
            }

            if (this.selectedProduct.isNew) {
              Text('新品')
                .fontSize(12)
                .fontColor('#FFFFFF')
                .backgroundColor('#34C759')
                .borderRadius(4)
                .padding({ left: 6, right: 6, top: 2, bottom: 2 })
            }
          }
          .alignItems(HorizontalAlign.End)
        }
        .width('100%')
        .margin({ bottom: 16 })

        // 商品名称
        Text(this.selectedProduct.name)
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
          .width('100%')
          .textAlign(TextAlign.Start)
          .margin({ bottom: 12 })

        // 商品描述
        Text(this.selectedProduct.description)
          .fontSize(14)
          .fontColor('#666666')
          .width('100%')
          .textAlign(TextAlign.Start)
          .margin({ bottom: 16 })

        // 评分和销量
        Row() {
          Row() {
            ForEach([1,2,3,4,5], (star:number) => {
              Image(star <= this.selectedProduct.rating ? $r('app.media.star_filled') : $r('app.media.star_outline'))
                .width(16)
                .height(16)
                .fillColor(star <= this.selectedProduct.rating ? '#FFD700' : '#E0E0E0')
            })

            Text(`${this.selectedProduct.rating}`)
              .fontSize(14)
              .fontColor('#666666')
              .margin({ left: 4 })
          }

          Text(`${this.selectedProduct.reviewCount}条评价`)
            .fontSize(14)
            .fontColor('#666666')
            .margin({ left: 16, right: 16 })

          Text(`${this.formatNumber(this.selectedProduct.salesCount)}已售`)
            .fontSize(14)
            .fontColor('#666666')
        }
        .width('100%')
        .margin({ bottom: 16 })

        // 商品标签
        Row() {
          Text('标签:')
            .fontSize(14)
            .fontColor('#666666')

          Flex({ wrap: FlexWrap.Wrap }) {
            ForEach(this.selectedProduct.tags, (tag:string) => {
              Text(tag)
                .fontSize(12)
                .fontColor('#666666')
                .backgroundColor('#F0F0F0')
                .borderRadius(12)
                .padding({ left: 8, right: 8, top: 4, bottom: 4 })
                .margin({ right: 8, bottom: 8 })
            })
          }
        }
        .width('100%')
        .margin({ bottom: 16 })

        // 操作按钮
        Row() {
          Button() {
            Row() {
              Image($r('app.media.cart_add'))
                .width(20)
                .height(20)
                .fillColor('#FFFFFF')
                .margin({ right: 8 })

              Text('加入购物车')
                .fontSize(16)
                .fontColor('#FFFFFF')
            }
          }
          .backgroundColor('#007AFF')
          .borderRadius(24)
          .width('48%')
          .height(48)
          .onClick(() => {
            this.addToCart(this.selectedProduct.id)
            this.showProductDetail = false
          })

          Button() {
            Text('立即购买')
              .fontSize(16)
              .fontColor('#FFFFFF')
          }
          .backgroundColor('#FF3B30')
          .borderRadius(24)
          .width('48%')
          .height(48)
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .padding(20)
      .alignItems(HorizontalAlign.Start)
    }
    .width('95%')
    .backgroundColor('#FFFFFF')
    .borderRadius(16)
    .maxHeight('85%')
  }
}

商品详情对话框的设计要点:

  1. 层次分明的信息结构:从上到下依次展示商品图片、价格信息、商品名称、描述、评分销量、标签和操作按钮,符合用户的阅读习惯和信息获取优先级。

  2. 丰富的视觉标识:使用不同的颜色和样式标识热销、新品、包邮等特殊属性,增强信息的可识别性。

  3. 灵活的布局:使用 Row 和 Column 组件灵活组合,实现复杂的信息布局。

  4. 明确的操作区域:底部提供"加入购物车"和"立即购买"两个主要操作按钮,按钮大小适中,易于点击。

  5. 滚动适配:通过 maxHeight 属性限制对话框最大高度,确保在内容过多时可以滚动查看。

2.2 购物车浮层

购物车浮层允许用户快速查看已添加的商品,并进行结算操作。

// 购物车浮层
Column() {
  // 顶部标题栏
  Row() {
    Text('购物车')
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .fontColor('#333333')

    Blank()

    Button() {
      Image($r('app.media.close'))
        .width(24)
        .height(24)
        .fillColor('#999999')
    }
    .backgroundColor('transparent')
    .width(36)
    .height(36)
    .onClick(() => {
      this.showCart = false
    })
  }
  .width('100%')
  .padding({ left: 20, right: 20, top: 20, bottom: 12 })

  // 购物车为空提示
  if (this.cartItems.length === 0) {
    Column() {
      Image($r('app.media.empty_cart'))
        .width(120)
        .height(120)
        .margin({ bottom: 16, top: 40 })

      Text('购物车还是空的')
        .fontSize(16)
        .fontColor('#999999')

      Button('去选购')
        .fontSize(16)
        .fontColor('#FFFFFF')
        .backgroundColor('#007AFF')
        .borderRadius(24)
        .width(160)
        .height(48)
        .margin({ top: 24 })
        .onClick(() => {
          this.showCart = false
        })
    }
    .width('100%')
    .layoutWeight(1)
    .justifyContent(FlexAlign.Center)
  } else {
    // 购物车商品列表
    List() {
      ForEach(this.cartItems, (item:CateItem) => {
        ListItem() {
          Row() {
            // 商品图片
            Image(this.products.find(p => p.id === item.productId)?.image)
              .width(80)
              .height(80)
              .objectFit(ImageFit.Cover)
              .borderRadius(8)

            // 商品信息
            Column() {
              Text(this.products.find(p => p.id === item.productId)?.name)
                .fontSize(14)
                .fontWeight(FontWeight.Bold)
                .fontColor('#333333')
                .maxLines(2)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .width('100%')
                .textAlign(TextAlign.Start)
                .margin({ bottom: 8 })

              // 价格
              Row() {
                Text(`¥${this.products.find(p => p.id === item.productId)?.price}`)
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#FF3B30')

                Blank()

                // 数量调整
                Row() {
                  Button('-')
                    .fontSize(16)
                    .fontColor('#333333')
                    .backgroundColor('#F0F0F0')
                    .width(28)
                    .height(28)
                    .borderRadius(14)
                    .onClick(() => {
                      if (item.quantity > 1) {
                        item.quantity--
                      } else {
                        this.cartItems = this.cartItems.filter(i => i.productId !== item.productId)
                      }
                    })

                  Text(item.quantity.toString())
                    .fontSize(14)
                    .fontColor('#333333')
                    .width(40)
                    .textAlign(TextAlign.Center)

                  Button('+')
                    .fontSize(16)
                    .fontColor('#333333')
                    .backgroundColor('#F0F0F0')
                    .width(28)
                    .height(28)
                    .borderRadius(14)
                    .onClick(() => {
                      const product = this.products.find(p => p.id === item.productId)
                      if (product && item.quantity < product.stock) {
                        item.quantity++
                      }
                    })
                }
              }
              .width('100%')
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 12 })
          }
          .width('100%')
          .padding({ top: 12, bottom: 12 })
        }
      })
    }
    .width('100%')
    .layoutWeight(1)
    .padding({ left: 20, right: 20 })

    // 底部结算栏
    Row() {
      Column() {
        Text('总计:')
          .fontSize(14)
          .fontColor('#666666')

        Text(`¥${this.getCartTotal().toFixed(2)}`)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF3B30')
      }
      .alignItems(HorizontalAlign.Start)

      Blank()

      Button('去结算')
        .fontSize(16)
        .fontColor('#FFFFFF')
        .backgroundColor('#FF3B30')
        .borderRadius(24)
        .width(120)
        .height(48)
    }
    .width('100%')
    .padding(20)
    .border({ width: { top: 1 }, color: { top: '#F0F0F0' } })
  }
}
.width('100%')
.height('70%')
.backgroundColor('#FFFFFF')
.borderRadius({ topLeft: 16, topRight: 16 })
.position({ x: 0, y: '30%' })

购物车浮层的设计要点:

  1. 清晰的标题和关闭按钮:顶部显示标题和关闭按钮,方便用户理解当前界面和退出操作。

  2. 空状态处理:当购物车为空时,显示友好的提示和引导用户去选购的按钮。

  3. 商品列表:使用 List 组件展示购物车中的商品,每个商品项包含图片、名称、价格和数量调整控件。

  4. 数量调整:提供增加和减少按钮,允许用户调整商品数量,并处理边界情况(数量为1时减少则移除商品,数量达到库存上限时禁止增加)。

  5. 结算区域:底部显示总价和结算按钮,明确引导用户完成购买流程。

  6. 半屏浮层:使用 position 属性将浮层定位在屏幕下半部分,保留上下文信息,提升用户体验。

3. 动态布局调整

3.1 响应式列数

在不同屏幕尺寸下,我们可以动态调整商品网格的列数,以提供最佳的视觉体验。

@State gridColumns: number = 2

onPortrait(mediaQueryResult: MediaQueryResult) {
  if (mediaQueryResult.matches) {
    // 竖屏模式
    this.gridColumns = 2
  }
}

onLandscape(mediaQueryResult: MediaQueryResult) {
  if (mediaQueryResult.matches) {
    // 横屏模式
    this.gridColumns = 3
  }
}

aboutToAppear() {
  // 监听屏幕方向变化
  mediaQuery.matchMediaSync('(orientation: portrait)').on('change', this.onPortrait)
  mediaQuery.matchMediaSync('(orientation: landscape)').on('change', this.onLandscape)
  
  // 初始化列数
  if (mediaQuery.matchMediaSync('(orientation: landscape)').matches) {
    this.gridColumns = 3
  } else {
    this.gridColumns = 2
  }
}

aboutToDisappear() {
  // 移除监听器
  mediaQuery.matchMediaSync('(orientation: portrait)').off('change', this.onPortrait)
  mediaQuery.matchMediaSync('(orientation: landscape)').off('change', this.onLandscape)
}

然后在 Grid 组件中使用动态列数:

Grid() {
  // GridItem 内容...
}
.columnsTemplate(this.gridColumns === 2 ? '1fr 1fr' : '1fr 1fr 1fr')

这样,当设备旋转或在不同尺寸的设备上运行时,网格布局会自动调整列数,提供最佳的视觉体验。

3.2 动态卡片大小

除了调整列数,我们还可以根据屏幕尺寸动态调整卡片的大小和内容布局。

@State cardImageHeight: number = 160
@State cardFontSize: number = 14

updateCardSize() {
  const screenWidth = px2vp(window.getWindowWidth())
  if (screenWidth > 600) {
    // 大屏设备
    this.cardImageHeight = 200
    this.cardFontSize = 16
  } else {
    // 小屏设备
    this.cardImageHeight = 160
    this.cardFontSize = 14
  }
}

aboutToAppear() {
  // 初始化卡片大小
  this.updateCardSize()
  
  // 监听窗口大小变化
  window.on('resize', () => {
    this.updateCardSize()
  })
}

aboutToDisappear() {
  // 移除监听器
  window.off('resize')
}

然后在 GridItem 中使用动态大小:

Image(product.image)
  .width('100%')
  .height(this.cardImageHeight)
  .objectFit(ImageFit.Cover)

Text(product.name)
  .fontSize(this.cardFontSize)
  .fontWeight(FontWeight.Bold)
  .fontColor('#333333')

这样,卡片的图片高度和文字大小会根据屏幕尺寸动态调整,提供更好的视觉体验。

4. 高级样式与视觉效果

4.1 卡片样式变体

为了增加视觉多样性,我们可以为不同类型的商品提供不同的卡片样式。

@Builder
ProductCard(product: EcommerceProduct) {
  Column() {
    // 基础卡片内容...
  }
  .width('100%')
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .shadow({
    radius: 6,
    color: 'rgba(0, 0, 0, 0.1)',
    offsetX: 0,
    offsetY: 2
  })
  // 根据商品类型应用不同的样式
  .border(product.isHot ? {
    width: 2,
    color: '#FF9500',
    style: BorderStyle.Solid
  } : null)
  .backgroundColor(product.isNew ? '#F8FFF8' : '#FFFFFF')
  .onClick(() => {
    this.selectedProduct = product
    this.showProductDetail = true
  })
}

这样,热销商品会有橙色边框,新品会有浅绿色背景,增加视觉差异化,帮助用户快速识别不同类型的商品。

4.2 高级动画效果

4.2.1 卡片加载动画

当商品数据正在加载时,我们可以添加骨架屏动画,提升用户体验。

@State isLoading: boolean = true

// 模拟数据加载
aboutToAppear() {
  this.isLoading = true
  setTimeout(() => {
    this.isLoading = false
  }, 1500)
}

@Builder
SkeletonCard() {
  Column() {
    // 图片占位
    Row()
      .width('100%')
      .height(160)
      .backgroundColor('#F0F0F0')
      .borderRadius({ topLeft: 12, topRight: 12 })
      .animation({
        duration: 1500,
        tempo: 1.0,
        curve: Curve.Linear,
        delay: 0,
        iterations: -1,
        playMode: PlayMode.Alternate
      })
      .opacity(this.isLoading ? 0.6 : 0.9)

    // 信息占位
    Column() {
      // 标题占位
      Row()
        .width('80%')
        .height(16)
        .backgroundColor('#F0F0F0')
        .borderRadius(8)
        .margin({ bottom: 8 })
        .animation({
          duration: 1500,
          tempo: 1.0,
          curve: Curve.Linear,
          delay: 200,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .opacity(this.isLoading ? 0.6 : 0.9)

      // 价格占位
      Row()
        .width('40%')
        .height(16)
        .backgroundColor('#F0F0F0')
        .borderRadius(8)
        .margin({ bottom: 8 })
        .animation({
          duration: 1500,
          tempo: 1.0,
          curve: Curve.Linear,
          delay: 400,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .opacity(this.isLoading ? 0.6 : 0.9)

      // 评分占位
      Row()
        .width('60%')
        .height(12)
        .backgroundColor('#F0F0F0')
        .borderRadius(6)
        .margin({ bottom: 8 })
        .animation({
          duration: 1500,
          tempo: 1.0,
          curve: Curve.Linear,
          delay: 600,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .opacity(this.isLoading ? 0.6 : 0.9)

      // 按钮占位
      Row()
        .width('100%')
        .height(32)
        .backgroundColor('#F0F0F0')
        .borderRadius(16)
        .animation({
          duration: 1500,
          tempo: 1.0,
          curve: Curve.Linear,
          delay: 800,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .opacity(this.isLoading ? 0.6 : 0.9)
    }
    .padding(12)
    .alignItems(HorizontalAlign.Start)
  }
  .width('100%')
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .shadow({
    radius: 6,
    color: 'rgba(0, 0, 0, 0.1)',
    offsetX: 0,
    offsetY: 2
  })
}

然后在 Grid 中根据加载状态显示不同的内容:

Grid() {
  if (this.isLoading) {
    // 显示骨架屏
    ForEach([1, 2, 3, 4, 5, 6], (item) => {
      GridItem() {
        this.SkeletonCard()
      }
    })
  } else {
    // 显示实际商品
    ForEach(this.getFilteredProducts(), (product: EcommerceProduct) => {
      GridItem() {
        this.ProductCard(product)
      }
    })
  }
}

4.2.2 交互反馈动画

当用户点击商品卡片或操作按钮时,添加适当的动画反馈,提升交互体验。

@State cardScale: number = 1.0

// 在 GridItem 中添加点击动画
GridItem() {
  Column() {
    // 商品卡片内容...
  }
  .width('100%')
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .shadow({
    radius: 6,
    color: 'rgba(0, 0, 0, 0.1)',
    offsetX: 0,
    offsetY: 2
  })
  .scale({ x: this.cardScale, y: this.cardScale })
  .animation({
    duration: 100,
    curve: Curve.FastOutSlowIn,
    iterations: 1,
    playMode: PlayMode.Normal
  })
  .onTouch((event: TouchEvent) => {
    if (event.type === TouchType.Down) {
      this.cardScale = 0.95
    } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
      this.cardScale = 1.0
    }
  })
  .onClick(() => {
    this.selectedProduct = product
    this.showProductDetail = true
  })
}

这样,当用户按下卡片时,卡片会有轻微的缩小效果,松开后恢复原状,提供更好的触觉反馈。

5. 高级数据处理

5.1 多维度筛选

在电商应用中,通常需要支持多维度的商品筛选,如分类、价格范围、品牌、评分等。

@State selectedBrands: string[] = []
@State minRating: number = 0

getFilteredProducts(): EcommerceProduct[] {
  let filtered = this.products

  // 分类过滤
  if (this.selectedCategory !== '全部') {
    filtered = filtered.filter(product => product.category === this.selectedCategory)
  }

  // 价格过滤
  filtered = filtered.filter(product =>
    product.price >= this.priceRange.min && product.price <= this.priceRange.max
  )

  // 品牌过滤
  if (this.selectedBrands.length > 0) {
    filtered = filtered.filter(product => this.selectedBrands.includes(product.brand))
  }

  // 评分过滤
  filtered = filtered.filter(product => product.rating >= this.minRating)

  // 搜索过滤
  if (this.searchKeyword.trim() !== '') {
    filtered = filtered.filter(product =>
      product.name.includes(this.searchKeyword) ||
      product.description.includes(this.searchKeyword) ||
      product.brand.includes(this.searchKeyword) ||
      product.tags.some(tag => tag.includes(this.searchKeyword))
    )
  }

  // 排序
  // ... 排序逻辑 ...

  return filtered
}

5.2 复杂排序算法

电商应用中的排序通常不仅仅是简单的价格或销量排序,而是综合考虑多个因素的复杂算法。

getFilteredProducts(): EcommerceProduct[] {
  let filtered = this.products
  
  // ... 过滤逻辑 ...

  // 排序
  switch (this.sortBy) {
    case '价格升序':
      filtered.sort((a, b) => a.price - b.price)
      break
    case '价格降序':
      filtered.sort((a, b) => b.price - a.price)
      break
    case '销量':
      filtered.sort((a, b) => b.salesCount - a.salesCount)
      break
    case '评分':
      filtered.sort((a, b) => b.rating - a.rating)
      break
    case '好评优先':
      filtered.sort((a, b) => {
        // 先按评分排序,评分相同则按评价数量排序
        if (b.rating !== a.rating) {
          return b.rating - a.rating
        }
        return b.reviewCount - a.reviewCount
      })
      break
    case '新品优先':
      filtered.sort((a, b) => {
        // 新品排在前面,然后按上架时间排序
        if (a.isNew !== b.isNew) {
          return a.isNew ? -1 : 1
        }
        // 这里假设有上架时间字段,实际应用中需要根据具体数据模型调整
        return b.id - a.id // 使用 ID 作为上架时间的近似值
      })
      break
    default: // 综合
      filtered.sort((a, b) => {
        // 综合考虑多个因素的复杂排序算法
        const scoreA = a.salesCount * 0.3 + a.rating * 1000 + a.reviewCount * 0.5 +
                      (a.isHot ? 500 : 0) + (a.isNew ? 300 : 0) + (a.isFreeShipping ? 100 : 0)
        const scoreB = b.salesCount * 0.3 + b.rating * 1000 + b.reviewCount * 0.5 +
                      (b.isHot ? 500 : 0) + (b.isNew ? 300 : 0) + (b.isFreeShipping ? 100 : 0)
        return scoreB - scoreA
      })
  }

  return filtered
}

这个复杂的排序算法综合考虑了商品的销量、评分、评价数量、是否热销、是否新品、是否包邮等多个因素,为每个商品计算一个综合得分,然后按得分排序。

6. 购物车管理

6.1 购物车数据结构

购物车是电商应用的核心功能之一,需要高效的数据结构和操作方法。

// 购物车项目接口
interface CateItem {
  productId: number;  // 商品ID
  quantity: number;   // 数量
  selected: boolean;  // 是否选中
}

// 购物车状态
@State cartItems: CateItem[] = []
@State allSelected: boolean = true

6.2 购物车操作方法

// 添加到购物车
addToCart(productId: number, quantity: number = 1) {
  const existingItem = this.cartItems.find(item => item.productId === productId)
  if (existingItem) {
    existingItem.quantity += quantity
  } else {
    this.cartItems.push({ productId, quantity, selected: true })
  }
}

// 从购物车移除
removeFromCart(productId: number) {
  this.cartItems = this.cartItems.filter(item => item.productId !== productId)
}

// 更新购物车商品数量
updateCartItemQuantity(productId: number, quantity: number) {
  const item = this.cartItems.find(item => item.productId === productId)
  if (item) {
    item.quantity = quantity
  }
}

// 切换商品选中状态
toggleCartItemSelection(productId: number) {
  const item = this.cartItems.find(item => item.productId === productId)
  if (item) {
    item.selected = !item.selected
    this.updateAllSelectedState()
  }
}

// 切换全选状态
toggleAllSelection() {
  this.allSelected = !this.allSelected
  this.cartItems.forEach(item => {
    item.selected = this.allSelected
  })
}

// 更新全选状态
updateAllSelectedState() {
  this.allSelected = this.cartItems.length > 0 && this.cartItems.every(item => item.selected)
}

// 获取选中的商品数量
getSelectedItemCount(): number {
  return this.cartItems.filter(item => item.selected).reduce((total, item) => total + item.quantity, 0)
}

// 获取选中的商品总价
getSelectedTotal(): number {
  return this.cartItems.filter(item => item.selected).reduce((total, item) => {
    const product = this.products.find(p => p.id === item.productId)
    return total + (product ? product.price * item.quantity : 0)
  }, 0)
}

// 清空购物车
clearCart() {
  this.cartItems = []
}

这些方法提供了完整的购物车管理功能,包括添加、移除、更新数量、选中/取消选中、全选/取消全选、计算总数量和总价等操作。

7. 总结

在本教程中,我们深入探讨了 HarmonyOS NEXT 电商网格布局的进阶特性,包括高级交互设计、动态布局调整、高级样式与视觉效果、高级数据处理和购物车管理等方面。

收藏00

登录 后评论。没有帐号? 注册 一个。