一、引导页布局思路
引导页采用Flex弹性布局方案,这种布局方案可以控制元素在水平和垂直方向上的排列规则。
(1)布局效果展示

(2)涉及知识点
1、通过设置参数direction ,可以决定主轴的方向,从而控制子元素的排列方向。会接受一个枚举FlexDirection,默认时水平方向从左往右。
2、wrap 属性控制当子元素主轴尺寸之和大于容器主轴尺寸时,Flex是单行布局还是多行布局。会接受一个枚举FlexWrap,默认时不压缩,如果子元素的宽度总和大于父元素的宽度,则子元素会被压缩宽度。
3、justifyContent 参数设置子元素在主轴方向的对齐方式,接受一个枚举FlexAlign。

4、alignItems 参数设置子元素在交叉轴的对齐方式,容器接受一个枚举ItemAlign: 容器组件设置交叉轴对齐;子元素同样接受一个枚举alignSelf :子元素设置交叉轴对齐,两者都设置子元素会覆盖容器的对齐方式 ;内容对齐方式可以设置alignContent ,接受一个枚举FlexAlign。
5、自适应拉伸:设置在子元素上,flexBasis:设置子元素在父容器主轴方向上的基准尺寸;**flexGrow:**设置父容器的剩余空间分配给此属性所在组件的比例;flexShrink:当父容器空间不足时,子元素的压缩比例。
(3)定时器的使用
1、引入状态变量并在页面处使用
javascript
//引导页倒计时
@State istime: number = 3;
//页面使用
Text(`${this.istime}`)
2、生命周期中使用定时器
javascript
aboutToAppear(): void {
//定时器倒计时
let timer=setInterval(() => {
this.istime--
if (this.istime==0) {
//不销毁会存在页面内存泄露问题
clearInterval(timer)
//跳转到登陆界面
router.replaceUrl({
url:"pages/Index"
})
}
},1000)
}
(4)响应式布局实现
1、常量定义包:BreakPointConstants
javascript
/**
*Constants for breakpoint
*/
export class BreakPointConstants{
/**
*这个单位页面上表示初始值
*/
static readonly BREAKPOINT_INIT = 'init';
/**
* 这个单位页面上表示手机
*/
static readonly BREAKPOINT_SM = 'sm';
/**
* 这个单位页面上表示折叠屏
*/
static readonly BREAKPOINT_MD = 'md';
/**
* 这个单位页面上表示pad
*/
static readonly BREAKPOINT_LG = 'lg';
/**
*这个单位页面上表示大屏
*/
static readonly BREAKPOINT_XL = 'xl';
/**
*这个单位页面上表示超大屏
*/
static readonly BREAKPOINT_XXL = 'xxl';
/**
*断点数组,根据不同的尺寸大小说明修改数组样式
*/
static readonly BREAKPOINT_VALUE:Array<string> = ['320vp','600vp','840vp','1080vp','1280vp'];
/**
*列在水平方向占多少份
*/
static readonly COLUMN_SM:string = '4';
static readonly COLUMN_MD:string = '8';
static readonly COLUMN_LG:string = '12';
/**
*
*/
static readonly GUTTER_X:number = 12;
/**
*
*/
static readonly SPAN_SM:number = 4;
static readonly SPAN_MD:number = 6;
static readonly SPAN_LG:number = 8;
/**
*
*/
static readonly OFFSET_MD:number = 1;
static readonly OFFSET_LG:number = 2;
/**
*
*/
static readonly CURRENT_BREAKPOINT:string = 'currentbreakpoint';
/**
*
*/
static readonly FONT_SIZE_SM:number = 14;
static readonly FONT_SIZE_MD:number = 16;
static readonly FONT_SIZE_LG:number = 18;
/**
*
*/
static readonly COVER_MARGIN_SM:number = 10;
static readonly COVER_MARGIN_MD:number = 30;
static readonly COVER_MARGIN_LG:number = 40;
/**
*
*/
static readonly RANGE_SM:string = '(320vp<=width<600vp)';
static readonly RANGE_MD:string = '(600vp<=width<840vp)';
static readonly RANGE_LG:string = '(840vp<=width<1080vp)';
static readonly RANGE_XL:string = '(1080vp<=width<1280vp)';
static readonly RANGE_XXL:string = '(1280vp<=width)';
}
2、查询工具包:
javascript
import {BreakPointConstants} from '../Constants/BreakPointConstants'
import { mediaquery } from '@kit.ArkUI'
export class BreakPointSystem{
//当前尺寸是多大,媒体查询,得到单位要将这个sm、md、lg、xl、xxl保存到全局,currentBreakpint作为key保存起来
private currentBreakpint:string = BreakPointConstants.BREAKPOINT_INIT
/**
*步骤一:屏幕大小的条件查询
*/
private smListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_SM)
private mdListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_MD)
private lgListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_LG)
private xlListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_XL)
private xxlListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_XXL)
//定义一个公共函数,用来保存当前屏幕的检测结果
//将sm、md、lg、xl、xxl保存到"应用的状态"
private updateCurrentBreakpoint(breakpoint:string){
if (this.currentBreakpint !== breakpoint) {
this.currentBreakpint = breakpoint
//将当前单位做应用存储,setOrCreate是创建初始化的意思,()中存储一个键值,名字和存储内容
AppStorage.setOrCreate<string>(BreakPointConstants.CURRENT_BREAKPOINT,this.currentBreakpint)
}
}
/**
*步骤二:给监听器绑定"change"事件
*/
private isBreakpointSM = (mediaQueryResult: mediaquery.MediaQueryResult)=>{
//如果匹配成功
if (mediaQueryResult.matches) {
//将sm的单位保存起来
this.updateCurrentBreakpoint(BreakPointConstants.BREAKPOINT_SM)
}
}
private isBreakpointMD = (mediaQueryResult: mediaquery.MediaQueryResult)=>{
//如果匹配成功
if (mediaQueryResult.matches) {
//将md的单位保存起来
this.updateCurrentBreakpoint(BreakPointConstants.BREAKPOINT_MD)
}
}
private isBreakpointLG = (mediaQueryResult: mediaquery.MediaQueryResult)=>{
//如果匹配成功
if (mediaQueryResult.matches) {
//将lg的单位保存起来
this.updateCurrentBreakpoint(BreakPointConstants.BREAKPOINT_LG)
}
}
private isBreakpointXL = (mediaQueryResult: mediaquery.MediaQueryResult)=>{
//如果匹配成功
if (mediaQueryResult.matches) {
//将xl的单位保存起来
this.updateCurrentBreakpoint(BreakPointConstants.BREAKPOINT_XL)
}
}
private isBreakpointXXL = (mediaQueryResult: mediaquery.MediaQueryResult)=>{
//如果匹配成功
if (mediaQueryResult.matches) {
//将xxl的单位保存起来
this.updateCurrentBreakpoint(BreakPointConstants.BREAKPOINT_XXL)
}
}
//这个函数外面要调用,因此不能再用private修饰
public register(){
this.smListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_SM);
this.smListener.on('change',this.isBreakpointSM);
this.mdListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_MD);
this.mdListener.on('change',this.isBreakpointMD);
this.lgListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_LG);
this.lgListener.on('change',this.isBreakpointLG);
this.xlListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_XL);
this.xlListener.on('change',this.isBreakpointXL);
this.xxlListener = mediaquery.matchMediaSync(BreakPointConstants.RANGE_XXL);
this.xxlListener.on('change',this.isBreakpointXXL);
}
//接触事件绑定,优化代码
public unregister(){
this.smListener.off('change',this.isBreakpointSM);
this.mdListener.off('change',this.isBreakpointMD);
this.lgListener.off('change',this.isBreakpointLG);
this.xlListener.off('change',this.isBreakpointXL);
this.xxlListener.off('change',this.isBreakpointXXL);
}
}
3、在引导页引入
javascript
import {BreakPointSystem} from '../utils/BreakPointSystem'
4、new实例化
javascript
private breakPointSystem:BreakPointSystem = new BreakPointSystem()
5、注意在工具包中将尺寸类型存到了 currentbreakpoint 中,初始化 StorageProp 中的尺寸
javascript
@StorageProp('currentbreakpoint') currentbreakpoint:string='init'
6、在对应生命周期中注册和销毁,并在对应样式中进行使用。
javascript
aboutToAppear(): void {
//注册
this.breakPointSystem.register()
//计算倍数
switch (this.currentbreakpoint){
case 'sm':
this.falg=1
this.offsetquantity=280
break;
case 'md':
this.falg=1.5
this.offsetquantity=420
break;
case 'lg':
this.falg=2
this.offsetquantity=600
break;
case 'xl':
this.falg=2.5
this.offsetquantity=700
break;
case 'xxl':
this.falg=3
this.offsetquantity=1300
break;
}
console.log('currentbreakpoint',this.currentbreakpoint)
}
aboutToDisappear(): void {
this.breakPointSystem.unregister()
}
(5)布局实现
javascript
import {BasicContants} from '../Constants/BasicContants'
import { router } from '@kit.ArkUI';
import {BreakPointSystem} from '../utils/BreakPointSystem'
@Entry
@Component
struct GuidePage {
//引导页倒计时
@State istime: number = 3;
@StorageProp('currentbreakpoint') currentbreakpoint:string='init'
@State falg:number=1
@State offsetquantity:number=280
private breakPointSystem:BreakPointSystem = new BreakPointSystem()
aboutToAppear(): void {
//注册
this.breakPointSystem.register()
//计算倍数
switch (this.currentbreakpoint){
case 'sm':
this.falg=1
this.offsetquantity=280
break;
case 'md':
this.falg=1.5
this.offsetquantity=420
break;
case 'lg':
this.falg=2
this.offsetquantity=600
break;
case 'xl':
this.falg=2.5
this.offsetquantity=700
break;
case 'xxl':
this.falg=3
this.offsetquantity=1300
break;
}
console.log('currentbreakpoint',this.currentbreakpoint)
//定时器倒计时
let timer=setInterval(() => {
this.istime--
if (this.istime==0) {
//不销毁会存在页面内存泄露问题
clearInterval(timer)
//跳转到登陆界面
router.replaceUrl({
url:"pages/Index"
})
}
},1000)
}
aboutToDisappear(): void {
this.breakPointSystem.unregister()
}
build() {
Flex({
direction: FlexDirection.Column,
alignItems: ItemAlign.Center
}) {
Text(`${this.istime}`)
.fontSize(30*this.falg)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
.width(50*this.falg)
.height(50*this.falg)
.borderRadius(50*this.falg/2)
.backgroundColor(Color.White)
.margin({ top: 20*this.falg,left:this.offsetquantity })
//LOGO
Column() {
Image($rawfile('GuidePage/Shoppingcentre.svg'))
.width(114*this.falg)
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.flexGrow(1)
Column({ space: 10 }) {
Text('购物商城')
.fontSize(30*this.falg)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
Text('Practice Mall Project')
.fontSize(16*this.falg)
.textAlign(TextAlign.Center)
}
.margin({ top: 12, bottom: 80 })
}
.height(BasicContants.FULL_HEIGHT)
.width(BasicContants.FULL_HEIGHT)
.backgroundColor('#f3f3f3')
}
}
二、主页布局思路
(1)布局效果展示

(2)设计知识点
1、Tabs组件定义导航:页面开发过程中,如果想要使用底部导航,可以采用此组件,仅可包含子组件TabContent。
2、可设置Tabs的参数有:barPosition :设置Tabs的页签位置,默认导航栏在顶部;index :设置当前显示页签的索引;可绑定控制器controller; 当然若要实现测边导航栏,需要给Tabs加一个属性vertical(true),表示设置为纵向Tab。
3、@Builder装饰器:自定义页面构造函数,也可以称为自定义页面组件模块。作用 是将页面中需要提取出来的布局代码,封装为一个函数,然后再重复利用。范围 是只针对当前这个组件,如果要针对多个组件,需要单独创建组件文件(自定义组件@Component)。目的是为了提取公共模块供当前页面使用,优化代码结构,提取一些代码,复杂页面简化下来,按照模块的方式来组装页面。
4、@Builder装饰器可以传参。语法规则 是可以定义在全局和局部,但都只能在组件内部定义使用,不能提取到外部定义在引用,在组件的内部调用需要用this 来使用,外部定义可以在多个组件都可以引入使用。传递参数有两种类型:值传递和引用传递,**注意:**在JS中只有值传递的概念,但在鸿蒙中有引入传递的概念。
5、值传递:将一个结果传递给函数,函数直接拿来用,当这个结果发生变化不影响函数内部渲染;引用传递:传递过去的是内存中数据的指针,一旦数据发生变化,那么原数据也会受到影响,接收参数默认用**$$**。
6、Swiper组件轮播图,Swiper属性有:loop循环播放;autoPlay自动播放;interval间隔时间;indicator指示器样式设置;itemSpace默认显示的间距;displayCount表示一次显示的张数。
7、Grid网格布局:适用于页面上比较规则的行列元素排列。主要涉及:日历的布局、计算器布局、商品的布局等。Grid代表网格容器,里面必存放的是GridItem元素;GridItem无需设置宽高,在Grid容器一旦设置行列过后,默认撑满整个容器。
8、rowsTemplate代表行元素设置的单位,fr来表示占的大小;columnsTemplate代表列元素设置的单位,fr也是代表占的大小;rowsGap和columnsGap来设置行与行和列与列之间的间隙;layoutDirection可以控制元素排列过程中的显示方向,row和column;可滚动效果的实现需要只设置一个宽或者高的属性rowsTemplate或者columnsTemplate,但这时GridItem的尺寸需要自己控制,超出所属的大小才会实现滚动效果。
9、关于网格布局Grid合并:给GridItem设置rowStart、rowEnd、columnStart、columnEnd来设置。
10、设计商品布局要考虑两个问题,采用List组件,用于页面中规则元素的排列,另外还需要考虑页面的响应式布局,在手机端一行显示两个商品,折叠屏显示三个,pad端显示四个。
11、common里面不要放静态资源,静态资源是存放在AppScope下的resource下的media。
12、Scroll组件在使用的过程中,如果里面存放了多个模块的话,默认只能显示一个,如果想要将模块显示出来,要在Scroll组件内部用一个column包裹。
13、List会有一个属性lanes用于控制一行显示多少个。
(3)底部导航栏设计
1、手机默认布局
javascript
interface TabBarItem {
text: ResourceStr
icon: ResourceStr
}
@Entry
@Component
struct Index {
@State currentIndex: number = 0
tabBarItems: TabBarItem[] = [
{
text: '首页',
icon: $rawfile('Index/home.svg')
},
{
text:'推荐',
icon: $rawfile('Index/record.svg'),
},
{
text: '购物车',
icon: $rawfile('Index/buy.svg'),
},
{
text: '我的',
icon: $rawfile('Index/aboutus.svg'),
}
]
@Builder
TabItemBuilder(index: number) {
Column() {
Image(this.tabBarItems[index].icon)
.fillColor(this.currentIndex === index ? '#DC2626' : '#A1A7B2')
.width(25)
.height(25)
.margin({ bottom: 4 })
.objectFit(ImageFit.Contain)
Text(this.tabBarItems[index].text)
.fontColor(this.currentIndex === index ? '#DC2626' : '#A1A7B2')
.fontSize(13)
.fontWeight(500)
.lineHeight(14)
}
.width('100%')
}
build() {
Column() {
// 底部TabBar
Tabs({ barPosition: BarPosition.End }) {
ForEach(this.tabBarItems, (item: TabBarItem, index: number) => {
TabContent() {
if (index == 0) {
//我的
Text('我的')
} else if (index == 1) {
//推荐
Text('推荐')
} else if (index == 2) {
//发现
Text('购物车')
} else if (index == 3) {
//我的
Text('我的')
}
}
.tabBar(this.TabItemBuilder(index))
}, (item: TabBarItem) => JSON.stringify(item)) //为每个 TabBarItem 生成一个唯一的键值,帮助框架高效地管理列表的更新和渲染
}
.onChange(index => {
this.currentIndex = index
})
}
.backgroundColor('#f3f3f3')
.height('100%')
.width('100%')
}
}
2、平板布局:因为要考虑到多端设备的适配,在平板端希望将底部导航栏提到左侧,此时需要改变两个参数,分别是 vertical 和barPosition,查询工具导入如一中的(4),
barPosition设置:
javascript
barPosition: this.currentbreakpoint == 'lg'?BarPosition.Start:BarPosition.End
vertical设置:
javascript
.vertical(this.currentbreakpoint == 'lg'?true:false)
(4)主页设计实现
说明
我们需要明确的是每个Tabs下展示的内容是一个组件 而不是页面,它不需要存在涉及路由跳转的功能。如果是单模块项目不涉及分工,那么这里我们先创建一个view的目录,在目录下分别创建四个Tab下的组件,分别命名为:Home、Product、Cart、My。

这里我们采用多模块设计机制,便于项目分工互不影响。
1、分别创建4个HAR静态资源共享包作为四个功能模块。

2、修改四个模块的oh-package.json5文件名字,以 Productlibrary模块为例。

3、在公共的oh-package.json5下加入这四个依赖包,RUN一下,
4、在oh_modules下会出现这四个静态资源共享包。

使用方法可以看项目(1)。
实现静态布局
javascript
import { BreakPointConstants } from "../Constants/BreakPointConstants"
import {ActivityDataModel,SwiperDataModel,ProductDataModel} from '../viewmodel/HomeModel'
@Component
export struct HomePage {
@StorageProp('currentBreakpoint') currentBreakpoint:string = 'sm'
/**
*轮播图约束
*/
@State swiperData:Array<SwiperDataModel>=[
{
_id:'1',
goods_id:1,
image_src:'https://img-baofun.zhhainiao.com/fs/108ddabe755acf8aa19aec30a400c849.jpg',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
{
_id:'2',
goods_id:2,
image_src:'https://i1.huishahe.com/uploads/tu/201910/9999/b1ec3a1982.jpg',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
{
_id:'3',
goods_id:3,
image_src:'https://i1.huishahe.com/uploads/tu/201910/9999/b1ec3a1982.jpg',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
]
/**
*活动导航栏约束
*/
@State activityTitle:Array<ActivityDataModel>=[
{
_id:'1',
image_src:'https://img-baofun.zhhainiao.com/fs/de49b589ca485590777787e556cf8946.jpg',
name:'测试活动',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
{
_id:'2',
image_src:'https://i1.huishahe.com/uploads/tu/201910/9999/b1ec3a1982.jpg',
name:'测试活动',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
{
_id:'3',
image_src:'https://i1.huishahe.com/uploads/tu/201910/9999/b1ec3a1982.jpg',
name:'测试活动',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
]
/**
*商品列表
*/
@State products:Array<ProductDataModel>=[
{_id : '',
add_time : 0,
cat_id : '',
cat_one_id : '',
cat_three_id : '',
cat_two_id : '',
goods_big_logo : '',
goods_id : 0,
goods_name : '',
goods_number : '',
goods_price : '',
goods_small_logo : '',
goods_weight : '',
hot_mumber : '',
is_promote : '',
upd_time : '',
}
]
//轮播图
@Builder SwiperModul(){
Swiper(){
ForEach(this.swiperData,(item:SwiperDataModel,index:number)=>{
Image(item.image_src)
.width('100%')
.height(160)
},(item:SwiperDataModel)=>item._id)
}
.margin({top:20})
.indicator(true)
.itemSpace(this.currentBreakpoint === BreakPointConstants.BREAKPOINT_SM?0:10)
.width('100%')
.autoPlay(true)
.borderRadius(16)
.interval(2000)
.displayCount(this.currentBreakpoint === BreakPointConstants.BREAKPOINT_LG?3:this.currentBreakpoint === BreakPointConstants.BREAKPOINT_MD?2:1)
}
//楼层导航
@Builder myNav(){
Grid(){
ForEach(this.activityTitle,(item:ActivityDataModel,index:number)=>{
GridItem(){
Column(){
Image(item.image_src)
.width(60)
.height(60)
Text(item.name)
}
}
.border({
width:1
,color:Color.Black
})
},(item:ActivityDataModel)=>item._id)
}
.rowsGap(10)
.columnsGap(10)
.rowsTemplate('1fr 1fr')
.columnsTemplate('1fr 1fr 1fr 1fr')
.width('100%')
.height(180)
.backgroundColor(Color.White)
.borderRadius(10)
.margin({top:20,bottom:10})
}
//商品列表
@Builder ProductList(){
if (this.products.length>0){
List({space:10}){
ForEach(this.products,(item:ProductDataModel,index:number)=>{
ListItem(){
Column(){
Image(item.goods_big_logo!=''?item.goods_big_logo:'https://tse3-mm.cn.bing.net/th/id/OIP-C.AQi41PrQnKFvJWm9OqSz-QAAAA?rs=1&pid=ImgDetMain')
.width('100%')
.height(150)
Column(){
Text(item.goods_name)
.fontSize(14)
.height(70)
Row(){
Text(`¥${item.goods_price}`)
.fontSize(18)
.fontColor(Color.Blue)
.width(60)
Image($rawfile('Home/buy.svg'))
.width(30)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding({left:5,right:5})
}
.width('96%')
.height(260)
.backgroundColor(Color.White)
}
},(item:ProductDataModel)=>item._id)
}
.lanes(2)
}else {
Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.Center,alignItems:ItemAlign.Center}){
Image('https://tse3-mm.cn.bing.net/th/id/OIP-C.AQi41PrQnKFvJWm9OqSz-QAAAA?rs=1&pid=ImgDetMain')
.width(40)
.height(40)
Text('页面暂无数据')
.fontColor(Color.Gray)
}
.width('100%')
.height('30%')
}
}
build() {
Column() {
Stack({alignContent:Alignment.Top}) {
//层叠模块第一部分添加背景
Column()
.backgroundColor('#F35E1A')
.width('100%')
.height(220)
.border({
radius: { bottomLeft: 40, bottomRight: 40 }
})
//定义一个盒子,进行内容布局
Column() {
//搜索模块
SearchModul()
//下面三个放在一个scroll组件中
Scroll(){
Column() {
//轮播图
this.SwiperModul()
//楼层导航
this.myNav()
//商品列表
this.ProductList()
}
}
}
.padding({left:12,top:10,right:12})
}
}
.height('100%')
.width('100%')
.backgroundColor('f3f3f3')
}
}
//搜索模块
@Builder function
SearchModul(){
Column() {
Flex({justifyContent:FlexAlign.SpaceBetween}) {
Image($rawfile('Home/shopping.svg'))
.height(30)
.fillColor(Color.White)
Image($rawfile('Home/scan.svg'))
.height(30)
.fillColor(Color.White)
}
.height(30)
.width('100%')
.margin({bottom:10})
//搜索框设计
Row(){
Image($rawfile('Home/search.svg'))
.width(22)
.height(22)
.margin({left:12})
}
.height(40)
.width('100%')
.backgroundColor(Color.White)
.borderRadius(20)
}
}
组件内部定义数据约束,完成本地数据渲染
HomeModel约束模型编写
javascript
/**
*约束轮播图数据格式
* "_id":"60dff3ee0f0f00003f0079c2",
* "goods_id":129,
* "image_src":"https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801123322.jpg",
* "navigator_url":"/pages/goods_detail/index?goods_id=129",
* "open_type":"navigate"
*/
export class SwiperResponseDataModel {
message: SwiperDataModel[];
meta: {
msg: string;
status: number;
};
constructor(data: Partial<SwiperResponseDataModel>) {
this.message = data.message || [];
this.meta = data.meta || { msg: '', status: 0 };
}
}
export class SwiperDataModel{
_id: string;
goods_id: number;
image_src: string;
navigator_url: string;
open_type: string;
constructor(data: Partial<SwiperDataModel>) {
this._id = data._id || '';
this.goods_id = data.goods_id || 0;
this.image_src = data.image_src || '';
this.navigator_url = data.navigator_url || '';
this.open_type = data.open_type || '';
}
}
/**
*约束活动列表数据格式
*/
export class ActivityResponseDataModel {
message: ActivityDataModel[];
meta: {
msg: string;
status: number;
};
constructor(data: Partial<ActivityResponseDataModel>) {
this.message = data.message || [];
this.meta = data.meta || { msg: '', status: 0 };
}
}
export class ActivityDataModel{
_id:string = ''
image_src:string = ''
name:string = ''
navigator_url:string = ''
open_type:string = ''
}
/**
*商品列表数据约束
*/
export class ProductResponseDataModel {
message: ProductDataModel[];
total: number;
meta: {
msg: string;
status: number;
};
constructor(data: Partial<ProductResponseDataModel>) {
this.message = data.message || [];
this.total = data.total || 0;
this.meta = data.meta || { msg: '', status: 0 };
}
}
export class ProductDataModel{
_id:string = ''
add_time:number = 0
cat_id:string = ''
cat_one_id:string = ''
cat_three_id:string = ''
cat_two_id:string = ''
goods_big_logo:string = ''
goods_id:string = ''
goods_name:string = ''
goods_number:string = ''
goods_price:string = ''
goods_small_logo:string = ''
goods_weight:string = ''
hot_mumber:string = ''
is_promote:string = ''
upd_time:string = ''
}
根据接口,引入网络请求,获取数据,替换页面模拟数据
1、工具请求封装
1.1 基础URL地址提取到constans/BasicConstants下
javascript
export class BasicConstants {
public static readonly BASE_URL = 'http://192.168.10.50:5001/';
}
1.2 将工具包写在 utils/HttpUtils 下
javascript
import { http } from "@kit.NetworkKit";
import { BasicConstants} from '../../constants/BasicConstants'
//封装请求代码,考虑到以后每个模块都要发送请求,默认放在common模块中。
//1、导入
//K代表请求的数据类型,T代表返回的数据类型
export function MyRequest<T,K>(
url:string,
method:http.RequestMethod,
requestData?:K
){
//2、创建http请求
const httpRequest = http.createHttp()
return new Promise((reslove:(value:T)=>void,reject:(value?:string)=>void)=>{
//request发送请求就是异步代码
httpRequest.request(
//不再简单是url,而是做一下拼接基础地址
BasicConstants.BASE_URL+url,
{
method:method,//请求方式
header:{//设置请求头
//前端浏览器告诉后端,前端传递的数据格式
'Content-Type': 'application/json'
},
extraData:JSON.stringify(requestData)||'', //设置请求要传给后端的数据
connectTimeout:9000, //前端发送数据给后端如果9秒没有结果,前端主动终止行为
readTimeout:9000, //读取数据超过9秒,告诉前端请求失败
},(error:Error,data:http.HttpResponse)=>{
//error没有内容代表成功
if (!error) {
console.log(`请求成功,返回数据:${JSON.stringify(data,null,2)}`)
//这里如何
reslove(JSON.parse(data.result as string))
// 取消订阅HTTP响应头事件。
httpRequest.off('headersReceive');
// 当该请求使用完毕时,开发者务必调用destroy方法主动销毁该JavaScript Object。
httpRequest.destroy();
}else {
console.log(`请求失败,具体原因:${JSON.stringify(error)}`)
reject(JSON.stringify(error))
// 取消订阅HTTP响应头事件。
httpRequest.off('headersReceive');
// 当该请求使用完毕时,开发者务必调用destroy方法主动销毁该JavaScript Object。
httpRequest.destroy();
}
})
})
}
//针对get post put delete 请求二次封装
//定义一个函数,函数的返回结果promise,而promise的返回结果
export function MyRequestGet<T,K>(url:string,requestData?:K):Promise<T>{
return MyRequest<T,K>(url,http.RequestMethod.GET,requestData)
}
export function MyRequestPost<T,K>(url:string,requestData?:K):Promise<T>{
return MyRequest<T,K>(url,http.RequestMethod.POST,requestData)
}
export function MyRequestPut<T,K>(url:string,requestData?:K):Promise<T>{
return MyRequest<T,K>(url,http.RequestMethod.PUT,requestData)
}
export function MyRequestDelete<T,K>(url:string,requestData?:K):Promise<T>{
return MyRequest<T,K>(url,http.RequestMethod.DELETE,requestData)
}
2、页面替换渲染
注意
当我们直接将请求工具在页面或者组件中使用时,此时其实是一个强耦合的,带来的坏处就是以后请求或者请求路径发生变化时就需要改对应的页面或者组件,此时我们想要将页面的请求和封装的函数是解耦的状态,这时候我们就在ets下新建一个apis目录,在这个目录下新建工具用来封装函数和页面需要的请求。

javascript
/**
*封装页面需要的各种请求
*/
import {MyRequestGet} from '../utils/HttpUtils'
//获取轮播图
export function findSwiperDataApi<T,K>(){
return MyRequestGet<T,K>('/home/swiperdata')
}
//获取分类信息
export function findCategoryDataApi<T,K>(){
return MyRequestGet<T,K>('/home/catitems')
}
//获取商品信息
export function findProductDataApi<T,K>(){
return MyRequestGet<T,K>('/goods/search')
}
export function findProductDataById<T,K>(params:K){
return MyRequestGet<T,K>('/goods/searchById',params)
}
页面中使用直接引入Api,
javascript
import {findSwiperDataApi,findCategoryDataApi,findProductDataApi,findProductDataById} from '../apis/ProductApi'
优点
1、页面完全没有请求代码,不管请求如何修改(请求路径请求参数)都不会影响页面的代码。
**2、**页面渲染时需要注意<T,K>,即T的接收类型是不是一个数组,避免出现错误。避免在生命周期里直接加async和await,应将其封装成一个函数,再在生命周期中引用。
javascript
//获取轮播图请求
fentchSwiperData = async ()=>{
const res = await findSwiperDataApi<ResponseDataModel,null>()
// console.log('轮播图数据',JSON.stringify(res))
this.swiperData = res.message
}
/**
*生命周期
*/
aboutToAppear(): void {
this.fentchSwiperData()
}
代码
javascript
import { BreakPointConstants } from "../Constants/BreakPointConstants"
import {SwiperResponseDataModel,ActivityResponseDataModel,ProductResponseDataModel,ActivityDataModel,SwiperDataModel,ProductDataModel} from '../viewmodel/HomeModel'
import {findSwiperDataApi,findCategoryDataApi,findProductDataApi,findProductDataById} from '../apis/ProductApi'
@Component
export struct HomePage {
@StorageProp('currentBreakpoint') currentBreakpoint:string = 'sm'
/**
*轮播图约束
*/
@State ResponseData: SwiperResponseDataModel =
{
message: [
{
_id: "60dff3ee0f0f00003f0079c2",
goods_id: 129,
image_src: "https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801123322.jpg",
navigator_url: "/pages/goods_detail/index?goods_id=129",
open_type: "navigate"
},
{
_id: "60dff4570f0f00003f0079c3",
goods_id: 395,
image_src: "https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801123518.jpg",
navigator_url: "/pages/goods_detail/index?goods_id=395",
open_type: "navigate"
},
{
_id: "60dff4620f0f00003f0079c4",
goods_id: 38,
image_src: "https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801123501.jpg",
navigator_url: "/pages/goods_detail/index?goods_id=38",
open_type: "navigate"
},
{
_id: "610624f40b2a000043001b72",
goods_id: 24,
image_src: "https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801123516.jpg",
navigator_url: "/pages/goods_detail/index?goods_id=24",
open_type: "navigate"
}
],
meta: {
msg: "获取成功",
status: 200
}
};
@State swiperData:Array<SwiperDataModel>=[
{
_id: "60dff3ee0f0f00003f0079c2",
goods_id: 129,
image_src: "https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801123322.jpg",
navigator_url: "/pages/goods_detail/index?goods_id=129",
open_type: "navigate"
},
{
_id: "60dff3ee0f0f00003f0079c2",
goods_id: 129,
image_src: "https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801123516.jpg",
navigator_url: "/pages/goods_detail/index?goods_id=129",
open_type: "navigate"
},
{
_id: "60dff3ee0f0f00003f0079c2",
goods_id: 129,
image_src: "https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801123501",
navigator_url: "/pages/goods_detail/index?goods_id=129",
open_type: "navigate"
},
]
/**
*活动导航栏约束
*/
@State cativityres:ActivityResponseDataModel={
message: [
{
_id: "60e0146a1b3200002c006103",
image_src: "https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/xuchaobo/20210801152611.png",
name: "旅行日记",
navigator_url: "/pages/category/index",
open_type: "switchTab"
}]
,
meta: {
msg: "获取成功",
status: 200
}
}
@State activityTitle:Array<ActivityDataModel>=[
{
_id:'1',
image_src:'https://img-baofun.zhhainiao.com/fs/de49b589ca485590777787e556cf8946.jpg',
name:'测试活动',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
{
_id:'2',
image_src:'https://i1.huishahe.com/uploads/tu/201910/9999/b1ec3a1982.jpg',
name:'测试活动',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
{
_id:'3',
image_src:'https://i1.huishahe.com/uploads/tu/201910/9999/b1ec3a1982.jpg',
name:'测试活动',
navigator_url:'/pages/product/product',
open_type:'navigate'
},
]
/**
*商品列表
*/
@State productres:ProductResponseDataModel={
message: [
{
_id: "60e088dc65150000ab002c3d",
add_time: 1516663280,
cat_id: "9",
cat_one_id: "1",
cat_three_id: "9",
cat_two_id: "3",
goods_big_logo: "",
goods_id: "57443",
goods_name: "创维(Skyworth) 65M6E 65英寸 4K超高清智能酷开网络液晶电视",
goods_number: "100",
goods_price: "4999",
goods_small_logo: "",
goods_weight: "100",
hot_mumber: "0",
is_promote: "false",
upd_time: "1516663280"
}]
,
total:0,
meta: {
msg: "获取成功",
status: 200
}
}
@State products:Array<ProductDataModel>=[
{_id : '',
add_time : 0,
cat_id : '',
cat_one_id : '',
cat_three_id : '',
cat_two_id : '',
goods_big_logo : '',
goods_id : '',
goods_name : '',
goods_number : '',
goods_price : '',
goods_small_logo : '',
goods_weight : '',
hot_mumber : '',
is_promote : '',
upd_time : '',
}
]
//获取轮播图请求
fentchSwiperData = async ()=>{
const res = await findSwiperDataApi<SwiperResponseDataModel,null>()
// console.log('轮播图数据',JSON.stringify(res))
this.swiperData = res.message
}
//获取导航栏
fentchActivityData = async ()=>{
const res = await findCategoryDataApi<ActivityResponseDataModel,null>()
this.activityTitle = res.message
}
//获取商品列表
fentchProductData = async ()=>{
const res = await findProductDataApi<ProductResponseDataModel,null>()
this.products = res.message
}
/**
*生命周期
*/
aboutToAppear(): void {
this.fentchSwiperData()
this.fentchActivityData()
this.fentchProductData()
}
//轮播图
@Builder SwiperModul(){
Swiper(){
ForEach(this.swiperData,(item:SwiperDataModel)=>{
Image(item.image_src)
.width('100%')
.height(160)
.objectFit(ImageFit.Contain)
},(item:SwiperDataModel)=>item._id)
}
.margin({top:20})
.indicator(true)
.itemSpace(this.currentBreakpoint === BreakPointConstants.BREAKPOINT_SM?0:10)
.width('100%')
.autoPlay(true)
.borderRadius(16)
.interval(2000)
.displayCount(this.currentBreakpoint === BreakPointConstants.BREAKPOINT_LG?3:this.currentBreakpoint === BreakPointConstants.BREAKPOINT_MD?2:1)
}
//楼层导航
@Builder myNav(){
Grid(){
ForEach(this.activityTitle,(item:ActivityDataModel,index:number)=>{
GridItem(){
Column(){
Image(item.image_src)
.width(60)
.height(60)
Text(item.name)
}
}
},(item:ActivityDataModel)=>item._id)
}
.rowsGap(10)
.columnsGap(10)
.rowsTemplate('1fr 1fr')
.columnsTemplate('1fr 1fr 1fr 1fr')
.width('100%')
.height(180)
.backgroundColor(Color.White)
.borderRadius(10)
.margin({top:20,bottom:10})
}
//商品列表
@Builder ProductList(){
if (this.products.length>0){
List({space:10}){
ForEach(this.products,(item:ProductDataModel,index:number)=>{
ListItem(){
Column(){
Image(item.goods_big_logo!=''?item.goods_big_logo:'https://tse3-mm.cn.bing.net/th/id/OIP-C.AQi41PrQnKFvJWm9OqSz-QAAAA?rs=1&pid=ImgDetMain')
.width('100%')
.height(150)
Column(){
Text(item.goods_name)
.fontSize(14)
.height(70)
Row(){
Text(`¥${item.goods_price}`)
.fontSize(18)
.fontColor(Color.Blue)
.width(60)
Image($rawfile('Home/buy.svg'))
.width(30)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding({left:5,right:5})
}
.width('96%')
.height(260)
.backgroundColor(Color.White)
}
},(item:ProductDataModel)=>item._id)
}
.lanes(2)
}else {
Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.Center,alignItems:ItemAlign.Center}){
Image('https://tse3-mm.cn.bing.net/th/id/OIP-C.AQi41PrQnKFvJWm9OqSz-QAAAA?rs=1&pid=ImgDetMain')
.width(40)
.height(40)
Text('页面暂无数据')
.fontColor(Color.Gray)
}
.width('100%')
.height('30%')
}
}
build() {
Column() {
Stack({alignContent:Alignment.Top}) {
//层叠模块第一部分添加背景
Column()
.backgroundColor('#F35E1A')
.width('100%')
.height(220)
.border({
radius: { bottomLeft: 40, bottomRight: 40 }
})
//定义一个盒子,进行内容布局
Column() {
//搜索模块
SearchModul()
//下面三个放在一个scroll组件中
Scroll(){
Column() {
//轮播图
this.SwiperModul()
//楼层导航
this.myNav()
//商品列表
this.ProductList()
}
}
}
.padding({left:12,top:10,right:12})
}
}
.height('100%')
.width('100%')
.backgroundColor('f3f3f3')
}
}
//搜索模块
@Builder function
SearchModul(){
Column() {
Flex({justifyContent:FlexAlign.SpaceBetween}) {
Image($rawfile('Home/shopping.svg'))
.height(30)
.fillColor(Color.White)
Image($rawfile('Home/scan.svg'))
.height(30)
.fillColor(Color.White)
}
.height(30)
.width('100%')
.margin({bottom:10})
//搜索框设计
Row(){
Image($rawfile('Home/search.svg'))
.width(22)
.height(22)
.margin({left:12})
}
.height(40)
.width('100%')
.backgroundColor(Color.White)
.borderRadius(20)
}
}