HarmonyOS应用开发ArkUI(TS)电商项目实战

项目介绍

本项目基于 HarmonyOS 的ArkUI框架TS扩展的声明式开发范式,关于语法和概念直接看官网官方文档地址:基于TS扩展的声明式开发范式

工具版本: DevEco Studio 3.1 Canary1

SDK版本: 3.1.9.7(API Version 8 Release)

效果演示

页面解析

主框架

使用容器组件:Tabs 、TabContent、作为主框架,底部Tab使用自定义布局样式,设置点击事件,每次点击更换选中的索引。来切换内容页面。

使用自定义组件来实现:首页、分类、购物车、我的的搭建。

@Entry
@Component
struct MainFrame {
  @State selectIndex: number = 0
  private controller: TabsController = new TabsController()
  private tabBar = getTabBarList()

  // 内容
  @Builder Content() {
    Tabs({ controller: this.controller }) {
      TabContent() {HomeComponent()}
      TabContent() {ClassifyComponent()}
      TabContent() {ShoppingCartComponent({ isShowLeft: false })}
      TabContent() {MyComponent()}
    }
    .width('100%')
    .height(0)
    .animationDuration(0)
    .layoutWeight(1)
    .scrollable(false)
    .barWidth(0)
    .barHeight(0)
  }

  // 底部导航
  @Builder TabBar() {
    Row() {
      ForEach(this.tabBar, (item: TabBarModel, index) => {
        Column() {
          Image(this.selectIndex === index ? item.iconSelected : item.icon)
            .width(23).height(23).margin({ right: index === 2 ? 3 : 0 })
          Text(item.name)
            .fontColor(this.selectIndex === index ? '#dc1c22' : '#000000')
            .margin({ left: 1, top: 2 })
        }.layoutWeight(1)
        .onClick(() => {
          this.selectIndex = index
          this.controller.changeIndex(index)
        })
      }, item => item.name)
    }.width('100%').height(50)
    .shadow({ radius: 1, color: '#e3e2e2', offsetY: -1 })
  }

  build() {
    Column() {
      this.Content()
      this.TabBar()
    }.width('100%').height('100%')
  }
}
首页

因为顶部标题栏需要浮在内容之上,所以根布局使用 容器组件Stack ,内容布局最外层使用 容器组件Scroll 来实现滑动,内容整体分为3部分:

  1. 轮播图:使用 容器组件Swiper
  2. 菜单:使用 容器组件Flex 、默认横向布局,子布局宽度分为五等分,设置Flex的参数:wrap: FlexWrap.Wrap可实现自动换行。
  3. 商品列表:和菜单的实现方式一样,只不过宽度是二等分

向下滑动时标题栏显示功能:使用 容器组件Scroll 的属性方法:onScroll来判断y轴方向的偏移量,根据偏移量计算出比例,改变标题栏的透明度。

(以下是部分代码)


@Component
export struct HomeComponent {
  // 滑动的y偏移量
  private yTotalOffset = 0
  // 标题栏透明度
  @State titleBarOpacity: number = 0
  // 轮播图列表
  private banners = [
    $r("app.media.banner1"), $r("app.media.banner2"), $r("app.media.banner3"),
    $r("app.media.banner4"), $r("app.media.banner5"), $r("app.media.banner6"),
  ]
  // 菜单列表
  private menuList = getHomeMenuList()
  // 商品列表
  private goodsList: Array<HomeGoodsModel> = getHomeGoodsList()


  // 轮播图
  @Builder Banner() {...}

  // 菜单
  @Builder Menu() {....}

  // 商品列表
  @Builder GoodsList() {...}

  build() {
    Stack({ alignContent: Alignment.Top }) {
      Scroll() {
        Column() {
          this.Banner()
          this.Menu()
          this.GoodsList()
        }.backgroundColor(Color.White)
      }.scrollBar(BarState.Off)
      .onScroll((xOffset, yOffset) => {
        this.yTotalOffset += yOffset
        const yTotalOffsetVP = px2vp(this.yTotalOffset)
        // 轮播图高度 350
        const scale = yTotalOffsetVP / 200
        this.titleBarOpacity = scale
      })

      Row(){
        TitleBar({
          title: '首页',
          isShowLeft: false,
          isShowRight: true,
          rightImage: $r('app.media.search')
        })
      }.opacity(this.titleBarOpacity)
    }.width('100%').height('100%')
  }
}
详情页

和首页类似,根布局使用容器组件Stack,内容布局最外层使用容器组件Scroll 来实现滑动。因为详情页有相同布局,使用装饰器@Builder来抽离公共布局。

向下滑动时标题栏显示功能原理和首页一样

(以下是部分代码)

import router from '@ohos.router';

@Entry
@Component
struct GoodsDetail {
  ....
  // 轮播图
  @Builder Banner() {...}

  // 内容item
  @Builder ContentItem(title: string, content: string, top=1) {
    Row() {
      Text(title).fontSize(13).fontColor('#808080')
      Text(content).fontSize(13).margin({ left: 10 }).fontColor('#ff323232')
      Blank()
      Image($r('app.media.arrow')).width(12).height(18)
    }.width('100%').padding(13)
    .backgroundColor(Color.White).margin({ top: top })
  }
  // 内容
  @Builder Content() {
    ...
    this.ContentItem('邮费', '满80包邮', 7)
    this.ContentItem('优惠', '减5元')
    this.ContentItem('规格', '山核桃坚果曲奇; x3', 7)
    this.ContentItem('配送', '北京市朝阳区大塔路33号')
  }

  // 评论
  @Builder Comment() {...}

  // 商品参数item
  @Builder GoodsParamItem(title: string, content: string) {
    Row() {
      Text(title).width(100).fontSize(13).fontColor('#808080')
      Text(content).fontSize(13).fontColor('#ff323232')
        .layoutWeight(1).padding({ right: 20 })
    }.width('100%').padding({ top: 15 })
    .alignItems(VerticalAlign.Top)
  }
  // 商品参数
  @Builder GoodsParam() {...}

  build() {
    Stack() {
      Scroll() {
        Column() {
          this.Banner()
          this.Content()
          this.Comment()
          this.GoodsParam()
        }
      }.scrollBar(BarState.Off)
      .onScroll((xOffset, yOffset) => {
        this.yTotalOffset += yOffset
        const yTotalOffsetVP = px2vp(this.yTotalOffset)
        // 轮播图高度 350
        const scale = yTotalOffsetVP / 350
        this.titleBarBgTmOpacity = 1 - scale
        if (scale > 0.4) {
          this.titleBarBgWhiteOpacity = scale - 0.2
        } else {
          this.titleBarBgWhiteOpacity = 0
        }
      })

      this.TopBottomOperateBar()
    }.width('100%').height('100%')
    .backgroundColor('#F4F4F4')
  }
}
分类

此页面很简单,就是左右两个容器组件List、一个宽度30%,一个宽度70%,使用循环渲染组件ForEach来渲染列表。

(以下是部分代码)

@Component
export struct ClassifyComponent {
  // 搜索
  @Builder Search() {...}

  // 内容
  @Builder Content() {
    Row() {
      // 左分类
      List() {...}.width('30%')

      // 右分类
      List({scroller:this.scroller}) {...}.width('70%')

    }.width('100%').layoutWeight(1)
    .alignItems(VerticalAlign.Top)
  }

  build() {
    Column() {
      this.Search()
      this.Content()
    }.width('100%').height('100%')
    .backgroundColor(Color.White)
  }
}
购物车

此界面功能:全选、单选、删除商品、更改商品的数量。当有商品选中,右上角删除按钮显示。更改商品数量同步更新合计的总价格。

使用List组件来实现列表。数据数组使用装饰器@State定义。数据更新必须是更改数组的项

(以下是部分代码)

@Component
export struct ShoppingCartComponent {
  ...
  // 商品详情
  @Builder GoodsItem(item: ShoppingCartModel, index: number) {...}

  build() {
    Column() {
      TitleBar()
        
      if (this.list.length > 0) {
        // 列表  
        List() {...}.layoutWeight(1)
        // 结算布局
        Row() {...}
      } else {
        Row() {
          Text('空空如也~')
        }
      }
    }.width('100%').height('100%')
    .backgroundColor('#F4F4F4')
  }

  selectOperation(item: ShoppingCartModel, index: number) {
    // 替换元素,才能更改数组,这样页面才能更新
    let newItem = {...item}
    newItem.isSelected = !item.isSelected
    this.list.splice(index, 1, newItem)

    this.calculateTotalPrice()
  }

  // 加/减操作
  addOrSubtractOperation(item: ShoppingCartModel, index: number, type: -1 | 1) {...}

  // 计算合计
  calculateTotalPrice() {
    let total = 0
    let selectedCount = 0
    for (const item of this.list) {
      if (item.isSelected) {
        selectedCount++
        total += item.newPrice * item.count
      }
    }
    this.totalPrice = total
    this.isAllSelected = selectedCount === this.list.length
  }
}

结尾

此项目没有比较难的点,都是照着官方文档,查阅API做出来的,依靠着声明式UI的简洁和强大,页面搭建效率很高。

上一篇:基于单片机电动车电压电流监测系统-三、 软件设计


下一篇:stack和queue的使用