当前位置: 首页 > article >正文

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

一.实验背景

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

实验源地址

开发者云实验_云实验KooLabs_在线实验_上云实践_云计算实验_AI实验_华为云官方实验平台-华为云

 实验目标

本实验一共需要完成以下三个部分的任务:本实验将模拟制作华为商城应用商品展示界,该款应用需要实现如下功能效果。

  • 用户切换左侧导航的商品类别,右侧滚动到对应的内容。
  • 滚动右侧具体商品的内容,左侧会切换对应的商品类别导航。 

实验效果 

二.实验详细解析

GoodsModel.ets

export default class GoodsModel {
  CommodityId: number; // 所属商品类别id
  goodsId: number;     // 商品id
  goodsName: string;   // 商品名称
  imageUrl: Resource;  // 商品展示图片
  price: number;       // 商品价格

  // 使用构造函数创建商品数据模型
  constructor(CommodityId: number, goodsId: number, goodsName: string, imageUrl: Resource, price: number) {
    this.CommodityId = CommodityId;
    this.goodsId = goodsId;
    this.goodsName = goodsName;
    this.imageUrl = imageUrl;
    this.price = price;
  }
}

        这里是使用ArkTS语法创建了一个GoodsModel类,constructor作为其构造器,用来记录商品品类,使用并默认导出这个类。

  • export:用于导出模块中的函数、类、接口或变量。
    • 一个模块可以有多个 export
    • 导出时,你必须指定导出的名称,这些名称在导入时必须一致。
    • 导入时,需要使用花括号 {} 来指定导入的项,并且可以重命名导入的项。
  • export default:用于导出模块中的默认值。这个默认值可以是函数、类、对象等。
    • 每个模块只能有一个默认导出。
    • 导出时,不需要指定名称。
    • 导入时,不需要花括号,并且可以为导入的项指定任何名称。

NavigationModel.ets

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

LinkDataModel.ets

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

NavigationViewModel.ets

        创建NavigationViewModel类,获取商品分类列表数据,返回商品类别的ID以及对应连接的具体商品信息列表。

//导入先前创建好的三个数据模型
//TODO
import NavigationModel from './NavigationModel';
import GoodsModel from './GoodsModel';
import LinkDataModel from './LinkDataModel';
class NavigationViewModel {
  /**
   * 获取商品分类列表数据
   *
   * 返回 Array<商品分类模型> 商品类别的ID 以及 对应连接的具体商品信息列表 linkDataList
   */
  getLinkData(): Array<NavigationModel> {
    let linkDataList: Array<NavigationModel> = [];
    let superId: number = 0;
    //TODO
    LINK_DATA.forEach((item: LinkDataModel) => {
      if (superId !== item.superId) {
        // 如果该分类id不存在,则添加此商品分类模型中
        let GoodsItem: NavigationModel = new NavigationModel(item.superId, item.superName, []);
        linkDataList.push(GoodsItem);
      }
      // 添加具体商品信息模型
      let GoodsItem: GoodsModel = new GoodsModel(superId, item.id, item.GoodsName, item.imageUrl, item.price);
      linkDataList[linkDataList.length-1].GoodsList.push(GoodsItem);
      superId = item.superId;
    });
    return linkDataList;
  }
}

let navigationViewModel = new NavigationViewModel();

export default navigationViewModel as NavigationViewModel;

// 创建所链接具体数据
const LINK_DATA: LinkDataModel[] = [
  new LinkDataModel(1, '热门商品', 1, 'HUAWEI P60 Art', $r('app.media.ic_img_1'), 0),
  new LinkDataModel(1, '热门商品', 2, 'HUAWEI P60 Pro', $r('app.media.ic_img_2'), 5000),
  new LinkDataModel(1, '热门商品', 3, 'HUAWEI Mate 50 Pro 4G', $r('app.media.ic_img_3'), 5000),
 ...,
  new LinkDataModel(6, '智慧屏', 36, '华为智慧屏 V98', $r('app.media.ic_img_10'),3000),
  new LinkDataModel(6, '智慧屏', 37, '华为智慧屏  V85', $r('app.media.ic_img_11'), 5000),
]

CommodityItem.ets

        创建导航栏组件,显示右侧商品导航栏。 

// 引入先前构建好的常量类别,框架代码中已提供
import Constants from '../common/constants/Constants';
@Component
export default struct CommodityItem {
  CommodityName?: string;
  @Prop isSelected: boolean = false;
  onClickAction = () => {
    // TODO

  }

  build() {
    Text(this.CommodityName)
      .fontSize($r('app.float.normal_font_size'))
      .fontColor(this.isSelected ? $r('app.color.base_font_color') : $r('app.color.normal_font_color'))
      .fontFamily(this.isSelected ? $r('app.string.hei_ti_medium') : $r('app.string.hei_ti'))
      .fontWeight(this.isSelected ? Constants.TITLE_FONT_WEIGHT : Constants.LABEL_FONT_WEIGHT)
      .textAlign(TextAlign.Center)
      .backgroundColor(this.isSelected ? $r('app.color.base_background') : '')
      .width(Constants.FULL_PERCENT)
      .height($r('app.float.classify_item_height'))
      .onClick(this.onClickAction)
  }
}

GoodsItem.ets

        创建商品组件,显示商品。

// 引入先前构建好的常量类别,框架代码中已提供
import Constants from '../common/constants/Constants';
// 引入之前创建好的具体商品数据模型
//TODO
import GoodsModel from '../viewmodel/GoodsModel';

// 构建具体商品信息子项
@Component
export default struct GoodsItem {
  //TODO
  @Prop itemStr:string = '';
  item?:GoodsModel;
  // 在创建自定义组件的新实例后,在执行其build()函数之前执行。
  //TODO
  aboutToAppear() {
    this.item = JSON.parse(this.itemStr);
  }
  build() {
    Row() {
      Row(){
        //TODO
        Image(this.item !== undefined ? this.item?.imageUrl : '')
          .height(Constants.FULL_PERCENT)
          .aspectRatio(1)
      }.height(70)
      .width(70)
      Column() {
        //TODO
        Text(  )
          .fontSize($r('app.float.normal_font_size'))
          .fontColor($r('app.color.base_font_color'))
          .fontFamily($r('app.string.hei_ti_medium'))
          .maxLines(Constants.TITLE_LINE_NUMBER)
          .textOverflow({ overflow: TextOverflow.Clip })
          .lineHeight($r('app.float.title_line_height'))
          .width(Constants.FULL_PERCENT)
        // 当商品的价格等于0时,这显示免费,否则显示具体价格。
        //TODO
        Text(this.item?.price === 0 ? $r('app.string.free_price') : $r('app.string.price_str', this.item?.price))
          .fontSize($r('app.float.header_font_size'))
          .fontColor($r('app.color.price_color'))
          .fontFamily($r('app.string.hei_ti_medium'))
      }
      .padding($r('app.float.course_item_padding'))
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
      .justifyContent(FlexAlign.SpaceBetween)
      .height(Constants.FULL_PERCENT)
    }
    .clip(true)
    .borderRadius($r('app.float.normal_border_radius'))
    .backgroundColor($r('app.color.start_window_background'))
    .width('100%')
    .height($r('app.float.course_item_height'))
  }
}

MainPage.ets

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

  • aboutToAppear()主要作加载过程,setTimeout()在延Constants.LOADING_DURATION时间后被执行,() => { ... }在这里本质上是一个回调函数。
  • @Builder允许在自定义组件内定义一个或多个自定义构建函数,同时只允许组内调用。
  • build()中还使用了列表List()组件,在其中通过ForEach遍历生成了多个商品项。
// 导入数据模型以及创建好的自定义组件
import Constants from '../common/constants/Constants';
//TODO
import NavigationModel from '../viewmodel/NavigationModel';
import GoodsModel from '../viewmodel/GoodsModel';
import GoodsItem from '../view/GoodsItem';
import CommodityItem from '../view/CommodityItem';
import NavigationViewModel from '../viewmodel/NavigationViewModel';
@Entry
@Component
struct IndexPage {
    //TODO
  @State currentCommodity: number = 0; // 被选中的左侧商品分类序号
  @State requestSuccess: boolean = false; // 是否加载成功
  private CommodityList: Array<NavigationModel> = []; // 左侧商品分类列表
  private CommodityScroller: Scroller = new Scroller(); // 左侧商品类别导航滚动条
  private scroller: Scroller = new Scroller(); // 右侧商品信息导航条
  // 搜索框
  @State changeValue: string = ''
  @State submitValue: string = ''
  controller: SearchController = new SearchController() // 搜索框控制器

  // 生命周期函数
  //TODO
  aboutToAppear() {
    // 在应用加载页面上显示“正在加载中”的文字提示
    setTimeout(() => {
      this.CommodityList = NavigationViewModel.getLinkData();
      this.requestSuccess = true;
    }, Constants.LOADING_DURATION);

  }

  // @Builder可以在自定义组件内定义一个或多个自定义构建函数,这里创建了左侧导航栏商品分类被选中时的样式。
  // 自定义构建函数可以在所属组件的build方法和其他自定义构建函数中调用,但不允许在组件外调用。
  // TODO
    @Builder   CommodityHeader(CommodityName: string) {
    Row() {
      Text(CommodityName)
        .fontSize($r('app.float.header_font_size'))
        .fontColor($r('app.color.base_font_color'))
        .fontFamily($r('app.string.hei_ti_medium'))
        .fontWeight(Constants.TITLE_FONT_WEIGHT)
    }
    .padding({ left: $r('app.float.item_padding_left') })
    .height($r('app.float.classify_item_height'))
    .width(Constants.FULL_PERCENT)
    .backgroundColor($r('app.color.base_background'))
  }
  // 定义当切换样式改变函数
  //TODO
  CommodityChangeAction(index: number, isCommodity: boolean) {
    if (this.currentCommodity !== index) {
      // 更改商品分类状态
      this.currentCommodity = index;
      if (isCommodity) {
        // 滚动右侧商品滚动条,滚动到选中区域
        this.scroller.scrollToIndex(index);
      } else {
        // 滚动左侧商品导航条,滚动到选中区域
        this.CommodityScroller.scrollToIndex(index);
      }
    }
  }
  build() {
    Column(){
      Row(){
        // 搜索框
        //TODO
        Search({ value: this.changeValue, placeholder: '手表', controller: this.controller })
          .searchButton('搜索')
          .width(350)
          .height(40)
          .backgroundColor('#F5F5F5')
          .placeholderColor(Color.Grey)
          .placeholderFont({ size: 14, weight: 400 })
          .textFont({ size: 14, weight: 400 })
          .onSubmit((value: string) => {
            this.submitValue = value
          })
          .onChange((value: string) => {
            this.changeValue = value
          })
      }.backgroundColor($r('app.color.classify_background'))
      // 界面整体使用Row组件实现横向布局,分为左右两部分。
      // 均使用List组件实现对导航和内容的数据展示,导航部分固定宽度,内容部分自适应屏幕剩余宽度并用ListItemGroup完成每个导航下的内容布局。
      Row() {
        if (this.requestSuccess) {
          List({ scroller: this.CommodityScroller }) {
            // 循环商品分类导航列表
            //TODO
            ForEach(this.CommodityList, (item: NavigationModel, index?: number) => {
              ListItem() {
                CommodityItem({
                  CommodityName: item.goodsName,
                  isSelected: this.currentCommodity === index,
                  onClickAction: () => {
                    if (index !== undefined) {
                      // 调用切换函数
                      this.CommodityChangeAction(index, true);
                    }
                  }
                })
              }
            }, (item: NavigationModel) => item.goodsName + this.currentCommodity)

          }
          .height(Constants.FULL_PERCENT)
          .width($r('app.float.classify_item_width'))
          .backgroundColor($r('app.color.classify_background'))
          .scrollBar(BarState.Off)
          // 循环具体商品信息列表
          List({ scroller: this.scroller }) {
            //TODO
            ForEach(this.CommodityList, (CommodityItem: NavigationModel) => {
              ListItemGroup({
                header: this.CommodityHeader(CommodityItem.goodsName),
                space: Constants.GOODS_ITEM_PADDING
              }) {
                ForEach(CommodityItem.GoodsList, (courseItem: GoodsModel) => {
                  ListItem() {
                    GoodsItem({ itemStr: JSON.stringify(courseItem) })
                  }
                }, (courseItem: GoodsModel) => `${courseItem.goodsId}`)
              }
            }, (item: NavigationModel) => `${item.commodityId}`)


          }
          .padding({ left: $r('app.float.item_padding_left'), right: $r('app.float.course_item_padding') })
          .sticky(StickyStyle.Header)
          .layoutWeight(1)
          .edgeEffect(EdgeEffect.None)
          .onScrollIndex((start: number) => this.CommodityChangeAction(start, false))
        } else {
          Text($r('app.string.loading'))
            .fontFamily($r('app.string.hei_ti_medium'))
            .textAlign(TextAlign.Center)
            .height(Constants.FULL_PERCENT)
            .width(Constants.FULL_PERCENT)
        }
      }

    }.backgroundColor($r('app.color.base_background'))
  }
}

三.总结

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


http://www.kler.cn/a/420049.html

相关文章:

  • C++面试突破---C/C++基础
  • openEuler安装UKUI桌面
  • QObject 类是 rviz::_AdditionalTopicSubscriber 的一个不可访问的基类 报错解决
  • MySQL——操作
  • 基于链表的基础笔试/面试题
  • 《智能体雏形开发(高阶实操)》开发计划概述
  • 单片机状态机实现多个按键同时检测单击、多击、长按等操作
  • Efficient Multimodal Large Language Models: A Survey (高效多模态大型语言模型综述-全文翻译)
  • React 前端框架4
  • 路径规划之启发式算法之三:鲸鱼优化算法(Whale Optimization Algorithm)
  • Anaconda3安装及使用
  • (数据结构与算法)如何提高学习算法的效率?面试算法重点有哪些?面试需要哪些能力?
  • Vue3学习宝典
  • 计算机毕业设计Spark+SpringBoot旅游推荐系统 旅游景点推荐 旅游可视化 旅游爬虫 景区客流量预测 旅游大数据 大数据毕业设计
  • 四、初识C语言(4)
  • Python 时间和日期
  • STM32 ADC --- 知识点总结
  • 什么语言适合做 Serverless 开发?
  • Elasticsearch 集成
  • JavaEE-线程池专题
  • 斯坦福李飞飞《AI Agent:多模态交互前沿调查》论文
  • 推进汽车互联:全面物联网基础设施框架的提案
  • 牛客网刷题 | BC122 有序序列判断
  • docker部署xxl-job(含镜像打包)
  • 一键解析RAW文件,GPS定位展示,摄影师专用照片管理软件
  • 单台服务器上创建多个端口MySQL服务