HarmonyOS5 购物商城app(一):商品展示(附代码)

206 阅读7分钟

基于ArkTS打造的电商应用界面设计与实现

在移动应用开发领域,ArkTS作为HarmonyOS生态的重要开发语言,凭借其简洁的语法和强大的组件化能力,正成为构建高性能应用的首选。本文将深入解析一个基于ArkTS开发的电商应用界面代码,从架构设计到交互实现,全方位展现如何利用ArkTS打造流畅且美观的电商购物体验。

数据模型设计:清晰的业务实体抽象

在电商应用中,数据模型是构建整个应用的基础。代码中定义了两个核心类,分别用于表示轮播图数据和商品信息,这种清晰的实体抽象为后续开发奠定了坚实基础。

BannerClass:轮播图数据模型

class BannerClass {
  id: string;
  imagePath: string;
  title: string;

  constructor(id: string, imagePath: string, title: string) {
    this.id = id;
    this.imagePath = imagePath;
    this.title = title;
  }
}

该模型包含三个基本属性:唯一标识id、图片路径imagePath和标题title。在实际应用中,轮播图通常用于展示促销活动或热门商品,通过这种简洁的模型设计,能够轻松实现轮播图数据的管理和展示。

ShopClass:商品信息模型

class ShopClass {
  id: string;
  imagePath: string;
  title: string;
  price: string;

  constructor(id: string, imagePath: string, title: string, price: string) {
    this.id = id;
    this.imagePath = imagePath;
    this.title = title;
    this.price = price;
  }
}

商品模型在电商应用中至关重要,此模型包含商品id、图片路径、商品名称和价格信息。值得注意的是,价格字段采用字符串类型,这在实际开发中较为常见,便于处理价格前缀(如"¥")或其他格式化需求。

首页设计:从布局到交互的完整实现

首页作为电商应用的门户,直接影响用户体验。代码中的Index组件采用了"轮播图+瀑布流"的经典布局模式,同时融入了丰富的交互效果。

轮播图组件:动态展示与视觉引导

Swiper() {
  ForEach(this.SliderImage, (item: BannerClass) => {
    Image($r(item.imagePath))
      .width('100%')
      .height(200)
      .objectFit(ImageFit.Cover)
      .borderRadius(20)
      .margin({ bottom: 10 })
  });
}
.autoPlay(true)
.interval(3000)
.indicatorStyle({ color: '#ffffff' })

轮播图通过Swiper组件实现,设置了自动播放(autoPlay(true))和3秒的切换间隔(interval(3000)),白色指示器(indicatorStyle)在视觉上与背景形成对比,提升了用户体验。每张图片采用Cover模式填充,确保图片完整显示且不失真,20px的圆角设计则符合现代UI的审美趋势。

瀑布流布局:高效的商品展示方案

WaterFlow() {
  ForEach(this.TutorialArray, (item: ShopClass) => {
    FlowItem() {
      this.badList(item)
    }
    .onClick(() => {
      this.mainStarIndex.pushPathByName('shopCartView', item);
    })
  });
}
.columnsTemplate('1fr 1fr')
.columnsGap(15)
.rowsGap(15)
.padding({ left: 12, right: 12, top: 5, bottom: 10 })
.backgroundColor('#f5f5f5')

瀑布流布局使用WaterFlow组件实现,采用两列布局(columnsTemplate('1fr 1fr')),行列间距均为15px,确保商品卡片之间有合适的留白。点击商品卡片时,通过mainStarIndex.pushPathByName导航到商品详情页,实现了从列表到详情的交互流程。

商品卡片组件:细节之处见真章

@Builder
badList(item: ShopClass) {
  Column({ space: 10 }) {
    // 商品图片 - 添加悬停效果
    Image($r(item.imagePath))
      .width('100%')
      .height(150)
      .objectFit(ImageFit.Cover)
      .borderRadius(12)
      .transition({ type: TransitionType.All, scale: { x: 0.95, y: 0.95 } })

    // 商品标题
    Text(item.title)
      .width('100%')
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .textAlign(TextAlign.Start)
      .fontFamily('HarmonyOS Sans')

    // 评分和价格
    Row() {
      // 评分部分
      Row({ space: 5 }) {
        Image($r('app.media.star'))
          .width(18)
        Text('4.5')
          .fontSize(15)
          .fontColor('#666666')
      }

      // 价格
      Text(`¥${item.price}`)
        .fontSize(18)
        .fontColor('#e6393f')
        .fontWeight(FontWeight.Bold)
        .layoutWeight(1)
        .textAlign(TextAlign.End)
    }
    .width('100%')
    .alignItems(VerticalAlign.Center)
  }
  .width('100%')
  .padding({ bottom: 15 })
  .backgroundColor('#FFFFFF')
  .borderRadius(15)
  .shadow({ color: '#dcdcdc', radius: 6, offsetX: 0, offsetY: 2 })
  .margin({ bottom: 10 })
  .hoverEffect(HoverEffect.Scale) // 添加悬停缩放效果
}

商品卡片通过@Builder装饰器封装为可复用组件,包含图片、标题、评分和价格信息。图片添加了悬停缩放过渡效果(transition),当用户手指悬停时,图片会轻微缩小,提供直观的交互反馈。价格使用红色(#e6393f)和加粗样式突出显示,符合电商应用的设计惯例。卡片整体采用白色背景、15px圆角和阴影效果,营造出层次感和立体感。

商品详情页:沉浸式购物体验构建

商品详情页是促成交易的关键环节,代码中的ShopsView组件实现了从轮播图展示到购买按钮的完整流程,注重细节设计和用户引导。

顶部轮播与导航:品牌与功能的双重展示

Swiper() {
  ForEach(this.SliderArray, (item: BannerClass) => {
    Image($r(item.imagePath))
      .width('100%')
      .height(300)
      .objectFit(ImageFit.Cover)
      .transition({ type: TransitionType.All, opacity: 0.8, scale: { x: 0.95, y: 0.95 } })
  });
}
.autoPlay(true)
.interval(3000)
.borderRadius(20)
.margin({ bottom: 15 })

// 返回按钮和标题栏
Row() {
  Image($r('app.media.black'))
    .width(30)
    .height(30)
    .onClick(() => {
      this.mainStarIndex.pop();
    })
    .margin({ right: 10 })

  Text('商品详情')
    .fontSize(24)
    .fontWeight(FontWeight.Bold)
    .fontFamily('HarmonyOS Sans')
    .layoutWeight(1)
    .textAlign(TextAlign.Center)
    .fontColor(Color.Red)
}
.width('100%')
.height(60)
.alignItems(VerticalAlign.Bottom)
.justifyContent(FlexAlign.Start)
.padding({ top: 0, left: 15, right: 15 })
.position({ top: 0 })

详情页顶部的轮播图高度增加到300px,提供更沉浸式的图片展示体验,同时添加了透明度和缩放的过渡效果,增强视觉吸引力。自定义的返回按钮和标题栏采用红色文字突出显示,与白色背景形成鲜明对比,便于用户识别和操作。

商品信息展示:从内容到交互的完整链路

// 商品名称
Text(this.article.title)
  .fontSize(26)
  .fontWeight(FontWeight.Bold)
  .fontColor('#333333')
  .textAlign(TextAlign.Center)
  .width('100%')
  .margin({ bottom: 10 })

// 价格标签
Row() {
  Text(`¥${this.article.price.split('¥')[1]}`)
    .fontSize(24)
    .fontColor('#e6393f')
    .fontWeight(FontWeight.Bold)

  Text('已售 1000+')
    .fontSize(14)
    .fontColor('#999999')
    .margin({ left: 10 })
}
.width('100%')
.padding({ left: 20, right: 20 })

// 商品描述
Text('这里是商品的详细描述,可以包含材质、尺寸、产地等重要信息。您可以通过滚动查看完整的商品介绍。')
  .fontSize(16)
  .fontColor('#555555')
  .lineHeight(26)
  .textAlign(TextAlign.Start)
  .width('100%')
  .padding({ left: 20, right: 20 })

商品名称使用26px的大号字体和粗体样式,确保用户一眼就能看到商品信息。价格部分通过split('¥')[1]处理,提取实际价格数值并以红色突出显示,旁边的销售数据("已售1000+")则增强了商品的可信度。商品描述采用较大的行高(26),提升长文本的可读性,为实际应用中的详细说明预留了空间。

购物引导:从信任到行动的转化设计

// 商品特色图标
Row({ space: 30 }) {
  Column() {
    Image($r('app.media.background'))
      .width(25)
      .height(25)
    Text('快递包邮')
      .fontSize(14)
      .fontColor('#666666')
  }

  Column() {
    Image($r('app.media.background'))
      .width(25)
      .height(25)
    Text('7天退换')
      .fontSize(14)
      .fontColor('#666666')
    }

    Column() {
      Image($r('app.media.background'))
        .width(25)
        .height(25)
      Text('正品保障')
        .fontSize(14)
        .fontColor('#666666')
    }
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.padding({ left: 20, right: 20 })

// 加入购物车按钮
Button('加入购物车')
  .onClick(() => {
    // 这里可以添加购物车逻辑
  })
  .width(240)
  .height(50)
  .fontSize(18)
  .backgroundColor('#ff4444')
  .borderRadius(30)
  .margin({ top: 25 })
  .shadow({ color: '#ff6666', radius: 10, offsetX: 0, offsetY: 4 })
  .hoverEffect(HoverEffect.Scale)

特色服务图标通过图标+文字的形式展示"快递包邮"、"7天退换"和"正品保障"等信息,增强用户对商品的信任感。加入购物车按钮采用醒目的红色(#ff4444),搭配阴影效果和悬停缩放动画,吸引用户点击。240px的宽度和50px的高度确保按钮在各种设备上都易于点击,符合移动端交互设计原则。

架构设计:Navigation与组件化思想

整个应用采用了Navigation导航框架,通过NavPathStack管理页面栈,实现了首页与详情页之间的平滑切换。@Provide@Consume装饰器的使用,实现了组件间的数据传递,体现了ArkTS的状态管理思想。

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

@Builder
shopPage(name: string, params: ShopClass | string) {
  if (name === 'shopCartView') {
    ShopsView({
      article: params as ShopClass
    });
  }
}

build() {
  Navigation(this.mainStarIndex) {
    // 页面内容
  }
  .navDestination(this.shopPage)
}

在详情页中,通过@Consume接收来自首页的导航参数:

@Component
export struct ShopsView {
  @Consume('mainStarIndex') mainStarIndex: NavPathStack;
  @Prop article: ShopClass;
  // 页面内容
}

这种架构设计使得页面导航和数据传递变得清晰可控,为后续扩展更多页面和功能奠定了良好的基础。

附:代码

class BannerClass {
  id: string;
  imagePath: string;
  title: string;

  constructor(id: string, imagePath: string, title: string) {
    this.id = id;
    this.imagePath = imagePath;
    this.title = title;
  }
}

class ShopClass {
  id: string;
  imagePath: string;
  title: string;
  price: string;

  constructor(id: string, imagePath: string, title: string, price: string) {
    this.id = id;
    this.imagePath = imagePath;
    this.title = title;
    this.price = price;
  }
}

@Entry
@Component
struct Index {

  private SliderImage: Array<BannerClass> = [
    new BannerClass('1', 'app.media.shop_swiper1', ''),
    new BannerClass('2', 'app.media.shop_swiper1', ''),
    new BannerClass('3', 'app.media.shop_swiper1', ''),
    new BannerClass('4', 'app.media.shop_swiper1', ''),
  ];

  private TutorialArray: Array<ShopClass> = [
    new ShopClass('1', 'app.media.shop_01', '商品1', '¥10'),
    new ShopClass('2', 'app.media.shop_02', '商品2', '¥10'),
    new ShopClass('3', 'app.media.shop_03', '商品3', '¥10'),
    new ShopClass('4', 'app.media.shop_04', '商品4', '¥10'),
    new ShopClass('5', 'app.media.shop_05', '商品5', '¥10'),
    new ShopClass('6', 'app.media.shop_06', '商品6', '¥10'),
    new ShopClass('7', 'app.media.shop_07', '商品7', '¥10'),
    new ShopClass('8', 'app.media.shop_08', '商品8', '¥10'),
    new ShopClass('9', 'app.media.shop_09', '商品9', '¥10'),
    new ShopClass('10', 'app.media.shop_10', '商品10', '¥10'),
    new ShopClass('11', 'app.media.shop_11', '商品11', '¥10'),
    new ShopClass('12', 'app.media.shop_12', '商品12', '¥10'),
  ];

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

  @Builder
  shopPage(name: string, params: ShopClass | string) {
    if (name === 'shopCartView') {
      ShopsView({
        article: params as ShopClass
      });
    }
  }

  build() {
    Navigation(this.mainStarIndex) {
      Column({ space: 15 }) {
        // 轮播图组件 - 添加自动播放和指示器
        Swiper() {
          ForEach(this.SliderImage, (item: BannerClass) => {
            Image($r(item.imagePath))
              .width('100%')
              .height(200)
              .objectFit(ImageFit.Cover)
              .borderRadius(20)
              .margin({ bottom: 10 })
          });
        }
        .autoPlay(true)
        .interval(3000)
        .indicatorStyle({ color: '#ffffff' })

        // 瀑布流布局 - 优化样式和间距
        WaterFlow() {
          ForEach(this.TutorialArray, (item: ShopClass) => {
            FlowItem() {
              this.badList(item)
            }
            .onClick(() => {
              this.mainStarIndex.pushPathByName('shopCartView', item);
            })
          });
        }
        .columnsTemplate('1fr 1fr')
        .columnsGap(15)
        .rowsGap(15)
        .padding({ left: 12, right: 12, top: 5, bottom: 10 })
        .backgroundColor('#f5f5f5')
      }
      .width('100%')
    }
    .hideTitleBar(true)
    .mode(NavigationMode.Stack)
    .navDestination(this.shopPage)
  }

  @Builder
  badList(item: ShopClass) {
    Column({ space: 10 }) {
      // 商品图片 - 添加悬停效果
      Image($r(item.imagePath))
        .width('100%')
        .height(150)
        .objectFit(ImageFit.Cover)
        .borderRadius(12)
        .transition({ type: TransitionType.All, scale: { x: 0.95, y: 0.95 } })

      // 商品标题
      Text(item.title)
        .width('100%')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .textAlign(TextAlign.Start)
        .fontFamily('HarmonyOS Sans')

      // 评分和价格
      Row() {
        // 评分部分
        Row({ space: 5 }) {
          Image($r('app.media.star'))
            .width(18)
            // .tintColor('#FFD700')
          Text('4.5')
            .fontSize(15)
            .fontColor('#666666')
        }

        // 价格
        Text(`¥${item.price}`)
          .fontSize(18)
          .fontColor('#e6393f')
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
          .textAlign(TextAlign.End)
      }
      .width('100%')
      .alignItems(VerticalAlign.Center)
    }
    .width('100%')
    .padding({ bottom: 15 })
    .backgroundColor('#FFFFFF')
    .borderRadius(15)
    .shadow({ color: '#dcdcdc', radius: 6, offsetX: 0, offsetY: 2 })
    .margin({ bottom: 10 })
    .hoverEffect(HoverEffect.Scale) // 添加悬停缩放效果
  }
}

// 商品详细页面
@Component
export struct ShopsView {
  @Consume('mainStarIndex') mainStarIndex: NavPathStack;
  @Prop article: ShopClass;

  private SliderArray: Array<BannerClass> = [
    new BannerClass('', `${this.article.imagePath}`, ''),
    new BannerClass('', `${this.article.imagePath}`, ''),
    new BannerClass('', `${this.article.imagePath}`, ''),
    new BannerClass('', `${this.article.imagePath}`, ''),
    new BannerClass('', `${this.article.imagePath}`, ''),
  ];

  build() {
    NavDestination() {
      Stack() {
        // 轮播图 - 添加指示器和过渡效果
        Swiper() {
          ForEach(this.SliderArray, (item: BannerClass) => {
            Image($r(item.imagePath))
              .width('100%')
              .height(300)
              .objectFit(ImageFit.Cover)
              .transition({ type: TransitionType.All, opacity: 0.8, scale: { x: 0.95, y: 0.95 } })
          });
        }
        .autoPlay(true)
        .interval(3000)
        .borderRadius(20)
        .margin({ bottom: 15 })
        // 返回按钮和标题栏
        Row() {
          Image($r('app.media.black'))
            .width(30)
            .height(30)
            .onClick(() => {
              this.mainStarIndex.pop();
            })
            .margin({ right: 10 })

          Text('商品详情')
            .fontSize(24)
            .fontWeight(FontWeight.Bold)
            .fontFamily('HarmonyOS Sans')
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
            .fontColor(Color.Red)
        }
        .width('100%')
        .height(60)
        .alignItems(VerticalAlign.Bottom)
        .justifyContent(FlexAlign.Start)
        .padding({  top: 0,left: 15, right: 15})
        .position({  top: 0})
      }
      .width('100%')

        // 商品信息展示区
        Column({ space: 25 }) {
          // 商品名称
          Text(this.article.title)
            .fontSize(26)
            .fontWeight(FontWeight.Bold)
            .fontColor('#333333')
            .textAlign(TextAlign.Center)
            .width('100%')
            .margin({ bottom: 10 })

          // 价格标签
          Row() {
            Text(`¥${this.article.price.split('¥')[1]}`)
              .fontSize(24)
              .fontColor('#e6393f')
              .fontWeight(FontWeight.Bold)

            Text('已售 1000+')
              .fontSize(14)
              .fontColor('#999999')
              .margin({left: 10})
          }
          .width('100%')
          .padding({ left: 20, right: 20 })

          // 商品描述
          Text('这里是商品的详细描述,可以包含材质、尺寸、产地等重要信息。您可以通过滚动查看完整的商品介绍。')
            .fontSize(16)
            .fontColor('#555555')
            .lineHeight(26)
            .textAlign(TextAlign.Start)
            .width('100%')
            .padding({ left: 20, right: 20 })

          // 分割线
          Divider()
            .strokeWidth(1)
            .color('#eeeeee')
            .margin({ top: 15, bottom: 15 })

          // 商品特色图标
          Row({ space: 30 }) {
            Column() {
              Image($r('app.media.background'))
                .width(25)
                .height(25)
              Text('快递包邮')
                .fontSize(14)
                .fontColor('#666666')
            }

            Column() {
              Image($r('app.media.background'))
                .width(25)
                .height(25)
              Text('7天退换')
                .fontSize(14)
                .fontColor('#666666')
            }

            Column() {
              Image($r('app.media.background'))
                .width(25)
                .height(25)
              Text('正品保障')
                .fontSize(14)
                .fontColor('#666666')
            }
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceEvenly)
          .padding({ left: 20, right: 20 })

          // 加入购物车按钮
          Button('加入购物车')
              .width(20)
              .height(20)
              .margin({ right: 10 })
          .onClick(() => {
            // 这里可以添加购物车逻辑
          })
          .width(240)
          .height(50)
          .fontSize(18)
          .backgroundColor('#ff4444')
          .borderRadius(30)
          .margin({ top: 25 })
          .shadow({ color: '#ff6666', radius: 10, offsetX: 0, offsetY: 4 })
          .hoverEffect(HoverEffect.Scale)
        }
        .width('100%')
        .alignItems(HorizontalAlign.Center)
        // .padding({ top:10, bottom: 30 })
        .backgroundColor('#f9f9f9')
    }
    .hideTitleBar(true)
    .onBackPressed(() => {
      // 自定义返回逻辑
      this.mainStarIndex.pop();
      return true;
    });
  }
}