1 购物商场应用App概述

本文我们最终会构建一款简易的购物商城应用APP,首先介绍购物商城应用功能。

1.1 购物应用功能

购物应用包含两级页面,分别是主页(商品浏览页签、购物车页签、我的页签)和商品详情页面。虽然只有两个页面,但展示了丰富的HarmonyOS ArkUI框架组件的应用,这些组件的功能和效果图。包括自定义弹窗容器组件(Dialog)、列表组件(List)、滑动容器组件(Swiper)、页签组件(Tabs)、按钮组件(Button)、图片组件(Image)、进度条组件(Progress)、格栅组件(Grid)、单选框组件(Toggle)、可滚动容器组件(Scroll)、弹性布局组件(Flex)、水平布局组件(Row)、垂直布局组件(Column)和路由容器组件(Navigator)

程序中所用到的资源文件都放置到resources/rawfile目录下。

1.2 购物应用效果展示

如图所示是商品浏览页签的效果图。

如图所示是购物车页签的效果图。

如图所示是我的页签的效果图。

如图所示是商品详情页面的效果图。

2 实战实现商品列表页签

主界面商品列表页签主要由下面3部分组成:

(1)顶部的Tabs组件。

(2)中间TabComlem组件内包含List组件。其中List组件的每个项是一个水平布局,该水平布局又是由一个垂直布局和一个Image组件组成;垂直布局又是由3个TeXt组件组成。

(3)底部的页签导航。

2.1 应用首页

pages/Index.ets文件作为应用的首页,起到组装其他子页面的作用。代码如下:

import { GoodsData } from '../model/GoodsData'
import { initializeOnStartup, getIconPath, getIconPathSelect } from '../model/GoodsDataModels'
import { ShoppingCart } from './ShoppingCart'
import { MyInfo } from './MyPage'

/**
 * 应用主页
 */
@Entry
@Component
struct Index {
  @Provide currentPage: number = 1
  private goodsItems: GoodsData[] = initializeOnStartup()

  build() {
    Column() {
      Scroll() {
        Column() {
          if (this.currentPage == 1) {
            // 商品列表页
            GoodsHome({ goodsItems: this.goodsItems })
          } else if (this.currentPage == 2) {
            // 购物车列表
            ShoppingCart()
          } else {
            // 我的
            MyInfo()
          }
        }
        .height(700)
      }
      .flexGrow(1)

      HomeBottom()
    }
    .backgroundColor("white")
  }
}

上述代码中,可用看到,应用首页由以下3部分组成:

(1)商品列表页。

(2)购物车列表页,

(3)"我的"页。

2.2 创建模型

新建一个与pages文件夹同级的model文件夹,并在model目录下新建ArsData.ets、GoodsData.ets、Menu.cts和GoodsDataModels.ets 文件,其中ArsData.ets、GoodsData.ets、Menu.ets是数据实体类,GoodsDataModels.ets是存放这3种实体数据集合,并定义了获取各种数据集合的方法。数据实体包含实体的属性和构造方法,可以通过newArsData()来获取ArsData对象。

ArsData.ets内容如下:

export class ArsData {
  id: number = 0;
  title: string = '';
  content: string = '';
}

GoodsData.ets内容如下:

export class GoodsData {
  id: number = 0;
  title: string = '';
  content: string = '';
  price: number = 0;
  imgSrc: string = '';
}

Menu.ets内容如下:

export class Menu {
  id: number = 0;
  title: string = '';
  num: number = 0;
}

export class ImageItem {
  id: number = 0;
  title: string = '';
  imageSrc: string = '';
}

GoodsDataModels.ets内容如下:

import { GoodsData } from './GoodsData'

import { Menu, ImageItem } from './Menu'
import { ArsData } from './ArsData'

export function initializeOnStartup(): Array<GoodsData> {
  return GoodsComposition;
}

export function getIconPath(): Array<string> {
  let IconPath: Array<string> = ['nav/icon-buy.png', 'nav/icon-shopping-cart.png', 'nav/icon-my.png']

  return IconPath;
}

export function getIconPathSelect(): Array<string> {
  let IconPathSelect: Array<string> =
    ['nav/icon-home.png', 'nav/icon-shopping-cart-select.png', 'nav/icon-my-select.png']

  return IconPathSelect;
}

export function getDetailImages(): Array<string> {
  let detailImages: Array<string> =
    ['computer/computer1.png', 'computer/computer2.png', 'computer/computer3.png', 'computer/computer4.png',
      'computer/computer5.png', 'computer/computer6.png']

  return detailImages;
}


export function getMenu(): Array<Menu> {
  return MyMenu;
}

export function getTrans(): Array<ImageItem> {
  return MyTrans;
}

export function getMore(): Array<ImageItem> {
  return MyMore;
}

export function getArs(): Array<ArsData> {
  return ArsList;
}

const GoodsComposition: GoodsData[] = [
  {
    "id": 1,
    "title": '华为 nova 8 Pro ',
    "content": '开售时间: 10:08',
    "price": 3999,
    "imgSrc": 'picture/HW (1).png'
  },
  {
    "id": 2,
    "title": '华为 Mate 30E Pro 5G',
    "content": '3期免息付款',
    "price": 5299,
    "imgSrc": 'picture/HW (2).png'
  },
  {
    "id": 3,
    "title": '华为 MatePad Pro',
    "content": '旗舰款',
    "price": 3799,
    "imgSrc": 'picture/HW (3).png'
  },
  {
    "id": 4,
    "title": '华为 Nova 8 Pro',
    "content": '新品上市',
    "price": 3999,
    "imgSrc": 'picture/HW (4).png'
  },
  {
    "id": 5,
    "title": '华为 WATCH FIT',
    "content": '多功能',
    "price": 769,
    "imgSrc": 'picture/HW (5).png'
  },
  {
    "id": 6,
    "title": '华为 nova 8 Pro ',
    "content": '开售时间: 10:08',
    "price": 3999,
    "imgSrc": 'picture/HW (6).png'
  },
  {
    "id": 7,
    "title": '华为 Mate 30E Pro 5G',
    "content": '3期免息付款',
    "price": 5299,
    "imgSrc": 'picture/HW (7).png'
  },
  {
    "id": 8,
    "title": '华为 MatePad Pro',
    "content": '旗舰款',
    "price": 3799,
    "imgSrc": 'picture/HW (8).png'
  },
  {
    "id": 9,
    "title": '华为 Nova 8 Pro',
    "content": '新品上市',
    "price": 3999,
    "imgSrc": 'picture/HW (9).png'
  },
  {
    "id": 10,
    "title": '华为 WATCH FIT',
    "content": '多功能',
    "price": 769,
    "imgSrc": 'picture/HW (10).png'
  }
]

const MyMenu: Menu[] = [
  {
    'id': 1,
    'title': '收藏夹',
    'num': 10
  },
  {
    'id': 2,
    'title': '搜索次数',
    'num': 1000
  },
  {
    'id': 3,
    'title': '关注',
    'num': 100
  },
  {
    'id': 4,
    'title': '粉丝',
    'num': 345
  }
]


const MyTrans: ImageItem[] = [
  {
    'id': 1,
    'title': '发布: 520',
    'imageSrc': 'nav/icon-menu-release.png'
  },
  {
    'id': 2,
    'title': '已售: 520',
    'imageSrc': 'nav/icon-menu-sell.png'
  },
  {
    'id': 3,
    'title': '已购: 10',
    'imageSrc': 'nav/icon-menu-buy.png'
  }
]

const MyMore: ImageItem[] = [
  {
    'id': 1,
    'title': '指南',
    'imageSrc': 'nav/icon-menu-buy.png'
  },
  {
    'id': 2,
    'title': '创作',
    'imageSrc': 'nav/icon-menu-buy.png'
  },
  {
    'id': 3,
    'title': '海报',
    'imageSrc': 'nav/icon-menu-buy.png'
  },
  {
    'id': 4,
    'title': '游戏',
    'imageSrc': 'nav/icon-menu-buy.png'
  },
  {
    'id': 5,
    'title': '零工',
    'imageSrc': 'nav/icon-menu-buy.png'
  },
  {
    'id': 6,
    'title': '个人资料',
    'imageSrc': 'nav/icon-menu-buy.png'
  },
  {
    'id': 7,
    'title': '关于',
    'imageSrc': 'nav/icon-menu-buy.png'
  },
  {
    'id': 8,
    'title': '租赁',
    'imageSrc': 'nav/icon-menu-buy.png'
  },
  {
    'id': 9,
    'title': '作者',
    'imageSrc': 'nav/icon-menu-buy.png'
  },

]

const ArsList: ArsData[] = [
  {
    'id': 0,
    'title': '屏幕尺寸',
    'content': '13.9 inches',
  },
  {
    'id': 1,
    'title': '内存',
    'content': '16 GB',
  },
  {
    'id': 2,
    'title': '市场名称',
    'content': 'HUAWEI MateBook X Pro',
  },
  {
    'id': 3,
    'title': '色域',
    'content': '100% sRGB color gamut (Typical)',
  },
  {
    'id': 4,
    'title': '电池容量',
    'content': '56 Wh (rated capacity)',
  },
  {
    'id': 5,
    'title': '存储',
    'content': '512 GB',
  },
  {
    'id': 6,
    'title': '分辨率',
    'content': '3000x2000',
  },
  {
    'id': 7,
    'title': '处理器',
    'content': '11th Gen Intel® Core™ i7-1165G7 Processor',
  },
  {
    'id': 8,
    'title': 'CPU处理器',
    'content': '4',
  },
  {
    'id': 9,
    'title': '发布时间',
    'content': 'January 2021',
  }
]

2.3 创建组件

在Index.ets文件中创建商品列表页签相关的组件,添加GoodsHome代码如下:

@Component
struct GoodsHome {
  private goodsItems: GoodsData[] = [];

  build() {
    Column() {
      Tabs() {
        TabContent() {
          GoodsList({ goodsItems: this.goodsItems });
        }
        .tabBar("热销榜")
        .backgroundColor(Color.White)

        TabContent() {
          GoodsList({ goodsItems: this.goodsItems });
        }
        .tabBar("推荐")
        .backgroundColor(Color.White)

        TabContent() {
          GoodsList({ goodsItems: this.goodsItems });
        }
        .tabBar("生活方式")
        .backgroundColor(Color.White)

        TabContent() {
          GoodsList({ goodsItems: this.goodsItems });
        }
        .tabBar("优惠")
        .backgroundColor(Color.White)
      }
      .barWidth(500)
      .barHeight(40)
      .scrollable(true)
      .barMode(BarMode.Scrollable)
      .backgroundColor('#F1F3F5')
      .height(700)

    }
    .alignItems(HorizontalAlign.Start)
    .width('100%')
  }
}

在GoodsHome中使用Tabs组件,在Tabs组件中设置4个TabContent,给每个TabContent设置tabBar属性,并设置TabContent容器中的内容GoodsList组件,GoodsList组件代码如下:

@Component
struct GoodsList {
  private goodsItems: GoodsData[] = [];

  build() {
    Column() {
      List() {
        ForEach(this.goodsItems, (item: GoodsData) => {
          ListItem() {
            GoodsListItem({ goodsItem: item })
          }
        }, (item: GoodsData) => item.id.toString())
      }
      .height('100%')
      .width('100%')
      .align(Alignment.Top)
      .margin({ top: 5 })
    }
  }
}

在GoodsList组件中遍历商品数据集合,Listltem组件中设置组件内容,并使用Navigator组件给每个Item设置顶级跳转路由(会跳转到商品详情页),GoodsListltem组件代码如下:

@Component
struct GoodsListItem {
  private goodsItem: GoodsData = new GoodsData();

  build() {
    Navigator({ target: 'pages/ShoppingDetail' }) {
      Row() {
        Column() {
          Text(this.goodsItem.title)
            .fontSize(14)
          Text(this.goodsItem.content)
            .fontSize(10)
          Text('¥' + this.goodsItem.price)
            .fontSize(14)
            .fontColor(Color.Red)
        }
        .height(100)
        .width('50%')
        .margin({ left: 20 })
        .alignItems(HorizontalAlign.Start)

        Image($rawfile(this.goodsItem.imgSrc))
          .objectFit(ImageFit.ScaleDown)
          .height(100)
          .width('40%')
          .renderMode(ImageRenderMode.Original)
          .margin({ right: 10, left: 10 })

      }
      .backgroundColor(Color.White)

    }
    .params({ goodsData: this.goodsItem })
    .margin({ right: 5 })
  }
}

从入口组件的代码中可以看出,我们定义了一个全局变量currentPage,并且使用@provide修饰,在其子组件(HuomBottom)中使用@Consume修饰。当子组件currentPage发生变化时,父组件currentPage也会发生变化,重新加载页面,显示不同的页签。在入口组件中,通过initializeOnStartup获取商品列表数据(goodsItems)并传入GoodsHome组件中,HomeBottom组件的代码如下:

@Component
struct HomeBottom {
  @Consume currentPage: number
  private iconPathTmp: string[] = getIconPath()
  private iconPathSelectsTmp: string[] = getIconPathSelect()
  @State iconPath: string[] = getIconPath()

  build() {
    Row() {
      List() {
        ForEach(this.iconPath, (item: string) => {
          ListItem() {
            Image($rawfile(item))
              .objectFit(ImageFit.Cover)
              .height(30)
              .width(30)
              .renderMode(ImageRenderMode.Original)
              .onClick(() => {
                if (item == this.iconPath[0]) {
                  this.iconPath[0] = this.iconPathTmp[0]
                  this.iconPath[1] = this.iconPathTmp[1]
                  this.iconPath[2] = this.iconPathTmp[2]
                  this.currentPage = 1
                }
                if (item == this.iconPath[1]) {
                  this.iconPath[0] = this.iconPathSelectsTmp[0]
                  this.iconPath[1] = this.iconPathSelectsTmp[1]
                  this.iconPath[2] = this.iconPathTmp[2]
                  this.currentPage = 2
                }
                if (item == this.iconPath[2]) {
                  this.iconPath[0] = this.iconPathSelectsTmp[0]
                  this.iconPath[1] = this.iconPathTmp[1]
                  this.iconPath[2] = this.iconPathSelectsTmp[2]
                  this.currentPage = 3
                }
              })
          }
          .width(120)
          .height(40)
        }, (item: string) => item)
      }
      .margin({ left: 10 })
      .align(Alignment.BottomStart)
      .listDirection(Axis.Horizontal)
    }
    .alignItems(VerticalAlign.Bottom)
    .height(30)
    .margin({ top: 10, bottom: 10 })
  }
}

底部组件是由一个横向的图片列表组成,iconPatb是底部初始状态下的3张图片路径数组。遍历iconPath数组,使用Image组件设置图片路径并添加到List中,给每个Image组件设置单击事件,单击更换底部3张图片。在HomcBottom中,iconPath使用的是@State修饰,当iconPath数组内容变化时,页面组件有使用到的地方都会随之发生变化。

3 实战:实现购物车页签

主界面购物车页签主要由下面3部分组成:

(1)顶部的Text组件。

(2)中间的List组件,其中List组件的item是一个水平的布局内包含一个togle组件,一个Image组件和一个垂直布局,其item中的垂直布局是由2个Text组件组成。

(3)底部一个水平布局包含两个Text组件。

构建一个购物车页签,给商品列表的每个商品设置一个单选框,可以选中与取消选中,底部“合计”值也会随之增加或减少,单击“结算”时会触发弹窗。下面我们来完成购物车页签。

3.1 创建一个页面

在pages目录下新建一个Page命名为"ShoppingCart"。在ShoppingCart.ets文件中添加入口组件,并导入需要使用到的数据实体类、方法和组件。ShoppingCart组件代码如下:

import { GoodsData } from '../model/GoodsData'
import { initializeOnStartup } from '../model/GoodsDataModels'
import { promptAction } from '@kit.ArkUI'


@Entry
@Component
export struct ShoppingCart {
  @Provide totalPrice: number = 0
  private goodsItems: GoodsData[] = initializeOnStartup()

  build() {
    Column() {
      Column() {
        Text('购物车')
          .fontColor(Color.Black)
          .fontSize(25)
          .margin({ left: 60, right: 60 })
          .align(Alignment.Center)
      }
      .backgroundColor('#FF00BFFF')
      .width('100%')
      .height(30)

      ShopCartList({ goodsItems: this.goodsItems });
      ShopCartBottom()
    }
    .alignItems(HorizontalAlign.Start)
  }
}

3.2 创建组件

新建ShopCarList组件用于存放购物车商品列表,ShopCarList组件代码如下:

@Component
struct ShopCartList {
  private goodsItems: GoodsData[] = [];

  build() {
    Column() {
      List() {
        ForEach(this.goodsItems, (item: GoodsData) => {
          ListItem() {
            ShopCartListItem({ goodsItem: item })
          }
        }, (item: GoodsData) => item.id.toString())
      }
      .height('100%')
      .width('100%')
      .align(Alignment.Top)
      .margin({ top: 5 })
    }
    .height(570)
  }
}

ShopCartListltem组件代码如下:

@Component
struct ShopCartListItem {
  @Consume totalPrice: number
  private goodsItem: GoodsData = new GoodsData();

  build() {
    Row() {
      Toggle({ type: ToggleType.Checkbox })
        .width(10)
        .height(10)
        .onChange((isOn: boolean) => {
          if (isOn) {
            this.totalPrice += parseInt(this.goodsItem.price + '', 0)
          } else {
            this.totalPrice -= parseInt(this.goodsItem.price + '', 0)
          }
        })
      Image($rawfile(this.goodsItem.imgSrc))
        .objectFit(ImageFit.ScaleDown)
        .height(100)
        .width(100)
        .renderMode(ImageRenderMode.Original)
      Column() {
        Text(this.goodsItem.title)
          .fontSize(14)
        Text('¥' + this.goodsItem.price)
          .fontSize(14)
          .fontColor(Color.Red)
      }
    }
    .height(100)
    .width(180)
    .margin({ left: 20 })
    .alignItems(VerticalAlign.Center)
    .backgroundColor(Color.White)
  }
}

在ShopCartListItem中使用Toggle的单选框类型来实现每个item的选择和取消选择,在Toggle的onChange事件中来改变totaIPrice的数值。

新建ShopCartBottom组件,ShopCartBottom组件的代码如下:

@Component
struct ShopCartBottom {
  @Consume totalPrice: number

  build() {
    Row() {
      Text('合计:  ¥' + this.totalPrice)
        .fontColor(Color.Red)
        .fontSize(18)
        .margin({ left: 20 })
        .width(150)
      Text('结算')
        .fontColor(Color.Black)
        .fontSize(18)
        .margin({ right: 20, left: 100 })
        .onClick(() => {
          promptAction.showToast({
            message: 'Checking Out',
            duration: 10,
            bottom: 100
          })
        })
    }
    .height(30)
    .width('100%')
    .backgroundColor('#FF7FFFD4')
    .alignItems(VerticalAlign.Bottom)
  }
}

当单击“结算”按钮时,将会有弹窗提醒。

4 实战实现我的页签

“我的”页签主要由下面4部分组成:

(1)顶部的水平布局。

(2)顶部下面的文本加数字的水平List。

(3)My Transaction模块,图片加文本的水平List。

(4)More模块,图片加文本的Grid。

构建主页“我的”页签,主要可以划分为下面几步。

4.1 创建一个页面

在pages目录下新建一个Page命名为"MyPage"。

在MyPage.ets文件中添加入口组件,MyInfo组件内容如下:

import { getMenu, getTrans, getMore } from '../model/GoodsDataModels'
import { Menu, ImageItem } from '../model/Menu'

@Entry
@Component
export struct MyInfo {
  build() {
    Column() {
      Row() {
        Image($rawfile('nav/mypic.jpg'))
          .margin({ left: 20 })
          .objectFit(ImageFit.Cover)
          .height(50)
          .width(50)
          .renderMode(ImageRenderMode.Original)
          .margin({ left: 40, right: 40 })

        Column() {
          Text('随缘')
            .fontSize(15)
          Text('会员名称 : HarmonyOS                     >')
        }
        .height(60)
        .margin({ left: 40, top: 10 })
        .alignItems(HorizontalAlign.Start)
      }

      TopList()
      MyTransList()
      MoreGrid()

    }
    .alignItems(HorizontalAlign.Start)
    .width('100%')
    .flexGrow(1)
  }
}

4.2 创建组件

入口组件中还包含TopList、MyIransList和MoreGrid  3个子组件。代码如下:

@Component
struct TopList {
  private menus: Menu[] = getMenu()

  build() {
    Row() {
      List() {
        ForEach(this.menus, (item: Menu) => {
          ListItem() {
            MenuItemView({ menu: item })
          }
        }, (item: Menu) => item.id.toString())
      }
      .height('100%')
      .width('100%')
      .margin({ top: 5 })
      .edgeEffect(EdgeEffect.None)
      .listDirection(Axis.Horizontal)
    }
    .width('100%')
    .height(50)
  }
}

@Component
struct MenuItemView {
  private menu: Menu = new Menu();

  build() {
    Column() {
      Text(this.menu.title)
        .fontSize(15)
      Text(this.menu.num + '')
        .fontSize(13)

    }
    .height(50)
    .width(80)
    .margin({ left: 8, right: 8 })
    .alignItems(HorizontalAlign.Start)
    .backgroundColor(Color.White)
  }
}

@Component
struct MyTransList {
  private imageItems: ImageItem[] = getTrans()

  build() {
    Column() {
      Text('我的交易')
        .fontSize(20)
        .margin({ left: 10 })
        .width('100%')
        .height(30)
      Row() {
        List() {
          ForEach(this.imageItems, (item: ImageItem) => {
            ListItem() {
              DataItem({ imageItem: item })
            }
          }, (item: ImageItem) => item.id.toString())
        }
        .height(70)
        .width('100%')
        .align(Alignment.Top)
        .margin({ top: 5 })
        .listDirection(Axis.Horizontal)
      }
    }
    .height(120)
  }
}

@Component
struct MoreGrid {
  private gridRowTemplate: string = ''
  private imageItems: ImageItem[] = getMore()
  private heightValue: number = 0;

  aboutToAppear() {
    let rows = Math.round(this.imageItems.length / 3);
    this.gridRowTemplate = '1fr '.repeat(rows);
    this.heightValue = rows * 75;
  }

  build() {
    Column() {
      Text('更多')
        .fontSize(20)
        .margin({ left: 10 })
        .width('100%')
        .height(30)
      Scroll() {
        Grid() {
          ForEach(this.imageItems, (item: ImageItem) => {
            GridItem() {
              DataItem({ imageItem: item })
            }
          }, (item: ImageItem) => item.id.toString())
        }
        .rowsTemplate(this.gridRowTemplate)
        .columnsTemplate('1fr 1fr 1fr')
        .columnsGap(8)
        .rowsGap(8)
        .height(this.heightValue)
      }
      .padding({ left: 16, right: 16 })
    }
    .height(400)
  }
}

在MyTransList和MoreGrid组件中都包含子组件Dataltem,为避免代码的重复,可以把多次要用到的结构体组件化,这里的结构体就是图片加上文本的上下结构体,Dataltem组件内容如下:

@Component
struct DataItem {
  private imageItem: ImageItem = new ImageItem();

  build() {
    Column() {
      Image($rawfile(this.imageItem.imageSrc))
        .objectFit(ImageFit.Contain)
        .height(50)
        .width(50)
        .renderMode(ImageRenderMode.Original)
      Text(this.imageItem.title)
        .fontSize(15)


    }
    .height(70)
    .width(80)
    .margin({ left: 15, right: 15 })
    .backgroundColor(Color.White)
  }
}

5 实战:商品详情页面

商品详情页面主要由以下5部分组成:

(1)顶部的返回栏。

(2)Swiper组件.

(3)中间多个Text组件组成的布局。

(4)参数列表.

(5)底部的Buy

将上面每一部分都封装成一个组件,然后再放到入口组件内,当单击顶部返回图标时返回到主页面的商品列表页签,单击底部“购买”时,会触发进度条弹窗。

5.1 创建一个页面

在pages目录下新建一个Page,命名为“ShoppingDetail”。在ShoppingDetail.ets文件中创建入口组件,组件内容如下:

import router from '@system.router';
import { ArsData } from '../model/ArsData'
import { getArs, getDetailImages } from '../model/GoodsDataModels'
import prompt from '@system.prompt';

@Entry
@Component
struct ShoppingDetail {
  private arsItems: ArsData[] = getArs()
  private detailImages: string[] = getDetailImages()

  build() {
    Column() {
      DetailTop()
      Scroll() {
        Column() {
          SwiperTop()
          DetailText()
          DetailArsList({ arsItems: this.arsItems })
          Image($rawfile('computer/computer1.png'))
            .height(220)
            .width('100%')
            .margin({ top: 30 })
          Image($rawfile('computer/computer2.png'))
            .height(220)
            .width('100%')
            .margin({ top: 30 })
          Image($rawfile('computer/computer3.png'))
            .height(220)
            .width('100%')
            .margin({ top: 30 })
          Image($rawfile('computer/computer4.png'))
            .height(220)
            .width('100%')
            .margin({ top: 30 })
          Image($rawfile('computer/computer5.png'))
            .height(220)
            .width('100%')
            .margin({ top: 30 })
          Image($rawfile('computer/computer6.png'))
            .height(220)
            .width('100%')
            .margin({ top: 30 })
        }
        .width('100%')
        .flexGrow(1)
      }
      .scrollable(ScrollDirection.Vertical)

      DetailBottom()
    }
    .height(630)

  }
}

5.2 创建组件

顶部DetailTop组件代码如下:

@Component
struct DetailTop {
  build() {
    Column() {
      Row() {
        Image($rawfile('detail/icon-return.png'))
          .height(20)
          .width(20)
          .margin({ left: 20, right: 250 })
          .onClick(() => {
            router.push({
              uri: "pages/Index"
            })
          })

      }
      .width('100%')
      .height(25)
      .backgroundColor('#FF87CEEB')
    }
    .width('100%')
    .height(30)
  }
}

SwiperTop组件代码如下:

@Component
struct SwiperTop {
  build() {
    Column() {
      Swiper() {
        Image($rawfile('computer/computer1.png'))
          .height(220)
          .width('100%')
        Image($rawfile('computer/computer2.png'))
          .height(220)
          .width('100%')
        Image($rawfile('computer/computer3.png'))
          .height(220)
          .width('100%')
        Image($rawfile('computer/computer4.png'))
          .height(220)
          .width('100%')
        Image($rawfile('computer/computer5.png'))
          .height(220)
          .width('100%')
        Image($rawfile('computer/computer6.png'))
          .height(220)
          .width('100%')

      }
      .index(0)
      .autoPlay(true)
      .interval(3000)
      .indicator(true)
      .loop(true)
      .height(250)
      .width('100%')
    }
    .height(250)
    .width('100%')
  }
}

DetailText组件代码如下:

@Component
struct DetailText {
  build() {
    Column() {
      Row() {
        Image($rawfile('computer/icon-promotion.png'))
          .height(30)
          .width(30)
          .margin({ left: 10 })
        Text('特价优惠: ¥9999')
          .fontColor(Color.White)
          .fontSize(20)
          .margin({ left: 10 })

      }
      .width('100%')
      .height(35)
      .backgroundColor(Color.Red)

      Column() {
        Text('新品上市: 华为 MateBook X Pro 2021')
          .fontSize(15)
          .margin({ left: 10 })
          .alignSelf(ItemAlign.Start)
        Text('13.9英寸,第11代英特尔®酷睿™ i7处理器,16GB内存,512GB存储,超薄商务笔记本,3K全面屏,多屏协作,翡翠绿')
          .fontSize(10)
          .margin({ left: 10 })
        Row() {
          Image($rawfile('nav/icon-buy.png'))
            .height(15)
            .width(15)
            .margin({ left: 10 })
          //TODO 暂不支持跑马灯组件,用Text代替
          Text('限时优惠')
            .fontSize(10)
            .fontColor(Color.Red)
            .margin({ left: 100 })

        }
        .backgroundColor(Color.Pink)
        .width('100%')
        .height(25)
        .margin({ top: 10 })

        Text('发货:         2天送达')
          .fontSize(13)
          .fontColor(Color.Red)
          .margin({ left: 10, top: 5 })
          .alignSelf(ItemAlign.Start)
        Text('送货地址:         中国 湖北武汉')
          .fontSize(13)
          .fontColor(Color.Red)
          .margin({ left: 10, top: 5 })
          .alignSelf(ItemAlign.Start)
          .onClick(() => {
            prompt.showDialog({ title: 'select address', })

          })
        // Text('保质:         正品保障')
        //   .fontSize(13)
        //   .margin({ left: 10, top: 5 })
        //   .alignSelf(ItemAlign.Start)
      }
      .height(150)
      .width('100%')
    }
    .height(160)
    .width('100%')
  }
}

DetailArsList组件代码如下:

@Component
struct DetailArsList {
  private arsItems: ArsData[] = [];

  build() {
    Scroll() {
      Column() {
        List() {
          ForEach(this.arsItems, (item: ArsData) => {
            ListItem() {
              ArsListItem({ arsItem: item })
            }
          }, (item: ArsData) => item.id.toString())
        }
        .height('100%')
        .width('100%')
        .margin({ top: 5 })
        .listDirection(Axis.Vertical)
      }
      .height(200)
    }
  }
}

ArsListltem组件代码如下:

@Component
struct ArsListItem {
  private arsItem: ArsData = new ArsData();

  build() {
    Row() {
      Text(this.arsItem.title + " :")
        .fontSize(11)
        .margin({ left: 20 })
        .flexGrow(1)
      Text(this.arsItem.content)
        .fontSize(11)
        .margin({ right: 20 })

    }
    .height(14)
    .width('100%')
    .backgroundColor(Color.White)
  }
}

DetailBottom组件代码如下:

@Component
struct DetailBottom {
  @Provide
  private value: number = 1
  dialogController: CustomDialogController = new CustomDialogController({
    builder: DialogExample(),
    cancel: this.existApp,
    autoCancel: true
  });

  onAccept() {

  }

  existApp() {

  }

  build() {
    Column() {
      Text('购买')
        .width(40)
        .height(25)
        .fontSize(20)
        .fontColor(Color.White)
        .onClick(() => {
          this.value = 1
          this.dialogController.open()
        })
    }
    .alignItems(HorizontalAlign.Center)
    .backgroundColor(Color.Red)
    .width('100%')
    .height(40)
  }
}

DialogExample自定义弹窗组件代码如下:

@CustomDialog
struct DialogExample {
  @Consume
  private value: number
  controller: CustomDialogController;

  build() {
    Column() {
      Progress({ value: this.value++ >= 100 ? 100 : this.value, total: 100, style: ProgressStyle.Eclipse })
        .height(50)
        .width(100)
        .margin({ top: 5 })

    }
    .height(60)
    .width(100)

  }
}

5.3 设置路由

为了能实现应用首页和商品详情页的切换,还需要在resources/base/profile/main_pages.json文件中增加路由配置。

{
  "src": [
    "pages/Index",
    "pages/ShoppingDetail"
  ]
}

6 小结

本文介绍了购物应用的完整开发过程,实现了包括商品列表页面、购物车页面、"我的"页面、详情页面等4块核心功能。通过文章的学习,让开发者们能够进一步掌握HarmonyOS的开发技能。

Logo

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

更多推荐