OpenHarmony实战开发-从0到1实现购物应用页面

概述

OpenHarmony ArkUI框架提供了丰富的动画组件和接口,开发者可以根据实际场景和开发需求,选用丰富的动画组件和接口来实现不同的动画效果。

本Codelab中,我们会构建一个简易的购物应用。应用包含两级页面,分别是主页(“商品浏览”页签、“购物车”页签、“我的”页签)和商品详情页面。效果如下图所示:

代码结构解读

本篇Codelab只对核心代码进行讲解,首先来介绍下整个工程的代码结构:

  • model:存放封装好的数据实体。
    • ArsData:我的页签相关参数实体。
    • GoodsData:商品列表页商品实体。
    • GoodsDataModels:各种实体的具体数据以及获取数据的方法。
    • Menu:我的页签菜单实体。
  • pages:存放页面。
    • HomePage:应用主页面,包含商品列表页签。
    • MyPage:我的页签。
    • ShoppingCartPage:购物车页签。
    • ShoppingDetail:商品详情页。
  • resources :存放工程使用到的资源文件。
    • resources/base/media:存放工程中使用的图片资源。
  • config.json:配置文件。

搭建OpenHarmony环境

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以Hi3516DV300开发板为例,参照以下步骤进行:

  1. 获取OpenHarmony系统版本:标准系统解决方案(二进制)

以3.0版本为例:

2.搭建烧录环境

  1. 完成DevEco Device Tool的安装
  2. 完成Hi3516开发板的烧录

3.搭建开发环境

  1. 开始前请参考工具准备,完成DevEco Studio的安装和开发环境配置。
  2. 开发环境配置完成后,请参考使用工程向导创建工程(模板选择“Empty Ability”),选择JS或者eTS语言开发。
  3. 工程创建完成后,选择使用真机进行调测。

构建商品列表页签

在本节中,我们将完成商品列表页签的设计,效果图如下:

从效果图可以看出,商品列表页签主要由三个部分组成:

  1. 顶部的Tabs组件。
  2. 中间TabContent组件内包含List组件。其中List组件的item是一个水平布局,由一个垂直布局和一个Image组件组成;item中的垂直布局由3个Text组件组成。
  3. 底部的导航页签navigation组件。

实现步骤如下:

  1. 在pages目录下面新建一个ETS Page,命名为HomePage.ets,在config.json文件的pages属性中会自动添加“pages/HomePage”页面路由。

说明:

  • 页面文件名不能使用组件名称,比如:Text.ets、Button.ets等。
  • 每个页面文件中必须包含入口组件。

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

let NextId = 0;
export class ArsData {
  id: string;
  title: string;
  content: string;

  constructor(title: string, content: string) {
    this.id = `${NextId++}`;
    this.title = title;
    this.content = content;
  }
}

GoodsData.ets代码如下:

let NextId = 0;
export class GoodsData {
  id: string;
  title: string;
  content: string;
  price: number;
  imgSrc: Resource;

  constructor(title: string, content: string, price: number, imgSrc: Resource) {
    this.id = `${NextId++}`;
    this.title = title;
    this.content = content;
    this.price = price;
    this.imgSrc = imgSrc;
  }
}

一个文件中可以包含多个class ,Menu.ets中就包含了Menu类和ImageItem类,Menu.ets代码如下

let NextId = 0;
export class Menu {
  id: string;
  title: string;
  num: number;

  constructor(title: string, num: number) {
    this.id = `${NextId++}`;
    this.title = title;
    this.num = num;
  }
}

export class ImageItem {
  id: string;
  title: string;
  imageSrc: Resource;

  constructor(title: string, imageSrc: Resource) {
    this.id = `${NextId++}`;
    this.title = title;
    this.imageSrc = imageSrc;
  }
}

GoodsDataModels.ets代码如下:

import {GoodsData} from './GoodsData'

import {Menu, ImageItem} from './Menu'
import {ArsData} from './ArsData'
//获取商品列表数据
export function initializeOnStartup(): Array<GoodsData> {
  let GoodsDataArray: Array<GoodsData> = []
  GoodsComposition.forEach(item => {
    console.log(item.title);
    GoodsDataArray.push(new GoodsData(item.title, item.content, item.price, item.imgSrc));
  })
  return GoodsDataArray;
}
//获取底部默认图片列表数据
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> {
  let MenuArray: Array<Menu> = []
  MyMenu.forEach(item => {
    MenuArray.push(new Menu(item.title,item.num));
  })
  return MenuArray;
}
//获取MyTrans数据列表
export function getTrans(): Array<ImageItem> {
  let ImageItemArray: Array<ImageItem> = []
  MyTrans.forEach(item => {
    ImageItemArray.push(new ImageItem(item.title,item.imageSrc));
  })
  return ImageItemArray;
}
//获取More数据列表
export function getMore(): Array<ImageItem> {
  let ImageItemArray: Array<ImageItem> = []
  MyMore.forEach(item => {
    ImageItemArray.push(new ImageItem(item.title,item.imageSrc));
  })
  return ImageItemArray;
}
//获取参数列表
export function getArs(): Array<ArsData> {
  let ArsItemArray: Array<ArsData> = []
  ArsList.forEach(item => {
    ArsItemArray.push(new ArsData(item.title,item.content));
  })
  return ArsItemArray;
}
//数据集合部分
...

3.在HomePage.ets文件中创建商品列表页签相关的组件,其中GoodsHome效果图如下:

代码如下:

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

  build() {
    Column() {
      Tabs() {
        TabContent() {
          GoodsList({ goodsItems: this.goodsItems });
        }
        .tabBar("Top Sellers")
        .backgroundColor(Color.White)

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

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

        TabContent() {
          GoodsList({ goodsItems: this.goodsItems });
        }
        .tabBar("Deals")
        .backgroundColor(Color.White)
      }
      .barWidth(540)
      .barHeight(50)
      .scrollable(true)
      .barMode(BarMode.Scrollable)
      .backgroundColor('#007DFF')
      .height('100%')
    }
    .alignItems(HorizontalAlign.Start)
  }
}

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

代码如下:

@Component
struct GoodsList {
  private goodsItems: GoodsData[]

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

在GoodsList组件中遍历商品数据集合,ListItem组件中设置组件内容,并使用Navigator组件给每个Item设置顶级跳转路由,GoodsListItem组件效果图如下:

代码如下:

@Component
struct GoodsListItem {
  private goodsItem: GoodsData

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

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

      }
      .backgroundColor(Color.White)

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

4.在HomePage.ets中创建文件入口组件(Index)以及底部页签导航组件(Navigation),导入需要使用到的数据实体类以及需要使用的方法和组件,每个page文件都必须包含一个入口组件,使用@Entry修饰,HomePage文件中的入口组件(Index)代码如下:

import { GoodsData, IconImage } from '../model/GoodsData'
import { initializeOnStartup, getIconPath, getIconPathSelect } from '../model/GoodsDataModels'
import { ShoppingCart } from './ShoppingCartPage.ets'
import { MyInfo } from './MyPage.ets'
import router from '@system.router';

@Entry
@Component
struct Index {
  @Provide currentPage: number = 1
  private goodsItems: GoodsData[] = initializeOnStartup()
  @State Build: Array<Object> = [
    {
      icon: $r('app.media.icon_home'),
      icon_after: $r('app.media.icon_buy1'),
      text: '首页',
      num: 0
    },
    {
      icon: $r('app.media.icon_shopping_cart'),
      icon_after: $r('app.media.icon_shopping_cart_select'),
      text: '购物车',
      num: 1
    },
    {
      icon: $r('app.media.icon_my'),
      icon_after: $r('app.media.icon_my_select'),
      text: '我的',
      num: 2
    }
  ]

  @Builder NavigationToolbar() {
    Flex({direction:FlexDirection.Row,wrap:FlexWrap.NoWrap,justifyContent:FlexAlign.SpaceAround}) {
      ForEach(this.Build, item => {
        Column() {
          Image(this.currentPage == item.num ? item.icon_after : item.icon)
            .width(25)
            .height(25)
          Text(item.text)
            .fontColor(this.currentPage == item.num ? "#ff7500" : "#000000")
        }
        .onClick(() => {
          this.currentPage = item.num
        })
      })
    }
  }

  build() {
    Column() {
      Navigation() {
        Flex() {
          if (this.currentPage == 0) {
            GoodsHome({ goodsItems: this.goodsItems })
          }
          if (this.currentPage == 1) {
            ShoppingCart() //购物车列表
          }
          if (this.currentPage == 2) {
            MyInfo() //我的
          }
        }
        .width('100%')
        .height('100%')
      }
      .toolBar(this.NavigationToolbar)
      .title("购物车")
      .hideTitleBar(this.currentPage == 1 ? false : true)
      .hideBackButton(true)
    }
  }
}

从入口组件的代码中可以看出,我们定义了一个全局变量currentPage ,当currentPage发生变化的时候,会显示不同的页签。在入口组件中,通initializeOnStartup获取商品列表数据(goodsItems)并传入GoodsHome组件中。效果图如下:

构建购物车页签

从上面效果图可以看出,主界面购物车页签主要由下面三部分组成:

  1. 顶部的title,由Navigation组件title属性设置。
  2. 中间的List组件,其中List组件的item是一个水平的布局内包含一个toggle组件,一个Image组件和一个垂直布局,其item中的垂直布局是由2个Text组件组成。
  3. 底部一个水平布局包含两个Text组件。

在本任务中我们主要是构建一个购物车页签,给商品列表的每个商品设置一个单选框,可以选中与取消选中,底部Total值也会随之增加或减少,点击Check Out时会触发弹窗。下面我们来完成ShoppingCart页签。

  1. 在pages目录下面新建一个ETS Page ,命名为ShoppingCart.ets,config.json文件pages属性中也会自动添加“pages/ShoppingCart”页面路由。
  2. 在ShoppingCartPage.ets文件中添加入口组件(ShoppingCart),并导入需要使用到的数据实体类、方法和组件。ShoppingCart组件代码如下:
import {GoodsData} from '../model/GoodsData'
import {initializeOnStartup} from '../model/GoodsDataModels'
import prompt from '@system.prompt';

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

  build() {
    Column() {
      ShopCartList({ goodsItems: this.goodsItems });
      ShopCartBottom()
    }
    .height('100%')
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }
}

3.新建ShopCartList组件用于存放购物车商品列表,ShopCartList组件效果图如下:

代码如下:

@Component
struct ShopCartList {
  private goodsItems: GoodsData[]

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

在ShopCartListItem中使用Toggle的单选框类型来实现每个item的选择和取消选择,在Toggle的onChage事件中来改变totalPrice的数值。ShopCartListItem组件效果图如下:

代码如下:

@Component
struct ShopCartListItem {
  @Consume totalPrice: number
  private goodsItem: GoodsData

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

4.新建ShopCartBottom组件,ShopCartBottom组件效果图如下:

代码如下:

@Component
struct ShopCartBottom {
  @Consume totalPrice: number

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

构建我的页签

从上面效果图可以看出,主界面我的页签主要由下面四部分组成:

  1. 顶部的水平布局。
  2. 顶部下面的文本加数字的水平List。
  3. My Transactio模块,图片加文本的水平List。
  4. More模块,图片加文本的Grid。

在本任务中,我们构建主页我的页签,主要可以划分成下面几步:

  1. 在pages目录下面新建一个ETS Page 命名为MyPage.ets,在config.json文件pages属性中也会自动添加“pages/MyPage”页面路由。
  2. 在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($r('app.media.icon_user'))
          .objectFit(ImageFit.Contain)
          .height(50)
          .width(50)
          .margin({left:10})
          .renderMode(ImageRenderMode.Original)
        Column() {
          Text('John Doe')
            .fontSize(15)
          Text('Member Name : John Doe                     >')
            .fontSize(15)
        }
        .height(60)
        .margin({ left: 20, top: 10 })
        .alignItems(HorizontalAlign.Start)
      }

      TopList()
      MyTransList()
      MoreGrid()

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

入口组件中还包含TopList,MyTransList和MoreGrid三个子组件。

3.在MyPage.ets文件中新建TopList组件,效果图如下:

代码如下:

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

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

getMenu()方法在上文中已有定义,是获取菜单列表的方法,TopList的子组件MenuItem内容如下:

@Component
struct MenuItem {
  private menu: Menu1

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

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

4.在MyPage.ets文件中新建MyTransList组件和MoreGrid组件,MyTransList组件效果如如下:

代码如下:

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

  build() {
    Column() {
      Text('My Transaction')
        .fontSize(20)
        .margin({ left: 10 })
        .width('100%')
        .height(30)
      Row() {
        List() {
          ForEach(this.imageItems, item => {
            ListItem() {
              DataItem({ imageItem: item })
            }
          }, item => item.id.toString())
        }
        .height(70)
        .width('100%')
        .edgeEffect(EdgeEffect.None)
        .margin({ top: 5 })
        .padding({ left: 16, right: 16 })
        .listDirection(Axis.Horizontal)
      }
    }
    .height(120)
  }
}

MoreGrid组件效果图如下:

代码如下:

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

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

  build() {
    Column() {
      Text('More')
        .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组件中都包含子组件DataItem,为避免的重复代码,可以把多次要用到的结构体组件化,这里的结构体就是图片加上文本的上下结构体,DataItem组件内容如下:

@Component
struct DataItem {
  private imageItem: ImageItem

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

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

构建商品详情页面

从上面效果图可以看出,商品详情页面主要由下面五部分组成:

  1. 顶部的返回栏。
  2. Swiper组件。
  3. 中间多个Text组件组成的布局。
  4. 参数列表。
  5. 底部的Buy。

在本任务中,把上面每一部分都封装成一个组件,然后再放到入口组件内,当点击顶部返回图标时返回到主页面的商品列表页签,点击底部Buy时,会触发进度条弹窗

  1. 在pages目录下面新建一个ETS Page, 命名为ShoppingDetail.ets,config.json文件pages属性中也会自动添加“pages/ShoppingDetail”页面路由。
  2. 在ShoppingDetail.ets文件中创建入口组件,组件内容如下:
@Entry
@Component
struct ShoppingDetail {
  private arsItems: ArsData[] = getArs()

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

      DetailBottom()
    }
    .height('90%')
    .width('100%')
  }
}

其中顶部DetailTop组件效果图如下:

代码如下:

@Component
struct DetailTop {
  build() {
    Column() {
      Row() {
        Image($r('app.media.icon_return'))
          .height(40)
          .width(40)
          .margin({left: 20})
          .onClick(() => {
            router.push({
              uri: "pages/HomePage"
            })
          })

      }
      .width('100%')
      .height(35)
      .backgroundColor('#FF87CEEB')
    }
    .width('100%')
    .height(40)
  }
}

3.SwiperTop组件效果图如下:

代码如下:

@Component
struct SwiperTop {
  build() {
    Column() {
      Swiper() {
        Image($r('app.media.computer1'))
          .height(220)
          .width('100%')
        Image($r('app.media.computer2'))
          .height(220)
          .width('100%')
        Image($r('app.media.computer3'))
          .height(220)
          .width('100%')
        Image($r('app.media.computer4'))
          .height(220)
          .width('100%')
        Image($r('app.media.computer5'))
          .height(220)
          .width('100%')
        Image($r('app.media.computer6'))
          .height(220)
          .width('100%')
      }
      .index(0)
      .autoPlay(true)
      .interval(3000)
      .indicator(true)
      .loop(true)
      .height(250)
      .width('100%')
    }
    .height(250)
    .width('100%')
  }
}

4.DetailText组件效果图如下:

代码如下:

@Component
struct DetailText {
  build() {
    Column() {
      Row() {
        Image($r('app.media.icon_promotion'))
          .objectFit(ImageFit.Contain)
          .height(30)
          .width(30)
          .margin({ left: 10 })
        Text('Special Offer: ¥9999')
          .fontColor(Color.White)
          .fontSize(20)
          .margin({ left: 10 })

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

      Column() {
        Text('New Arrival: HUAWEI MateBook X Pro 2021')
          .fontSize(18)
          .margin({ left: 10 })
          .alignSelf(ItemAlign.Start)
        Text('13.9-Inch, 11th Gen Intel® Core™ i7, 16 GB of Memory, 512 GB of Storage, Ultra-slim Business Laptop, 3K FullView Display, Multi-screen 
                          Collaboration, Emerald Green')
          .fontSize(14)
          .margin({ left: 10 })
        Row() {
          Image($r('app.media.icon_buy'))
            .objectFit(ImageFit.Contain)
            .height(30)
            .width(30)
            .margin({ left: 10 })
          Text('Limited offer')
            .fontSize(15)
            .fontColor(Color.Red)
            .margin({ left: 100 })

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

        Text(' Shipment:         2-day shipping')
          .fontSize(13)
          .fontColor(Color.Red)
          .margin({ left: 10, top: 5 })
          .alignSelf(ItemAlign.Start)
        Text('    Ship To:         Hubei,Wuhan,China')
          .fontSize(13)
          .fontColor(Color.Red)
          .margin({ left: 10, top: 5 })
          .alignSelf(ItemAlign.Start)
          .onClick(() => {
            prompt.showDialog({ title: 'select address', })

          })
        Text('Guarantee:         Genuine guaranteed')
          .fontSize(13)
          .margin({ left: 10, top: 5 })
          .alignSelf(ItemAlign.Start)
      }
      .height(170)
      .width('100%')
    }
    .height(180)
    .width('100%')
  }
}

DetailArsList组件效果图如下:

代码如下:

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

ArsListItem组件代码如下:

@Component
struct ArsListItem {
  private arsItem: 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)
  }
}

5.DetailBottom组件效果图如下:

代码如下:

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

  onAccept() {

  }

  existApp() {

  }

  build() {
    Column() {
      Text('Buy')
        .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('10%')
  }
}

DialogExample自定义弹窗组件效果图如下:

代码如下:

@CustomDialog
struct DialogExample {
  @Consume
  private value: number
  controller: CustomDialogController;
  action: () => void;

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

    }
    .height(60)
    .width(100)
  }
}

相关概念与参考

API 参考

  1. Tabs组件
  2. CustomDialog自定义弹窗
  3. List组件
  4. Grid组件
  5. Image组件
  6. Button组件
  7. Text组件
  8. Progress组件
  9. Navigator组件
  10. TabContent组件
  11. Row组件
  12. Column组件
  13. Flex组件
  14. Scroll组件
  15. Navigation组件

总结

本篇CodeLab灵活使用了一些组件来实现页面效果:

  1. 使用Tabs组件完成商品分类。
  2. 使用List组件完成商品列表、图片列表、参数列表等。
  3. 使用Swiper组件完成图片的循环轮播。
  4. 使用Toggle组件完成购物车商品的选择。

为了帮助大家更深入有效的学习到鸿蒙开发知识点,小编特意给大家准备了一份全套最新版的HarmonyOS NEXT学习资源,获取完整版方式请点击→《HarmonyOS教学视频》

HarmonyOS教学视频:语法ArkTS、TypeScript、ArkUI等.....视频教程

鸿蒙生态应用开发白皮书V2.0PDF:

获取完整版白皮书请点击→《鸿蒙生态应用开发白皮书V2.0PDF》

鸿蒙 (Harmony OS)开发学习手册

一、入门必看

  1. 应用开发导读(ArkTS)
  2. ……

二、HarmonyOS 概念

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全
  5. ........

三、如何快速入门?《做鸿蒙应用开发到底学习些啥?》

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

四、开发基础知识

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

五、基于ArkTS 开发

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

更多了解更多鸿蒙开发的相关知识可以参考:《鸿蒙 (Harmony OS)开发学习手册》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/496438.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【正点原子FreeRTOS学习笔记】————(7)任务调度

这里写目录标题 一、开启任务调度器&#xff08;熟悉&#xff09;二、启动第一个任务&#xff08;熟悉&#xff09;2.1&#xff0c;prvStartFirstTask () /* 开启第一个任务 */2.2&#xff0c;vPortSVCHandler () /* SVC中断服务函数 */ 三、任务切换&#xff08;掌握&#xff…

【元胞自动机】MATLAB界面聚合的元胞自动机模拟完整实现运行

文末有完整代码分享链接 文件介绍 automain 为元胞自动机主函数 choosedirection 选择方向函数&#xff0c;主函数调用 judgedirection 判断位置函数&#xff0c;主函数调用 neighbor 求每个元胞的邻居函数&#xff0c;主函数调用 surfaceness 求表面粗糙度 porosity 求孔隙率…

开源AI引擎|信息抽取与文本分类项目案例:提升12345政务投诉处理效率

一、实际案例介绍 采集员案件上报流程是城市管理和问题解决的关键环节&#xff0c;涉及对案件类别的选择、案件来源的记录、详细案件描述的填写以及现场图片的上传。这一流程要求采集员准确、详细地提供案件信息&#xff0c;以便系统能够自动解析关键数据并填写相关内容&#…

Python读取PDF文字转txt,解决分栏识别问题,能读两栏

搜索了一下&#xff0c;大致有这些库能将PDF转txt 1. PyPDF/PyPDF2&#xff08;截止2024.03.28这两个已经合并成了一个&#xff09;pypdf PyPI 2. pdfplumber GitHub - jsvine/pdfplumber: Plumb a PDF for detailed information about each char, rectangle, line, et cete…

VSCode 如何同步显示网页在手机或者平板上

首先要确保 ①电脑上安装了VsCode ②VsCode安装插件LiveServer 安装成功之后 连续按住 Alt L 、Alt O 会跳转到对应的html页面上 http://127.0.0.1:5500/....... 是这个开头的 然后打开网络 如果桌面有网上邻居的可以直接点桌面的网上邻居 进来找到WLAN这个…

spark核心概念

DAG 所谓DAG就是有向无环图&#xff0c;其实就是个无环的流程&#xff0c;Spark的核心是根据RDD来实现的&#xff0c;Spark Scheduler!则为Spark核心实现的重要一环&#xff0c;其作用就是任务调度。Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据&#xff0c;根据…

AI智能分析网关V4如何使用GB28181注册到EasyCVR平台?具体步骤是什么?

旭帆科技的智能分析网关V4内含近40种智能分析算法&#xff0c;包括人体、车辆、消防、环境卫生、异常检测等等&#xff0c;在消防安全、生产安全、行为检测等场景应用十分广泛。如常见的智慧工地、智慧校园、智慧景区、智慧城管等等&#xff0c;还支持抓拍、记录、告警、语音对…

JavaScript基础练习题之计算数组元素的和与平均值

一、如何使用JavaScript计算数组元素的和与平均值&#xff1f; 二、正确的源程序 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>计算数组元素的和与平均值</title></head><body><h1>计算数组元…

HarmonyOS 应用开发之AbilityStage组件容器

AbilityStage是一个 Module 级别的组件容器&#xff0c;应用的HAP在首次加载时会创建一个AbilityStage实例&#xff0c;可以对该Module进行初始化等操作。 AbilityStage与Module一一对应&#xff0c;即一个Module拥有一个AbilityStage。 DevEco Studio默认工程中未自动生成Ab…

Linux常见指令解析一

Linux常见指令解析一 常见指令1. ls 指令2.pwd 命令3.cd 命令4.touch 命令5.mkdir 命令6.rmdir指令 && rm 指令7.man 指令8.cp 指令9.cat 命令 && tac 命令10.mv 指令11.more 指令12.less 指令13.head 指令14.tail 指令15.cal 指令 常见指令 1. ls 指令 语法…

包含具有多种类型信息的3D模型

1982年&#xff0c;Gbor Bojr开始使用类似于1975年的建筑描述系统技术来开发建筑信息软件。随后&#xff0c;1984年Bojr发布了用于Apple Lisa操作系统的Graphisoft的Radar CH。该软件技术被称为ArchiCAD于1987年重新推出&#xff0c;这是第一个能够在个人计算机上运行的建筑信息…

【源码分析】一文看透集合容器

一文看透集合容器 一、Mapa. HashMapb.ConcurrentHashMapc.HashTabled. TreeMap 二、Collectiona. ListArrayListLinkedListVectorCopyOnWriteArrayList对比和自身思考思考&#xff1a;为什么都拒绝使用Vector啊&#xff1f;它线程安全诶 b. SetHashSetTreeSetCopyOnWriteArray…

2024年【烟花爆竹产品涉药】免费试题及烟花爆竹产品涉药考试技巧

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【烟花爆竹产品涉药】免费试题及烟花爆竹产品涉药考试技巧&#xff0c;包含烟花爆竹产品涉药免费试题答案和解析及烟花爆竹产品涉药考试技巧练习。安全生产模拟考试一点通结合国家烟花爆竹产品涉药考试最新大纲…

135.分发糖果

javapublic class Solution {public int candy(int[] ratings) {// 获取孩子人数int len ratings.length;// 初始化一个数组存储每个孩子的糖果数&#xff0c;默认第一个孩子有1颗糖果int[] candyVec new int[len];candyVec[0] 1;// 阶段1&#xff1a;从左到右遍历for (int …

MongoDB内存过高问题分析解决

告警 公司有个3.2.7版本的mongo复制集&#xff0c;最近几天频繁告警内存过高。 服务器配置16C64G内存。mongo备节点内存使用到55G&#xff0c;触发告警。 以下内容基于3.2.7版本&#xff0c;3.2.7版本已经太老&#xff0c;很多后来的命令和配置&#xff0c;3.2.7都没有。 …

C++自主点餐系统

一、 题目 设计一个自助点餐系统&#xff0c;方便顾客自己点餐&#xff0c;并提供对餐厅销售情况的统计和管理功能。 二、 业务流程图 三、 系统功能结构图 四、 类的设计 五、 程序代码与说明 头文件1. SystemMap.h #pragma once #ifndef SYSTEMMAP #define SYSTEMMAP #in…

vue3全局引入element-plus使用Message教程

文章目录 安装引入 Element Plus和组件样式示例注意安装与引入&#xff1a;按需引入&#xff1a;API 使用&#xff1a;样式问题&#xff1a;组件上下文&#xff1a;版本兼容性&#xff1a;错误处理&#xff1a; 这是 Element UI 的 Vue 3 版本。ElMessage 是 Element Plus 中的…

在Linux上使用nginx反向代理部署Docker网站

在政务云上部署Web环境&#xff0c;为了保证服务器安全&#xff0c;甲方只开放一个端口且只允许使用https协议进行访问&#xff0c;经过思考&#xff0c;决定使用docker部署网站&#xff0c;使用nginx反向代理&#xff0c;通过不同的二级域名访问不同的端口。 1 使用docker部署…

编程语言|C语言——C语言变量的存储方式

前言 变量是程序中数据的存储空间的抽象。变量的存储方式可分为静态存储和动态存储两种。 静态存储变量通常是在程序编译时就分配一定的存储空间并一直保持不变&#xff0c;直至整个程序结束。在上一部分中介绍的全局变量的存储方式即属于此类存储方式。 动态存储变量是在程序执…

超越极限!《无名之辈》高阶武学与战术应对策略一览!

欢迎来到《无名之辈》世界&#xff01;在这里&#xff0c;决战不仅需要勇气&#xff0c;更需要智慧和策略。为了让你在游戏中游刃有余&#xff0c;以下是一份全面的游戏攻略&#xff0c;助你成为战场上的无敌之王&#xff01; 一、主角战斗技巧&#xff1a; 反击属性至关重要&a…