【HarmonyOS开发】华为商城应用页面实行示例解析(ArkTS实战解析) ...

打印 上一主题 下一主题

主题 998|帖子 998|积分 3004

一.实行配景

本次项目为华为云鸿蒙应用入门级开发者认证的实行项目,借此来巩固对ArkTS的学习。
实行源地点

   开发者云实行_云实行KooLabs_在线实行_上云实践_云盘算实行_AI实行_华为云官方实行平台-华为云
   实行目标


   本实行一共需要完成以下三个部门的使命:本实行将模仿制作华为商城应用商品展示界,该款应用需要实现如下功能结果。
  

  • 用户切换左侧导航的商品类别,右侧滚动到对应的内容。
  • 滚动右侧详细商品的内容,左侧会切换对应的商品类别导航。 
  实行结果 


二.实行详细解析

GoodsModel.ets

  1. export default class GoodsModel {
  2.   CommodityId: number; // 所属商品类别id
  3.   goodsId: number;     // 商品id
  4.   goodsName: string;   // 商品名称
  5.   imageUrl: Resource;  // 商品展示图片
  6.   price: number;       // 商品价格
  7.   // 使用构造函数创建商品数据模型
  8.   constructor(CommodityId: number, goodsId: number, goodsName: string, imageUrl: Resource, price: number) {
  9.     this.CommodityId = CommodityId;
  10.     this.goodsId = goodsId;
  11.     this.goodsName = goodsName;
  12.     this.imageUrl = imageUrl;
  13.     this.price = price;
  14.   }
  15. }
复制代码
        这里是使用ArkTS语法创建了一个GoodsModel类,constructor作为其构造器,用来记载商品品类,使用并默认导出这个类。
   

  • export:用于导出模块中的函数、类、接口或变量。

    • 一个模块可以有多个 export。
    • 导出时,你必须指定导出的名称,这些名称在导入时必须划一。
    • 导入时,需要使用花括号 {} 来指定导入的项,并且可以重命名导入的项。

  • export default:用于导出模块中的默认值。这个默认值可以是函数、类、对象等。

    • 每个模块只能有一个默认导出。
    • 导出时,不需要指定名称。
    • 导入时,不需要花括号,并且可以为导入的项指定任何名称。

  NavigationModel.ets

        此文件中也是创建了一个NavigationModel的默认导出类。记载商品分类。
LinkDataModel.ets

        创建了一个LinkDataModel联合类。将商品与分类联合起来。
NavigationViewModel.ets

        创建NavigationViewModel类,获取商品分类列表数据,返回商品类别的ID以及对应连接的详细商品信息列表。
  1. //导入先前创建好的三个数据模型
  2. //TODO
  3. import NavigationModel from './NavigationModel';
  4. import GoodsModel from './GoodsModel';
  5. import LinkDataModel from './LinkDataModel';
  6. class NavigationViewModel {
  7.   /**
  8.    * 获取商品分类列表数据
  9.    *
  10.    * 返回 Array<商品分类模型> 商品类别的ID 以及 对应连接的具体商品信息列表 linkDataList
  11.    */
  12.   getLinkData(): Array<NavigationModel> {
  13.     let linkDataList: Array<NavigationModel> = [];
  14.     let superId: number = 0;
  15.     //TODO
  16.     LINK_DATA.forEach((item: LinkDataModel) => {
  17.       if (superId !== item.superId) {
  18.         // 如果该分类id不存在,则添加此商品分类模型中
  19.         let GoodsItem: NavigationModel = new NavigationModel(item.superId, item.superName, []);
  20.         linkDataList.push(GoodsItem);
  21.       }
  22.       // 添加具体商品信息模型
  23.       let GoodsItem: GoodsModel = new GoodsModel(superId, item.id, item.GoodsName, item.imageUrl, item.price);
  24.       linkDataList[linkDataList.length-1].GoodsList.push(GoodsItem);
  25.       superId = item.superId;
  26.     });
  27.     return linkDataList;
  28.   }
  29. }
  30. let navigationViewModel = new NavigationViewModel();
  31. export default navigationViewModel as NavigationViewModel;
  32. // 创建所链接具体数据
  33. const LINK_DATA: LinkDataModel[] = [
  34.   new LinkDataModel(1, '热门商品', 1, 'HUAWEI P60 Art', $r('app.media.ic_img_1'), 0),
  35.   new LinkDataModel(1, '热门商品', 2, 'HUAWEI P60 Pro', $r('app.media.ic_img_2'), 5000),
  36.   new LinkDataModel(1, '热门商品', 3, 'HUAWEI Mate 50 Pro 4G', $r('app.media.ic_img_3'), 5000),
  37. ...,
  38.   new LinkDataModel(6, '智慧屏', 36, '华为智慧屏 V98', $r('app.media.ic_img_10'),3000),
  39.   new LinkDataModel(6, '智慧屏', 37, '华为智慧屏  V85', $r('app.media.ic_img_11'), 5000),
  40. ]
复制代码
CommodityItem.ets

        创建导航栏组件,显示右侧商品导航栏。 
  1. // 引入先前构建好的常量类别,框架代码中已提供
  2. import Constants from '../common/constants/Constants';
  3. @Component
  4. export default struct CommodityItem {
  5.   CommodityName?: string;
  6.   @Prop isSelected: boolean = false;
  7.   onClickAction = () => {
  8.     // TODO
  9.   }
  10.   build() {
  11.     Text(this.CommodityName)
  12.       .fontSize($r('app.float.normal_font_size'))
  13.       .fontColor(this.isSelected ? $r('app.color.base_font_color') : $r('app.color.normal_font_color'))
  14.       .fontFamily(this.isSelected ? $r('app.string.hei_ti_medium') : $r('app.string.hei_ti'))
  15.       .fontWeight(this.isSelected ? Constants.TITLE_FONT_WEIGHT : Constants.LABEL_FONT_WEIGHT)
  16.       .textAlign(TextAlign.Center)
  17.       .backgroundColor(this.isSelected ? $r('app.color.base_background') : '')
  18.       .width(Constants.FULL_PERCENT)
  19.       .height($r('app.float.classify_item_height'))
  20.       .onClick(this.onClickAction)
  21.   }
  22. }
复制代码
GoodsItem.ets

        创建商品组件,显示商品。
  1. // 引入先前构建好的常量类别,框架代码中已提供
  2. import Constants from '../common/constants/Constants';
  3. // 引入之前创建好的具体商品数据模型
  4. //TODO
  5. import GoodsModel from '../viewmodel/GoodsModel';
  6. // 构建具体商品信息子项
  7. @Component
  8. export default struct GoodsItem {
  9.   //TODO
  10.   @Prop itemStr:string = '';
  11.   item?:GoodsModel;
  12.   // 在创建自定义组件的新实例后,在执行其build()函数之前执行。
  13.   //TODO
  14.   aboutToAppear() {
  15.     this.item = JSON.parse(this.itemStr);
  16.   }
  17.   build() {
  18.     Row() {
  19.       Row(){
  20.         //TODO
  21.         Image(this.item !== undefined ? this.item?.imageUrl : '')
  22.           .height(Constants.FULL_PERCENT)
  23.           .aspectRatio(1)
  24.       }.height(70)
  25.       .width(70)
  26.       Column() {
  27.         //TODO
  28.         Text(  )
  29.           .fontSize($r('app.float.normal_font_size'))
  30.           .fontColor($r('app.color.base_font_color'))
  31.           .fontFamily($r('app.string.hei_ti_medium'))
  32.           .maxLines(Constants.TITLE_LINE_NUMBER)
  33.           .textOverflow({ overflow: TextOverflow.Clip })
  34.           .lineHeight($r('app.float.title_line_height'))
  35.           .width(Constants.FULL_PERCENT)
  36.         // 当商品的价格等于0时,这显示免费,否则显示具体价格。
  37.         //TODO
  38.         Text(this.item?.price === 0 ? $r('app.string.free_price') : $r('app.string.price_str', this.item?.price))
  39.           .fontSize($r('app.float.header_font_size'))
  40.           .fontColor($r('app.color.price_color'))
  41.           .fontFamily($r('app.string.hei_ti_medium'))
  42.       }
  43.       .padding($r('app.float.course_item_padding'))
  44.       .layoutWeight(1)
  45.       .alignItems(HorizontalAlign.Start)
  46.       .justifyContent(FlexAlign.SpaceBetween)
  47.       .height(Constants.FULL_PERCENT)
  48.     }
  49.     .clip(true)
  50.     .borderRadius($r('app.float.normal_border_radius'))
  51.     .backgroundColor($r('app.color.start_window_background'))
  52.     .width('100%')
  53.     .height($r('app.float.course_item_height'))
  54.   }
  55. }
复制代码

MainPage.ets

        此文件为程序入口页面,将上述所有文件调度起来形成页面。
   

  • aboutToAppear()主要作加载过程,setTimeout()在延Constants.LOADING_DURATION时间后被实行,() => { ... }在这里本质上是一个回调函数。
  • @Builder允许在自界说组件内界说一个或多个自界说构建函数,同时只允许组内调用。
  • build()中还使用了列表List()组件,在其中通过ForEach遍历生成了多个商品项。
  1. // 导入数据模型以及创建好的自定义组件
  2. import Constants from '../common/constants/Constants';
  3. //TODO
  4. import NavigationModel from '../viewmodel/NavigationModel';
  5. import GoodsModel from '../viewmodel/GoodsModel';
  6. import GoodsItem from '../view/GoodsItem';
  7. import CommodityItem from '../view/CommodityItem';
  8. import NavigationViewModel from '../viewmodel/NavigationViewModel';
  9. @Entry
  10. @Component
  11. struct IndexPage {
  12.     //TODO
  13.   @State currentCommodity: number = 0; // 被选中的左侧商品分类序号
  14.   @State requestSuccess: boolean = false; // 是否加载成功
  15.   private CommodityList: Array<NavigationModel> = []; // 左侧商品分类列表
  16.   private CommodityScroller: Scroller = new Scroller(); // 左侧商品类别导航滚动条
  17.   private scroller: Scroller = new Scroller(); // 右侧商品信息导航条
  18.   // 搜索框
  19.   @State changeValue: string = ''
  20.   @State submitValue: string = ''
  21.   controller: SearchController = new SearchController() // 搜索框控制器
  22.   // 生命周期函数
  23.   //TODO
  24.   aboutToAppear() {
  25.     // 在应用加载页面上显示“正在加载中”的文字提示
  26.     setTimeout(() => {
  27.       this.CommodityList = NavigationViewModel.getLinkData();
  28.       this.requestSuccess = true;
  29.     }, Constants.LOADING_DURATION);
  30.   }
  31.   // @Builder可以在自定义组件内定义一个或多个自定义构建函数,这里创建了左侧导航栏商品分类被选中时的样式。
  32.   // 自定义构建函数可以在所属组件的build方法和其他自定义构建函数中调用,但不允许在组件外调用。
  33.   // TODO
  34.     @Builder   CommodityHeader(CommodityName: string) {
  35.     Row() {
  36.       Text(CommodityName)
  37.         .fontSize($r('app.float.header_font_size'))
  38.         .fontColor($r('app.color.base_font_color'))
  39.         .fontFamily($r('app.string.hei_ti_medium'))
  40.         .fontWeight(Constants.TITLE_FONT_WEIGHT)
  41.     }
  42.     .padding({ left: $r('app.float.item_padding_left') })
  43.     .height($r('app.float.classify_item_height'))
  44.     .width(Constants.FULL_PERCENT)
  45.     .backgroundColor($r('app.color.base_background'))
  46.   }
  47.   // 定义当切换样式改变函数
  48.   //TODO
  49.   CommodityChangeAction(index: number, isCommodity: boolean) {
  50.     if (this.currentCommodity !== index) {
  51.       // 更改商品分类状态
  52.       this.currentCommodity = index;
  53.       if (isCommodity) {
  54.         // 滚动右侧商品滚动条,滚动到选中区域
  55.         this.scroller.scrollToIndex(index);
  56.       } else {
  57.         // 滚动左侧商品导航条,滚动到选中区域
  58.         this.CommodityScroller.scrollToIndex(index);
  59.       }
  60.     }
  61.   }
  62.   build() {
  63.     Column(){
  64.       Row(){
  65.         // 搜索框
  66.         //TODO
  67.         Search({ value: this.changeValue, placeholder: '手表', controller: this.controller })
  68.           .searchButton('搜索')
  69.           .width(350)
  70.           .height(40)
  71.           .backgroundColor('#F5F5F5')
  72.           .placeholderColor(Color.Grey)
  73.           .placeholderFont({ size: 14, weight: 400 })
  74.           .textFont({ size: 14, weight: 400 })
  75.           .onSubmit((value: string) => {
  76.             this.submitValue = value
  77.           })
  78.           .onChange((value: string) => {
  79.             this.changeValue = value
  80.           })
  81.       }.backgroundColor($r('app.color.classify_background'))
  82.       // 界面整体使用Row组件实现横向布局,分为左右两部分。
  83.       // 均使用List组件实现对导航和内容的数据展示,导航部分固定宽度,内容部分自适应屏幕剩余宽度并用ListItemGroup完成每个导航下的内容布局。
  84.       Row() {
  85.         if (this.requestSuccess) {
  86.           List({ scroller: this.CommodityScroller }) {
  87.             // 循环商品分类导航列表
  88.             //TODO
  89.             ForEach(this.CommodityList, (item: NavigationModel, index?: number) => {
  90.               ListItem() {
  91.                 CommodityItem({
  92.                   CommodityName: item.goodsName,
  93.                   isSelected: this.currentCommodity === index,
  94.                   onClickAction: () => {
  95.                     if (index !== undefined) {
  96.                       // 调用切换函数
  97.                       this.CommodityChangeAction(index, true);
  98.                     }
  99.                   }
  100.                 })
  101.               }
  102.             }, (item: NavigationModel) => item.goodsName + this.currentCommodity)
  103.           }
  104.           .height(Constants.FULL_PERCENT)
  105.           .width($r('app.float.classify_item_width'))
  106.           .backgroundColor($r('app.color.classify_background'))
  107.           .scrollBar(BarState.Off)
  108.           // 循环具体商品信息列表
  109.           List({ scroller: this.scroller }) {
  110.             //TODO
  111.             ForEach(this.CommodityList, (CommodityItem: NavigationModel) => {
  112.               ListItemGroup({
  113.                 header: this.CommodityHeader(CommodityItem.goodsName),
  114.                 space: Constants.GOODS_ITEM_PADDING
  115.               }) {
  116.                 ForEach(CommodityItem.GoodsList, (courseItem: GoodsModel) => {
  117.                   ListItem() {
  118.                     GoodsItem({ itemStr: JSON.stringify(courseItem) })
  119.                   }
  120.                 }, (courseItem: GoodsModel) => `${courseItem.goodsId}`)
  121.               }
  122.             }, (item: NavigationModel) => `${item.commodityId}`)
  123.           }
  124.           .padding({ left: $r('app.float.item_padding_left'), right: $r('app.float.course_item_padding') })
  125.           .sticky(StickyStyle.Header)
  126.           .layoutWeight(1)
  127.           .edgeEffect(EdgeEffect.None)
  128.           .onScrollIndex((start: number) => this.CommodityChangeAction(start, false))
  129.         } else {
  130.           Text($r('app.string.loading'))
  131.             .fontFamily($r('app.string.hei_ti_medium'))
  132.             .textAlign(TextAlign.Center)
  133.             .height(Constants.FULL_PERCENT)
  134.             .width(Constants.FULL_PERCENT)
  135.         }
  136.       }
  137.     }.backgroundColor($r('app.color.base_background'))
  138.   }
  139. }
复制代码
三.总结

        笔者在此只是大概对某些地方做了粗略的总结与表明,对于别的有疑问的地方笔者可自行到华为官网搜刮查询。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表