登录固定账号和密码:
- 接口文档
【apifox】面试宝典 个人中心-保存用户数据信息 - 教学练测项目-面试宝典-鸿蒙
登录固定账号和密码:
账号:hmheima
密码:Hmheima%123
UI设计稿
【腾讯 CoDesign】面试宝典 CoDesign - 腾讯自研设计协作平台 访问密码:2YDD
【腾讯 CoDesign】面试宝典 https://codesign.qq.com/app/s/449325994072442 ON9V
✔️我的页面-沉浸式显示模式处理
课程目标
- 我的页面要突破
手机安全区域
来全屏显示(沉浸式模式显示
),其他页面不需要 - 在Mine组件的aboutToAppear中使用
window
模块的getLastWindow
和setWindowLayoutFullScreen
两个方法来处理全屏显示
设置沉浸式模式的特点:
- 在任何一个页面中设置过一次之后,其他页面也会跟着全屏显示
这么处理会出现问题:从其他tab标签进入我的tab标签时,会出现底部tab栏闪动
-
- 解决:在进入应用时(index.tes页面)就开启所有页面的全屏模式
- 带来新的问题:不需要全屏显示的页面内容会突破安全区域显示,导致内容显示不正常
import { window } from '@kit.ArkUI'@Component
export struct Mine {aboutToAppear(): void {// 1.0 利用了系统的window 这个api的getLastWindow方法获取到了当前的窗口window.getLastWindow(getContext()).then(win=>{// 2.0 利用当前窗口的setWindowLayoutFullScreen方法设置全屏: true:设置全屏 false:取消全屏win.setWindowLayoutFullScreen(true) // 开启了当前页面的沉浸式模式(开启全屏模式)})}build() {Column(){Text('我的' + Math.random().toFixed(2))}.height('100%').width('100%').backgroundColor(Color.Pink)}
}
✔️index.ets中统一开启整个应用的沉浸式模式
课程目标
- 在index.ets页面的aboutToAppear中使用
window
模块的getLastWindow
和setWindowLayoutFullScreen
两个方法来处理整个应用的全屏显示
由于整个应用都开启了全屏显示,那些不需要全屏显示的页面也一并开启了,导致出现了问题:
问题:内容突破了整个安全区域,靠手机顶部显示了内容
解决:需要通过获取手机的安全高度来结合 padding来适配顶部内容的正常显示
import hilog from '@ohos.hilog'
import { Logger } from '../common/utils/Logger'
import { Home } from '../views/home/home'
import { InterView } from '../views/interview/interview'
import { Mine } from '../views/mine/mine'
import { Project } from '../views/project/project'
import { window } from '@kit.ArkUI'@Entry
@Component
struct Index {@State currentIndex:number = 0 // 表示默认选中的是第一个tab栏aboutToAppear(): void {// 1.0 利用了系统的window 这个api的getLastWindow方法获取到了当前的窗口window.getLastWindow(getContext()).then(win=>{// 2.0 利用当前窗口的setWindowLayoutFullScreen方法设置全屏: true:设置全屏 false:取消全屏win.setWindowLayoutFullScreen(true) // 开启了当前页面的沉浸式模式(开启全屏模式)})}// 自定义tab的标签栏@Builder tabBuilder(text:string,icon:ResourceStr,selectedIcon:ResourceStr,index:number){Column({space:5}){Image(this.currentIndex === index?selectedIcon:icon ).height(28).aspectRatio(1)Text(text)}}build() {Row() {Column() {// 使用Tab组件完成导航栏的使用// $$this.currentIndex 双向数据绑定,自定更新currentIndex的值,功能等同于onChange事件Tabs({index:$$this.currentIndex}){TabContent(){// Text('首页')if(this.currentIndex === 0){Home()}}.tabBar(this.tabBuilder('首页',$r('app.media.home'),$r('app.media.home_select'),0))TabContent(){// Text('项目')if(this.currentIndex === 1) {Project()}}.tabBar(this.tabBuilder('项目',$r('app.media.tabbar_project'),$r('app.media.ic_tabbar_project_select'),1))TabContent(){// Text('面经')if(this.currentIndex === 2) {InterView()}}.tabBar(this.tabBuilder('面经',$r('app.media.interview'),$r('app.media.interview_select'),2))TabContent(){// Textif(this.currentIndex === 3) {Mine()}}.tabBar(this.tabBuilder('我的',$r('app.media.tabbar_mine'),$r('app.media.ic_tabbar_mine_select'),3))}.animationDuration(0) // 去掉切换动画.barPosition(BarPosition.End) // tabs在底部显示}.width('100%')}.height('100%')}
}
✔️Index.ets中获取安全高度保存到AppStorage中
课程目标
- 通过
window
模块的getLastWindow
和getWindowAvoidArea
来获取安全高度
window.AvoidAreaType.TYPE_SYSTEM 获取导航栏安全高度,但是获取不到底部指示器的高度
window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR -> 获取底部指示器的高度
- 安全高度获取到的是px单位,需要使用px2vp函数转换成vp单位
- 将安全区域高度保存到AppStorage中共享给其他页面使用
import { Home } from '../views/home/Home'
import { InterviewList } from '../views/interview/InterviewList'
import { my } from '../views/my/my'
import { ProjectList } from '../views/project/ProjectList'
import { window } from '@kit.ArkUI'@Entry
@Component
struct Index {@State currentIndex: number = 0@State bottomHeight:number = 0async aboutToAppear() {const win = await window.getLastWindow(getContext())// 1. 设置沉浸式模式(开启全屏)win.setWindowLayoutFullScreen(true)// 2. 获取安全区域的高度const area = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)let topHeight = px2vp(area.topRect.height) //获取到了顶部安全区域的高度,但是单位是px,为了显示正常要转成vp// area.bottomRect.height -> 0// 使用AppStroage存储起来,将来所有页面都可以使用AppStorage.setOrCreate('topHeight', topHeight)// 3. 获取底部的导航条的高度(Home键)const areaIndictor = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR)let bottomHeight = px2vp(areaIndictor.bottomRect.height)this.bottomHeight = bottomHeightAppStorage.setOrCreate('bottomHeight', bottomHeight)// 设置当前页面中的容器padding底部预留出bottomHeight// AlertDialog.show({ message: area.bottomRect.height + '----' + areaIndictor.bottomRect.height })}build() {Column() {// 用Tabs来完成首页底部导航栏的实现Tabs({ index: $$this.currentIndex }) {TabContent() {if (this.currentIndex == 0) {Home()}}.tabBar(this.tabbarBuilder('首页', $r('app.media.home'), $r('app.media.home_select'), 0))TabContent() {if (this.currentIndex == 1) {ProjectList()}}.tabBar(this.tabbarBuilder('项目', $r('app.media.tabbar_project'), $r('app.media.ic_tabbar_project_select'), 1))TabContent() {if (this.currentIndex == 2) {InterviewList()}}.tabBar(this.tabbarBuilder('面经', $r('app.media.interview'), $r('app.media.interview_select'), 2))TabContent() {if (this.currentIndex == 3) {my()}}.tabBar(this.tabbarBuilder('我的', $r('app.media.tabbar_mine'), $r('app.media.ic_tabbar_mine_select'), 3))}.barPosition(BarPosition.End).animationDuration(0)}.height('100%').width('100%').padding({bottom:this.bottomHeight})}// 1. 实现底部导航栏的自定义构建函数@BuildertabbarBuilder(text: string, icon: ResourceStr, selectedIcon: ResourceStr, index: number) {Column({ space: 5 }) {Image(this.currentIndex == index ? selectedIcon : icon).height(30)Text(text).fontColor(this.currentIndex == index ?Color.Orange :Color.Black)}}
}
@Component
export struct Home {// 获取AppStraoage中的数据@StorageProp("topHeight") topHeight:number = 0build() {Column(){Text('首页' + Math.random().toFixed(2))}.padding({top:this.topHeight}).height('100%').width('100%')}}
✔️首页、项目、面经、我的页面安全区域高度适配
课程目标
- 从AppStorage中获取安全区域高度,结合padding适配页面内容的顶部显示
@Component
export struct Home {// 获取AppStraoage中的数据@StorageProp("topHeight") topHeight:number = 0build() {Column(){Text('首页' + Math.random().toFixed(2))}.padding({top:this.topHeight}).height('100%').width('100%')}}
@Component
export struct InterView {// 获取AppStraoage中的数据@StorageProp("topHeight") topHeight:number = 0build() {Column(){Text('面经' + Math.random().toFixed(2))}.padding({top:this.topHeight}).height('100%').width('100%')}
}
@Component
export struct Project {// 获取AppStraoage中的数据@StorageProp("topHeight") topHeight:number = 0build() {Column(){Text('项目' + Math.random().toFixed(2))}.padding({top:this.topHeight}).height('100%').width('100%').backgroundColor(Color.Pink)}
}
@Component
export struct Mine {// 获取AppStraoage中的数据@StorageProp("topHeight") topHeight:number = 0build() {Column(){Text('我的' + Math.random().toFixed(2))}.padding({top:this.topHeight}).height('100%').width('100%').backgroundColor(Color.Pink)}
}
✔️设置安全区域文字颜色
课程目标
- 通过
window
模块的getLastWindow
和setWindowSystemBarProperties({statusBarContentColor:颜色})
来设置安全区域文字颜色
-
- #FFFFFF -> 设置白色
- #000000 -> 设置黑色
import { window } from '@kit.ArkUI'@Component
export struct Mine {// 获取AppStraoage中的数据@StorageProp("topHeight") topHeight:number = 0// 进入页面触发aboutToAppear(): void {window.getLastWindow(getContext()).then(win=>{win.setWindowSystemBarProperties({statusBarContentColor:'#FFFFFF'}) // 设置安全区域内容的颜色为白色})}build() {Column(){Text('我的' + Math.random().toFixed(2))}.padding({top:this.topHeight}).height('100%').width('100%').backgroundColor(Color.Pink)}
}
一旦执行了颜色设置代码,所有页面都会是同一个颜色,如果需要改颜色,需要在指定页面重新设置一次新颜色
练习发现的问题:
- 颜色代码 一定要设置成大写的 #FFFFFF #fff #ffffff有些模拟器不生效
✔️沉浸式模式类封装
课程目标
- 封装安全区域工具类windowManager.ets,用来设置沉浸式模式以及设置安全区域的字体颜色
-
- enableFullScreen - 开启全屏
- disableFullScreen - 关闭全屏
- getAvoidAreaTop获取顶部安全区域高度,并返回该值
- getAvoidAreaBottom获取底部导航条安全区域高度,并返回该值
- settingStatusBarContentColor -设置安全区域文字为白色或者黑色
- 在index.ets中调用windowManager.ets完成沉浸模式的开启
- 利用AppStorage对安全区域高度做不同页面的兼容性处理
const win = await window.getLastWindow(getContext())// 1. 设置沉浸式模式(开启全屏)win.setWindowLayoutFullScreen(true)
async aboutToAppear() {const win = await window.getLastWindow(getContext())win.setWindowSystemBarProperties({statusBarContentColor:'#ffffff'})}
// 2. 获取安全区域的高度
const area = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)
let topHeight = px2vp(area.topRect.height) //获取到了顶部安全区域的高度,但是单位是px,为了显示正常要转成vp
// area.bottomRect.height -> 0
// 使用AppStroage存储起来,将来所有页面都可以使用
AppStorage.setOrCreate('topHeight', topHeight)// 3. 获取底部的导航条的高度(Home键)
const areaIndictor = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR)
let bottomHeight = px2vp(areaIndictor.bottomRect.height)
this.bottomHeight = bottomHeight
AppStorage.setOrCreate('bottomHeight', bottomHeight)
最终封装完整代码
import { window } from '@kit.ArkUI'export class windowManager {// 1. 开启全屏static async enableFullScreen() {const win = await window.getLastWindow(getContext())win.setWindowLayoutFullScreen(true)}// 2. 关闭全屏static async disableFullScreen() {const win = await window.getLastWindow(getContext())win.setWindowLayoutFullScreen(false)}// 3. 获取顶部安全区域高度static async getAvoidAreeTop() {const win = await window.getLastWindow(getContext())const area = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)// 在Appstroage中保存起来const topHeight = px2vp(area.topRect.height)AppStorage.setOrCreate('topHeight', topHeight)return topHeight}// 4. 获取底部导航条高度static async getAvoidAreeBottom() {const win = await window.getLastWindow(getContext())const area = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR)// 在Appstroage中保存起来const bottomHeight = px2vp(area.bottomRect.height)AppStorage.setOrCreate('bottomHeight', bottomHeight)return bottomHeight}// 5. 设置安全区域文字的颜色static async settingStatusBarContentColor(color: '#FFFFFF' | '#000000') {const win = await window.getLastWindow(getContext())win.setWindowSystemBarProperties({ statusBarContentColor: color })}
}
使用封装好的类方法:
import { Home } from '../views/home/Home'
import { InterviewList } from '../views/interview/InterviewList'
import { my } from '../views/my/my'
import { ProjectList } from '../views/project/ProjectList'
import { window } from '@kit.ArkUI'
import { windowManager } from '../utils/windowManager'@Entry
@Component
struct Index {@State currentIndex: number = 0@State bottomHeight: number = 0async aboutToAppear() {// 1. 开启全屏windowManager.enableFullScreen()// 2. 获取顶部安全区域高度windowManager.getAvoidAreeTop()// 2. 获取底部的导航栏的高度,赋值给this.bottomHeightthis.bottomHeight = await windowManager.getAvoidAreeBottom()}build() {Column() {// 用Tabs来完成首页底部导航栏的实现Tabs({ index: $$this.currentIndex }) {TabContent() {if (this.currentIndex == 0) {Home()}}.tabBar(this.tabbarBuilder('首页', $r('app.media.home'), $r('app.media.home_select'), 0))TabContent() {if (this.currentIndex == 1) {ProjectList()}}.tabBar(this.tabbarBuilder('项目', $r('app.media.tabbar_project'), $r('app.media.ic_tabbar_project_select'), 1))TabContent() {if (this.currentIndex == 2) {InterviewList()}}.tabBar(this.tabbarBuilder('面经', $r('app.media.interview'), $r('app.media.interview_select'), 2))TabContent() {if (this.currentIndex == 3) {my()}}.tabBar(this.tabbarBuilder('我的', $r('app.media.tabbar_mine'), $r('app.media.ic_tabbar_mine_select'), 3))}.barPosition(BarPosition.End).animationDuration(0)}.height('100%').width('100%').padding({ bottom: this.bottomHeight })}// 1. 实现底部导航栏的自定义构建函数@BuildertabbarBuilder(text: string, icon: ResourceStr, selectedIcon: ResourceStr, index: number) {Column({ space: 5 }) {Image(this.currentIndex == index ? selectedIcon : icon).height(30)Text(text).fontColor(this.currentIndex == index ?Color.Orange :Color.Black)}}
}
import { window } from '@kit.ArkUI'
import { windowManager } from '../../utils/windowManager'@Component
export struct my {@StorageProp('topHeight') topHeight: number = 0async aboutToAppear() {windowManager.settingStatusBarContentColor('#FFFFFF')}build() {Column() {Text('我的组件')}.height('100%').width('100%').backgroundColor(Color.Pink).padding({ top: this.topHeight })}
}
- 接口文档
【apifox】面试宝典 个人中心-保存用户数据信息 - 教学练测项目-面试宝典-鸿蒙
登录固定账号和密码:
账号:hmheima
密码:Hmheima%123
✔️腾讯 CoDesignUI设计稿查看
课程目标
- 鸿蒙开发:因为腾讯CoDesignUI设计稿中还没有鸿蒙平台,所以暂时 选择Android平台,设定375宽 来适配鸿蒙界面的开发
- 使用鼠标右键,可以通过弹层选中需要查看的元素
- 设计稿
【腾讯 CoDesign】面试宝典 CoDesign - 腾讯自研设计协作平台 访问密码:2YDD
✔️【登录】静态结构搭建
pages/LoginPage.ets
课程目标
- 学会【腾讯 CoDesign】UI设计稿查看方式(选择android,自定义倍率0.5 -> 375宽)
- 结合ArkUI组件完成静态结构的搭建
import { router } from '@kit.ArkUI'@Entry
@Component
struct LoginPage {@State username:string = 'hmheima'@State password:string = 'Hmheima%123'@State isAgree:boolean = false@State islogin:boolean = falsebuild() {Column() {// logoColumn({ space: 10 }) {Image($r('app.media.icon')).height(55).aspectRatio(1)Text('面试宝典').fontSize(28).fontColor('#121826')Text('搞定企业面试真题,就用面试宝典').fontSize(14).fontColor('#6F6F6F')}.margin({ top: 170 })// 登录区域Column({space:20}) {TextInput({text:$$this.username}).backgroundColor(Color.White).width('90%').borderRadius(0)TextInput({text:$$this.password}).type(InputType.Password).backgroundColor(Color.White).width('90%')Row(){Checkbox().select(this.isAgree).selectedColor('#FA6D1D').onChange(value=>{this.isAgree = value})Text('已阅读并同意').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('用户协议').fontSize(14).padding({ right: 4})Text('和').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('隐私政策').fontSize(14).onClick(() => {router.pushUrl({url: 'pages/PreviewWebPage'})})}.width('90%')Button({type:ButtonType.Normal}){Row(){if(this.islogin){LoadingProgress().height(28).aspectRatio(1).color(Color.White)}Text('登录')}}.borderRadius(4).width(328).height(45).fontColor(Color.White).linearGradient({angle:135,colors:[['#FCA21C',0],['#FA6D1D',1]]})}.margin({top:50})}.width('100%').height('100%')}
}
✔️【登录】隐私政策页面加载
pages/PreviewWebPage.ets
课程目标
- 创建PreviewWebPage.ets
- router.pushUrl从LoginPage.ets跳转到PreviewWebPage.ets
- PreviewWebPage.ets中利用webView加载rawfile中的html文件
webViewController:webview.WebviewController = new webview.WebviewController()Web({src:$rawfile('index.html'),//src:"http://www.xxx.com/index.html",controller:this.webViewController})
import { webview } from '@kit.ArkWeb'@Entry
@Component
struct PreviewWebPage {// 1. 实例化webview控制器对象webController = new webview.WebviewController()build() {Navigation() {// Web组件只能在模拟器或者真机中使用,预览器不支持// 2. 使用Web组件加载本地rawfile中的index.htmlWeb({src: $rawfile('index.html'), //这种不需要开启网络权限// src:'http://itheima.com', //这种访问需要开网络权限controller: this.webController //3. 设置第1步实例化的控制器对象})}.title('隐私政策').titleMode(NavigationTitleMode.Mini)}
}
注意:
Web第二个参数controller不能省略,必选
src: 可以指向本地的一个文件,也可以指向网络的一个html文件,如果是网络文件,则需要在src/main/module.json5 配置INTERNET网络访问权限
预览器不支持加载网页功能,需要到模拟器或者真机中验证功能结果
【登录】登录逻辑处理
课程目标
- 理解接口文档中接口鉴权的描述方式,知道如何翻译成代码实现
- 完成利用http模块进行登录接口的post请求 -> 返回一个token -> 本地保存token (AppStroage来保存)
- 完成利用AppStorage对登录成功后的token进行存储
// 所有接口返回数据的类型
export interface iResponseModel<T> {/*** 请求成功10000标志*/code: number;/*** 返回数据*/data: T;/*** 请求成功*/message: string;/*** 请求成功标志*/success: boolean;
}/*** 登录成功之后的 返回数据*/
export interface iLoginUserModel {/*** 用户头像地址*/avatar?: string;/*** 连续打卡天数*/clockinNumbers: number;/*** 用户id*/id: string;/*** 昵称*/nickName: string;/*** token过期后,刷新token使用*/refreshToken: string;/*** 分享加密串*/shareInfo: string;/*** 后续交互使用的token*/token: string;/*** 学习时长,单位s*/totalTime: number;/*** 昵称*/username?: string;
}
✔️【登录】- 参数合法性检查
课程目标
- 能对用户名和密码文本框做非空验证处理
- 对用户协议勾选做验证处理
import { promptAction, router } from '@kit.ArkUI'@Entry
@Component
struct LoginPage {@State username:string = 'hmheima'@State password:string = 'Hmheima%123'@State isAgree:boolean = falseasync login(){// 1.0 页面合法性检查// 1.0.1 进行用户名和密码的非空判断if(this.username === '' || this.password === ''){promptAction.showToast({message:'用户名和密码不能为空'})return}// 1.0.2 判断用户如果没有勾选同意协议,则验证失败if(!this.isAgree){promptAction.showToast({message:'请勾选协议'})this.isAgree = true // 设置勾选上协议return}}build() {Column() {// logoColumn({ space: 10 }) {Image($r('app.media.icon')).height(55).aspectRatio(1)Text('面试宝典').fontSize(28).fontColor('#121826')Text('搞定企业面试真题,就用面试宝典').fontSize(14).fontColor('#6F6F6F')}.margin({ top: 170 })// 登录区域Column({space:20}) {TextInput({text:$$this.username}).backgroundColor(Color.White).width('90%').borderRadius(0)TextInput({text:$$this.password}).type(InputType.Password).backgroundColor(Color.White).width('90%')Row(){Checkbox().select(this.isAgree).selectedColor('#FA6D1D').onChange(value=>{this.isAgree = value})Text('已阅读并同意').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('用户协议').fontSize(14).padding({ right: 4})Text('和').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('隐私政策').fontSize(14).onClick(() => {router.pushUrl({// 跳转到PreviewWebPageurl: 'pages/PreviewWebPage'})})}.width('90%')Button(){Row(){LoadingProgress().height(28).aspectRatio(1).color(Color.White)Text('登录')} }.onClick(()=>{// 调用登录方法this.login()}).borderRadius(4).width(328).height(45).fontColor(Color.White).linearGradient({angle:135,colors:[['#FCA21C',0],['#FA6D1D',1]]})}.margin({top:50})}.width('100%').height('100%')}
}
✔️【登录】- 使用axios模块请求登录接口
http请求需要先配置INTERNET权限,方法:
src/main/module.json5中增加:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
课程目标
- 能使用axios模块的reqeust方法以post方式请求登录接口,并做好异常处理
-
- 安装axios模块 ohpm i @ohos/axios
定义数据类型
// 所有接口返回数据的类型
export interface iResponseModel<T> {/*** 请求成功10000标志*/code: number;/*** 返回数据*/data: T;/*** 请求成功*/message: string;/*** 请求成功标志*/success: boolean;
}/*** 登录成功之后的 返回数据*/
export interface iLoginUserModel {/*** 用户头像地址*/avatar?: string;/*** 连续打卡天数*/clockinNumbers: number;/*** 用户id*/id: string;/*** 昵称*/nickName: string;/*** token过期后,刷新token使用*/refreshToken: string;/*** 分享加密串*/shareInfo: string;/*** 后续交互使用的token*/token: string;/*** 学习时长,单位s*/totalTime: number;/*** 昵称*/username?: string;
}
import { promptAction, router } from '@kit.ArkUI'
import axios, { AxiosResponse, AxiosError } from '@ohos/axios'
import { iLoginUserModel, iResponseModel } from '../models/datamodel'@Entry
@Component
struct LoginPage {@State username: string = 'hmheima'@State password: string = 'Hmheima%123'@State isAgree: boolean = false@State islogin: boolean = false// 负责登录async login() {// 1. 参数合法性检查/** ● 能对用户名和密码文本框做非空验证处理● 对用户协议勾选做验证处理* */if (this.username == '' || this.password == '') {return promptAction.showToast({ message: '用户名和密码必填' })}if (!this.isAgree) {promptAction.showToast({ message: '请先勾选协议' })this.isAgree = truereturn}// 2. axios请求服务器接口try {const req = axios.create()let res: AxiosResponse<iResponseModel<iLoginUserModel>> =await req.request<null, AxiosResponse<iResponseModel<iLoginUserModel>>, object>({method: 'POST',url: 'https://api-harmony-teach.itheima.net/hm/login',data: {username: this.username,password: this.password}})AlertDialog.show({ message: JSON.stringify(res.data.data, null, 2) })// 3. 将服务器响应回来的数据保存到AppStroage中// 3.1 判断服务器响应体中的code==10000的时候才保存数据,否则提示用户响应体中的message// 4. 跳转到首页} catch (err) {// 将来服务器的状态码是非200的,就会自动触发catchlet errObj: AxiosError = err //最终将err大错误对象,转为AxiosErrorAlertDialog.show({ message: errObj.message }) //最后提示给用户的是message字符串}}build() {Column() {// logoColumn({ space: 10 }) {Image($r('app.media.icon')).height(55).aspectRatio(1)Text('面试宝典').fontSize(28).fontColor('#121826')Text('搞定企业面试真题,就用面试宝典').fontSize(14).fontColor('#6F6F6F')}.margin({ top: 170 })// 登录区域Column({ space: 20 }) {TextInput({ text: $$this.username }).backgroundColor(Color.White).width('90%').borderRadius(0)TextInput({ text: $$this.password }).type(InputType.Password).backgroundColor(Color.White).width('90%')Row() {Checkbox().select(this.isAgree).selectedColor('#FA6D1D').onChange(value => {this.isAgree = value})Text('已阅读并同意').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('用户协议').fontSize(14).padding({ right: 4 })Text('和').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('隐私政策').fontSize(14).onClick(() => {router.pushUrl({url: 'pages/PreviewWebPage'})})}.width('90%')Button({ type: ButtonType.Normal }) {Row() {if (this.islogin) {LoadingProgress().height(28).aspectRatio(1).color(Color.White)}Text('登录')}}.borderRadius(4).width(328).height(45).fontColor(Color.White).linearGradient({angle: 135,colors: [['#FCA21C', 0],['#FA6D1D', 1]]}).onClick(() => {this.login()})}.margin({ top: 50 })}.width('100%').height('100%')}
}
✔️【登录】- 处理登录接口响应数据
课程目标
- 处理响应体中逻辑状态码10000的情况 -> 响应数据中如果code的值为10000则表示成功响应
- 处理响应体中逻辑状态码不为10000 -> 响应数据中如果code的值不为10000则表示失败响应
import { promptAction, router } from '@kit.ArkUI'
import axios, { AxiosResponse, AxiosError } from '@ohos/axios'
import { iLoginUserModel, iResponseModel } from '../models/datamodel'@Entry
@Component
struct LoginPage {@State username: string = 'hmheima'@State password: string = 'Hmheima%123'@State isAgree: boolean = false@State islogin: boolean = false// 负责登录async login() {// 1. 参数合法性检查/** ● 能对用户名和密码文本框做非空验证处理● 对用户协议勾选做验证处理* */if (this.username == '' || this.password == '') {return promptAction.showToast({ message: '用户名和密码必填' })}if (!this.isAgree) {promptAction.showToast({ message: '请先勾选协议' })this.isAgree = truereturn}// 2. axios请求服务器接口try {const req = axios.create()let res: AxiosResponse<iResponseModel<iLoginUserModel>> =await req.request<null, AxiosResponse<iResponseModel<iLoginUserModel>>, object>({method: 'POST',url: 'https://api-harmony-teach.itheima.net/hm/login',data: {username: this.username,password: this.password}})// AlertDialog.show({ message: JSON.stringify(res, null, 2) })// 3. 处理服务器响应回来的报文体的数据// 3.1 判断服务器响应体中的code==10000的时候才保存数据,否则提示用户响应体中的messageif (res.data.code != 10000) {return promptAction.showToast({ message: res.data.message })}// 3.2 将服务器响应回来的数据保存到AppStroage中AppStorage.setOrCreate('user', res.data.data)// 4. 跳转到首页router.replaceUrl({ url: 'pages/Index' })} catch (err) {// 将来服务器的状态码是非200的,就会自动触发catchlet errObj: AxiosError = err //最终将err大错误对象,转为AxiosErrorAlertDialog.show({ message: errObj.message }) //最后提示给用户的是message字符串}}build() {Column() {// logoColumn({ space: 10 }) {Image($r('app.media.icon')).height(55).aspectRatio(1)Text('面试宝典').fontSize(28).fontColor('#121826')Text('搞定企业面试真题,就用面试宝典').fontSize(14).fontColor('#6F6F6F')}.margin({ top: 170 })// 登录区域Column({ space: 20 }) {TextInput({ text: $$this.username }).backgroundColor(Color.White).width('90%').borderRadius(0)TextInput({ text: $$this.password }).type(InputType.Password).backgroundColor(Color.White).width('90%')Row() {Checkbox().select(this.isAgree).selectedColor('#FA6D1D').onChange(value => {this.isAgree = value})Text('已阅读并同意').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('用户协议').fontSize(14).padding({ right: 4 })Text('和').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('隐私政策').fontSize(14).onClick(() => {router.pushUrl({url: 'pages/PreviewWebPage'})})}.width('90%')Button({ type: ButtonType.Normal }) {Row() {if (this.islogin) {LoadingProgress().height(28).aspectRatio(1).color(Color.White)}Text('登录')}}.borderRadius(4).width(328).height(45).fontColor(Color.White).linearGradient({angle: 135,colors: [['#FCA21C', 0],['#FA6D1D', 1]]}).onClick(() => {this.login()})}.margin({ top: 50 })}.width('100%').height('100%')}
}
✔️【登录】保存token 并跳转到首页
课程目标
- 完成利用AppStorage保存登录成功后返回的token数据
- 利用router.pushUrl方法跳转到 pages/Index页面
// 将整个响应回来的data数据存储(选择的方案:AppStorage)AppStorage.setOrCreate('user', 登录后响应的数据)// 直接跳转到首页router.replaceUrl({ url: 'pages/Index' })
import { promptAction, router } from '@kit.ArkUI'
import axios, { AxiosResponse, AxiosError } from '@ohos/axios'
import { iLoginUserModel, iResponseModel } from '../models/datamodel'@Entry
@Component
struct LoginPage {@State username: string = 'hmheima'@State password: string = 'Hmheima%123'@State isAgree: boolean = false@State islogin: boolean = false// 负责登录async login() {// 1. 参数合法性检查/** ● 能对用户名和密码文本框做非空验证处理● 对用户协议勾选做验证处理* */if (this.username == '' || this.password == '') {return promptAction.showToast({ message: '用户名和密码必填' })}if (!this.isAgree) {promptAction.showToast({ message: '请先勾选协议' })this.isAgree = truereturn}// 2. axios请求服务器接口try {const req = axios.create()let res: AxiosResponse<iResponseModel<iLoginUserModel>> =await req.request<null, AxiosResponse<iResponseModel<iLoginUserModel>>, object>({method: 'POST',url: 'https://api-harmony-teach.itheima.net/hm/login',data: {username: this.username,password: this.password}})// AlertDialog.show({ message: JSON.stringify(res, null, 2) })// 3. 处理服务器响应回来的报文体的数据// 3.1 判断服务器响应体中的code==10000的时候才保存数据,否则提示用户响应体中的messageif (res.data.code != 10000) {return promptAction.showToast({ message: res.data.message })}// 3.2 将服务器响应回来的数据保存到AppStroage中AppStorage.setOrCreate('user', res.data.data)// 4. 跳转到首页router.replaceUrl({ url: 'pages/Index' })} catch (err) {// 将来服务器的状态码是非200的,就会自动触发catchlet errObj: AxiosError = err //最终将err大错误对象,转为AxiosErrorAlertDialog.show({ message: errObj.message }) //最后提示给用户的是message字符串}}build() {Column() {// logoColumn({ space: 10 }) {Image($r('app.media.icon')).height(55).aspectRatio(1)Text('面试宝典').fontSize(28).fontColor('#121826')Text('搞定企业面试真题,就用面试宝典').fontSize(14).fontColor('#6F6F6F')}.margin({ top: 170 })// 登录区域Column({ space: 20 }) {TextInput({ text: $$this.username }).backgroundColor(Color.White).width('90%').borderRadius(0)TextInput({ text: $$this.password }).type(InputType.Password).backgroundColor(Color.White).width('90%')Row() {Checkbox().select(this.isAgree).selectedColor('#FA6D1D').onChange(value => {this.isAgree = value})Text('已阅读并同意').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('用户协议').fontSize(14).padding({ right: 4 })Text('和').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('隐私政策').fontSize(14).onClick(() => {router.pushUrl({url: 'pages/PreviewWebPage'})})}.width('90%')Button({ type: ButtonType.Normal }) {Row() {if (this.islogin) {LoadingProgress().height(28).aspectRatio(1).color(Color.White)}Text('登录')}}.borderRadius(4).width(328).height(45).fontColor(Color.White).linearGradient({angle: 135,colors: [['#FCA21C', 0],['#FA6D1D', 1]]}).onClick(() => {this.login()})}.margin({ top: 50 })}.width('100%').height('100%')}
}
import { promptAction, router } from '@kit.ArkUI'
import axios, { AxiosResponse } from '@ohos/axios'
import { iLoginUserModel, iResponseModel } from '../models/datamodel'@Entry
@Component
struct LoginPage {@State username: string = 'hmheima'@State password: string = 'Hmheima%123'@State isAgree: boolean = falseasync login() {const req = axios.create()let res: AxiosResponse<iResponseModel<iLoginUserModel>> =await req.request<null, AxiosResponse<iResponseModel<iLoginUserModel>>>({method: 'POST',url: 'https://api-harmony-teach.itheima.net/hm/login',data: {username: this.username,password: this.password}})// AlertDialog.show({ message: JSON.stringify(res.data, null, 2) })// 3.0 处理响应数据// 数据转换let resData = res.data// 3.0.1 处理状态码不为10000的情况->失败if(resData.code !== 10000){promptAction.showToast({message:resData.message})return}// 3.0.2 处理成功的状态 ,将登录接口响应回来的整个数据保存到AppStorage中AppStorage.setOrCreate('user',resData.data)// 3.0.3 跳转到首页router.replaceUrl({url:'pages/Index'})}build() {Column() {// logoColumn({ space: 10 }) {Image($r('app.media.icon')).height(55).aspectRatio(1)Text('面试宝典').fontSize(28).fontColor('#121826')Text('搞定企业面试真题,就用面试宝典').fontSize(14).fontColor('#6F6F6F')}.margin({ top: 170 })// 登录区域Column({ space: 20 }) {TextInput({ text: this.username }).backgroundColor(Color.White).width('90%').borderRadius(0)TextInput({ text: this.password }).type(InputType.Password).backgroundColor(Color.White).width('90%')Row() {Checkbox().select(this.isAgree).selectedColor('#FA6D1D').onChange(value => {this.isAgree = value})Text('已阅读并同意').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('用户协议').fontSize(14).padding({ right: 4 })Text('和').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('隐私政策').fontSize(14).onClick(() => {router.pushUrl({url: 'pages/PreviewWebPage'})})}.width('90%')Button({ type: ButtonType.Normal }) {Row() {LoadingProgress().height(28).aspectRatio(1).color(Color.White)Text('登录')}}.borderRadius(4).width(328).height(45).fontColor(Color.White).linearGradient({angle: 135,colors: [['#FCA21C', 0],['#FA6D1D', 1]]}).onClick(() => {this.login()})}.margin({ top: 50 })}.width('100%').height('100%')}
}
在Home.ets组件获取AppStorage中的用户数据 ->来测试appstroage是否保存数据成功
import { iLoginUserModel } from '../../models/AccountModel'@Component
export struct Home {// 获取AppStraoage中的数据@StorageProp("topHeight") topHeight:number = 0// 获取登录成功后的用户信息,里面包含有token@StorageProp('user') currentUser:iLoginUserModel = {} as iLoginUserModelaboutToAppear(): void {AlertDialog.show({message:JSON.stringify(this.currentUser,null,2)})}build() {Column(){Text('首页' + Math.random().toFixed(2))}.padding({top:this.topHeight}).height('100%').width('100%')}}
✔️【登录】- 其他处理
课程目标
- 处理登录 loadingProgress的显示和隐藏
import { promptAction, router } from '@kit.ArkUI'
import http from '@ohos.net.http'
import { Logger } from '../common/utils/Logger'
import { iResponseModel } from '../models/AccountModel'@Entry
@Component
struct LoginPage {@State username: string = 'hmheima'@State password: string = 'Hmheima%123'@State isAgree: boolean = false@State isloading:boolean = false // 控制登录中的图标显示的async login() {// 打开loading组件this.isloading = true// 1.0 页面合法性检查// 1.0.1 进行用户名和密码的非空判断if (this.username === '' || this.password === '') {promptAction.showToast({ message: '用户名和密码不能为空' })return}// 1.0.2 判断用户如果没有勾选同意协议,则验证失败if (!this.isAgree) {promptAction.showToast({ message: '请勾选协议' })this.isAgree = true // 设置勾选上协议return}try {// 2.0 请求登录接口获取服务器的响应 https://api-harmony-teach.itheima.net/hm/loginconst req = axios.create()let res: AxiosResponse<iResponseModel<iLoginDataModel>> =await req.request<null, AxiosResponse<iResponseModel<iLoginDataModel>>>({method: 'POST',url: 'https://api-harmony-teach.itheima.net/hm/login',data: {username: this.username,password: this.password}})// AlertDialog.show({ message: JSON.stringify(res.data, null, 2) })// 3.0 处理响应数据// 数据转换let resData = res.data// 3.0.1 处理状态码不为10000的情况->失败if (resData.code !== 10000) {promptAction.showToast({ message: resData.message })return}// 3.0.2 处理成功的状态 ,将登录接口响应回来的整个数据保存到AppStorage中AppStorage.setOrCreate('user', resData.data)// 3.0.3 跳转到首页router.replaceUrl({ url: 'pages/Index' })} catch (err) {this.isloading = false // 如果代码错误则关闭Logger.error('登录页面-登录逻辑错误:', JSON.stringify(err))}}build() {Column() {// logoColumn({ space: 10 }) {Image($r('app.media.icon')).height(55).aspectRatio(1)Text('面试宝典').fontSize(28).fontColor('#121826')Text('搞定企业面试真题,就用面试宝典').fontSize(14).fontColor('#6F6F6F')}.margin({ top: 170 })// 登录区域Column({ space: 20 }) {TextInput({ text: $$this.username }).backgroundColor(Color.White).width('90%').borderRadius(0)TextInput({ text: $$this.password }).type(InputType.Password).backgroundColor(Color.White).width('90%')Row() {Checkbox().select(this.isAgree).selectedColor('#FA6D1D').onChange(value => {this.isAgree = value})Text('已阅读并同意').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('用户协议').fontSize(14).padding({ right: 4 })Text('和').fontSize(14).fontColor($r('app.color.ih_gray_color')).padding({ right: 4 })Text('隐私政策').fontSize(14).onClick(() => {router.pushUrl({// 跳转到PreviewWebPageurl: 'pages/PreviewWebPage'})})}.width('90%')Button() {Row() {if (this.isloading) {LoadingProgress().height(28).aspectRatio(1).color(Color.White)}Text('登录')}}.onClick(() => {// 调用登录方法this.login()}).borderRadius(4).width(328).height(45).fontColor(Color.White).linearGradient({angle: 135,colors: [['#FCA21C', 0],['#FA6D1D', 1]]})}.margin({ top: 50 })}.width('100%').height('100%')}
}
✔️【我的】用户信息展示
课程目标
- 完成利用@StorageProp 或 @StorageLink展示登录用户信息
-
- 因为在我的页面是纯展示用户头像和昵称,所以此处选择@StorageProp来获取数据即可
import { router } from '@kit.ArkUI'interface Nav {icon: ResourceStrname: stringonClick?: () => voidother?: string
}interface Tool {icon: ResourceStrname: stringvalue?: stringonClick?: () => void
}@Component
export struct Mine {@State clockCount: number = 0@BuildernavBuilder(nav: Nav) {GridCol() {Column() {Image(nav.icon).width(30).aspectRatio(1).margin({ bottom: 10 })Text(nav.name).fontSize(14).fontColor($r('app.color.common_gray_03')).margin({ bottom: 4 })if (nav.other) {Row() {Text(nav.other).fontSize(12).fontColor($r('app.color.common_gray_01'))Image($r('sys.media.ohos_ic_public_arrow_right')).width(12).aspectRatio(1).fillColor($r('app.color.common_gray_01'))}}}.onClick(() => {nav.onClick && nav.onClick()})}}@BuildertoolsBuilder(tool: Tool) {Row() {Image(tool.icon).width(16).aspectRatio(1).margin({ right: 12 })Text(tool.name).fontSize(14)Blank()if (tool.value) {Text(tool.value).fontSize(12).fontColor($r('app.color.common_gray_01'))}Image($r('sys.media.ohos_ic_public_arrow_right')).width(16).aspectRatio(1).fillColor($r('app.color.common_gray_01'))}.height(50).width('100%').padding({ left: 16, right: 10 })}build() {Column({ space: 16 }) {Row({ space: 12 }) {Image($r('app.media.ic_mine_avatar')).width(55).aspectRatio(1).borderRadius(55)Column({ space: 4 }) {Text('昵称').fontSize(18).fontWeight(600).width('100%').margin({ bottom: 5 })Row() {Text('编辑个人信息').fontColor($r('app.color.ih_gray_color')).fontSize(11).margin({ right: 4 })Image($r('app.media.icon_edit')).width(10).height(10).fillColor($r('app.color.ih_gray_color'))}.onClick(() => {router.pushUrl({url: '修改信息页面'})}).width('100%')}.layoutWeight(1).alignItems(HorizontalAlign.Start)Text('放打卡组件')// HdClockIn({ clockCount: this.clockCount })}.width('100%').height(100)GridRow({ columns: 4 }) {this.navBuilder({ icon: $r('app.media.ic_mine_history'), name: '历史记录' })this.navBuilder({ icon: $r('app.media.ic_mine_collect'), name: '我的收藏' })this.navBuilder({ icon: $r('app.media.ic_mine_like'), name: '我的点赞' })this.navBuilder({icon: $r('app.media.ic_mine_study'), name: '累计学时', other: '4 小时', onClick: () => {router.pushUrl({ url: '跳转到学习记录页面' })}})}.backgroundColor(Color.White).padding(16).borderRadius(8)Column() {this.toolsBuilder({ icon: $r('app.media.ic_mine_notes'), name: '前端常用词' })this.toolsBuilder({ icon: $r('app.media.ic_mine_ai'), name: '面通AI' })this.toolsBuilder({ icon: $r('app.media.ic_mine_invite'), name: '推荐分享' })this.toolsBuilder({ icon: $r('app.media.ic_mine_file'), name: '意见反馈' })this.toolsBuilder({ icon: $r('app.media.ic_mine_info'), name: '关于我们' })this.toolsBuilder({ icon: $r('app.media.ic_mine_setting'), name: '设置' })}.backgroundColor(Color.White).borderRadius(8)}.padding($r('app.float.common_gutter')).backgroundColor($r('app.color.common_gray_bg')).linearGradient({colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]}).width('100%').height('100%')}
}
import { router } from '@kit.ArkUI'
import { iLoginUserModel } from '../../models/datamodel'interface Nav {icon: ResourceStrname: stringonClick?: () => voidother?: string
}interface Tool {icon: ResourceStrname: stringvalue?: stringonClick?: () => void
}@Component
export struct my {@State clockCount: number = 0@StorageProp('topHeight') topHeight: number = 0@StorageLink('user') currentUser: iLoginUserModel = {} as iLoginUserModel@BuildernavBuilder(nav: Nav) {GridCol() {Column() {Image(nav.icon).width(30).aspectRatio(1).margin({ bottom: 10 })Text(nav.name).fontSize(14).fontColor($r('app.color.common_gray_03')).margin({ bottom: 4 })if (nav.other) {Row() {Text(nav.other).fontSize(12).fontColor($r('app.color.common_gray_01'))Image($r('sys.media.ohos_ic_public_arrow_right')).width(12).aspectRatio(1).fillColor($r('app.color.common_gray_01'))}}}.onClick(() => {nav.onClick && nav.onClick()})}}@BuildertoolsBuilder(tool: Tool) {Row() {Image(tool.icon).width(16).aspectRatio(1).margin({ right: 12 })Text(tool.name).fontSize(14)Blank()if (tool.value) {Text(tool.value).fontSize(12).fontColor($r('app.color.common_gray_01'))}Image($r('sys.media.ohos_ic_public_arrow_right')).width(16).aspectRatio(1).fillColor($r('app.color.common_gray_01'))}.height(50).width('100%').padding({ left: 16, right: 10 })}build() {Column({ space: 16 }) {Row({ space: 12 }) {Image(this.currentUser.avatar).width(55).aspectRatio(1).borderRadius(55)Column({ space: 4 }) {Text(this.currentUser.nickName).fontSize(18).fontWeight(600).width('100%').margin({ bottom: 5 })Row() {Text('编辑个人信息').fontColor($r('app.color.ih_gray_color')).fontSize(11).margin({ right: 4 })Image($r('app.media.icon_edit')).width(10).height(10).fillColor($r('app.color.ih_gray_color'))}.onClick(() => {router.pushUrl({url: '修改信息页面'})}).width('100%')}.layoutWeight(1).alignItems(HorizontalAlign.Start)Text('放打卡组件')// HdClockIn({ clockCount: this.clockCount })}.width('100%').height(100)GridRow({ columns: 4 }) {this.navBuilder({ icon: $r('app.media.ic_mine_history'), name: '历史记录' })this.navBuilder({ icon: $r('app.media.ic_mine_collect'), name: '我的收藏' })this.navBuilder({ icon: $r('app.media.ic_mine_like'), name: '我的点赞' })this.navBuilder({icon: $r('app.media.ic_mine_study'),name: '累计学时',other: '4 小时',onClick: () => {router.pushUrl({ url: '跳转到学习记录页面' })}})}.backgroundColor(Color.White).padding(16).borderRadius(8)Column() {this.toolsBuilder({ icon: $r('app.media.ic_mine_notes'), name: '前端常用词' })this.toolsBuilder({ icon: $r('app.media.ic_mine_ai'), name: '面通AI' })this.toolsBuilder({ icon: $r('app.media.ic_mine_invite'), name: '推荐分享' })this.toolsBuilder({ icon: $r('app.media.ic_mine_file'), name: '意见反馈' })this.toolsBuilder({ icon: $r('app.media.ic_mine_info'), name: '关于我们' })this.toolsBuilder({ icon: $r('app.media.ic_mine_setting'), name: '设置' })}.backgroundColor(Color.White).borderRadius(8)}.padding({top: this.topHeight,left: 16,right: 16,bottom: 16}).backgroundColor($r('app.color.common_gray_bg')).linearGradient({colors: [['#FFB071', 0], ['#f3f4f5', 0.3], ['#f3f4f5', 1]]}).width('100%').height('100%')}
}
✔️【首页】静态布局结构分析
src/main/ets/views/home/Home.ets
课程目标
- 理解首页的整个组件构成:
-
- 搜索组件 - HdSearch.ets
- 打卡组件 - HdClockIn.ets
- 题目分类组件 - HomeCategoryComp.ets
- 题目列表组件 - QuestionListComp.ets
- 题目列表单个内容item组件 - QuestionItemComp.ets
- 难易程度组件-HdTag.ets
- 创建搜索组件HdSearch.ets,并迁移静态结构 -静态结构直接拷贝-功能后面实现
- 创建打卡组件HdClockIn.ets,并迁移静态结构- 静态结构直接拷贝 - 功能后面实现
import { router } from '@kit.ArkUI'@Component
export struct HdSearch {@StatereadonlyMode: boolean = trueph: string = ''bg: string = ''color: string = ''build() {Row() {Row({ space: 4 }) {Image($r("app.media.ic_common_search")).width($r('app.float.hd_search_icon_size')).aspectRatio(1).fillColor(this.color || $r('app.color.common_gray_02'))Text(this.ph || $r('app.string.hd_search_placeholder')).fontColor(this.color || $r('app.color.common_gray_02')).fontSize($r('app.float.common_font14'))}.layoutWeight(1).height($r('app.float.hd_search_height')).backgroundColor(this.bg || $r('app.color.common_gray_border')).borderRadius($r('app.float.hd_search_radius')).justifyContent(FlexAlign.Center)}.onClick(() => {router.pushUrl({ url: 'pages/SearchPage' })})}
}
@Component
export struct HdClockIn {@StateclockCount: number = 0build() {Stack({ alignContent: Alignment.End }) {Image(this.clockCount > 0 ? $r('app.media.clocked') : $r('app.media.unclock')).objectFit(ImageFit.Fill)if (this.clockCount > 0) {Column() {Text('已连续打卡').fontSize(8)Text() {Span(this.clockCount.toString()).fontWeight(600).fontSize(12)Span(' 天').fontSize(10)}}.width('50')} else {Text('打卡').width('50').textAlign(TextAlign.Center).fontSize((18)).fontWeight(500).fontColor('#333C4F').margin({ bottom: (4) })}}.width((74)).height((28))}
}
【首页】轮播图
没有接口对接,直接拷贝静态结构实现占位
import { HdClockIn } from '../../common/components/HdClockIn'
import { HdSearch } from '../../common/components/HdSearch'
import { iLoginUserModel } from '../../models/AccountModel'@Component
export struct Home {// 获取AppStraoage中的数据@StorageProp("topHeight") topHeight: number = 0// 获取登录成功后的用户信息,里面包含有token@StorageProp('user') currentUser: iLoginUserModel = {} as iLoginUserModelaboutToAppear(): void {// AlertDialog.show({message:JSON.stringify(this.currentUser,null,2)})}build() {Column() {Row() {// 1.0 搜索组件静态HdSearch().layoutWeight(3) // 占3/4份// 2.0 打卡组件HdClockIn().layoutWeight(1) //占1/4}// 3.0 轮播图Swiper(){Image($r('app.media.banner_qa')).objectFit(ImageFit.Fill).height(200)Image($r('app.media.banner_pj')).objectFit(ImageFit.Fill).height(200)Image($r('app.media.banner_ai')).objectFit(ImageFit.Fill).height(200)}.padding(10).autoPlay(true).indicator(DotIndicator.dot().selectedColor(Color.White).bottom(20))}.padding({ top: this.topHeight }).height('100%').width('100%')}
}
✔️【首页】打卡组件功能实现
src/main/ets/common/components/HdClockIn.ets
课程目标
- ✔️完成在HdClockIn.ets中get请求
clockinInfo
(接口文档)获取打卡数据并完成判断显示最终结果
/*** 返回数据*/
export interface iClock {/*** 连续签到天数*/clockinNumbers: number;/*** 签到信息*/clockins: Clockin[];/*** 当天是否签到*/flag: boolean;/*** 累计签到天数*/totalClockinNumber: number;
}export interface Clockin {/*** 签到时间*/createdAt: string;id: string;
}
import axios, { AxiosResponse, AxiosError } from '@ohos/axios'
import { iClock } from '../models/clockinmodel'
import { iLoginUserModel, iResponseModel } from '../models/datamodel'@Component
export struct HdClockIn {@State clockCount: number = 0 // >0显示已连续打卡xx天 <=0 显示打卡@StorageProp('user') currentUser: iLoginUserModel = {} as iLoginUserModelaboutToAppear(): void {// 请求服务器接口 https://api-harmony-teach.itheima.net/hm/clockinInfo 获取打卡天数this.getClockCount()}async getClockCount() {try {// 1. 导入axios相关包发请求const req = axios.create()let res: AxiosResponse<iResponseModel<iClock>> = await req.request({url: 'https://api-harmony-teach.itheima.net/hm/clockinInfo',// 根据接口文档携带token请求服务器数据,否则抛出401异常代码,拿不到正常数据headers: {'Authorization': `Bearer ${this.currentUser.token}`}})// 2. 请求url获得数据赋值this.clockCountthis.clockCount = res.data.data.clockinNumbers} catch (err) {let errObj: AxiosError = errAlertDialog.show({ message: errObj.message })}}build() {Stack({ alignContent: Alignment.End }) {Image(this.clockCount > 0 ? $r('app.media.clocked') : $r('app.media.unclock')).objectFit(ImageFit.Fill)if (this.clockCount > 0) {Column() {Text('已连续打卡').fontSize(8)Text() {Span(this.clockCount.toString()).fontWeight(600).fontSize(12)Span(' 天').fontSize(10)}}.width('50')} else {Text('打卡').width('50').textAlign(TextAlign.Center).fontSize((18)).fontWeight(500).fontColor('#333C4F').margin({ bottom: (4) })}}.width((74)).height((28))}
}
- ✔️HdClockIn.ets 内部点击自己容器时,post请求接口
clockin
(接口文档) 完成打卡功能
import axios, { AxiosResponse, AxiosError } from '@ohos/axios'
import { iClock } from '../models/clockinmodel'
import { iLoginUserModel, iResponseModel } from '../models/datamodel'@Component
export struct HdClockIn {@State clockCount: number = 0 // >0显示已连续打卡xx天 <=0 显示打卡@StorageProp('user') currentUser: iLoginUserModel = {} as iLoginUserModelaboutToAppear(): void {// 请求服务器接口 https://api-harmony-teach.itheima.net/hm/clockinInfo 获取打卡天数this.getClockCount()}// 1. 获取打卡天数async getClockCount() {try {// 1. 导入axios相关包发请求const req = axios.create()let res: AxiosResponse<iResponseModel<iClock>> = await req.request({url: 'https://api-harmony-teach.itheima.net/hm/clockinInfo',headers: {'Authorization': `Bearer ${this.currentUser.token}`}})// 2. 请求url获得数据赋值this.clockCountthis.clockCount = res.data.data.clockinNumbers} catch (err) {let errObj: AxiosError = errAlertDialog.show({ message: errObj.message })}}// 2. 打卡操作async ClockIn() {try {// 1. 发送post请求https://api-harmony-teach.itheima.net/hm/clockin// 记得带上Authorization对应的token(务必要先登录)const req = axios.create()let res: AxiosResponse<iResponseModel<iClock>> =await req.request({method: 'POST',url: 'https://api-harmony-teach.itheima.net/hm/clockin',headers: {'Authorization': `Bearer ${this.currentUser.token}`}})// AlertDialog.show({ message: JSON.stringify(res.data) })// 2. 获取服务器的数据赋值给this.clockCountthis.clockCount = res.data.data.clockinNumbers} catch (err) {let errObj: AxiosError = errAlertDialog.show({ message: errObj.message })}}build() {Stack({ alignContent: Alignment.End }) {Image(this.clockCount > 0 ? $r('app.media.clocked') : $r('app.media.unclock')).objectFit(ImageFit.Fill)if (this.clockCount > 0) {Column() {Text('已连续打卡').fontSize(8)Text() {Span(this.clockCount.toString()).fontWeight(600).fontSize(12)Span(' 天').fontSize(10)}}.width('50')} else {Text('打卡').width('50').textAlign(TextAlign.Center).fontSize((18)).fontWeight(500).fontColor('#333C4F').margin({ bottom: (4) }).onClick(() => {this.ClockIn()})}}.width((74)).height((28))}
}
相关文章:
登录固定账号和密码:
接口文档 【apifox】面试宝典 个人中心-保存用户数据信息 - 教学练测项目-面试宝典-鸿蒙 登录固定账号和密码: 账号:hmheima 密码:Hmheima%123 UI设计稿 【腾讯 CoDesign】面试宝典 CoDesign - 腾讯自研设计协作平台 访问密码࿱…...
深度学习/强化学习调参技巧
深度调优策略 1. 学习率调整 技巧:学习率是最重要的超参数之一。过大可能导致训练不稳定,过小则收敛速度慢。可以使用学习率衰减(Learning Rate Decay)或自适应学习率方法(如Adam、RMSprop)来动态调整学习…...
Java常用正则表达式(身份证号、邮箱、手机号)格式校验
目录 身份证号的正则表达式 代码解释 正则表达式 方法 isValidIDCard 注意事项 校验邮箱的正则表达式 代码解释 正则表达式 方法 isValidEmail 注意事项 手机号的正则表达式 中国大陆手机号校验(支持空字符串) 代码解释 通用手机号校验&am…...
1.12.信息系统的分类【ES】
专家系统(ES)技术架构深度解析 一、ES核心定义 🧠 智能决策中枢 由三大核心能力构建的领域专家模拟系统: 存储专家级领域知识(10^4规则量级)支持不确定性推理(置信度>85%)动态…...
c语言程序设计--(结构体、共用体)冲刺考研复试中的面试问答,来看看我是怎么回答的吧!
结构体 1、谈谈你对结构体的理解。 答,首先在结构的基础知识上,结构是一些值的集合,这些值称为成员变量结构的每个成员可以是不同类型的变量。结构体其实就是把(一些单一类型的数据)不同类型的数据组合在一起的做法便…...
闭包:JavaScript 中的隐形大杀器
你可能已经在很多地方听说过闭包这个词,尤其是涉及到 JavaScript 的作用域和异步操作时。闭包是 JavaScript 中非常核心的概念,然而它又非常容易让开发者感到困惑。今天我们就来深入剖析闭包,帮助你真正理解它的工作原理,以及如何…...
分布式锁—7.Curator的分布式锁一
大纲 1.Curator的可重入锁的源码 2.Curator的非可重入锁的源码 3.Curator的可重入读写锁的源码 4.Curator的MultiLock源码 5.Curator的Semaphore源码 1.Curator的可重入锁的源码 (1)InterProcessMutex获取分布式锁 (2)InterProcessMutex的初始化 (3)InterProcessMutex.…...
linux---天气爬虫
代码概述 这段代码实现了一个天气查询系统,支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型,并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求,并解析返回的 JSON 数据来展示天气信息。 #in…...
C++蓝桥杯基础篇(九)
片头 嗨!小伙伴们,大家好~ 今天我们将学习蓝桥杯基础篇(十),学习函数相关知识,准备好了吗?咱们开始咯! 一、函数基础 一个典型的函数定义包括以下部分:返回类型、函数名…...
Java Spring MVC (2)
常见的Request Controller 和 Response Controller 的区别 用餐厅点餐来理解 想象你去一家餐厅吃饭: Request Controller(接单员):负责处理你的点餐请求,记录你的口味、桌号等信息。Response Controller(…...
Elasticsearch:使用 BigQuery 提取数据
作者:来自 Elastic Jeffrey Rengifo 了解如何使用 Python 在 Elasticsearch 中索引和搜索 Google BigQuery 数据。 BigQuery 是 Google 的一个平台,允许你将来自不同来源和服务的数据集中到一个存储库中。它还支持数据分析,并可使用生成式 AI…...
接口-菜品分页查询
业务内容 页面上菜品根据菜品名称、菜品分类、售卖状态三个字段进行分页查询。 在请求参数中携带了菜品名称、菜品分类、售卖状态三个字段参数。 返回PageResult类型的实体。 注意:在返回数据中在records下有个categoryName,这个字段的内容在category…...
springboot3 RestClient、HTTP 客户端区别
1 RestClient使用 RestClient 是 Spring 6.1 M2 中引入的同步 HTTP 客户端,它取代了 RestTemplate。同步 HTTP 客户端以阻塞方式发送和接收 HTTP 请求和响应,这意味着它会等待每个请求完成后才继续下一个请求。本文将带你了解 RestClient 的功能以及它与…...
自我训练模型:通往未来的必经之路?
摘要 在探讨是否唯有通过自我训练模型才能掌握未来的问题时,文章强调了底层技术的重要性。当前,许多人倾向于关注应用层的便捷性,却忽视了支撑这一切的根本——底层技术。将模型简单视为产品是一种短视行为,长远来看,理…...
RuoYi框架添加自己的模块(学生管理系统CRUD)
RuoYi框架添加自己的模块(学生管理系统) 框架顺利运行 首先肯定要顺利运行框架了,这个我不多说了 设计数据库表 在ry数据库中添加表tb_student 表字段如图所示 如图所示 注意id字段是自增的 注释部分是后面成功后前端要展示的部分 导入…...
linux查看python版本
1.查看Linux是否安装python yum list all | grep python 2.Linux安装python yum install python 3.Linux查看python版本 python -V...
算法题(89):单项链表
审题: 本题需要我们实现一个可以执行三个指令的数据结构来解决这里的问题 思路: 方法一:利用数组模拟链表 由于这里涉及插入删除操作,所以我们不能使用数组结构存储数据,这样子会超时,所以我们就利用数组来…...
开源之夏经验分享|Koupleless 社区黄兴抗:在开源中培养工程思维
开源之夏经验分享|Koupleless 社区黄兴抗:在开源中培养工程思维 文|黄兴抗 电子信息工程专业 Koupleless 社区贡献者 就读于南昌师范学院,电子信息工程专业的大三学生。 本文 2634 字,预计阅读 7 分钟 今天 SOFAStack 邀…...
体验开源openeuler openharmony stratovirt模拟器
文档 openeuler社区面向数字基础设施的开源操作系统 openharmony社区 OpenHarmony是由开放原子开源基金会(OpenAtom Foundation)孵化及运营的开源项目, 目标是面向全场景、全连接、全智能时代、基于开源的方式,搭建一个智能终端设备操作系统…...
【AI实践】基于TensorFlow/Keras的CNN(卷积神经网络)简单实现:手写数字识别的工程实践
深度神经网络系列文章 【AI深度学习网络】卷积神经网络(CNN)入门指南:从生物启发的原理到现代架构演进【AI实践】基于TensorFlow/Keras的CNN(卷积神经网络)简单实现:手写数字识别的工程实践 引言 在深度…...
深入探讨AI-Ops架构 第一讲 - 运维的进化历程以及未来发展趋势
首先,让我们一起回顾运维的进化之路,然后再深入探讨AI-Ops架构的细节。 运维的进化历程 1. AI 大范围普及前的运维状态 (传统运维) 在AI技术尚未广泛渗透到运维领域之前,我们称之为传统运维,其主要特点是: 人工驱动…...
2025年全球生成式AI消费应用发展趋势报告
原文链接:The Top 100 Gen AI Consumer Apps - 4th Edition | Andreessen Horowitz 核心要点:本报告由a16z发布,深度解析了2025年全球生成式AI消费应用的发展格局,揭示了技术迭代与商业化加速的双重趋势。 报告显示,A…...
VBA 列方向合并单元格,左侧范围大于右侧范围
实现功能如下: excel指定行列范围内的所有单元格 规则1:每一列的连续相同的值合并单元格 规则2:每一列的第一个非空单元格与其下方的所有空白单元格合并单元 规则3:优先左侧列合并单元格,合并后,右侧的单元…...
设计AI芯片架构的入门 研究生入行数字芯片设计、验证的项目 opentitan
前言 这几年芯片设计行业在国内像坐过山车。时而高亢,时而低潮。最近又因为AI的热潮开始high起来。到底芯片行业的规律是如何? 我谈谈自己观点:芯片设计是“劳动密集型”行业。 “EDA和工具高度标准化和代工厂的工艺标准化之后,芯…...
【弹性计算】异构计算云服务和 AI 加速器(二):适用场景
异构计算云服务和 AI 加速器(二):适用场景 1.图形处理2.视频处理3.计算4.人工智能 异构计算 目前已经被广泛地应用于生产和生活当中,主要应用场景如下图所示。 1.图形处理 GPU 云服务器在传统的图形处理领域具有强大的优势&…...
JVM常用概念之移动GC和局部性
问题 非移动GC一定比移动GC好吗? 基础知识 移动GC和非移动GC 移动GC 在进行垃圾回收时,为了减少碎片而移动对象来顺利完成垃圾回收的GC。 Serial GC 在单线程环境下,它在标记-清除(Mark-Sweep)算法的基础上进行…...
微服务保护:Sentinel
home | Sentinelhttps://sentinelguard.io/zh-cn/ 微服务保护的方案有很多,比如: 请求限流 线程隔离 服务熔断 服务故障最重要原因,就是并发太高!解决了这个问题,就能避免大部分故障。当然,接口的并发…...
使用Wireshark截取并解密摄像头画面
在物联网(IoT)设备普及的今天,安全摄像头等智能设备在追求便捷的同时,往往忽视了数据传输过程中的加密保护。很多摄像头默认通过 HTTP 协议传输数据,而非加密的 HTTPS,从而给潜在攻击者留下了可乘之机。本文…...
IDEA 基础配置: maven配置 | 服务窗口配置
文章目录 IDEA版本与MAVEN版本对应关系maven配置镜像源插件idea打开服务工具窗口IDEA中的一些常见问题及其解决方案IDEA版本与MAVEN版本对应关系 查找发布时间在IDEA版本之前的dea2021可以使用maven3.8以及以前的版本 比如我是idea2021.2.2 ,需要将 maven 退到 apache-maven-3.…...
20250-3-8 树的存储结构
一、树的逻辑结构回顾 树:一个分支结点可以有多课子树 如果按照二叉树的存储来实现树的存储,则只依靠数组下标,无法反映结点之间的逻辑关系。 二、双亲表示法(顺序存储) 1.因此:我们可以用链式存储的方法&…...
Visual-RFT视觉强化微调:用「试错学习」教会AI看图说话
📜 文献卡 英文题目: Visual-RFT: Visual Reinforcement Fine-Tuning;作者: Ziyu Liu; Zeyi Sun; Yuhang Zang; Xiaoyi Dong; Yuhang Cao; Haodong Duan; Dahua Lin; Jiaqi WangDOI: 10.48550/arXiv.2503.01785摘要翻译: 像OpenAI o1这样的大型推理模型中的强化微调…...
PDF处理控件Aspose.PDF,如何实现企业级PDF处理
PDF处理为何成为开发者的“隐形雷区”? “手动调整200页PDF目录耗时3天,扫描件文字识别错误导致数据混乱,跨平台渲染格式崩坏引发客户投诉……” 作为开发者,你是否也在为PDF处理的复杂细节消耗大量精力?Aspose.PDF凭…...
DeepSeek-R1本地化部署(Mac)
一、下载 Ollama 本地化部署需要用到 Ollama,它能支持很多大模型。官方网站:https://ollama.com/ 点击 Download 即可,支持macOS,Linux 和 Windows;我下载的是 mac 版本,要求macOS 11 Big Sur or later,Ol…...
Swift Package Manager (SPM) 创建并集成本地库
在macOS 项目中,使用 Swift Package Manager (SPM) 创建并集成本地库的完整步骤。 创建一个macos应用程序,选择 swift、oc、swiftui都可以。 创建好应用之后,开始创建SPM本地库。 打开终端app,进入项目根目录,逐次输…...
分布式锁—6.Redisson的同步器组件
大纲 1.Redisson的分布式锁简单总结 2.Redisson的Semaphore简介 3.Redisson的Semaphore源码剖析 4.Redisson的CountDownLatch简介 5.Redisson的CountDownLatch源码剖析 1.Redisson的分布式锁简单总结 (1)可重入锁RedissonLock (2)公平锁RedissonFairLock (3)联锁MultiL…...
文献分享: ConstBERT固定数目向量编码文档
😂图放这了,大道至简的 idea \text{idea} idea不愧是 ECIR \text{ECIR} ECIR 👉原论文 1. ConstBERT \textbf{1. ConstBERT} 1. ConstBERT的原理 1️⃣模型的改进点:相较于 ColBERT \text{ColBERT} ColBERT为每个 Token \text{Tok…...
如何使用SSH命令安全连接并转发端口到远程服务器
ssh -p 22546 rootconnect.westc.gpuhub.com d6IS/mQKq/iG ssh -CNgv -L 6006:127.0.0.1:6006 rootconnect.westc.gpuhub.com -p 22546 第一条命令:用于登录远程服务器,进行交互式操作。第二条命令:用于建立 SSH 隧道,进行端口转…...
SolidWorks 转 PDF3D 技术详解
在现代工程设计与制造流程中,不同软件间的数据交互与格式转换至关重要。将 SolidWorks 模型转换为 PDF3D 格式,能有效解决模型展示、数据共享以及跨平台协作等问题。本文将深入探讨 SolidWorks 转 PDF3D 的技术原理、操作流程及相关注意事项,…...
9.2 EvictionManager源码解读
本节重点总结 : evictionManager初始化了两个相同的manager对象 evictionManager做本机驱逐pod的判定和厨房evictionAdmitHandler用来kubelet创建Pod前进依据本机的资源压力进行准入检查 evictionManager判断内存驱逐阈值有两种方法 第一种使用内核的memcg的通知机制ÿ…...
考研数一非数竞赛复习之Stolz定理求解数列极限
在非数类大学生数学竞赛中,Stolz定理作为一种强大的工具,经常被用来解决和式数列极限的问题,也被誉为离散版的’洛必达’方法,它提供了一种简洁而有效的方法,使得原本复杂繁琐的极限计算过程变得直观明了。本文&#x…...
整理一下高级设施农业栽培学这门课程的所有知识点
整理一下高级设施农业栽培学这门课程的所有知识点 以下是高级设施农业栽培学这门课程从入门到精通需要学习的知识点: 一、设施农业概述 设施农业的概念与发展历程 了解设施农业的定义、特点及作用,掌握其发展历程、现状与未来趋势。熟悉国内外设施农业…...
2025最新软件测试面试八股文(含答案+文档)
1、请试着比较一下黑盒测试、白盒测试、单元测试、集成测试、系统测试、验收测试的区别与联系。 参考答案: 黑盒测试:已知产品的功能设计规格,可以进行测试证明每个实现了的功能是否符合要求。 白盒测试:已知产品的内部工作过程…...
系统架构设计师—系统架构设计篇—基于体系结构的软件开发方法
文章目录 概述基于体系结构的开发模型-ABSDM体系结构需求体系结构设计体系结构文档化体系结构复审体系结构实现体系结构演化 概述 基于体系结构(架构)的软件设计(Architecture-Based Software Design,ABSD)方法。 AB…...
求最大公约数【C/C++】
大家好啊,欢迎来到本博客( •̀ ω •́ )✧,我将带领大家详细的了解最大公约数的思想与解法。 一、什么是公约数 公约数,也称为公因数,是指两个或多个整数共有的因数。具体来说,如果一个整数能被两个或多个整数整除&…...
Transformer 代码剖析16 - BLEU分数(pytorch实现)
一、BLEU算法全景图 #mermaid-svg-uwjb5mQ2KAC6Rqbp {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-uwjb5mQ2KAC6Rqbp .error-icon{fill:#552222;}#mermaid-svg-uwjb5mQ2KAC6Rqbp .error-text{fill:#552222;stroke:…...
手机屏幕摔不显示了,如何用其他屏幕临时显示,用来导出资料或者清理手机
首先准备一个拓展坞 然后 插入一个外接的U盘 插入鼠标 插入有数字小键盘区的键盘 然后准备一根高清线,一端链接电脑显示器,一端插入拓展坞 把拓展坞的连接线,插入手机充电口(可能会需要转接头) 然后确保手机开机 按下键盘…...
labelimg标注的xml标签转换为yolo格式标签
本文不生产技术,只做技术的搬运工!!! 前言 在yolo训练时,我们需要对图像进行标注,而使用labelimg标注时如果直接选择输出yolo格式的数据集,则原始数据的很多信息无法被保存,因此一版…...
Linux云计算SRE-第十七周
1. 做三个节点的redis集群。 1、编辑redis节点node0(10.0.0.100)、node1(10.0.0.110)、node2(10.0.0.120)的安装脚本 [rootnode0 ~]# vim install_redis.sh#!/bin/bash # 指定脚本解释器为bashREDIS_VERSIONredis-7.2.7 # 定义Redis的版本号PASSWORD123456 # 设置Redis的访问…...
K8S学习之基础十八:k8s的灰度发布和金丝雀部署
灰度发布 逐步扩大新版本的发布范围,从少量用户逐步扩展到全体用户。 特点是分阶段发布、持续监控、逐步扩展 适合需要逐步验证和降低风险的更新 金丝雀部署 将新版本先部署到一小部分用户或服务器,观察其表现,再决定是否全面推广。 特点&…...
WSL with NVIDIA Container Toolkit
一、wsl 下安装 docker 会提示安装 docekr 桌面版,所以直接安装 docker 桌面版本即可 二、安装 NVIDIA Container Toolkit NVIDIA Container Toolkit仓库 https://github.com/NVIDIA/nvidia-container-toolkitgithub.com/NVIDIA/nvidia-container-toolkit 安装…...