登录固定账号和密码:

  • 接口文档

【apifox】面试宝典 个人中心-保存用户数据信息 - 教学练测项目-面试宝典-鸿蒙

登录固定账号和密码:

账号:hmheima

密码:Hmheima%123

UI设计稿

【腾讯 CoDesign】面试宝典 CoDesign - 腾讯自研设计协作平台 访问密码:2YDD

【腾讯 CoDesign】面试宝典 https://codesign.qq.com/app/s/449325994072442 ON9V

✔️我的页面-沉浸式显示模式处理

课程目标

  • 我的页面要突破手机安全区域来全屏显示(沉浸式模式显示),其他页面不需要
  • 在Mine组件的aboutToAppear中使用window模块的 getLastWindowsetWindowLayoutFullScreen两个方法来处理全屏显示

设置沉浸式模式的特点:

  1. 在任何一个页面中设置过一次之后,其他页面也会跟着全屏显示

这么处理会出现问题:从其他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模块的 getLastWindowsetWindowLayoutFullScreen两个方法来处理整个应用的全屏显示

由于整个应用都开启了全屏显示,那些不需要全屏显示的页面也一并开启了,导致出现了问题:

问题:内容突破了整个安全区域,靠手机顶部显示了内容

解决:需要通过获取手机的安全高度来结合 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模块的getLastWindowgetWindowAvoidArea 来获取安全高度

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模块的getLastWindowsetWindowSystemBarProperties({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)}
}

一旦执行了颜色设置代码,所有页面都会是同一个颜色,如果需要改颜色,需要在指定页面重新设置一次新颜色

练习发现的问题:

  1. 颜色代码 一定要设置成大写的 #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))}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件测试的基础入门(二)

文章目录 一、软件&#xff08;开发&#xff09;的生命周期什么是生命周期软件&#xff08;开发&#xff09;的生命周期需求分析计划设计编码测试运行维护 二、常见的开发模型瀑布模型流程优点缺点适应的场景 螺旋模型流程优点缺点适应的场景 增量模型和迭代模型流程适应的场景…

大模型架构记录2

一 应用场景 1.1 prompt 示例 1.2 自己搭建一个UI界面&#xff0c;调用接口 可以选用不同的模型&#xff0c;需要对应的API KEY 二 Agent 使用 2.1 构建GPT

PQL查询和监控各类中间件

1 prometheus的PQL查询 1.1 Metrics数据介绍 prometheus监控中采集过来的数据统一称为Metrics数据&#xff0c;其并不是代表具体的数据格式&#xff0c;而是一种统计度量计算单位当需要为某个系统或者某个服务做监控时&#xff0c;就需要使用到 metrics prometheus支持的met…

maven学习

Maven 概述 Maven 是一个基于 POM&#xff08;Project Object Model&#xff0c;项目对象模型&#xff09; 的项目管理和构建工具&#xff0c;主要用于 Java 项目。它通过一个中央信息管理模型&#xff08;POM 文件&#xff09;来管理项目的构建、依赖、文档、报告等。Maven 的…

STM32-I2C通信外设

目录 一&#xff1a;I2C外设简介 二&#xff1a;I2C外设数据收发 三&#xff1a;I2C的复用端口 四&#xff1a;主机发送和接收 五&#xff1a;硬件I2C读写MPU6050 相关函数&#xff1a; 1.I2C_ GenerateSTART 2.I2C_ GenerateSTOP 3.I2C_ AcknowledgeConfig 4.I2C…

c语言程序设计--(数据的存储)冲刺考研复试面试简答题,看看我是怎么回答的吧!!!!!

目录 1、整型在内存中的存储是怎样的&#xff1f; 2、原码反码补码的计算方式是什么&#xff1f; 3、对于整形数据在内存中存的都是二进制补码是为什么&#xff1f; 2、什么是大端小端存储&#xff1f; 3、为什么要有大端和小端的存储方式呢&#xff1f; 1、整型在内存中的…

记录小白使用 Cursor 开发第一个微信小程序(二):创建项目、编译、预览、发布(250308)

文章目录 记录小白使用 Cursor 开发第一个微信小程序&#xff08;二&#xff09;&#xff1a;创建项目、编译、预览、发布&#xff08;250308&#xff09;一、创建项目1.1 生成提示词1.2 生成代码 二、编译预览2.1 导入项目2.2 编译预览 三、发布3.1 在微信开发者工具进行上传3…

Java 大视界 -- 基于 Java 的大数据实时数据处理框架性能评测与选型建议(121)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

MySQL索引数据结构

目录 1 索引常用的数据结构 1.1 二叉树 1.2 平衡二叉树 1.3 红黑树 1.3 Hash表 1.4 B树 1.4 B树 2 MySQL索引的数据结构 2.1 MyISAM存储引擎索引 2.2 InnoDB存储引擎索引 2.2.1 聚集索引 2.2.2 非聚集索引 2.2.3 联合索引数 2.2.4 hash索引 1 索引常用的数据结构 1.1 二叉树 二…

链表算法题目

1.两数相加 两个非空链表&#xff0c;分别表示两个整数&#xff0c;只不过是反着存储的&#xff0c;即先存储低位在存储高位。要求计算这两个链表所表示数的和&#xff0c;然后再以相同的表示方式将结果表示出来。如示例一&#xff1a;两个数分别是342和465&#xff0c;和为807…

深入解析 BitBake 日志机制:任务调度、日志记录与调试方法

1. 引言&#xff1a;为什么 BitBake 的日志机制至关重要&#xff1f; BitBake 是 Yocto 项目的核心构建工具&#xff0c;用于解析配方、管理任务依赖&#xff0c;并执行编译和打包任务。在 BitBake 构建过程中&#xff0c;日志记录机制不仅用于跟踪任务执行情况&#xff0c;还…

C++--迭代器(iterator)介绍---主要介绍vector和string中的迭代器

目录 一、迭代器&#xff08;iterator&#xff09;的定义 二、迭代器的类别 三、使用迭代器 3.1 迭代器运算符 3.2 迭代器的简单应用&#xff1a;使用迭代器将string对象的第一个字母改为大写 3.3 将迭代器从一个元素移动到另外一个元素 3.4 迭代器运算 3.5 迭代器的复…

基于multisim的自动干手器设计与仿真

1 设计的任务与要求 设计一个输出 5V 的直流稳压电源。用开关的闭合模拟手挡住光线的功能。用灯的亮灭模拟烘干吹风功能。 2 方案论证与选择 2.1 自动干手器的系统方案 本设计由5V直流电源、红外发射电路、红外接收电路、灯模拟电路构成。 1. 5V直流电源系统 这一部分是整…

【算法学习之路】7.链表算法

链表算法 前言一.原地逆置思路一&#xff1a;头插法思路二&#xff1a;双指针法思路3&#xff1a;递归 例题&#xff1a;1.头插法2.双指针法3&#xff0c;递归 二.双指针快慢指针&#xff1a;一个指针快一个指针慢例题1例题2 前言 我会将一些常用的算法以及对应的题单给写完&am…

分布式锁—7.Curator的分布式锁

大纲 1.Curator的可重入锁的源码 2.Curator的非可重入锁的源码 3.Curator的可重入读写锁的源码 4.Curator的MultiLock源码 5.Curator的Semaphore源码 1.Curator的可重入锁的源码 (1)InterProcessMutex获取分布式锁 (2)InterProcessMutex的初始化 (3)InterProcessMutex.…

IPD(集成产品开发)简介

参考&#xff1a;IPD咨询_研发管理咨询_IPD集成产品开发-百思特管理咨询集团 一、什么是IPD IPD到底是什么&#xff1f;一套体系&#xff1f;一些流程&#xff1f;还是一种模式&#xff1f; 华为在整个企业内部改革中最重要的两个项目一个是ISC(集成供应链)&#xff0c;另外…

解决Jenkins默认终止Shell产生服务进程的问题

1、Windows环境 Jenkins进行Build steps的使用Execute Windows batch command启动微服务&#xff08;Jar包&#xff09;&#xff0c;Jenkins会默认终止Shell产生的服务进程&#xff0c;而在命令行能够正常运行的服务进程。 1.1 使用命令行启动服务是正常 使用命令行执行 正常…

C 语言数据结构(三):栈和队列

目录 1. 栈 1.1 栈的概念及结构 1.2 栈的实现 2. 队列 2.1 队列的概念及结构 2.2 队列的实现 &#x1f4ac; &#xff1a;如果你在阅读过程中有任何疑问或想要进一步探讨的内容&#xff0c;欢迎在评论区畅所欲言&#xff01;我们一起学习、共同成长~&#xff01; &#x…

多模态分子预训练模型 - SPMM 评测

SPMM 是 Structure-Property Multi-Modal foundation model 的简称&#xff0c;一种多模态分子性质-结构双向预训练模型。作者是韩国科学技术研究院人工智能研究生院&#xff0c;大田&#xff0c;韩国的 Jong Chul Ye&#xff0c;于 2024 年 3 月 14 日 发表在 nature communic…

算法.习题篇

算法 — 地大复试 模拟 while循环和MOD循环计数 1.约瑟夫问题 http://bailian.openjudge.cn/practice/3254 using namespace std;bool isNoPeople(vector<bool> c)//判断当前数组是否一个小孩都没有了 {bool nopeople true;for (bool ival : c){if ( ival true)nop…