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

 打卡页

打卡页应该分为两种状态,未登录状态和登录状态,未登录状态提醒用户登录,登录状态则可以查询打卡圈(即自己和别人的打卡情况)

这个页面主要分为“英语打卡圈”标题和下半部分,所以上面是相同的,下面为封装的自定义组件。

未登录状态:

未登录状态部分很简单,主要就是提醒用户前往登录,因此点击“去登录”后要进行跳转,跳转到登录页。代码如下:

unLoginBuilder() {
    Column({ space: 30 }) {
      Image($r("app.media.ic_unLogin_bg"))
        .width(177)
        .height(177)
      Text('未登录暂⽆数据')
        .fontSize(14)
        .fontColor('#999999')
      Button('去登录')
        .fontColor(Color.White)
        .fontSize(14)
        .width(100)
        .height(34)
        .backgroundColor('#43C6A0')
        .onClick(() => router.pushUrl({ url: 'pages/LoginPage' }))
    }

登录状态

登录后的页面,很明显是一个list列表,每个打卡信息用卡片的形式展现,因此我们要封装一个卡片组件,除此之外,我们可以看到右侧有两个按钮,一个回到顶部和一个刷新按钮。

我们要怎么获取这些信息呢,这就需要用到接口了,通过接口从后端调取数据然后来进行渲染。封装的函数如下:

 async getPostInfoPage(){
    let response = await getAllPost(this.page,10)
    response.data.data.records.forEach(post=>this.postInfoList.push(new PostInfo(post)))
    this.total=response.data.data.total
    this.page++
  }

调用getAllPost接口, 传入的page是页数,10是每页包含多少条打卡信息。获取之后通过foreach转换为PostInfo格式,PostInfo是封装好的,专门用来存储打卡卡片信息的一个数据格式。然后存在postInfoList列表中,用于后续的渲染。

export class PostInfo {
  id: number;
  postText: string;
  rightCount: number;
  answeredCount: number;
  timeUsed: number;
  createTime: string;
  likeCount: number;
  nickname: string;
  avatarUrl: string
  isLike: boolean;
  constructor(post:{id: number, postText: string, rightCount: number, answeredCount: number, timeUsed: number, createTime: string, likeCount: number, nickname: string, avatarUrl: string, isLike: boolean}) {
    this.id = post.id;
    this.postText = post.postText;
    this.rightCount = post.rightCount;
    this.answeredCount = post.answeredCount;
    this.timeUsed = post.timeUsed;
    this.createTime = post.createTime;
    this.likeCount = post.likeCount;
    this.nickname = post.nickname;
    this.avatarUrl = post.avatarUrl;
    this.isLike = post.isLike;
  }
}

卡片组件

通过前面那么多次的讲述,相信大家可以很快看出来这个卡片的结构,分为三部分,上面为Image +text +blank +text,中间就是一个text,下面则是和前面统计页面相同的卡片页面,可以复用我们前面封装的组件信息。值得注意的是在卡片右边还有一个小小的点赞按钮。

点赞

点赞的UI是不难,需要注意的是他的逻辑,第一、一个人只能点赞一次,再点一次会取消点赞,因此我们需要一个boolean变量来记录。第二、多个不同用户点赞的话,他的点赞数应该是累加的,需要我们上传到数据库,要不我们永远只能看到0和1.(代码中的cancelLike和Like就是两个接口,用于记录此用户是否点赞过这个卡片)

Column({ space: 10 }) {
      Row({ space: 10 }) {
        Image(this.post.avatarUrl)
          .height(40)
          .width(40)
          .borderRadius(20)
        Text(this.post.nickname)
          .height(40)
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
        Blank()
        Text(this.post.createTime)
          .height(40)
          .fontSize(14)
          .fontColor('#999999')
          .fontWeight(FontWeight.Medium)
      }.width('100%')

      Text(this.post.postText)
        .width('100%')
      Row() {
        Column() {
          StatItem({
            icon: $r('app.media.ic_timer_white'),
            name: '⽤时',
            fontColor: Color.White
          }) {
            Text(convertMillisecondsToTime(this.post.timeUsed))
              .statTextStyle()
          }

          StatItem({
            icon: $r('app.media.ic_accuracy_white'),
            name: '准确率',
            fontColor: Color.White
          }) {
            Text((this.post.answeredCount === 0 ? 0 : this.post.rightCount / this.post.answeredCount * 100).toFixed(0) + '%')
              .statTextStyle()
          }

          StatItem({
            icon: $r('app.media.ic_count_white'),
            name: '个数',
            fontColor: Color.White
          }) {
            Text(this.post.answeredCount.toString())
              .statTextStyle()
          }
        }
        .padding(10)
        .borderRadius(10)
        .layoutWeight(1)
        .backgroundImage($r('app.media.img_post_bg'))
        .backgroundImageSize(ImageSize.Cover)

        Column() {
          Text(this.post.likeCount.toString())
            .fontSize(12)
            .fontWeight(FontWeight.Medium)
            .fontColor(this.post.isLike ? '#3ECBA1' : '#000000')
          Image(this.post.isLike ? $r('app.media.ic_post_like_selected') : $r('app.media.ic_post_like'))
            .width(26)
            .height(26)
            .onClick(() => {
              //todo:点赞/取消点赞
              if(this.post.isLike){
                this.post.isLike=false
                this.post.likeCount--
                cancelLike(this.post.id)
              }else{
                this.post.isLike=true
                this.post.likeCount++
                like(this.post.id)
              }
            })
        }.width(50)
      }.width('100%')
      .alignItems(VerticalAlign.Bottom)
    }

 

回到顶部 
Column({ space: 20 }) {
        Button({ type: ButtonType.Circle }) {
          Image($r('app.media.ic_top'))
            .height(14)
            .width(14)
        }
        .height(40)
        .width(40)
        .backgroundColor(Color.Black)
        .opacity(0.5)
        .onClick(() => {
          //返回顶部
          this.scroller.scrollToIndex(0)
        })

UI不必多说,就是一个按钮组件中放置了一个Image,主要是回到顶部的逻辑是怎么样的呢。这里我们需要用的一个新的api:scroll可滚动的容器组件,当子组件的布局尺寸超过父组件的尺寸时,内容可以滚动。其中有个scrollToIndex属性,作用是滑动到指定Index,仅适用与grid和list布局,正好我们就是list布局,因此只要我们填入0,他就可以滑动到索引为0的地方,也就是列表的最上方,这样就实现了回到顶部的功能。

刷新

这个逻辑比较简单,将所有信息重置为初始状态,然后重新获取一次列表,就实现了刷新功能。

 async refresh(){
    this.isLoading=true
    this.postInfoList=[]
    this.page=1
    this.total=0
    await this.getPostInfoPage()
    this.isLoading=false
  }

登录页

这个页面中有一个很明显和其他页面不同的地方是,他的左上角有一个返回按钮,因为登录页面一般不是一个长期使用的页面,一般我们最开始使用登录一次,后续就可以使用了。因而只是一个临时界面,一般由打卡圈界面、个人界面或者打卡弹窗跳转而来,所以返回按钮使用到了router路由中的back函数,这样就可以跳转回刚才的界面。

Image($r('app.media.ic_back'))
        .backStyle()
        .alignSelf(ItemAlign.Start)
        .onClick(() => {
          //返回上⼀⻚⾯
          router.back()
        })

 输入手机号码和验证码是用到两个TextInput组件,这也是很常用的组件,不过多赘述。这个获取验证码按钮是采用调取后端接口,直接返回一个验证码并显示在TextInput中,登录也是采用了调用后端接口的方式,将手机号和验证码发给后端进行登录。

 Button('获取验证码')
            .buttonStyle(Color.White, Color.Black)
            .onClick(async () => {
              //获取验证码
             let response = await sendCode(this.phone)
              this.code = response.data.data
            })
 Button('⽴即登录')
          .buttonStyle(Color.Black, Color.White)
          .width('100%')
          .margin({ top: 50 })
          .onClick(async () => {
            //登录
            let response = await login(this.phone,this.code)
            this.token = response.data.data
            }

 

个人页

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

调用接口

这个项目中前后端交互的方式是大部分学前端同学比较熟悉的axios,首先你要先下载第三方库axios。Axios 是一个基于 Promise 的 HTTP 客户端,它可以让你更方便地进行 HTTP 请求和处理响应,支持异步操作,并提供了简洁的 API 接口。

首先,我们先创建一个axios实例

export const instance = axios.create({
  baseURL: 'http://192.168.0.103:3000',
  timeout: 2000
})

baseUrl的作用是用变量名代替后面长串的url,因为每个api都是拼接在baseurl之后的,前半部分都是相同的。timeout是超时时长,超过这个时间则视为请求超时,需要检查网络或者url是否正确等。

请求拦截器

// 添加请求拦截器
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  // 通过AppStorage获取token
  const token = AppStorage.Get('token')
  if (token) {
    // 若token存在,则将其添加到请求头
    config.headers['token'] = token
  }
  return config;
}, (error: AxiosError) => {
  //若出现异常,则提示异常信息
  promptAction.showToast({ message: error.message })
  return Promise.reject(error);
});

 每个代码块的作用已用注释卸载了代码块之前,这里介绍一下token,我对token的认知是:token是一个用于标识用户身份的代码串。当一个用户注册后,后端就会给其赋予一个token,每次用户请求操作时,就会在请求头中带上token,后端接收后通过解析token,就可以知道是哪个用户进行了操作。

响应拦截器

// 添加响应拦截器
instance.interceptors.response.use((response: AxiosResponse) => {
  // 若服务器返回的是正常数据,不做任何处理
  if (response.data.code === 200) {
    return response
  } else {
    //若服务器返回的是异常数据,则提示异常信息
    promptAction.showToast({ message: response.data.message })
    return Promise.reject(response.data.message)
  }
}, (error: AxiosError) => {
  //若出现异常,则提示异常信息
  promptAction.showToast({ message: error.message })
  return Promise.reject(error);
});

API

下面展示一个get请求和一个post请求

Get

get请求一般用于请求数据

//获取验证码
export function sendCode(phone: string) {
  return instance.get('/word/user/code', { params: { phone: phone } });
}

比如这个获取验证码的请求,我们传递手机号给后端,后端return给我们一个验证码。instance是刚刚我们创建的axios实例。

Post

post请求一般用于发送数据

//登录
export function login(phone: string, code: string) {
  return instance.post('/word/user/login', { phone: phone, code: code });
}

比如这个登录请求,我们需要传递手机号和验证码给后端,后端进行验证,成功则登录,失败则返回错误信息。

不管是post还是get或者其他请求,我们都可以看到一个形如“/word/user/login”的字符串,这个就是拼接到前面实例中的baseUrl之后形成完整的url,使得请求得以成功发送。

  • 25
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值