【鸿蒙HarmonyOS.4】健身APP项目 从0开始 第一天(借鉴b站某厂程序员)

目录

制作欢迎页面

用户协议弹窗制作

首选项存储欢迎页面完结

制作首页

遇到的问题


本项目开发了一款健身app,有进入app界面、首页界面、任务项界面、成就界面、个人界面,可以自由选择任务项界面中存在的运动项目并添加到今日的日程当中,并根据添加的运动项目计算当日的运动量(卡路里),累计达成目标天数获得成就。

  • 制作欢迎页面

在pages中创建SplashIndex.ets作为欢迎界面

  • 用户协议弹窗制作

在dialog中创建UserPrivacyDialog.ets作为用户协议弹窗

  • 首选项存储欢迎页面完结

  • 制作首页

在pages中创建MainIndex.ets作为首页界面

  • 遇到的问题

在SplashIndex类欢迎界面中定义两个常量存储首选项中的键时,将string写成大写S开头的String,导致在写定义首选项

let preferences = data_preferences.getPreferences(this.context, H_STORE)

和记录用户数据到首选项

res.put(IS_PRIVACY, true).then(() => {
        res.flush();   //写入
        //记录日志
        Logger.debug('SplashIndex', 'isPrivacy记录成功')
      }).catch((err: Error) => {
        Logger.error('SplashIndex', 'isPrivacy记录失败,原因: '+ err)
      })

时,H_STORE 和 IS_PRIVACY报错 Argument of type 'String' is not assignable to parameter of type 'string'. 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible. <tsCheck>

原因:

string 是一个原始数据类型,而 String 是一个包装对象。原始数据类型 string 用于表示文本数据,而 String 对象提供了一些方法来操作这些文本数据。TypeScript 的类型检查器会区分这两者,并在类型不匹配时给出错误或警告。

错误信息 "Argument of type 'String' is not assignable to parameter of type 'string'" 意味着尝试将一个 String 对象实例传递给一个期望 string 类型参数的函数或方法。为了解决这个问题,应该确保传递的是原始的 string 类型,而不是 String 对象。


import UserPrivacyDialog from '../dialog/UserPrivacyDialog'   //导入用户协议弹窗
import common from '@ohos.app.ability.common'
import data_preferences from '@ohos.data.preferences'
import Logger from '../utils/Logger'                          //导入华为官方写好的,用来记录日志的。
import router from '@ohos.router'

//使用首选项存储用户是否点击过同意
//定义两个常量存储首选项中的键
const H_STORE: string = 'BaiZhanKeepStore'
const IS_PRIVACY: string = 'isPrivacy'     //这里的string的S大小写也有区别,会对aboutToAppear()中的S产生影响

@Entry
@Component
struct SplashIndex {
  //定义一个生命周期,用context代替common.UIAbilityContext
  context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;

  //加载用户协议弹窗
  dialogController: CustomDialogController = new CustomDialogController({
    builder: UserPrivacyDialog({
      //在弹窗内需要同意和取消两个方法
      cancel: () => {this.exitApp()},                  //点击取消会直接退出此应用
      confirm: () => {this.onConfirm()}  //点击同意跳转到onConfirm()
    })
  })

  onConfirm() {
    //定义首选项
    let preferences = data_preferences.getPreferences(this.context, H_STORE)
    //异步处理首选项中的数据
    preferences.then((res) => {
      //记录用户数据到首选项
      res.put(IS_PRIVACY, true).then(() => {
        res.flush();   //写入
        //记录日志
        Logger.debug('SplashIndex', 'isPrivacy记录成功')
      }).catch((err: Error) => {
        Logger.error('SplashIndex', 'isPrivacy记录失败,原因: '+ err)
      })
    })
  }

  //退出app
  exitApp() {
    this.context.terminateSelf()   //使用terminateSelf()方法实现关闭此应用
  }

  //让欢迎界面加载的时候弹出用户协议弹窗
  aboutToAppear() {
    //在加载用户界面时首先要取出加载用户H_STORE的值
    let preferences = data_preferences.getPreferences(this.context, H_STORE)//调用getPreferences来获取到他的值,需传入两个参数,第一个生命周期this.context,第二个取的值的键
    //取数据时需要异步处理
    preferences.then((res) => {
      res.get(IS_PRIVACY, false).then((isPrivate) => {
        //判断传入参数值
        if(isPrivate === true) {
          //说明用户已经点击过同意后跳转到首页
          this.jumpToMain()
        }else {
          //弹出弹窗
          this.dialogController.open()
        }
      })
    })
  }

  //跳转到首页
  jumpToMain() {
    setTimeout(() => {    //不能直接跳入,所以写一个定时任务
      router.replaceUrl({url: ''})
    }, 2000)              //时间设定2000毫秒
  }

  //在页面加载完成之后,不需要用到这个页面,即清除此页面
  aboutToDisappear() {
    clearTimeout()
  }

  //制作欢迎界面
  build() {
    Column() {
      Image($r('app.media.logo'))
        .width(100)
        .margin({top: 120})
      Text('欢迎进入百战健身')
        .fontSize(25)
        .fontColor('#FFFFFF')
        .fontWeight(800)        //加粗
        .letterSpacing(0.8)     //字距
        .opacity(0.7)           //透明度
        .margin({top: 20, bottom: 140})  //字体边框
      Image($r('app.media.titlePhoto'))
        .width('80%')
    }
    .width('100%')
    .height('100%')
    .backgroundImage($r('app.media.welcomeBg'))
    .backgroundImageSize({width: '100%', height: '100%'}) //图片大小
    .backgroundImagePosition({x: 0, y: 0})  //图片偏移量
  }
}
MainIndex类:
//制作首页

@Entry
@Component
struct MainIndex {
  @State selectIndex: number = 0    //默认展示第一个页面

  //自定义TabBar组件用来,不用仅展示文字,还可展示图标
  @Builder TabBarBuilder(index: number, normalIcon: ResourceStr, selIcon: ResourceStr, text: string) {
    Column() {
      Image(this.selectIndex === index ? selIcon : normalIcon)  //使用三元运算符做出判断,若this.selectIndex === index展示selIcon否则展示normalIcon
        .width(20)
      Text(text)
        .fontSize(10)
        .fontColor(this.selectIndex === index ? $r('app.color.tab_bar_sel') : $r('app.color.tab_bar_normal'))  //三元运算符,当相等时,变成被选择时的颜色
    }
  }

  //页面底部图标创建
  build() {
    Tabs({
      barPosition: BarPosition.End,   //参数,默认Tabs组件是纵向的,BarPosition用来设置页签,即主页、成就、个人页显示的位置,使用End则展示在页面的底部
      index: this.selectIndex         //默认展示第一个页面
    }) {
      //主页,子组件
      TabContent()
        .tabBar(this.TabBarBuilder(
          0,
          $r('app.media.ic_tabs_home_normal'),
          $r('app.media.ic_tabs_home_sel'),
          "主页"
        ))
      //成就页
      TabContent()
        .tabBar(this.TabBarBuilder(
          1,
          $r('app.media.ic_tabs_achievement_normal'),
          $r('app.media.ic_tabs_achievement_sel'),
          "成就"
        ))
      //个人页
      TabContent()
        .tabBar(this.TabBarBuilder(
          2,
          $r('app.media.ic_tabs_mine_normal'),
          $r('app.media.ic_tabs_mine_sel'),
          "个人"
        ))
    }

    //给Tabs组件增加一个事件,图标字体和图片都变蓝
    .onChange((index: number) => {
      this.selectIndex = index          //点击图标变蓝
    })
  }
}
UserPrivacyDialog类:
@Preview
@CustomDialog

//用户协议弹窗制作
 export default struct UserPrivacyDialog {
  controller: CustomDialogController = new CustomDialogController({builder: ''})

  //两个按钮事件
  cancel: Function = () => {}
  confirm: Function = () => {}

  build() {
    Column({space: 10}) {
      //弹窗内的所有内容
      Text($r('app.string.welcome_title'))
        .fontSize(20)
        .fontWeight(FontWeight.Bolder)
        .margin({top: 8, bottom: 5})
      Text() {
        Span($r('app.string.welcome_span_one'))
      }
      Text() {
        //内容中有绿色和黑色内容,则使用Span分开来写,分别单独设置字体
        //使用../resources/base/element/color.json  中的协议颜色即可
        Span($r('app.string.welcome_span_two'))
        Span($r('app.string.personal_privacy_protocol'))
          .fontColor($r('app.color.privacy_color'))
        Span($r('app.string.welcome_span_three'))
        Span($r('app.string.user_protocol'))
          .fontColor($r('app.string.welcome_span_four'))
        Span($r('app.string.welcome_span_four'))
      }
      Text() {
        Span($r('app.string.welcome_span_end'))
      }

      //设置按钮
      Button('同意')
        .fontColor(Color.White)
        .backgroundColor("#ff06ae27")
        .width(150)
        .onClick(() => {            //点击事件
          this.confirm()            //执行同意
          this.controller.close()   //将弹窗关闭掉
        })
      Button('不同意')
        .fontColor("#d09d9d9d")
        .backgroundColor("#412ef550")
        .width(150)
        .onClick(() => {            //点击事件
          this.cancel()             //执行不同意
          this.controller.close()   //将弹窗关闭掉
        })
    }
    .width('80%')
    .height('75%')
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值