鸿蒙5自定义组件封装:可复用的商品卡片组件

暗雨OL
发布于 2025-6-27 22:02
浏览
0收藏

一、组件设计目标
在鸿蒙5应用开发中,商品卡片是电商类应用的核心UI元素。本组件将封装一个可复用的商品卡片组件,具备以下特性:

支持多种布局模式(列表/网格)
响应式布局适应不同设备
支持主题切换(日间/夜间模式)
支持加载动画和骨架屏
内置点击动画效果
二、组件参数设计
// ProductCardTypes.ets
export interface ProductInfo {
id: string;
image: ResourceStr; // 商品图片
title: string; // 商品标题
description: string; // 商品描述
price: number; // 当前价格
originalPrice?: number; // 原价(可选)
discount?: number; // 折扣率(可选)
rating?: number; // 评分(0-5)
isFavorite?: boolean; // 是否收藏
}

export interface ProductCardConfig {
layout: ‘list’ | ‘grid’; // 布局模式
theme: ‘light’ | ‘dark’; // 主题模式
showSkeleton: boolean; // 是否显示骨架屏
}
三、商品卡片组件完整实现
// ProductCard.ets
import { ProductInfo, ProductCardConfig } from ‘./ProductCardTypes’;

@Component
export default struct ProductCard {
// 组件配置参数
@State config: ProductCardConfig = {
layout: ‘grid’,
theme: ‘light’,
showSkeleton: true
};

// 商品数据
@Prop productInfo: ProductInfo;

// 全局主题上下文
@StorageLink(‘appTheme’) appTheme: ‘light’ | ‘dark’ = ‘light’;

// 收藏点击事件
@Emit favoriteChange: (productId: string, isFavorite: boolean) => void;

// 卡片点击事件
@Emit cardClick: (productId: string) => void;

// 构建加载骨架屏
buildSkeleton() {
Column() {
Stack() {
Rect().width(‘100%’).height(200).fill(‘#E0E0E0’)

    Column({ space: 8 }) {
      Rect().width('60%').height(16).fill('#EEEEEE')
      Rect().width('40%').height(16).fill('#EEEEEE')
      Rect().width('30%').height(14).fill('#EEEEEE')
      
      Row({ space: 12 }) {
        Rect().width(80).height(14).fill('#EEEEEE')
        Rect().width(60).height(14).fill('#EEEEEE')
      }
    }
    .padding(12)
    .alignItems(HorizontalAlign.Start)
    .position({ x: 0, y: 200 })
  }
  .width('100%')
}
.opacity(0.8)
.animation({
  duration: 1000,
  curve: Curve.EaseInOut,
  iterations: -1,       // 无限循环
  playMode: PlayMode.AlternateReverse
})

}

// 构建实际卡片内容
buildCardContent() {
Column() {
// 商品图片区域
Stack() {
// 商品主图
Image(this.productInfo.image)
.objectFit(ImageFit.Cover)
.width(‘100%’)
.aspectRatio(1)

    // 折扣标签
    if (this.productInfo.discount && this.productInfo.discount > 0) {
      Row() {
        Text($r('app.string.discount', this.productInfo.discount))
          .fontSize(10)
          .fontColor('#FFFFFF')
      }
      .padding(4)
      .borderRadius({
        topLeft: 4,
        bottomRight: 4
      })
      .backgroundColor('#FF2157')
      .position({ x: 0, y: 0 })
    }
    
    // 收藏按钮
    Button() {
      Image($r(this.productInfo.isFavorite ? 
        'app.media.ic_favorite_filled' : 
        'app.media.ic_favorite'))
        .width(20)
        .height(20)
    }
    .type(ButtonType.Icon)
    .backgroundColor('#00000033') // 半透背景
    .width(36)
    .height(36)
    .borderRadius(18)
    .onClick(() => {
      // 触发收藏状态变化
      this.favoriteChange(this.productInfo.id, !this.productInfo.isFavorite);
      
      // 点击动画
      animateTo({
        duration: 300,
        curve: Curve.Friction
      }, () => {
        // 动画效果
      });
    })
    .position({ x: '80%', y: '90%' })
  }
  
  // 商品信息区域
  Column({ space: 4 }) {
    // 商品标题
    Text(this.productInfo.title)
      .fontSize(16)
      .fontWeight(500)
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
    
    // 商品描述
    Text(this.productInfo.description)
      .fontSize(12)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
      .maxLines(2)
      .fontColor(this.appTheme === 'light' ? '#666666' : '#AAAAAA')
    
    // 价格区域
    Row({ space: 8 }) {
      // 当前价格
      Text(`¥${this.productInfo.price.toFixed(2)}`)
        .fontColor('#FF2157')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
      
      // 原价(带删除线)
      if (this.productInfo.originalPrice && 
          this.productInfo.originalPrice > this.productInfo.price) {
        Text(`¥${this.productInfo.originalPrice.toFixed(2)}`)
          .fontColor('#999999')
          .fontSize(12)
          .decoration({ type: TextDecorationType.LineThrough })
      }
      
      Blank()
    }
    .layoutWeight(1)
    .height(36)
    .alignItems(VerticalAlign.Center)
    
    // 评分区域
    if (this.productInfo.rating) {
      Row() {
        ForEach(Array.from({length: 5}), (_, index) => {
          Image($r(index < Math.floor(this.productInfo.rating) ? 
            'app.media.ic_star_filled' : 
            'app.media.ic_star'))
            .width(16)
            .height(16)
        })
        
        Text(`(${this.productInfo.rating.toFixed(1)})`)
          .fontSize(12)
          .margin({ left: 4 })
      }
    }
  }
  .padding(12)
}
.backgroundColor(this.appTheme === 'light' ? '#FFFFFF' : '#1E1E1E')
.borderRadius(12)
.shadow({
  radius: this.appTheme === 'light' ? 4 : 0,
  color: this.appTheme === 'light' ? '#00000010' : '#000000',
  offsetX: 1,
  offsetY: 2
})

}

build() {
Column() {
if (this.config.showSkeleton) {
this.buildSkeleton();
} else {
this.buildCardContent();
}
}
.onClick(() => {
// 点击动画效果
animateTo({
duration: 200,
curve: Curve.EaseOut
}, () => {
// 缩放动画
});

  // 触发点击事件
  this.cardClick(this.productInfo.id);
})

}
}
四、组件使用示例
// HomePage.ets
import ProductCard from ‘…/components/ProductCard’;

@Entry
@Component
struct HomePage {
// 示例商品数据
@State productList: Array<ProductInfo> = [
{
id: ‘p001’,
image: $r(‘app.media.product1’),
title: ‘华为Mate 60 Pro’,
description: ‘麒麟9000s芯片 昆仑玻璃’,
price: 6999,
originalPrice: 7999,
discount: 12,
rating: 4.8,
isFavorite: false
},
{
id: ‘p002’,
image: $r(‘app.media.product2’),
title: ‘华为Watch 4’,
description: ‘智能健康手表’,
price: 2699,
originalPrice: 2999,
discount: 10,
rating: 4.7,
isFavorite: true
}
];

// 收藏状态切换处理
handleFavorite(productId: string, isFavorite: boolean) {
// 在实际应用中这里应该调用API更新状态
let index = this.productList.findIndex(p => p.id === productId);
if (index !== -1) {
this.productList[index].isFavorite = isFavorite;
}
}

build() {
Column() {
// 列表布局
List({ space: 20 }) {
ForEach(this.productList, (item: ProductInfo) => {
ListItem() {
ProductCard({
productInfo: item,
config: {
layout: ‘list’,
theme: ‘light’,
showSkeleton: false
},
favoriteChange: this.handleFavorite.bind(this),
cardClick: (productId: string) => {
// 跳转到商品详情页
router.push({ url: ‘pages/DetailPage’, params: { id: productId } });
}
})
.margin({ top: 10, bottom: 10 })
}
})
}
.layoutWeight(1)
}
.padding(16)
.backgroundColor(‘#F5F5F5’)
}
}
五、组件的优势与特性
​​响应式设计​​
@StorageLink(‘appTheme’) appTheme: ‘light’ | ‘dark’ = ‘light’;
自动适配日间/夜间模式,颜色和阴影动态调整
​​交互体验优化​​
.onClick(() => {
animateTo({…}, () => { /* 动画 */ });
this.cardClick(this.productInfo.id);
})
内置点击动画,提升用户体验
​​性能优化​​
使用 @Prop 确保数据单向流动
ForEach 渲染提高列表性能
骨架屏优化加载体验
​​可扩展性​​
通过 ProductCardConfig 配置不同布局模式
自定义事件系统(@Emit)
支持国际化资源引用
​​自适应布局​​
layout: ‘list’ | ‘grid’
组件自动根据布局模式调整UI结构和间距
六、组件使用建议
​​数据加载最佳实践​​
// 加载数据时显示骨架屏
this.config.showSkeleton = true;
fetchProducts().then(data => {
this.productList = data;
this.config.showSkeleton = false;
});
​​主题切换处理​​
// 监听系统主题变化
themeManager.on(‘themeChange’, (theme) => {
this.appTheme = theme;
});
​​内存优化​​
aboutToDisappear() {
// 释放资源引用
this.productList.forEach(item => {
releaseResource(item.image);
});
}
这个可复用的商品卡片组件封装了电商应用的常见UI需求,遵循鸿蒙5的设计规范,可无缝集成到各类电商应用中。通过参数化配置,开发者可以轻松实现不同布局风格的商品展示需求。

分类
标签
收藏
回复
举报
回复
    相关推荐