HarmonyOS APP应用开发项目- MCA助手(Day01持续更新中~)

news/2024/7/8 15:25:50 标签: harmonyos, 移动开发, ArkTS

简言:

gitee地址:https://gitee.com/whltaoin_admin/money-controller-app.git
端云一体化开发在线文档:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/agc-harmonyos-clouddev-view-0000001700053733-V5

注:此App参照此教程进行二次修改:https://www.bilibili.com/video/BV1q5411v7o7

一、简介

moneyControllerApp(MCA)

这款精心打造的个人财务管理应用,是您理财路上的智慧伙伴。凭借前沿的智能化技术与直观易用的界面设计,它将化繁为简,让您的财务状况一目了然。无论是日常收支的记录,还是复杂财务的分析,都能轻松应对。它不仅帮助您有效掌控每一笔收入与支出,更助您洞悉财务趋势,科学规划未来,让财富增长之路更加清晰可见。从此,财务管理不再是难题,而是通往财务自由的桥梁。
在鸿蒙HarmonyOS Next版本的加持下,这款个人财务管理应用的性能与体验再度升级,成为您理财旅程中的超级智慧伙伴。鸿蒙系统的分布式技术,使得应用运行更加流畅稳定,数据同步更加快速准确,即使在多设备间切换,也能无缝衔接,确保您的财务信息实时更新,安全无忧。

二、什么是端云一体化开发

为丰富HarmonyOS对云端开发的支持、实现端云联动,DevEco Studio以Cloud Foundation Kit(云开发服务)为底座、在传统的“端开发”基础上新增“云开发”能力,开发者在创建工程时选择合适的云开发工程模板,即可在DevEco Studio内同时完成HarmonyOS应用的端侧与云侧开发,体验端云一体化协同开发。

三、开发环境介绍

编辑器DevEco Studio NEXT Developer Beta1
SDK11
操作系统Window 10 专业版
模拟器HarmonyOS Emulator Version: 5.0.3.405
HarmonyOS Version: HarmonyOS NEXT Developer Beta1

四、项目初始化

  • 步骤一:
/*
1 create project
2 application选择>>>[cloudDev] Empty Ability>>>Next
*/
  • 步骤二:输入图中信息后>>>点击Finish
    • 注意:存放路径不建议使用中文字符

image.png

  • 步骤三:进入项目主页>>>点击右上角的头像进行用户登录。

image.png

  • 步骤四:
// 1 进入网址并进行登录:https://developer.huawei.com/consumer/cn/
// 2 登录后在网站首页点击管理中心
// 3 点击左侧边栏(生态服务-应用服务)>>>点击AppGallery Connect
// 4 进入到以下页面

image.png

  • 步骤四:
// 1 点击我的项目>>>新建项目
// 2 数据处理位置选择中国并设置为默认
// 3 点击完成后并添加应用
// 4 注意:创建应用时如果想要自定义包名的话,定义的包名必须和新建项目时写的包名一致。
// 5 创建应用完成后,点击Next后,新建项目既可创建完成。

image.png

五、项目构建静态页面

登录注册页面

  • 效果图

image.png
image.png
结构:

// 一个页面:Login.etc
// 两个组件:
// 头部标题组件:titleComponent.ets
// 表单组件:InputComponent.ets

image.png

  • 代码
// Login.ets
import InputComponent from '../components/InputComponent';
import TitleComponent from '../components/TitleComponent';
import { typeNode } from '@ohos.arkui.node';
import { TESTTYPE } from '@ohos/hypium/src/main/Constant';

@Entry
@Component
struct Login {
  @State message: string = 'Login';
  // 倒计时
  @State countDown :number = 60
  timer :number=0
  @State isRegister:boolean= false

  // 发送验证码
  sendCode(){
    this.startCountDown()

  }
  // 开始倒计时
  startCountDown(){
  this.timer =   setInterval(()=>{
      this.countDown--
      if(this.countDown===0){
        this.countDown=60
        clearInterval(this.timer)
      }
    },1000)
  }


  build() {
      Column(){
        // title
        TitleComponent({title:"登录"})
        // login_content
        Stack({alignContent:Alignment.Top}){
          Image($r("app.media.Login_icon")).width(88).height(88).offset({y:-44}).zIndex(999)

       Column({space:10}){
            // emial
          InputComponent({title:"电子邮箱",inputIcon:$r("app.media.mail_icon"),placeholder:"请输入邮箱信息"})
            // pwd
          InputComponent({title:"密码",inputIcon:$r("app.media.pwd_icon"),placeholder:"请输入密码",inputType:InputType.Password})
            // VCode
          if(this.isRegister){
            Column(){
              Text("验证码").width("100%").textAlign(TextAlign.Start).fontWeight(500)
                .fontSize(16).fontColor(Color.Black).margin({bottom:14})
              Row(){
                TextInput({placeholder:"请输入验证码"})


                  .layoutWeight(1)
                  .backgroundColor(Color.Transparent)
                  .border({
                    width:1,
                    color:"#ff9b9b9b"

                  }).borderRadius(10)
                Button(this.countDown==60?"点击获取验证码":`${this.countDown}s`).fontSize("10").margin({left:10}).width(100).padding(0).onClick((event: ClickEvent) => {
                  if(this.countDown===60){
                    this.sendCode()
                  }else{
                    AlertDialog.show({
                      message:"正在获取验证码,请等待..."
                    })
                  }


                })

              }.width("100%").height(50)

            }
          }
            // login_btn
            Button(this.isRegister?"注册":"登录").width(228).backgroundColor("#ff09b19d").margin({top:50})
              .onClick(()=>{
                // 登录方法
              })
            // re_btn
         Row(){
           Text(this.isRegister?"去登录":"去注册").fontSize(12).onClick(()=>{
             this.isRegister= !this.isRegister
           })
           Text("|").padding({left:10,right:10})
           Text("忘记密码").fontSize(12)
         }.width("100%").layoutWeight(1).justifyContent(FlexAlign.Center)

       }.width("100%").height("100%").padding({left:14,right:14}).margin({top:44})

        }.width("90%").backgroundColor(Color.White).margin({top:44}).layoutWeight(1)
        .borderRadius(20)


      }.width("100%").height("100%").backgroundColor($r("app.color.page_Color"))
  }
}
// InputComponent.ets

@Component
export  default struct InputComponent {
  @Prop title:string
  @Prop inputIcon:Resource
  @Prop placeholder:string
  @Prop  inputType:InputType=InputType.Normal
  @State changeStatus:boolean =false
  build() {
    Column(){
      Text(this.title).width("100%").textAlign(TextAlign.Start).fontWeight(500)
        .fontSize(16).fontColor(Color.Black).margin({bottom:14})
      Row(){
        Image(this.inputIcon).width(40).aspectRatio(1)
        TextInput({placeholder:this.placeholder})
          .onFocus(()=>{
            // 聚焦
            this.changeStatus=true
            console.log("result>>>",this.changeStatus)
          })
          .onBlur(()=>{
            // 失去
            this.changeStatus=false
            console.log("result>>>",this.changeStatus)
          })

          .layoutWeight(1)
          .backgroundColor(Color.Transparent)
          .type(this.inputType)




      }.width("100%").height(50).padding({left:10,right:10}).borderRadius(10)
      .border({
        width:2,color:this.changeStatus?"#002884":Color.White
      })
    }
  }
}
// 页面标题组件 TitleComponent.ets

@Component
export  default struct TitleComponent {
  @Prop title :string
  build() {

    Row(){
      Image($r("app.media.Button_left")).width("44").height(41).objectFit(ImageFit.ScaleDown)
      Text(this.title).fontColor("#ff403f3f").fontWeight(700).fontSize(20).height(40)
      Text("")


    }.width("100%").justifyContent(FlexAlign.SpaceBetween).padding({left:20,right:20,top:12,bottom:12})
  }
}

主页框架及底部导航栏

  • 效果图(点击底部图标后,可以切换到对应页面并修改选中图标的底色。)

image.png
image.png

  • 功能点及编写思路
1 看着效果图像是多个页面编写而成的,其实就只有一个页面,通过tabs组件框架,嵌套其他组件从而形成多页面效果
2 框架编写思路:
整理和页面通用的数据并提取,在主页定义一个tabs组件,
分别定义5个页面的组件,和底部导航栏的组件
3 图标切换状态思路:
因为底部导航栏的数据是封装到了一个数组中,可以给每个对象定义一个ID属性,同时在主框架中定义一个
装饰器变量来监听tabs的onchange事件,因为ongchange事件会传递tab的下标,所有可以将传递的下标赋值给装饰器变量,
再将装饰器变量传递给底部导航栏图标组件,从而判断是否选中切换图标。

  • 结构:
  • image.png
实体类:
  BtnNavData
页面:
    MainPage
组件:
  CBtnNavImage
  DataStatistics
  Home
  My
  Wallet

代码:

// MainPage
import CBtnNavImage from './components/CBtnNavImage'
import { createBtnNavDataList,BtnNavData } from './model/BtnNavData'
@Entry
@Component
struct MainPage {
   @State btnNavItemid :number=0
  @State btnNavDataList:BtnNavData[] =createBtnNavDataList()
  // tabBar
  @Builder
  tabItemBar(item :BtnNavData){
     CBtnNavImage({btnNavData:item,isSelect:this.btnNavItemid})
  }
  build() {
    Tabs({barPosition:BarPosition.End}){
      ForEach(this.btnNavDataList,(item:BtnNavData,index)=>{
        TabContent(){
          Text(this.btnNavItemid.toString())

        }.tabBar(  this.tabItemBar(item))
      })

    }.onChange((index)=>{
      // 切换图标
      // console.log("result>>>>",index)
       if(index !=2){
         this.btnNavItemid =index
       }
    })
    .backgroundImage($r("app.media.Subtract"))
    .backgroundImagePosition(Alignment.BottomEnd)
    .backgroundImageSize({
      width:"100%",
      height:50
    })

  }
}
// BtnNavData
interface  IBtnNavData{
  selectIcon:Resource
  nowIcon:Resource
  title:string
  id:number

}
export   class BtnNavData{
  selectIcon:Resource
  nowIcon:Resource
  title:string
  id:number
  isQrcode:boolean

  constructor(obj:IBtnNavData,isQrcode=false) {
    this.selectIcon=obj.selectIcon
    this.nowIcon=obj.nowIcon
    this.title=obj.title
    this.id=obj.id
    this.isQrcode =isQrcode
  }
}

export  const createBtnNavDataList =():BtnNavData[]=>{
  return [
    new BtnNavData(
      {
        id:0,
        title:"首页",
        nowIcon:$r("app.media.home_icon_unselect"),
        selectIcon:$r("app.media.home_icon_select"),
      }
    ),
    new BtnNavData(
      {
        id:1,
        title:"数据展示",
        nowIcon:$r("app.media.data_icon_unselect"),
        selectIcon:$r("app.media.data_icon_select"),
      }
    ),
    new BtnNavData(
       {
        id:2,
        title:"扫一扫",
        nowIcon:$r("app.media.qrcode_icon"),
        selectIcon:$r("app.media.qrcode_icon"),

      },true
    ),
    new BtnNavData(
      {
        id:3,
        title:"钱包",
        nowIcon:$r("app.media.wallet_icon_unselect"),
        selectIcon:$r("app.media.wallet_icon_select"),

      }
    ),
    new BtnNavData(
      {
        id:4,
        title:"我的",
        nowIcon:$r("app.media.my_icon_unselect"),
        selectIcon:$r("app.media.my_icon_select"),

      }
    )

  ]
}
// CBtnNavImage
import { createBtnNavDataList,BtnNavData } from '../model/BtnNavData'
@Component

export default struct CBtnNavImage {
  @Prop btnNavData:BtnNavData
  @Prop isSelect :number =0
  build() {
    Column(){
      Image(this.isSelect ==this.btnNavData.id ?this.btnNavData.selectIcon:this.btnNavData.nowIcon).width(20).height(20)
        .offset({ y:this.btnNavData.isQrcode? -15 :0 })



    }.width("100%").justifyContent(FlexAlign.Center).height("100%")

  }
}
//  其余文件均为占位,并未编写

day01持续更新中…


http://www.niftyadmin.cn/n/5537356.html

相关文章

【RNN练习】LSTM-火灾温度预测

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 前期准备工作 import torch.nn.functional as F import numpy as np import pandas as pd import torch from torch import nn1. 导入数据 data pd.read_cs…

上海市计算机学会竞赛平台2023年2月月赛丙组区间的并

题目描述 给定一个数轴上的 𝑛n 个闭区间,第 𝑖i 个闭区间的两端点为[𝑎𝑖,𝑏𝑖][ai​,bi​],它们的并集可以表示为若干不相交的闭区间,请按照左端点从小到大的顺序输出…

mybatispuls 分页插件的基本原理是什么?

MyBatis-Plus 是一个基于 MyBatis 的增强框架,它提供了许多额外的功能,其中分页插件是一个常用的功能。分页插件的基本原理是拦截 SQL 语句,在执行查询之前对 SQL 进行修改,以实现分页的功能。以下是 MyBatis-Plus 分页插件的基本原理及其工作机制: 1. 基本原理 分页插件…

【解码现代 C++】:实现自己的智能 【String 类】

目录 1. 经典的String类问题 1.1 构造函数 小李的理解 1.2 析构函数 小李的理解 1.3 测试函数 小李的理解 1.4 需要记住的知识点 2. 浅拷贝 2.1 什么是浅拷贝 小李的理解 2.2 需要记住的知识点 3. 深拷贝 3.1 传统版写法的String类 3.1.1 拷贝构造函数 小李的理…

Unity+OpenCV+Dlib实现换脸+图片生成+上传服务器+生成二维码[纯干货]

UnityOpenCVDlib实现换脸图片生成上传服务器生成二维码 功能描述 一句话描述:让游客体验一下当宇航员的乐趣。 具体功能:游客通过摄像头拍照,生成有着“自己的脸”的宇航员的图片,然后展示二维码,供游客下载。 效果…

Elasticsearch 使用聚合进行数据分析

在大数据时代,数据的价值不仅仅在于存储,更在于如何从海量数据中提取出有价值的信息。Elasticsearch,作为一个强大的搜索引擎和数据分析平台,通过其内置的聚合(Aggregations)功能,为我们提供了一…

数据库详细复习第三章SQL语句

SQL 第三章:SQL语句3.1 SQL概述3.1.3 SQL 语句类型1、数据定义语句2、数据操纵语言3、数据查询语言4、数据控制语言5、事务处理语言 3.1.4 SQL数据类型1、字符串型2、整数型3、浮点数型4、货币型5、日期型 3.2 数据定义语句3.2.1 数据库的定义3.2.2 数据库表对象的定…

大数据面试题之数据库(1)

目录 数据库中的事务是什么,MySQL中是怎么实现的 MySQL事务的特性? 数据库事务的隔离级别?解决了什么问题?默认事务隔离级别? 脏读,幻读,不可重复读的定义 MySQL怎么实现可重复读? 数据库第三范式和第四范式区别? MySQL的…