登录固定账号和密码:账号:hmheima

  • 接口文档

【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/30286.html

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

相关文章

Docker和DockerCompose基础教程及安装教程

Docker的应用场景 Web 应用的自动化打包和发布。自动化测试和持续集成、发布。在服务型环境中部署和调整数据库或其他的后台应用。从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。 CentOS Docker 安装 使用官方安装脚本自动安装 安装命令…

C语言学习笔记-进阶(7)字符串函数3

1. strstr的使用和模拟实现 char * strstr ( const char * str1, const char * str2); Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1. &#xff08;函数返回字符串str2在字符串str1中第⼀次出现的位置&#x…

Elasticsearch为索引设置自动时间戳,ES自动时间戳

文章目录 0、思路1、配置 ingest pipeline2、在索引映射中启用_source字段的时间戳3、使用 index template 全局设置时间戳4、写入测试数据5、验证结果6、总结 在使用 Elasticsearch 进行数据存储和检索时&#xff0c;时间戳字段是一个非常重要的组成部分。它可以帮助我们追踪数…

2024四川大学计算机考研复试上机真题

2024四川大学计算机考研复试上机真题 2024四川大学计算机考研复试机试真题 历年四川大学计算机考研复试机试真题 在线评测&#xff1a;https://app2098.acapp.acwing.com.cn/ 分数求和 题目描述 有一分数序列&#xff1a; 2/1 3/2 5/3 8/5 13/8 21/13… 求出这个数列的前 …

韩国互联网巨头 NAVER 如何借助 StarRocks 实现实时数据洞察

作者&#xff1a; Youngjin Kim Team Leader, NAVER Moweon Lee Data Engineer, NAVER 导读&#xff1a;开源无国界&#xff0c;在“StarRocks 全球用户精选案例”专栏中&#xff0c;我们将介绍韩国互联网巨头 NAVER 的 StarRocks 实践案例。 NAVER 成立于 1999 年&#xff0…

VanillaVueSvelteReactSolidAngularPreact前端框架/库的简要介绍及其优势

VanillaVueSvelteReactSolidAngularPreact前端框架/库的简要介绍及其优势。以下是这些前端框架/库的简要介绍及其优势&#xff1a; 1. Vanilla 定义&#xff1a;Vanilla 并不是一个框架&#xff0c;而是指 原生 JavaScript&#xff08;即不使用任何框架或库&#xff09;。优势…

图像滑块对比功能的开发记录

背景介绍 最近&#xff0c;公司需要开发一款在线图像压缩工具&#xff0c;其中的一个关键功能是让用户直观地比较压缩前后的图像效果。因此&#xff0c;我们设计了一个对比组件&#xff0c;它允许用户通过拖动滑块&#xff0c;动态调整两张图像的显示区域&#xff0c;从而清晰…

从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(十一) 实现服务端和客户端socketio 连接

1.后端部分 socketIO文档参考Socket.IO 首先在lib下新建socket.js文件 参考服务器API | Socket.IO import {Server} from socket.io; import http from http import express from "express"const app express() const server http.createServer(app) const io …

慕慕手记项目日志 项目从开发到部署多环境配置 2025-3-8

慕慕手记项目日志 项目从开发到部署多环境配置 2025-3-8 现在是已经到了课程的第十章了&#xff0c;开始进行配置项目环境了。现在要完成的任务是项目可以正常运行&#xff0c;而且可以自由切换配置&#xff0c;开发/测试。 下面是当前的目录结构图&#xff1a; 现在来解释一…

《Python实战进阶》No15: 数据可视化:Matplotlib 与 Seaborn 的高级用法

No15: 数据可视化&#xff1a;Matplotlib 与 Seaborn 的高级用法 Matplotlib 是 Python 中最受欢迎的数据可视化软件包之一&#xff0c;支持跨平台运行&#xff0c;它是 Python 常用的 2D 绘图库&#xff0c;同时它也提供了一部分 3D 绘图接口。Matplotlib 通常与 NumPy、Pand…

C++学习之格斗小游戏综合案例

C格斗游戏效果视频 1.案例简介 #include "broadSword.h" //构造函数 BroadSword::BroadSword() { FileManager fm; map<string, map<string, string>> mWeapon; fm.loadCSVData("Weapons.csv", mWeapon); //武器id string id …

LeetCodeHot100

1.两数之和 解题思路&#xff1a; 1.暴力解法 两个for循环解决问题 时间复杂度为 O(n2) class Solution {public int[] twoSum(int[] nums, int target) {int n nums.length;for (int i 0; i < n; i) {for (int j i 1; j < n; j) {if (nums[i] nums[j] target) {…

大语言模型进化论:从达尔文到AI的启示与展望

文章大纲 引言大语言模型中的“进化论”思想体现遗传变异过度繁殖和生存斗争大模型“过度繁殖”与“生存竞争”机制解析**一、过度繁殖:技术迭代的指数级爆发****二、生存竞争:计算资源的达尔文战场****三、生存竞争胜出关键要素****四、行业竞争格局演化趋势**核心结论自然选…

Spring Boot自动装配原理

实例&#xff1a; 引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 配置服务器 redis自动装配 原理&#xff1a; 一切都源于一个关键的注解…

在Windows系统上安装和配置Redis服务

&#x1f31f; 在Windows系统上安装和配置Redis服务 Redis是一个高性能的键值存储数据库&#xff0c;广泛用于缓存、消息队列和实时分析等场景。虽然Redis最初是为Linux设计的&#xff0c;但也有Windows版本可供使用。今天&#xff0c;我将详细介绍如何在Windows系统上安装Red…

《安富莱嵌入式周报》第351期:DIY半导体制造,工业设备抗干扰提升方法,NASA软件开发规范,小型LCD在线UI编辑器,开源USB PD电源,开源锂电池管理

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV16C95YEEZs 《安富莱嵌入式周报》第351期&#xff1a;DIY半导体…

Vue3实战学习(IDEA中打开、启动与搭建Vue3工程极简脚手架教程(2025超详细教程)、Windows系统命令行启动Vue3工程)(2)

目录 一、命令行中重新启动已搭建好的Vue3工程。(快速上手) &#xff08;0&#xff09;Windows环境下使用命令行从零到一手动搭建Vue3工程教程。 &#xff08;1&#xff09;首先找到已建Vue3工程的目录。 &#xff08;2&#xff09;无需再下载依赖包&#xff0c;直接执行npm ru…

Visual Studio 2022新建c语言项目的详细步骤

步骤1&#xff1a;点击创建新项目 步骤2&#xff1a;到了项目模板 --> 选择“控制台应用” (在window终端运行代码。默认打印"Hello World") --> 点击 “下一步” 步骤3&#xff1a;到了配置新项目模块 --> 输入“项目名称” --> 更改“位置”路径&…

23年以后版本pycharm找不到conda可执行文件解决办法

这个问题很痛苦&#xff0c;折磨了我半天。 就是链接远程服务器的时候 就一直以为这三个都要配置 就这个conda环境这里怎么都找不到服务器的虚拟环境的python可执行文件&#xff0c;非常痛苦。 后面查找了资料&#xff0c;找了好久&#xff0c;才发现&#xff0c;原来只需要配…

基于SpringBoot的商城管理系统(源码+部署教程)

运行环境 数据库&#xff1a;MySql 编译器&#xff1a;Intellij IDEA 前端运行环境&#xff1a;node.js v12.13.0 JAVA版本&#xff1a;JDK 1.8 主要功能 基于Springboot的商城管理系统包含管理端和用户端两个部分&#xff0c;主要功能有&#xff1a; 管理端 首页商品列…