HarmonyOS学习第六周(实践版)(二)

[复制链接]
发表于 2026-2-4 03:27:32 | 显示全部楼层 |阅读模式
 打卡页

打卡页应该分为两种状态,未登录状态和登录状态,未登录状态提示用户登录,登录状态则可以查询打卡圈(即自己和别人的打卡环境)
这个页面紧张分为“英语打卡圈”标题和下半部分,以是上面是雷同的,下面为封装的自界说组件。
未登录状态:


未登录状态部分很简单,紧张就是提示用户前往登录,因此点击“去登录”后要举行跳转,跳转到登录页。代码如下:
  1. unLoginBuilder() {
  2.     Column({ space: 30 }) {
  3.       Image($r("app.media.ic_unLogin_bg"))
  4.         .width(177)
  5.         .height(177)
  6.       Text('未登录暂⽆数据')
  7.         .fontSize(14)
  8.         .fontColor('#999999')
  9.       Button('去登录')
  10.         .fontColor(Color.White)
  11.         .fontSize(14)
  12.         .width(100)
  13.         .height(34)
  14.         .backgroundColor('#43C6A0')
  15.         .onClick(() => router.pushUrl({ url: 'pages/LoginPage' }))
  16.     }
复制代码
登录状态


登录后的页面,很显着是一个list列表,每个打卡信息用卡片的情势显现,因此我们要封装一个卡片组件,除此之外,我们可以看到右侧有两个按钮,一个回到顶部和一个革新按钮。
我们要怎么获取这些信息呢,这就必要用到接口了,通过接口从后端调取数据然厥后举行渲染。封装的函数如下:
  1. async getPostInfoPage(){
  2.     let response = await getAllPost(this.page,10)
  3.     response.data.data.records.forEach(post=>this.postInfoList.push(new PostInfo(post)))
  4.     this.total=response.data.data.total
  5.     this.page++
  6.   }
复制代码
调用getAllPost接口, 传入的page是页数,10是每页包罗多少条打卡信息。获取之后通过foreach转换为PostInfo格式,PostInfo是封装好的,专门用来存储打卡卡片信息的一个数据格式。然后存在postInfoList列表中,用于后续的渲染。
  1. export class PostInfo {
  2.   id: number;
  3.   postText: string;
  4.   rightCount: number;
  5.   answeredCount: number;
  6.   timeUsed: number;
  7.   createTime: string;
  8.   likeCount: number;
  9.   nickname: string;
  10.   avatarUrl: string
  11.   isLike: boolean;
  12.   constructor(post:{id: number, postText: string, rightCount: number, answeredCount: number, timeUsed: number, createTime: string, likeCount: number, nickname: string, avatarUrl: string, isLike: boolean}) {
  13.     this.id = post.id;
  14.     this.postText = post.postText;
  15.     this.rightCount = post.rightCount;
  16.     this.answeredCount = post.answeredCount;
  17.     this.timeUsed = post.timeUsed;
  18.     this.createTime = post.createTime;
  19.     this.likeCount = post.likeCount;
  20.     this.nickname = post.nickname;
  21.     this.avatarUrl = post.avatarUrl;
  22.     this.isLike = post.isLike;
  23.   }
  24. }
复制代码
卡片组件

通过前面那么多次的报告,信赖各人可以很快看出来这个卡片的布局,分为三部分,上面为Image +text +blank +text,中心就是一个text,下面则是和前面统计页面雷同的卡片页面,可以复用我们前面封装的组件信息。值得留意的是在卡片右边尚有一个小小的点赞按钮。

点赞

点赞的UI是不难,必要留意的是他的逻辑,第一、一个人只能点赞一次,再点一次会取消点赞,因此我们必要一个boolean变量来记录。第二、多个差异用户点赞的话,他的点赞数应该是累加的,必要我们上传到数据库,要不我们永世只能看到0和1.(代码中的cancelLike和Like就是两个接口,用于记录此用户是否点赞过这个卡片)
  1. Column({ space: 10 }) {
  2.       Row({ space: 10 }) {
  3.         Image(this.post.avatarUrl)
  4.           .height(40)
  5.           .width(40)
  6.           .borderRadius(20)
  7.         Text(this.post.nickname)
  8.           .height(40)
  9.           .fontSize(14)
  10.           .fontWeight(FontWeight.Bold)
  11.         Blank()
  12.         Text(this.post.createTime)
  13.           .height(40)
  14.           .fontSize(14)
  15.           .fontColor('#999999')
  16.           .fontWeight(FontWeight.Medium)
  17.       }.width('100%')
  18.       Text(this.post.postText)
  19.         .width('100%')
  20.       Row() {
  21.         Column() {
  22.           StatItem({
  23.             icon: $r('app.media.ic_timer_white'),
  24.             name: '⽤时',
  25.             fontColor: Color.White
  26.           }) {
  27.             Text(convertMillisecondsToTime(this.post.timeUsed))
  28.               .statTextStyle()
  29.           }
  30.           StatItem({
  31.             icon: $r('app.media.ic_accuracy_white'),
  32.             name: '准确率',
  33.             fontColor: Color.White
  34.           }) {
  35.             Text((this.post.answeredCount === 0 ? 0 : this.post.rightCount / this.post.answeredCount * 100).toFixed(0) + '%')
  36.               .statTextStyle()
  37.           }
  38.           StatItem({
  39.             icon: $r('app.media.ic_count_white'),
  40.             name: '个数',
  41.             fontColor: Color.White
  42.           }) {
  43.             Text(this.post.answeredCount.toString())
  44.               .statTextStyle()
  45.           }
  46.         }
  47.         .padding(10)
  48.         .borderRadius(10)
  49.         .layoutWeight(1)
  50.         .backgroundImage($r('app.media.img_post_bg'))
  51.         .backgroundImageSize(ImageSize.Cover)
  52.         Column() {
  53.           Text(this.post.likeCount.toString())
  54.             .fontSize(12)
  55.             .fontWeight(FontWeight.Medium)
  56.             .fontColor(this.post.isLike ? '#3ECBA1' : '#000000')
  57.           Image(this.post.isLike ? $r('app.media.ic_post_like_selected') : $r('app.media.ic_post_like'))
  58.             .width(26)
  59.             .height(26)
  60.             .onClick(() => {
  61.               //todo:点赞/取消点赞
  62.               if(this.post.isLike){
  63.                 this.post.isLike=false
  64.                 this.post.likeCount--
  65.                 cancelLike(this.post.id)
  66.               }else{
  67.                 this.post.isLike=true
  68.                 this.post.likeCount++
  69.                 like(this.post.id)
  70.               }
  71.             })
  72.         }.width(50)
  73.       }.width('100%')
  74.       .alignItems(VerticalAlign.Bottom)
  75.     }
复制代码
 
回到顶部 
  1. Column({ space: 20 }) {
  2.         Button({ type: ButtonType.Circle }) {
  3.           Image($r('app.media.ic_top'))
  4.             .height(14)
  5.             .width(14)
  6.         }
  7.         .height(40)
  8.         .width(40)
  9.         .backgroundColor(Color.Black)
  10.         .opacity(0.5)
  11.         .onClick(() => {
  12.           //返回顶部
  13.           this.scroller.scrollToIndex(0)
  14.         })
复制代码
UI不必多说,就是一个按钮组件中放置了一个Image,紧张是回到顶部的逻辑是怎么样的呢。这里我们必要用的一个新的api:scroll可滚动的容器组件,当子组件的布局尺寸高出父组件的尺寸时,内容可以滚动。此中有个scrollToIndex属性,作用是滑动到指定Index,仅实用与grid和list布局,恰恰我们就是list布局,因此只要我们填入0,他就可以滑动到索引为0的地方,也就是列表的最上方,如许就实现了回到顶部的功能。

革新

这个逻辑比力简单,将全部信息重置为初始状态,然后重新获取一次列表,就实现了革新功能。
  1. async refresh(){
  2.     this.isLoading=true
  3.     this.postInfoList=[]
  4.     this.page=1
  5.     this.total=0
  6.     await this.getPostInfoPage()
  7.     this.isLoading=false
  8.   }
复制代码
登录页


这个页面中有一个很显着和其他页面差异的地方是,他的左上角有一个返回按钮,由于登录页面一样寻常不是一个长期使用的页面,一样寻常我们最开始使用登录一次,后续就可以使用了。因而只是一个临时界面,一样寻常由打卡圈界面、个人界面大概打卡弹窗跳转而来,以是返回按钮使用到了router路由中的back函数,如许就可以跳转回刚才的界面。
  1. Image($r('app.media.ic_back'))
  2.         .backStyle()
  3.         .alignSelf(ItemAlign.Start)
  4.         .onClick(() => {
  5.           //返回上⼀⻚⾯
  6.           router.back()
  7.         })
复制代码
 输入手机号码和验证码是用到两个TextInput组件,这也是很常用的组件,不外多赘述。这个获取验证码按钮是接纳调取后端接口,直接返回一个验证码并表如今TextInput中,登录也是接纳了调用后端接口的方式,将手机号和验证码发给后端举行登录。
  1. Button('获取验证码')
  2.             .buttonStyle(Color.White, Color.Black)
  3.             .onClick(async () => {
  4.               //获取验证码
  5.              let response = await sendCode(this.phone)
  6.               this.code = response.data.data
  7.             })
复制代码
  1. Button('⽴即登录')
  2.           .buttonStyle(Color.Black, Color.White)
  3.           .width('100%')
  4.           .margin({ top: 50 })
  5.           .onClick(async () => {
  6.             //登录
  7.             let response = await login(this.phone,this.code)
  8.             this.token = response.data.data
  9.             }
复制代码
 

个人页


个人页比力简单,退出登录则将信息重置,未登录则点击跳转到登录页面,在这就不讲啦。

调用接口

这个项目中前后端交互的方式是大部分学前端同砚比力认识的axios,起首你要先下载第三方库axios。Axios 是一个基于 Promise 的 HTTP 客户端,它可以让你更方便地举行 HTTP 哀求和处理惩罚相应,支持异步利用,并提供了简便的 API 接口。
起首,我们先创建一个axios实例
  1. export const instance = axios.create({
  2.   baseURL: 'http://192.168.0.103:3000',
  3.   timeout: 2000
  4. })
复制代码
baseUrl的作用是用变量名取代背面长串的url,由于每个api都是拼接在baseurl之后的,前半部分都是雷同的。timeout是超时时长,高出这个时间则视为哀求超时,必要查抄网络大概url是否精确等。
哀求拦截器
  1. // 添加请求拦截器
  2. instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  3.   // 通过AppStorage获取token
  4.   const token = AppStorage.Get('token')
  5.   if (token) {
  6.     // 若token存在,则将其添加到请求头
  7.     config.headers['token'] = token
  8.   }
  9.   return config;
  10. }, (error: AxiosError) => {
  11.   //若出现异常,则提示异常信息
  12.   promptAction.showToast({ message: error.message })
  13.   return Promise.reject(error);
  14. });
复制代码
 每个代码块的作用已用表明卸载了代码块之前,这里先容一下token,我对token的认知是:token是一个用于标识用户身份的代码串。当一个用户注册后,后端就会给其赋予一个token,每次用户哀求利用时,就会在哀求头中带上token,后端吸取后通过剖析token,就可以知道是哪个用户举行了利用。

相应拦截器
  1. // 添加响应拦截器
  2. instance.interceptors.response.use((response: AxiosResponse) => {
  3.   // 若服务器返回的是正常数据,不做任何处理
  4.   if (response.data.code === 200) {
  5.     return response
  6.   } else {
  7.     //若服务器返回的是异常数据,则提示异常信息
  8.     promptAction.showToast({ message: response.data.message })
  9.     return Promise.reject(response.data.message)
  10.   }
  11. }, (error: AxiosError) => {
  12.   //若出现异常,则提示异常信息
  13.   promptAction.showToast({ message: error.message })
  14.   return Promise.reject(error);
  15. });
复制代码
API

下面展示一个get哀求和一个post哀求
Get

get哀求一样寻常用于哀求数据
  1. //获取验证码
  2. export function sendCode(phone: string) {
  3.   return instance.get('/word/user/code', { params: { phone: phone } });
  4. }
复制代码
比如这个获取验证码的哀求,我们转达手机号给后端,后端return给我们一个验证码。instance是刚刚我们创建的axios实例。
Post

post哀求一样寻常用于发送数据
  1. //登录
  2. export function login(phone: string, code: string) {
  3.   return instance.post('/word/user/login', { phone: phone, code: code });
  4. }
复制代码
比如这个登录哀求,我们必要转达手机号和验证码给后端,后端举行验证,乐成则登录,失败则返回错误信息。

不管是post照旧get大概其他哀求,我们都可以看到一个形如“/word/user/login”的字符串,这个就是拼接到前面实例中的baseUrl之后形成完备的url,使得哀求得以乐成发送。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表