Harmony 个人中心(页面交互、跳转、导航、容器组件)
个人中心
- 前言
- 正文
- 一、创建工程
- 二、登录
- ① 更换启动页面
- ② 拓展修饰符
- ③ 页面跳转
- ④ 等待进度条
- 三、导航栏
- 四、首页
- ① 轮播图
- ② 网格列表
- 五、我的
- ① 带参数跳转
- 六、源码
前言
今天是1024,祝各位程序员们,钱多事少离家近,不秃也强bug黄。在上一篇文章中,我们了解了DevEco Studio的主推开发语言ArkTS
,并写了一个简单的例子,本文我们将学习另外一个例子来加深我们对于鸿蒙应用开发的理解。
正文
本文的例子同样来源于HarmonyOS学堂,根据源码内容我们来反推开发过程,看开发过程中能学到那些知识点。
一、创建工程
首先我们在DevEco Studio中创建一个名为MyCenter的功能,如下图所示:
点击Finish
创建项目,项目创建好之后,通过预览就能看到Hello World
,下面来说明一下这次开发的个人中心App包含的内容,首先是一个登录页面,登录进去之后可以通过底部导航切换页面内容,分别是首页和个人的内容,下面我们首先来写登录页面。
二、登录
在创建工程时会自带一个页面,就是我们所看到的Index.ets,那么现在我们需要创建一个登录页面,鼠标右键pages
→New
→Page
。
会出现一个弹窗,我们输入页面的名称为Login。
点击Finish完成页面的创建,创建完成之后你可以在resource/main/base/profile/main_pages.json
中看到我们增加的登录页面的配置。
① 更换启动页面
当前有两个页面,Index和Login,因为我们一打开App就进入Login页面,所以我们将两个内容的位置换一下,代码如下所示:
{"src": ["pages/Login","pages/Index"]
}
这样当App运行的时候第一个页面就是Login了,是不是很简单,下面我们要做的就是写这个登录页面的UI和功能,先来看看这个页面的UI效果
根据这个UI,我们可以得出,页面整体效果为纵向布局,中间两处文字为蓝色文字和底部三个登录方式为横向,为了方便使用,首先我们增加文字的颜色和图标,首先修改resources/base/element/color.json
中的代码如下所示:
{"color": [{"name": "start_window_background","value": "#FFFFFF"},{"name": "white","value": "#FFFFFF"},{"name": "background","value": "#F1F3F5"},{"name": "title_text_color","value": "#182431"},{"name": "login_more_text_color","value": "#99182431"},{"name": "placeholder_color","value": "#99182431"},{"name": "line_color","value": "#33182431"},{"name": "login_button_color","value": "#007DFF"},{"name": "login_blue_text_color","value": "#007DFF"},{"name": "other_login_text_color","value": "#838D97"},{"name": "loading_color","value": "#182431"},{"name": "mainPage_selected","value": "#1698CE"},{"name": "mainPage_normal","value": "#6B6B6B"},{"name": "mainPage_backgroundColor","value": "#F1F3F5"},{"name": "home_grid_fontColor","value": "#99182431"},{"name": "setting_button_backgroundColor","value": "#E5E8EA"},{"name": "setting_button_fontColor","value": "#FA2A2D"}]
}
颜色弄好了,然后将源码中resources/base/element/meida
下的图标复制到你的项目中即可。
下面我们可以开始写登录页面了,首先我们修改build()
函数中的内容,定义一个纵向布局,然后设置颜色,内容大小和内容填充,代码如下所示:
@Entry
@Component
struct Login {build() {// 页面纵向布局Column() {.backgroundColor($r('app.color.background')).height('100%').width('100%').padding({left: 12,right: 12,bottom: 24})}
}
此时保存一下预览效果是一片空白,下面我们在Column()
中增加一个图标和两段文字,代码如下所示:
//LogoImage($r('app.media.logo')).width(78).height(78).margin({ top: 100, bottom: 16 })Text('登录').fontSize(24).fontWeight(FontWeight.Medium).fontColor($r('app.color.title_text_color'))Text('登录帐号以使用更多服务').fontSize(16).fontColor($r('app.color.login_more_text_color')).margin({ top: 16, bottom: 30 })
然后再预览一下效果如图所示:
下面我们来写账号和密码的输入框,添加如下所示代码:
//账号输入框TextInput({ placeholder: '账号' }).maxLength(11).type(InputType.Number).placeholderColor($r('app.color.placeholder_color')).height(45).fontSize(18).backgroundColor($r('app.color.background')).width('100%').padding({ left: 0 }).margin({ top: 12 }).onChange((value: string) => {//获取输入的内容})//下划线Line().width('100%').height(2).backgroundColor($r('app.color.line_color'))//密码输入框TextInput({ placeholder: '密码' }).maxLength(8).type(InputType.Password).placeholderColor($r('app.color.placeholder_color')).height(45).fontSize(18).backgroundColor($r('app.color.background')).width('100%').padding({ left: 0 }).margin({ top: 12 }).onChange((value: string) => {//获取输入的内容})//下划线Line().width('100%').height(2).backgroundColor($r('app.color.line_color'))
我们首先预览一下:
接下来我们说一下可以说明的点,首先就是TextInput的输入类型,如果为Password,则会自带一个按钮,点击可以查看密码的明文内容,这一点我还是很喜欢的,不用自己写了,同时我们看到还有一个onChange()
,里面会实时同步你输入的内容,因此如果我们想要获取输入框的内容,就需要定义变量来接收,可以在Login中定义变量,代码如下所示:
@State account: string = '';@State password: string = '';
这是账号和密码的输入值,依次为内容赋值,将账号输入框下的内容赋值给account
, 密码输入框下的内容赋值给password
,代码如下所示:
//账号输入框TextInput({ placeholder: '账号' })....onChange((value: string) => {//获取输入的内容this.account = value;})...//密码输入框TextInput({ placeholder: '密码' })....onChange((value: string) => {//获取输入的内容this.password = value;})...
② 拓展修饰符
下面我们介绍一下拓展修饰符,首先我们看一下刚才所写的代码,如下图所示:
可以看到我标注的部分代码一致,但是确实是输入框和线所需要的设置,那么我们可以将重复的内容样式通过拓展修饰符封装一个样式函数,供同类样式使用,下面我们在Login{}
外面增加一个inputStyle()
函数,代码如下所示:
/*** 输入框的通用样式*/
@Extend(TextInput) function inputStyle() {.placeholderColor($r('app.color.placeholder_color')).height(45).fontSize(18).backgroundColor($r('app.color.background')).width('100%').padding({ left: 0 }).margin({ top: 12 })
}
这里我们用到@Extend
修饰符,它里面是所修饰的类型,这里输入的是TextInput
组件,里面的代码就是刚才的标注的代码,挪过来而已,然后我们再写一个lineStyle()
函数,代码如下所示:
@Extend(Line) function lineStyle() {.width('100%').height(2).backgroundColor($r('app.color.line_color'))
}
注意这两个函数添加的位置,如下图所示:
如果你写在组件内部会报错的,然后我们再更新一下刚才的输入框和线的代码,如下图所示:
相比上面的原始写法就简洁很多了,减少重复的代码,因为输入框下方的两个蓝色文字也是一样的样式,所以再增加一个扩展样式,代码如下所示:
@Extend(Text) function blueTextStyle() {.fontColor($r('app.color.login_blue_text_color')).fontSize(14).fontWeight(FontWeight.Medium)
}
在下划线后面增加代码,如下所示:
Row() {Text('短信验证码登录').blueTextStyle()Text('忘记密码').blueTextStyle()}.justifyContent(FlexAlign.SpaceBetween).width('100%').margin({ top: 8 })
通过一个横向布局,装载两个Text,通过justifyContent(FlexAlign.SpaceBetween)
设置布局中的内容,左右都靠边,看一下预览效果:
下面我们写登录按钮和注册的UI,在上述代码后面继续增加代码,如下所示:
//登录按钮Button('登录', { type: ButtonType.Capsule }).width('90%').height(40).fontSize(16).fontWeight(FontWeight.Medium).backgroundColor($r('app.color.login_button_color')).margin({ top: 80, bottom: 12 }).onClick(() => {// 登录})Text('注册账号').fontColor($r('app.color.login_blue_text_color')).fontSize(16).fontWeight(FontWeight.Medium)
这里的代码其实就没有什么好解释的,一目了然,重点是点击登录按钮之后在onClick()
函数中执行的代码。
③ 页面跳转
下面我们在Login{}
里面增加一个登录的函数,代码如下所示:
/*** 登录*/login(): void {if (this.account === '' || this.password === '') {//显示ToastpromptAction.showToast({ message: '账号或密码为空' })return}router.replaceUrl({ url: 'pages/Index' });}
这里的代码用到了promptAction
和router
,两个都是鸿蒙内部的插件,你输入后如果发现有红色下划波浪线,那么就需要导包,鼠标放在波浪线上,使用快捷键,Alt + Enter会出现一个弹窗,
选择第一项就会将所需要的插件导入到当前的组件中,导入后就不会报错了,导入内容如下图所示:
然后在登录按钮的点击事件中调用登录函数,如下图所示:
重新预览一下,点击登录按钮试试看,如下图所示:
随便输入账号和密码再点击登录,就会跳转到Index页面,你可以试试看呀,不过我们当前登录页面的内容其实还没有写完,因为有一些细节还需要处理,首先要做的就是页面完整,在注册账号的Text后面再添加如下代码:
//空白填充组件,具有自动填充容器空余部分的能力。仅当父组件为Row/Column时生效。Blank()Text('其他登录方式').fontColor($r('app.color.other_login_text_color')).fontSize(12).fontWeight(FontWeight.Medium).margin({ top: 48, bottom: 12 })Row({ space: 44 }) {this.imageButton($r('app.media.login_method1'))this.imageButton($r('app.media.login_method2'))this.imageButton($r('app.media.login_method3'))}
这里因为3个按钮样式一致,只是图片资源不同,因此我们可以通过@Builder
修饰器构建一个按钮,在Login{}
中添加如下代码:
/*** 其他登录方式按钮* @param src*/@BuilderimageButton(src: Resource) {Button({ type: ButtonType.Circle, stateEffect: true }) {Image(src)}.height(48).width(48).backgroundColor($r('app.color.background'))}
预览效果如下图所示:
④ 等待进度条
通常来说登录不会是一下子就成功的,涉及到登录之后的一些数据处理,网络延时,通常我们会在点击登录之后显示一个等待进度,告诉用户正在登录中,稍安勿躁,因为如果你登陆的时候没有什么变化,然后登录有很久,用户会以为你的App卡死了,直接就给你卸载。
那我们同样可以定义几个变量,在Login{}
中添加如下代码:
//是否显示加载条@State isShowProgress: boolean = false;//超时标识private timeOutId: number = -1;
我们通过这个isShowProgress
变量来设置是否显示加载进度,这个timeOutId
用来做超时处理,比如登录多久可以进入主页面,简单模拟一下,首先要做的就是修改这个isShowProgress
变量为true
,修改login()
函数,代码如下所示:
login(): void {if (this.account === '' || this.password === '') {//显示ToastpromptAction.showToast({ message: '账号或密码为空' })return}//内容不为空则显示加载进度条this.isShowProgress = true;if (this.timeOutId === -1) {//设置超时处理,两秒后执行页面跳转到主页this.timeOutId = setTimeout(() => {this.isShowProgress = false;this.timeOutId = -1;router.replaceUrl({ url: 'pages/Index' });}, 2000);}}
这里我们将isShowProgress
赋值为true
,那么就会触发UI刷新,因此我们需要在build()函数中,合适的位置判断这个isShowProgress
值,注意这个加载进度不是一个弹窗,它会占用页面的空间,所以还记得我们之前所写的那个Blank()
组件吗?我们在它的上面添加如下代码:
//是否显示等待进度条if (this.isShowProgress) {LoadingProgress().color($r('app.color.loading_color')).width(30).height(30).margin({ top: 20 })}
添加位置如下图所示:
这里通过判断isShowProgress
来显示等待进度条,那么就还有取消显示的地方,我们再回到login()
函数,观察下面这段代码:
if (this.timeOutId === -1) {//设置超时处理,两秒后执行页面跳转到主页this.timeOutId = setTimeout(() => {this.isShowProgress = false;this.timeOutId = -1;router.replaceUrl({ url: 'pages/Index' });}, 2000);}
首先判断这个参数是否为-1,是的话则增加一个2秒超时处理,在超时结束时我们将isShowProgress
设置为false,那么刷新UI的时候就会去掉之前所显示的加载进度条,同时将timeOutId
再设置为-1,最后跳转页面。我们还可以增加一个生命周期的处理,在Login{}
中增加如下代码:
/*** 组件的生命周期,组件销毁时执行*/aboutToDisappear() {clearTimeout(this.timeOutId);this.timeOutId = -1;}
因为当你跳转页面时,当前的组件就会销毁,就会触发这个生命周期函数,所以我们在这里进行销毁超时处理,下面我们来看一下运行的效果。
三、导航栏
登录后我们进入Index页面,也就是主页面,我们先看看主页面的内容
通过这两张图,我们可以看到,主页面有两部分,选项卡和选项卡内容,通过底部选项卡点击进行切换,那么在写这个页面的时候应该怎么入手呢?首先我们应该先写选项卡,也就是底部导航这一部分内容。
下面我们修改一下Index.ets
中的代码,如下所示:
@Entry
@Component
struct Index {@State currentIndex: number = 0private tabsController: TabsController = new TabsController()@Builder TabBuilder(title: string, index: number, selectedImg: Resource, normalImg: Resource) {Column() {Image(this.currentIndex === index ? selectedImg : normalImg).width(24).height(24)Text(title).margin({ top: 4 }).fontSize(10).fontColor(this.currentIndex === index ? $r('app.color.mainPage_selected') : $r('app.color.mainPage_normal'))}.justifyContent(FlexAlign.Center).height(26).width('100%').onClick(() => {this.currentIndex = indexthis.tabsController.changeIndex(this.currentIndex)})}build() {Tabs({barPosition: BarPosition.End,controller: this.tabsController}) {TabContent() {// 首页内容}.padding({ left: 12, right: 12 }).backgroundColor($r('app.color.mainPage_backgroundColor')).tabBar(this.TabBuilder('首页', 0, $r('app.media.home_selected'), $r('app.media.home_normal')))TabContent() {// 我的内容}.padding({ left: 12, right: 12 }).backgroundColor($r('app.color.mainPage_backgroundColor')).tabBar(this.TabBuilder('我的', 1, $r('app.media.mine_selected'), $r('app.media.mine_normal')))}.width('100%').backgroundColor(Color.White).barHeight(56).barMode(BarMode.Fixed).onChange((index: number) => {this.currentIndex = index})}
}
下面我们来分析一下这段代码,首先我们定义了currentIndex
变量,用于记录当前选项卡的下标,然后定义了一个tabsController
,用于进行选项卡的控制,接下来使用@Builder
装饰器来构建Tab的内容,使用纵向布局将图标和文字居中摆放,根据currentIndex
和当前Index
的判断来进行Tab的选中、未选中状态。currentIndex
默认为0,则是默认选中第一个Tab,也就是首页Tab,在Tab的点击事件中,我们更新currentIndex
的值,然后再使用this.tabsController.changeIndex(this.currentIndex)
进行切换Tab选项。
然后来看build()
函数中的代码,这里我们使用了Tabs()
组件,通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。我们看里面传的参数,这里重点是第一个参数,这个的barPosition不是下标的意思,而是设置Tabs的页签位置。默认值:BarPosition.Start
,这里的默认值实际上还要结合Tabs
组件的vertical
属性来结合使用。
vertical
设置为false是为横向Tabs,设置为true时为纵向Tabs。默认值:false,我们没有在代码中设置这个属性,所以默认就是纵向的,那么我们再结合这个BarPosition
的值来看:
Start
,vertical属性方法设置为true时,页签位于容器左侧;vertical属性方法设置为false时,页签位于容器顶部。End
,vertical属性方法设置为true时,页签位于容器右侧;vertical属性方法设置为false时,页签位于容器底部。
那么现在就是Tabs就是在屏幕底部,Tabs可以在屏幕上下左右进行摆放。
在Tabs()
中放置了两个TabContent()
,TabContent,仅在Tabs中使用,对应一个切换页签的内容视图,这个内容视图我们后面来写,这个组件有一个tabBar()
属性,用于装载Tab内容,这里就用到我们之前所构建的TabBuilder()
函数。
最后我们再了解一下Tabs()
组件的其它两个属性:
BarMode
有两个属性,1. Scrollable:每一个TabBar均使用实际布局宽度,超过总长度(横向Tabs的barWidth,纵向Tabs的barHeight)后可滑动。2. Fixed:所有TabBar平均分配barWidth宽度(纵向时平均分配barHeight高度)。onChange
,Tab页签切换后触发的事件。index
:当前显示的index索引,索引从0开始计算。触发该事件的条件:1、TabContent支持滑动时,组件触发滑动时触发。2、通过控制器API接口调用。3、通过状态变量构造的属性值进行修改。4、通过页签处点击触发。
通过这些说明,相信你已经知道Tabs()
的用法了,下面我们保存预览一下Index,默认是Home,点击Mine,如下图所示:
四、首页
在写这个首页的内容之前,我们先看一下整个页面的布局,如图
首页内容呈纵向摆放,同时需要考虑屏幕大小,因此我们可以加一个滑动控件,再看里面的内容,首先是一个标题,标题下面是轮播图,然后是两个网格列表。这样页面内容就介绍完了,那么我么应该怎么来写这个页面的内容呢?
① 轮播图
首先我们完成标题和轮播图,在ets
下创建一个viewmodel
包,该包下创建一个IndexViewModel.ets
文件,代码如下所示:
export class IndexViewModel {/*** 获取轮播图数据*/getSwiperImages(): Array<Resource> {let swiperImages: Resource[] = [$r('app.media.fig1'),$r('app.media.fig2'),$r('app.media.fig3'),$r('app.media.fig4')]return swiperImages}
}export default new IndexViewModel()
通过这个getSwiperImages()
来获取轮播图数据,下面我们可以构建主页面的组件了,在在ets
下创建一个view
包,包下新建一个Home.ets文件,里面代码如下所示:
import mainViewModel from '../viewmodel/IndexViewModel';/*** 首页*/
@Component
export default struct Home {private swiperController: SwiperController = new SwiperController();build() {Scroll() {Column({ space: 12 }) {//首页Column() {Text('首页').fontWeight(FontWeight.Medium).fontSize(24).margin({ top: 12 }).padding({ left: 12 })}.width('100%').alignItems(HorizontalAlign.Start)//轮播图Swiper(this.swiperController) {ForEach(mainViewModel.getSwiperImages(), (img: Resource) => {Image(img).borderRadius(16)}, (img: Resource) => JSON.stringify(img.id))}.margin({ top: 24 }).autoPlay(true)}}.height('100%')}
}
这里的代码就是一个按照我们上面所说的思路来设计的,滚动条里面有标题和轮播图,并设置轮播图自动轮播,在滚动组件中内容未填满页面高度的情况下,内容就会居中显示,我们将Home放在Index中,如下图所示:
然后我们预览Index,看看预览效果图:
② 网格列表
下面我们再来写网格列表,首先要做的就是制造一些数据,先创建一个数据Bean,在ets
下创建一个bean
包,该包下创建一个ItemData.ets
文件,代码如下所示:
export default class ItemData {title: Resource|string;img: Resource;others?: Resource|string;constructor(title: Resource|string, img: Resource, others?: Resource|string) {this.title = title;this.img = img;this.others = others;}
}
这个Bean中只有三个数据,标题、图片,其他。下面我们在IndexViewModel
中制造一些假数据,写两个函数,代码如下所示:
/*** 获取第一个网格数据*/getFirstGridData(): Array<ItemData> {let firstGridData: ItemData[] = [new ItemData('我的最爱', $r('app.media.love')),new ItemData('历史记录', $r('app.media.record')),new ItemData('消息', $r('app.media.message')),new ItemData('购物车', $r('app.media.shopping')),new ItemData('我的目标', $r('app.media.target')),new ItemData('圈子', $r('app.media.circle')),new ItemData('收藏', $r('app.media.favorite')),new ItemData('回收站', $r('app.media.recycle'))]return firstGridData}/*** 获取第二个网格数据*/getSecondGridData(): Array<ItemData> {let secondGridData: ItemData[] = [new ItemData('排行榜', $r('app.media.top'), '当前热品尽在掌握'),new ItemData('新品首发', $r('app.media.new'), '最新潮牌,马上发布'),new ItemData('大牌闪购', $r('app.media.brand'), '更多大牌敬请期待'),new ItemData('发现好物', $r('app.media.found'), '更多内容等您探索')]return secondGridData}
这里我们需要导入ItemData,还记得是怎么导入的吗?因为创建others?: Resource|string;
的时候,使用了一个?
,表示可以为空,下面我们在Home中增加这两个网格的UI展示,代码如下所示:
import mainViewModel from '../viewmodel/IndexViewModel';
import ItemData from '../bean/ItemData';/*** 首页*/
@Component
export default struct Home {private swiperController: SwiperController = new SwiperController();build() {Scroll() {Column({ space: 12 }) {//首页...//轮播图...//第一个网格布局Grid() {ForEach(mainViewModel.getFirstGridData(), (item: ItemData) => {GridItem() {Column() {Image(item.img).width(24).height(24)Text(item.title).fontSize(12).margin({ top: 4 })}}}, (item: ItemData) => JSON.stringify(item))}.columnsTemplate('1fr 1fr 1fr 1fr').rowsTemplate('1fr 1fr').columnsGap(8).rowsGap(12).padding({ top: 12, bottom: 12 }).height(124).backgroundColor(Color.White).borderRadius(24)Text('列表').fontSize(16).fontWeight(FontWeight.Medium).width('100%').margin({ top: 12 })//第二个网格布局Grid() {ForEach(mainViewModel.getSecondGridData(), (secondItem: ItemData) => {GridItem() {Column() {Text(secondItem.title).fontSize(16).fontWeight(FontWeight.Medium)Text(secondItem.others).margin({ top: 4 }).fontSize(12).fontColor($r('app.color.home_grid_fontColor'))}.alignItems(HorizontalAlign.Start)}.padding({ top: 8, left: 8 }).borderRadius(12).align(Alignment.TopStart).backgroundImage(secondItem.img).backgroundImageSize(ImageSize.Cover).width('100%').height('100%')}, (secondItem: ItemData) => JSON.stringify(secondItem))}.width('100%').height(260).columnsTemplate('1fr 1fr').rowsTemplate('1fr 1fr').columnsGap(8).rowsGap(12).margin({ bottom: 55 })}}.height('100%')}
}
这里注意一下我将之前写过的一些代码省略了,所以这里你就不要复制粘贴了,其实网格列表和普通列表在数据渲染的方式上一样,只不过网格列表有一些其他的属性,我们需要了解。
columnsTemplate
:string类型,设置当前网格布局列的数量,不设置时默认1列。例如, ‘1fr 1fr 1fr 1fr’ 是将父组件分4列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占1份,第四列占1份。设置为’0fr’时,该列的列宽为0,不显示GridItem。设置为其他非法值时,GridItem显示为固定1列。rowsTemplate
:string类型,设置当前网格布局行的数量,不设置时默认1行。例如,‘1fr 1fr’是将父组件分两行,将父组件允许的高分为2等份,第一行占1份,第二行占1份,设置为’0fr’,则这一行的行宽为0,这一行GridItem不显示。设置为其他非法值,按固定1行处理。columnsGap
:Length类型,设置列与列的间距。默认值:0,设置为小于0的值时,按默认值显示。rowsGap
:Length类型,设置行与行的间距。默认值:0,设置为小于0的值时,按默认值显示。
其余的属性就没有什么好说的,下面我们再预览一下Index,如下图所示:
此时你点击我的,可以看到什么也没有,下面我们来写我的。
五、我的
首先我们看一下我的页面的图
内容同样是呈纵向摆放的,上面是个人信息,中间这里是一个功能列表,最下面是退出按钮,下面我们首先提供列表的数据,在IndexViewModel
中写一个函数,代码如下所示:
/*** 获取设置列表数据*/getSettingListData(): Array<ItemData> {let settingListData: ItemData[] = [new ItemData('推送通知', $r('app.media.news'), '开关'),new ItemData('数据管理', $r('app.media.data'), null),new ItemData('菜单设置', $r('app.media.menu'), null),new ItemData('关于', $r('app.media.about'), null),new ItemData('清除缓存', $r('app.media.storage'), null),new ItemData('隐私协议', $r('app.media.privacy'), null)]return settingListData}
然后我们在view包下先建一个Mine.ets
,代码如下所示:
import router from '@ohos.router';
import promptAction from '@ohos.promptAction';
import ItemData from '../bean/ItemData';
import mainViewModel from '../viewmodel/IndexViewModel';/*** 我的*/
@Component
export default struct Mine {@Builder settingCell(item: ItemData) {Row() {Row({ space: 12 }) {Image(item.img).width(22).height(22)Text(item.title).fontSize(16)}// 设置功能item最右侧的功能项if (item.others === null) {//可以进入下一级页面Image($r('app.media.right_grey')).width(12).height(24)} else {//开关Toggle({ type: ToggleType.Switch, isOn: false })}}.justifyContent(FlexAlign.SpaceBetween).width('100%').padding({left: 8,right: 22})}build() {Scroll() {Column({ space: 12 }) {Column() {Text('我的').fontWeight(FontWeight.Medium).fontSize(24).margin({ top: 12 }).padding({ left: 12 })}.width('100%').alignItems(HorizontalAlign.Start)// 个人信息Row() {Image($r('app.media.account')).width(48).height(48)Column() {Text('李先生').fontSize(20)Text('lonelyxxx@qq.com').fontSize(12).margin({ top: 4 })}.alignItems(HorizontalAlign.Start).margin({ left: 24 })}.margin({ top: 24 }).alignItems(VerticalAlign.Center).width('100%').height(96).backgroundColor(Color.White).padding({ left: 24 }).borderRadius(16)// 功能列表List() {ForEach(mainViewModel.getSettingListData(), (item: ItemData) => {ListItem() {//构建每一个itemthis.settingCell(item)}.height(48)}, (item: ItemData) => JSON.stringify(item))}.backgroundColor(Color.White).width('100%').height('42%')// 为列表增加分隔线.divider({strokeWidth: 1,color: Color.Grey,startMargin: 42,endMargin: 42}).borderRadius(16).padding({ top: 4, bottom: 4 })Blank()Button('退出登录', { type: ButtonType.Capsule }).width('90%').height(40).fontSize(16).fontColor($r('app.color.setting_button_fontColor')).fontWeight(FontWeight.Medium).backgroundColor($r('app.color.setting_button_backgroundColor')).margin({ bottom: 55 }).onClick(() => {promptAction.showToast({ message: '退出登录' })router.replaceUrl({ url: 'pages/Login' })})}.height('100%')}}
}
这个代码乍一看很多,下面我们来分析一下,从上往下来,首先是标题和个人信息,这部分就是UI效果,没有什么好说的,然后最关键的功能列表,这里通过@Builder
来装饰settingCell()
函数。通过item的other来判断是否需要显示不同的效果,代码如下所示:
if (item.others === null) {//可以进入下一级页面Image($r('app.media.right_grey')).width(12).height(24)} else {//开关Toggle({ type: ToggleType.Switch, isOn: false })}
为null就是一个向右的图标,不为null就是一个开关,默认为false。中间的列表加载就没有什么好说二的,最后的退出登录按钮点击之后就会调用router.replaceUrl({ url: 'pages/Login' })
,返回到登录页面,这里使用的是replaceUrl
,用应用内的某个页面替换当前页面,并销毁被替换的页面。
下面我们通过Index预览一下看看效果:
① 带参数跳转
现在我们登录后的账号并没有其他作用,我们可以把账号替换为李先生,首先我们需要修改登录按钮点击事件的代码,如下所示:
router.replaceUrl({url: 'pages/Index',params: {account: this.account}});
就是在跳转页面的时候添加一个params属性,然后放入键和值,然后我们在Mine组件中增加一行代码:
//接收传递过来的参数@State account: string = router.getParams()?.['account'];
这样就能拿到传递的参数值,然后设置到Text中即可。
下面运行一下看看效果
本文就到这里了,鸿蒙提供的一些学习资料是很全面的,通过阅读加上实操过程中的测试可以很快上手应用开发。
六、源码
如果对你有所帮助的话,不妨 Star 或 Fork,山高水长,后会有期~
源码地址:MyCenter
相关文章:

Harmony 个人中心(页面交互、跳转、导航、容器组件)
个人中心 前言正文一、创建工程二、登录① 更换启动页面② 拓展修饰符③ 页面跳转④ 等待进度条 三、导航栏四、首页① 轮播图② 网格列表 五、我的① 带参数跳转 六、源码 前言 今天是1024,祝各位程序员们,钱多事少离家近,不秃也强bug黄。在…...

AlDente Pro for Mac: 掌控电池充电的终极解决方案
你是否曾经为了保护你的MacBook的电池,而苦恼于无法控制它的充电速度?AlDente Pro for Mac 是一款专为Mac用户设计的电池管理工具,它能帮助你解决这个问题。 AlDente Pro for Mac 是一款电池最大充电限制软件,它能够让你自由地设…...

tomcat的负载均衡、动静分离(nginx联动)
动静分离: 访问静态页面和动态页面分开 实现动态和静态页面负载均衡 实验5台虚拟机 一、动态负载均衡 3台虚拟机模拟: 代理服务器:30 tomcat动态页面:21、22 代理服务器: proxy_pass http://tomcat; proxy_set_h…...
基于单片机的温湿度检测及远程控制系统设计
目 录 引 言. 2 第一章 绪 论. 2 1.1 单片机简介 2 1.2 传感器简介 2 1.3 LCD液晶显示器简介 2 1.4 本设计的主要内容和目标 2 第二章 系统总体设计. 2 2.1 系统功能要求与技术指标 2 2.1.1 功能要求. 2 2.1.2 技术指标. 2 2.2 系统设计思路 2 2.3系统设计原则 2 2.4 系…...

前后端交互系统:在Node.js中运行JavaScript
在Node.js中运行JavaScript,您需要编写适用于服务器端的代码,而不是浏览器端的代码。以下是一些示例代码,用于在Node.js中创建一个简单的HTTP服务器并在浏览器中访问它: // 引入Node.js内置的http模块 const http require(http);…...

Maven学习
Maven介绍 Maven是Apache的一个开源项目,主要服务于基于Java平台的项目构建,依赖管理和项目信息管理。 Maven可以让团队能够更科学的构建项目,我们可以用配置文件的方式,对项目的名称、描述、项目版本号、项目依赖等信息进行描述…...

《动手学深度学习 Pytorch版》 10.2 注意力汇聚:Nadaraya-Watson 核回归
import torch from torch import nn from d2l import torch as d2l1964 年提出的 Nadaraya-Watson 核回归模型是一个简单但完整的例子,可以用于演示具有注意力机制的机器学习。 10.2.1 生成数据集 根据下面的非线性函数生成一个人工数据集,其中噪声项 …...

测试C#调用Windows Media Player组件
新建基于.net framework的Winform项目,可以通过添加引用的方式选择COM组件中的Windows Media Player组件,如下图所示: 也可以在VS2022的工具箱空白处点右键,选择“选择项…”菜单。 在弹出的选择工具箱项窗口中…...
面试经典150题——Day20
文章目录 一、题目二、题解 一、题目 14. Longest Common Prefix Write a function to find the longest common prefix string amongst an array of strings. If there is no common prefix, return an empty string “”. Example 1: Input: strs [“flower”,“flow”…...

[SQL开发笔记]AND OR运算符复杂表达式开发实例
结合 AND & OR实例:通过圆括号使用and或or来组成复杂的表达式 目标数据库及表:使用 DRobot数据库,"T_Drobot" 表 假设我们需要查询"T_Drobot" 表,并从"T_Drobot"表中查询选取creator为 "…...

如何将本地 PDF 文件进行翻译
在日常工作和学习中,我们经常会遇到需要翻译 PDF 文件的情况。比如,我们需要将一份英文的技术文档翻译成中文,或者将一份中文的法律文件翻译成英文。 传统上,我们可以使用专业翻译软件或服务来翻译 PDF 文件。但是,这…...
Node.js的readline模块 命令行交互的模块
Node.js是一个非常流行的JavaScript运行时环境,它提供了许多内置模块来帮助我们开发应用程序。其中之一是readline模块,它提供了一种简单的方法来读取用户输入并进行交互。 本文将详细介绍readline模块的API和使用案例,并附有代码注释。 re…...

前沿重器[36] | ACL23-基于检索的大语言模型-报告阅读
前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享,从中抽取关键精华的部分和大家分享,和大家一起把握前沿技术。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。(算起来,专项启动已经…...
2023秋招笔试算法Python3题解
诸神缄默不语-个人CSDN博文目录 签两方了,感觉秋招已经结束了,所以发布一下之前写的笔试编程题题解。 不全。可能有些题我会继续补。 不保证能过。 后续依然有可能继续刷算法题,但是就另外专门写博文来解析了。 打码是因为原则上其实是不让公…...

uniapp--点击上传图片到oss再保存数据给后端接口
项目采用uniapp与uview2.0组件库 --1.0的也可以参考一下,大差不差 一、项目要求与样式图 点击上传n张图片到oss,然后点击提交给后端 二、思路 1、打开上传按钮,弹出框内出现上传图片和提交按钮 2、点击上传图片区域,打开本地图…...
创建Secret(使用kubectl)
创建Secret(使用kubectl) 假设某个 Pod 需要访问数据库。在您执行 kubectl 命令所在机器的当前目录,创建文件 ./username.txt 文件和 ./password.txt 暂存数据库的用户名和密码,后续我们根据这两个文件配置 kubernetes secrets。…...

Notepad++正则查询替换操作
Notepad编辑器查找功能非常强大,本处记录一些实战中常用到复杂查询替换操作。 注意:如果是重要文件,替换操作前最好备份;当前一个操作后也可以用ctrlz恢复。 查找重复行 用查找(ctrlf)功能,用正则表达式模式匹配。 查…...
Hive特殊函数的使用
Hive特殊函数的使用 with ascastget_json_objectunix_timestampfrom_unixtime with as 在Hive中,WITH AS是一种子查询的用法,用于在查询的开头定义一个临时表达式。它的语法结构如下: WITH [表达式名称] AS (子查询表达式 )在这个结构中,[表…...

Unity Spine 指定导入新Spine动画的默认材质
指定导入新Spine动画的默认材质 找到Spine的Editor导入配置如何修改方法一: 你可以通过脚本 去修改Assets/Editor/SpineSettings.asset文件方法二:通过面板手动设置 找到Spine的Editor导入配置 通常在 Assets/Editor/SpineSettings.asset 配置文件对应着 Edit/Prefe…...

lvs负载均衡集群
目录 一、集群: 1、集群的目的: 2、集群的类型: 3、集群的可靠性指标: 4、设计集群时需要考虑的原则: 二、lvs集群: 1、lvs集群中的术语: 2、lvs访问的大致流程: 三、lvs的…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...