HarmonyOS APP应用开发项目- MCA助手

本文涉及的产品
多模态交互后付费免费试用,全链路、全Agent
简介: moneyControllerApp(MCA)是一款基于鸿蒙HarmonyOS Next开发的个人财务管理应用,采用端云一体化架构,支持多设备协同与数据实时同步。应用通过DevEco Studio集成Cloud Foundation Kit,实现高效云端联动,具备登录注册、主页导航、钱包管理、个人信息维护及支付功能,界面简洁,操作流畅,助力用户智能化管理财务。项目开源,支持二次开发。

简言:

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

SDK

11

操作系统

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
  • 注意:存放路径不建议使用中文字符

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

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

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

五、项目构建静态页面

1. 登录注册页面

  • 效果图

结构:

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

  • 代码
// 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})
  }
}

2. 主页框架及底部导航栏

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

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


3. 钱包页面

  • 效果图

  • 结构:
主页面:Wallet.ets
子页面:addCard.ets
组件:
BankCardComponent 银行卡片
TitleComponent  顶部标题
  • 编写思路:
// 钱包页面和主页页面效果类似,复制其修改部分既可。
  • 代码
// 页面三:钱包 Wallet
import { BankCardComponent } from '../../components/BankCardComponent'
import TitleComponent from '../../components/TitleComponent'
@Component
export  default struct Wallet {
  build() {
    Column(){
      TitleComponent({title:"钱包",is_addIcon:true})
      Column(){
        // Text("您好,").width("100%").fontWeight(500)
        // Text("欢迎回来!").width("100%").fontWeight(500).fontSize(18).margin({top:5,bottom:15})
        // card
        Swiper(){
          BankCardComponent()
          BankCardComponent()
        }.loop(true)
        .autoPlay(true)
        .indicator(
          Indicator.dot().
          color(Color.White)
            .selectedColor(Color.White)
            .selectedItemWidth(20)
        ).borderRadius(20)
        // 功能分类
        Text("最近联系").width("100%").fontWeight(500).fontSize(18).margin({top:5,bottom:15}).margin({top:30,bottom:20})
        Row({space:20}){
          Image($r("app.media.avatar_icon")).width(50).borderRadius(8)
          Image($r("app.media.avatar_icon")).width(50).borderRadius(8)
          Image($r("app.media.avatar_icon")).width(50).borderRadius(8)
          Image($r("app.media.avatar_icon")).width(50).borderRadius(8)
        }.width("100%")
        // 功能分类
        Text("交易信息").width("100%").fontWeight(500).fontSize(18).margin({top:5,bottom:15}).margin({top:30,bottom:20})
        Column(){
          List(){
            ForEach((Array.from({length:10})),()=>{
              ListItem(){
                Row(){
                  Image($r("app.media.avatar_icon")).width(36).borderRadius(18).margin({left:5,right:5})
                  Column(){
                    Text("便利店").width("100%").fontSize(14).fontColor("#666")
                    Text("2024年6月29日").width("100%").fontSize(12).fontColor("#999")
                  }.layoutWeight(1)
                  Text("¥1250.50").backgroundColor("#ffffe0e0").borderRadius(20).width(100).height(35)
                    .textAlign(TextAlign.Center).fontSize(12).fontColor("#f00")
                }.width("100%").height(50).backgroundColor("#fffafafa").borderRadius(10).margin({bottom:10})
              }
            })
          }
        }.width("100%").layoutWeight(1)
      }.width("100%").layoutWeight(1).padding({left:20,right:20})
    }.width("100%").height("100%").backgroundColor("#ffffffff")
  }
}
// 添加银行卡 子页面:AddCard
import InputComponent from '../../components/InputComponent';
import TitleComponent from '../../components/TitleComponent';
@Extend(Text)
function  titleTextStyle(){
  .width("100%").fontWeight(500).fontSize(18).margin({top:30,bottom:20})
}
@Entry
@Component
struct AddCard {
  @State message: string = 'Hello World';
  build() {
   Column(){
    // titile
     TitleComponent({title:"添加新的银行卡",routerUrl:'',is_icon:true})
    // content
    Column({space:30}){
      Text("卡片信息").titleTextStyle()
      InputComponent({title:'银行卡号',placeholder:'XXXXXXXX  XXX XXXXXX',isInputIcon:false})
      InputComponent({title:'持卡人姓名',placeholder:'请输入持卡人姓名',isInputIcon:false})
      Row({space:10}){
        InputComponent({title:'CCV',placeholder:'2533',isInputIcon:false}).layoutWeight(1)
        InputComponent({title:'到期时间',placeholder:'30-06-2024',isInputIcon:false}).layoutWeight(1)
      }
      Button("下一步").width(228).backgroundColor("#ff09b19d").margin({top:50})
    }.width("100%").height("100%").padding({left:20,right:20})
   }.width("100%").height("100%").backgroundImage($r("app.media.pageBg"))
  }
}
// 银行卡片组件:BankCardComponent
@Component
export   struct BankCardComponent {
  build() {
    Column(){
      Row(){
        Text("中国银行").layoutWeight(1).fontColor(Color.White).fontSize(14).fontWeight(500)
        Image($r("app.media.card_icon")).width(36)
      }
      Text(){
        Span("¥").fontSize(12)
        Span("25,230,00").fontSize(24).fontWeight(700)
      }.width("100%").fontColor(Color.White).margin({top:20})
      Row(){
        Text("xxxxxxxxxx  xx xxxxx ").layoutWeight(1).fontColor(Color.White).fontSize(14).fontWeight(500)
        Text("26/24").fontColor(Color.White).fontSize(12).padding({right:40})
      }.margin({top:15})
    }.width("100%").height(150).backgroundColor("#ff09d7d3").padding(20)
  }
}
// 页面标题组件: TitleComponent
import text from '@ohos.graphics.text'
import router from '@ohos.router'
@Component
export  default struct TitleComponent {
  @Prop title :string
  @Prop is_icon:boolean
  @Prop is_addIcon:boolean
  @Prop routerUrl:string
  @Prop titleColor:string
  build() {
    Row(){
      if(this.is_icon){
        Image($r("app.media.Button_left")).width("44").height(30).objectFit(ImageFit.ScaleDown).borderRadius(5)
          .onClick(()=>{
            router.back()
          })
      }
      Text(this.title).fontColor(this.titleColor).fontWeight(700).fontSize(20).height(40).layoutWeight(1).textAlign(TextAlign.Center)
      if(this.is_addIcon){
        Text("+").fontColor(Color.White).fontSize(25).fontWeight(500).border({width:2})
          .borderRadius(30).width(25).height(25).fontColor(Color.Black).lineHeight(25).textAlign(TextAlign.Center).onClick(()=>{
            router.pushUrl({
              url:'pages/bank/AddCard'
            })
        })
      }
    }.width("100%").justifyContent(FlexAlign.SpaceBetween).padding({left:20,right:20,top:12,bottom:12})
  }
}

4. 个人页面

  • 效果图

  • 结构
组件:
BankCardComponent 银行卡片
TitleComponent  顶部标题
InputDateComponent 选择日期弹框
InputComponent 普通表单输入框
页面:
  My 个人主页
  InfoEdit 个人信息修改页
  QrCodePage 个人信息二维码生成页
工具类:tools
  • 代码
// 页面四:个人信息页
import TitleComponent from '../../components/TitleComponent'
import router from '@ohos.router'
import { Router } from '@kit.ArkUI'
@Component
export  default struct My {
  build() {
    Column(){
      TitleComponent({title:"个人资料",titleColor:"#ffff"})
      Stack({alignContent:Alignment.Start}){
        Column().width("100%").height(120)
          .backgroundColor("#ffc3f6e1")
          .margin({top:50}).borderRadius(20).shadow({radius:10,color:"#fff"})
        Column(){
          Image($r("app.media.user")).width(66).height(66).borderRadius(22)
            .border({
            width:5,
            color:'#ff09b06d',
              style:BorderStyle.Solid
          }).shadow({radius:10,color:"#fff"})
          Text("追风的少年").offset({x:80,y:-30}).width("100%")
          Text("财富的意义,在于分享与贡献,而非单纯的积累。").fontSize(14).fontColor("#ff969191").margin({top:10})
            .offset({y:-10}).margin({right:10})
        }.width("100%").alignItems(HorizontalAlign.Start).margin({left:10})
        Image($r("app.media.right_i")).height(20).offset({
          y:60,x:270
        })
      }.width("100%").padding({left:30,right:30})
       Row(){
        Image($r("app.media.edit_icon")).height(30).margin({right:20})
        Text("编辑个人信息").layoutWeight(1).fontSize(14)
         Image($r("app.media.right_icon")).height(25)
       }.height(40).padding({left:5,right:10}).backgroundColor("#fff").margin(20)
      .borderRadius(10).shadow({radius:20,color:"#ff70e7d5"}).onClick(()=>{
        router.pushUrl({
          url:"pages/info/InfoEdit"
        })
       })
      Row(){
        Image($r("app.media.qrcode_icon_external")).height(25).margin({left:5,right:30})
        Text("个人二维码").layoutWeight(1).fontSize(14)
        Image($r("app.media.right_icon")).height(25)
      }.height(40).padding({left:5,right:10}).backgroundColor("#fff").margin(20)
      .borderRadius(10).shadow({radius:20,color:"#ff70e7d5"}).onClick(()=>{
        router.pushUrl({
          url :'pages/info/QrCodePage'
        })
      })
    }.width("100%").height("100%").backgroundImage($r("app.media.myPageBg"))
    .backgroundImageSize({width:"100%",height:"100%"})
  }
}
// 个人信息修改页
import InputComponent from '../../components/InputComponent';
import InputDateComponent from '../../components/InputDateComponent';
import TitleComponent from '../../components/TitleComponent';
@Extend(Text)
function  titleTextStyle(){
  .width("100%").fontWeight(500).fontSize(18).margin({top:30,bottom:20})
}
@Entry
@Component
struct InfoEdit {
  @State message: string = 'Hello World';
  selectedDate: Date = new Date("2010-1-1")
  build() {
    Column(){
      // titile
      TitleComponent({title:"编辑个人信息",routerUrl:'',is_icon:true})
      // content
      Column({space:30}){
        Text("个人信息").titleTextStyle()
        InputComponent({title:'姓名',placeholder:'请输入您的姓名',isInputIcon:false})
        InputComponent({title:'联系电话',placeholder:'请输入你的手机号码',isInputIcon:false})
        Row({space:10}){
          InputComponent({title:'性别',placeholder:'2533',isInputIcon:false}).layoutWeight(1)
          InputDateComponent ({title:'出生日期',placeholder:'30-06-2024',isInputIcon:false}).layoutWeight(1)
        }
        Button("下一步").width(228).backgroundColor("#ff09b19d").margin({top:50})
      }.width("100%").height("100%").padding({left:20,right:20})
    }.width("100%").height("100%").backgroundImage($r("app.media.pageBg"))
  }
}
// 个人信息二维码生成页
import TitleComponent from '../../components/TitleComponent';
import { randomColor } from '../../util/tools';
@Entry
@Component
struct QrCodePage {
  @State message: string = 'Hello World';
  @State BgColor :string = "#ffc2f17d"
  build() {
    Column() {
      // titile
      TitleComponent({title:"",routerUrl:'',is_icon:true})
      QRCode("1").margin({top:40}).height(200).aspectRatio(1).backgroundColor(Color.Transparent)
      Blank()
      Row({space:20}){
        Text("换个样式").onClick(()=>{
           this.BgColor = randomColor()
        })
        Text("|")
        Text("保存图片")
      }.width("100%").justifyContent(FlexAlign.Center).margin({bottom:20})
    }
    .height('100%').backgroundColor(this.BgColor)
    .width('100%')
  }
}
// 日期选择框
@Component
export  default struct InputDateComponent {
  @Prop title:string
  @Prop inputIcon:Resource
  @Prop placeholder:string
  @Prop  inputType:InputType=InputType.Normal
  @State changeStatus:boolean =false
  @Prop isInputIcon:boolean = true
  selectedDate: Date = new Date()
  build() {
    Column(){
      Text(this.title).width("100%").textAlign(TextAlign.Start).fontWeight(500)
        .fontSize(16).fontColor(Color.Black).margin({bottom:14})
      Row(){
        if (this.isInputIcon) {
          Image(this.inputIcon).width(40).aspectRatio(1)
        }
        TextInput({placeholder:this.placeholder})
          .onClick(()=>{
            DatePickerDialog.show({
              start: new Date("1970-1-1"),
              end: new Date("2100-12-31"),
              selected: this.selectedDate,
              showTime:true,
              useMilitaryTime:false,
              // disappearTextStyle: {color: Color.Pink, font: {size: '22fp', weight: FontWeight.Bold}},
              // textStyle: {color: '#ff00ff00', font: {size: '18fp', weight: FontWeight.Normal}},
              // selectedTextStyle: {color: '#ff182431', font: {size: '14fp', weight: FontWeight.Regular}},
              onDateAccept: (value: Date) => {
                // 通过Date的setFullYear方法设置按下确定按钮时的日期,这样当弹窗再次弹出时显示选中的是上一次确定的日期
                this.selectedDate = value
                console.info("DatePickerDialog:onDateAccept()" + value.toString())
              },
              onCancel: () => {
                console.info("DatePickerDialog:onCancel()")
              },
              onDateChange: (value: Date) => {
                console.info("DatePickerDialog:onDateChange()" + value.toString())
              }
            })
          })
          .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
      })
    }
  }
}
// 普通输入框
@Component
export  default struct InputComponent {
  @Prop title:string
  @Prop inputIcon:Resource
  @Prop placeholder:string
  @Prop  inputType:InputType=InputType.Normal
  @State changeStatus:boolean =false
  @Prop isInputIcon:boolean = true
  build() {
    Column(){
      Text(this.title).width("100%").textAlign(TextAlign.Start).fontWeight(500)
        .fontSize(16).fontColor(Color.Black).margin({bottom:14})
      Row(){
        if (this.isInputIcon) {
          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":"#ffcbcccd"
      })
    }
  }
}
// 随机颜色生成方法
// 十六进制的随机颜色
export  function randomColor():string{
  let color:string = "#"
  let colors:string[] = [
    "a","b", "c","d", "e","f",
    "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
  ]
  for (let i = 0; i <8 ; i++) {
    color+=colors[Math.floor(Math.random()*15)]
  }
  return color
}

5. 支付页面

  • 效果图

  • 结构
页面:PayPage
自定义弹框组件:PayCustomDialogExample
  • 代码
// 付款页
import TitleComponent from '../../components/TitleComponent';
import PayCustomDialogExample from './PayCustomDialogExample';
@Entry
@Component
struct PayPage {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: PayCustomDialogExample(),
    alignment:DialogAlignment.Bottom,
    customStyle:true
  })
  onPageShow(): void {
    this.dialogController.open()
  }
  @State message: string = 'Hello World';
  build() {
    Column() {
      TitleComponent({title:"支付",is_icon:true})
    }
    .height('100%')
    .width('100%').backgroundColor("#ff5f5d5d")
  }
}
// 付款弹框组件
import InputComponent from '../../components/InputComponent'
@CustomDialog
export  default struct PayCustomDialogExample {
  controller: CustomDialogController = new CustomDialogController({
    builder: PayCustomDialogExample({}),
  })
  build() {
    Column() {
      Text("付款给").border({width:{bottom:1},color:'#ffe2e2e2'}).width("100%").lineHeight(20)
        .textAlign(TextAlign.Center).padding({top:10,bottom:10})
        .fontWeight(500).fontColor("#ff044a6e")
      Stack({alignContent:Alignment.Top}){
        Column().width("100%").height(80).shadow({radius:60,color:"#ffcfcfcf"}).borderRadius(20).margin({top:50})
        Column({space:5}){
          Image($r("app.media.HOS")).height(50).borderRadius(10).aspectRatio(2).margin({top:20})
          Text("HarmonyOS APP应用开发").fontSize(14).fontWeight(700)
          Text("2024-06-30").fontSize(12).fontColor("#666")
        }
      }.width("100%").padding({right:50,left:50,bottom:20})
      Text("支付账户").fontWeight(700).fontSize(18).height(40)
        .width("100%").padding({left:20})
      Row(){
        Column().width(50).backgroundColor("#0ff").height(30).margin(5).borderRadius(5)
        Column(){
          Text("中国银行储蓄卡").fontColor("#ff033048").fontSize(14).width("100%")
          Text("xxxxxx  xxxxx  xxxx ").fontColor("#999").fontSize(12).width("100%")
        }.layoutWeight(1)
        Image($r("app.media.right_icon")).width(20)
      }.height(40).margin({left:20,right:20}).shadow({radius:60,color:"#ffcfcfcf"}).borderRadius(10)
      Text("支付金额").fontWeight(700).fontSize(18).height(40)
        .width("100%").padding({left:20}).margin({top:10,bottom:10})
      Text("¥155.55").fontWeight(700).fontSize(24).height(40)
     Column({space:10}){
       InputComponent({title:'用户订单姓名',placeholder:'输入你的名称'})
       InputComponent({title:'用户订单电话号码',placeholder:'输入您的电话号码'})
     }.width("100%").padding({left:20,right:20})
      Button("下一步").width(228).backgroundColor("#ff09b19d").margin({top:50})
    }.height("100%").width("100%").margin({top:80}).borderRadius({topLeft:20,topRight:20}).backgroundColor(Color.White)
  }
}

day02 项目结构基本搭建完成,静态页面基本编写完成


六、邮箱认证服务开通流程

目录
相关文章
|
9天前
|
人工智能 运维 安全
|
7天前
|
人工智能 异构计算
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
|
8天前
|
机器学习/深度学习 人工智能 自然语言处理
B站开源IndexTTS2,用极致表现力颠覆听觉体验
在语音合成技术不断演进的背景下,早期版本的IndexTTS虽然在多场景应用中展现出良好的表现,但在情感表达的细腻度与时长控制的精准性方面仍存在提升空间。为了解决这些问题,并进一步推动零样本语音合成在实际场景中的落地能力,B站语音团队对模型架构与训练策略进行了深度优化,推出了全新一代语音合成模型——IndexTTS2 。
676 23
|
8天前
|
人工智能 测试技术 API
智能体(AI Agent)搭建全攻略:从概念到实践的终极指南
在人工智能浪潮中,智能体(AI Agent)正成为变革性技术。它们具备自主决策、环境感知、任务执行等能力,广泛应用于日常任务与商业流程。本文详解智能体概念、架构及七步搭建指南,助你打造专属智能体,迎接智能自动化新时代。
|
14天前
|
人工智能 JavaScript 测试技术
Qwen3-Coder入门教程|10分钟搞定安装配置
Qwen3-Coder 挑战赛简介:无论你是编程小白还是办公达人,都能通过本教程快速上手 Qwen-Code CLI,利用 AI 轻松实现代码编写、文档处理等任务。内容涵盖 API 配置、CLI 安装及多种实用案例,助你提升效率,体验智能编码的乐趣。
1106 110
|
人工智能 数据可视化 数据挖掘
Quick BI 体验&征文有奖!
瓴羊生态推出Quick BI 征文激励计划,鼓励用户分享数据分析实践经验与技术洞察,征集高质量原创文章。内容围绕AI功能体验与BI案例实践,设季奖、年奖及参与奖,优秀作者可获现金奖励、产品内测资格及官方认证形象。投稿截止至2026年3月31日。
Quick BI 体验&征文有奖!