HarmonyOS App开发——购物商城应用App开发

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的开发技能。

相关推荐
无穷小亮2 小时前
Flutter框架跨平台鸿蒙开发——Excel函数教程APP的开发流程
flutter·华为·excel·harmonyos·鸿蒙
无穷小亮2 小时前
Flutter框架跨平台鸿蒙开发——打字练习APP开发流程
flutter·华为·harmonyos·鸿蒙
九 龙4 小时前
Flutter框架跨平台鸿蒙开发——水电缴费提醒APP的开发流程
flutter·华为·harmonyos·鸿蒙
摘星编程5 小时前
React Native鸿蒙版:StackNavigation页面返回拦截
react native·react.js·harmonyos
BlackWolfSky5 小时前
鸿蒙中级课程笔记4—应用程序框架进阶1—Stage模型应用组成结构、UIAbility启动模式、启动应用内UIAbility
笔记·华为·harmonyos
Miguo94well6 小时前
Flutter框架跨平台鸿蒙开发——植物养殖APP的开发流程
flutter·华为·harmonyos·鸿蒙
九 龙6 小时前
Flutter框架跨平台鸿蒙开发——电影拍摄知识APP的开发流程
flutter·华为·harmonyos·鸿蒙
星辰徐哥6 小时前
鸿蒙APP开发从入门到精通:ArkUI组件库详解与常用组件实战
华为·app·harmonyos·组件·arkui·组件库
九 龙7 小时前
Flutter框架跨平台鸿蒙开发——如何养花APP的开发流程
flutter·华为·harmonyos·鸿蒙