Harmony OS个人项目——简单记账APP(6)

一、制作账单页面

该页面用来展示本月至目前为止,每天支出和收入总计,以列表的形式展现,不难分析出,该页面主要是一个List组件,结合ForEach循环遍历数组实现数据的展示。其中,需要保存这个月的收支情况,所以需要实现数据持久化,为了方便编写,设计了一个类,包含日期,一天支出,一天收入三个属性元素。将对该类的数据持久化操作也单独放到一个文件中进行导入。

1、账单页面实现代码

其中调用了数据库操作类,将数据库中的数据存到数组中进行展示。

import router from '@ohos.router'
import dailyModel from '../model/DailyModel';
import dailyInfo from '../viewmodel/dailyInfo';

@Entry
@Component
struct Bill {
  //获取当前时间
  time: Date = new Date()
  //当月天数
  day: number = this.time.getDate()
  //当前日期
  date: string = this.time.getFullYear()+'/'+this.time.getMonth()+'/'
  //每日收支
  @State dailyStatic: dailyInfo[] = []

  //生命周期函数,在build执行之前执行函数中的内容
  aboutToAppear(){
    //获取当月每日的收入支出情况数组数据
    dailyModel.getTaskList()
      .then(dailyStatic => {
        this.dailyStatic = dailyStatic
        console.log('taskLog','每日收支记录加载成功')
      })
  }

  build() {
   Column(){
     //标题栏,返回按钮以及标题
     Row(){
       Image($r('app.media.back'))
         .width(50)
         .borderRadius(50)
         //点击事件,实现页面跳转
         .onClick(() => {
           router.pushUrl(
             {
               url: 'pages/Index'
             },
             router.RouterMode.Single,
             err => {
               if(err){
                 console.log('返回主页失败')
               }
             }
           )
         })
       Text('本月账单')   //标题
         .fontSize(30)
         .margin({left: 10})
     }
     .width('100%')
     .margin({bottom: 30})

     Row(){
       Column(){
         //提示文本信息
         Row(){
           Text('日期')
             .fontSize(25)
           Text('支出')
             .fontSize(25)
           Text('收入')
             .fontSize(25)
         }
         .width('100%')
         .margin({top: 10, bottom: 10})
         .justifyContent(FlexAlign.SpaceAround)
         //分割线
         Row(){
           Divider()
             .color(Color.Black)
             .strokeWidth(3)
         }
         .width('100%')
         //每日收支列表
         Row(){
           List(){
             ForEach(
               this.dailyStatic,
               (item: dailyInfo, index) => {
                 ListItem(){
                   Row(){
                     Column(){
                       Text(item.date.substring(5))
                         .fontSize(20)
                     }
                     .width('30%')
                     Column(){
                       //如果当日没有支出则显示'-'
                       if(item.payout == 0){
                         Text('-')
                           .fontSize(20)
                       } else {
                         Text('-¥'+item.payout.toFixed(2))
                           .fontSize(20)
                       }
                     }
                     .width('30%')
                     Column(){
                       //如果当日没有收入则显示'-'
                       if(item.income == 0){
                         Text('-')
                           .fontSize(20)
                       } else {
                         Text('¥'+item.income.toFixed(2))
                           .fontSize(20)
                       }
                     }
                     .width('30%')
                   }
                   .width('100%')
                   .margin({bottom: 10})
                   .justifyContent(FlexAlign.SpaceAround)
                 }
               }
             )
           }
         }
         .width('100%')
         .height(600)
         .margin({top: 10})
         .alignItems(VerticalAlign.Top)

       }
       .width('100%')
       .backgroundColor(Color.White)
       .borderRadius(10)
       .shadow({radius: 20, color: Color.Black, offsetX: 0, offsetY: 0})
     }
     .width('90%')
   }
    .width('100%')
    .height('100%')
    .backgroundColor('#ffbfe8fd')
  }
}

2、每日情况类的创建代码

包括三个属性,日期,该日期下的总支出和总收入。

export default class dailyInfo{
  //日期
  date: string
  //当日总支出
  payout: number
  //当日总收入
  income: number

  constructor(date: string, payout: number, income: number) {
    this.date = date
    this.payout = payout
    this.income = income
  }
}

3、数据库操作类的代码

该类主要是进行数据库操作的,其中初始化数据库操作在EntryAbility页面中执行;

对查询操作进行了一些改变,因为有可能用户在开始使用时不是当月第一天,或者是用户某天没进行使用,那么当天的数据就不会插入到数据库,所以当我们展示每天信息时需要填充每一天的信息,于是这里运用了逻辑表达式对这些日期进行添加,将数据通过日期连贯起来。具体是通过变量记录上一条数据的日期,然后与下一条数据日期对比,遍历两者之间相差的日期,也可能不差,那就不会进入循环;

因为对数据更改时,当天第一次打开时数据库中不会有这天的数据,所以需要先执行插入操作,所以这里设计了一个函数用于判断是否有这天的数据;

在更新操作时,收入支出金额是累加,不是替换,所以需要先获取当天的原数据,再累加更改为新数据。

这里的设计有很多冗余,但是用着方便,就暂时先不考虑优化的问题。

import relationalStore from '@ohos.data.relationalStore' //导入关系型数据库模板
import dailyInfo from '../viewmodel/dailyInfo'

class DailyModel{

  //当前日期
  time: Date = new Date()
  date: string = this.time.getFullYear()+'/'+this.time.getMonth()+'/'
  day: number = this.time.getDate()
  //数据库实例
  private rdbStore: relationalStore.RdbStore
  //表名
  private tableName: string = 'DAILY'

  //初始化
  initTaskDB(context){
    const config = {
      name: 'MyApplication.db',  //数据库名称
      securityLevel: relationalStore.SecurityLevel.S1  //安全级别
    }
    //创建表的sql语句
    const sql = `CREATE TABLE IF NOT EXISTS DAILY (
                DATE TEXT PRIMARY KEY,
                PAYOUT DOUBLE,
                INCOME DOUBLE
               )`
    //执行rdb
    relationalStore.getRdbStore(context,config,(err,rdbStore) => {
      if(err){
        console.log('taskLog','获取rdbStore失败!')
        return
      }
      //执行sql语句创建表
      rdbStore.executeSql(sql)
      console.log('taskLog','创建daily成功!')
      //赋值
      this.rdbStore = rdbStore
    })
  }

  //查询列表
  async getTaskList(){
    //创建查询条件
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    //执行查询,获取结果集
    let result = await this.rdbStore.query(predicates,['DATE','PAYOUT','INCOME'])
    //临时存放数组
    let dailyStatic: dailyInfo[] = []
    //记录上一条执行的结果日期
    let last = 1
    //遍历结果集
    while(!result.isAtLastRow){
      result.goToNextRow()  //移动到下一行
      let date = result.getString(result.getColumnIndex('DATE'))   //获取日期
      let payout = result.getDouble(result.getColumnIndex('PAYOUT'))   //获取支出
      let income = result.getDouble(result.getColumnIndex('INCOME'))   //获取收入
      //当前结果日期的日部分
      let next = parseInt(date.substring(this.date.length))

      for(let i = last; i < next; i++){
        const date = this.date+i.toString();  //构造日期字符串
        const payout = 0;  //默认支出为0
        const income = 0;   //默认收入为0
        dailyStatic.push({date,payout,income})   //添加到临时数组
      }
      //将当前数据添加到临时数组
      dailyStatic.push({date, payout, income})
      //更新上一个处理的日期
      last = next+1;
    }

    for(let i = last; i <= this.day; i++){
      const date = this.date+i.toString();  //构造日期字符串
      const payout = 0;  //默认支出为0
      const income = 0;  //默认收入为0
      dailyStatic.push({date, payout, income})
    }

    console.log('taskLog','查询到数据:',JSON.stringify(dailyStatic))
    return dailyStatic  //返回查询结果
  }

  //查询该数据是否存在
  async getTask(date: string): Promise<boolean>{
    //创建查询条件
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    //执行查询
    let result = await this.rdbStore.query(predicates,['DATE','PAYOUT','INCOME'])
    //遍历结果集
    while(!result.isAtLastRow){
      result.goToNextRow()  //移动到下一行
      let value = result.getString(result.getColumnIndex('DATE'))  //获取日期
      //如果当前日期存在一条数据
      if(value == date){
        console.log('taskLog','查询到数据')
        return true  //返回true
      }
    }
    console.log('taskLog','未查询到数据')
    return false
  }

  //添加
  addTask(date: string, payout: number, income: number): Promise<number>{
    return this.rdbStore.insert(this.tableName,{date, payout, income})
  }

  //更新支出
  async updateTaskStatus(date: string, amount: number, value: number){
    //创建查询条件
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    //添加日期条件
    predicates.equalTo('DATE',date)
    //执行查询
    let result = await this.rdbStore.query(predicates,['DATE','PAYOUT','INCOME'])
    //移动到结果集第一行
    result.goToNextRow()
    //如果当前value值为1,则执行更改支出金额,否则更改收入金额
    if(value){
      //新的支出金额
      let payout =amount + result.getDouble(result.getColumnIndex('PAYOUT'))
      let data = {'payout': payout}
      //执行更新操作
      return this.rdbStore.update(data,predicates)
    } else {
      //新的收入金额
      let income =amount + result.getDouble(result.getColumnIndex('INCOME'))
      let data = {'income': income}
      //执行更新操作
      return this.rdbStore.update(data,predicates)
    }

  }

  //删除——未实现(目前未用到)

}
//创建数据模型实例
let dailyModel = new DailyModel();
//导出模型实例
export default dailyModel as DailyModel

4、执行数据库数据更新操作

在addAmount页面中,当当天打开app时没有当天的数据,就执行插入操作,如果有,根据添加的明细进行更改收入,支出数据。

在aboutToAppear函数中进行判断是否为第一次打开操作。

 在handleAddTask函数中进行更新操作,如果当前进行的时支出操作,代码如下,收入操作类似。

 二、对我的页面进行数据持久化操作

当时这个页面在制作完成之后,就在考虑持久化的问题,但是这只有四个数据,并且只需要存一次,所以使用关系型数据库就有点太奢侈了,所以打算通过用户首选项进行实现,但是实在是没有时间,也重新简单学习了一下,有很多需要数据同步的地方,还是用关系型数据库比较简单,所以最终还是用的关系型数据库。

1、我的页面数据类的实现

一共包含四个属性元素,预算,总支出,总收入和结余。

export default class mineInfo{
  //预算
  budget: number
  //总支出
  month_payout: number
  //总收入
  month_income: number
  //结余
  balance: number
}

2、数据库操作类的代码

该类主要是对数据库进行操作,其初始化再EntryAbility中调用;

查询操作会在打开页面时就进行调用,如果是第一次打开,没有进行过设置,则会添加一条默认的数据,就不用在之后修改数据时再进行判断是否有这一条数据;

更新操作,通过一个flag值来判断当前需要更新的是哪一个变量,注意的是,不管更新哪一个,结余选项都需要进行更新。

import relationalStore from '@ohos.data.relationalStore'
import mineInfo from '../viewmodel/mineInfo'
class MineModel {
  //数据库实例
  private rdbStore: relationalStore.RdbStore
  //表名
  private tableName: string = 'MINE'
  //初始化
  initTaskDB(context) {
    const config = {
      name: 'MyApplication.db', //数据库名称
      securityLevel: relationalStore.SecurityLevel.S1 //安全级别
    }
    //创建表的sql语句
    const sql = `CREATE TABLE IF NOT EXISTS MINE (
                ID INTEGER,
                BUDGET DOUBLE,
                MONTH_PAYOUT DOUBLE,
                MONTH_INCOME DOUBLE,
                BALANCE DOUBLE
               )`
    //执行rdb
    relationalStore.getRdbStore(context, config, (err, rdbStore) => {
      if (err) {
        console.log('taskLog', '获取rdbStore失败!')
        return
      }
      //执行sql语句创建表
      rdbStore.executeSql(sql)
      console.log('taskLog', '创建mine成功!')
      //赋值
      this.rdbStore = rdbStore
    })
  }

  //查询数据
  async getTask() {
    //创建查询条件
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    //执行查询
    let result = await this.rdbStore.query(predicates, ['ID','BUDGET', 'MONTH_PAYOUT', 'MONTH_INCOME','BALANCE'])
    //临时数据存放
    let mine: mineInfo = {budget: 0, month_payout: 0, month_income: 0, balance: 0}
    let i = 0
    while (!result.isAtLastRow) {
      //移动到结果集第一行
      result.goToNextRow()
      let budget = result.getDouble(result.getColumnIndex('BUDGET'))
      let month_payout = result.getDouble(result.getColumnIndex('MONTH_PAYOUT'))
      let month_income = result.getDouble(result.getColumnIndex('MONTH_INCOME'))
      //更新数据对象
      mine.budget = budget
      mine.month_payout = month_payout
      mine.month_income = month_income
      mine.balance = budget - month_payout + month_income
      i++;
    }
    //如果没有查询到数据,添加一条默认数据
    if(i === 0){
      this.addTask(1,0,0,0,0)
    }

    return mine
  }

  //添加
  addTask(id: number, budget: number, month_payout: number, month_income: number, balance: number): Promise<number>{
    return this.rdbStore.insert(this.tableName,{id, budget, month_payout, month_income, balance})
  }

  //更新数据
  async updateTaskStatus(value: number, flag: number){
    //创建查询条件
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    predicates.equalTo('ID', 1)
    //执行查询
    let result = await this.rdbStore.query(predicates,['ID','BUDGET','MONTH_PAYOUT','MONTH_INCOME'])
    //移动到结果集第一行
    result.goToNextRow()
    //如果当前flag值为1,则执行更改预算,为2更改总支出,为3更改总收入,每一种情况都需要更新结余
    if(flag === 1){
      let month_payout = result.getDouble(result.getColumnIndex('MONTH_PAYOUT'))
      let month_income = result.getDouble(result.getColumnIndex('MONTH_INCOME'))
      let balance = value - month_payout + month_income
      //新的预算金额
      let data = {'budget': value, 'balance': balance}
      //执行更新操作
      return this.rdbStore.update(data,predicates)
    } else if(flag === 2) {
      let budget = result.getDouble(result.getColumnIndex('BUDGET'))
      let month_income = result.getDouble(result.getColumnIndex('MONTH_INCOME'))
      let balance = budget - value + month_income
      //新的支出金额
      let month_payout = value + result.getDouble(result.getColumnIndex('MONTH_PAYOUT'))
      let data = {'month_payout': month_payout, 'balance': balance}
      //执行更新操作
      return this.rdbStore.update(data,predicates)
    } else {
      let budget = result.getDouble(result.getColumnIndex('BUDGET'))
      let month_payout = result.getDouble(result.getColumnIndex('MONTH_PAYOUT'))
      let balance = budget - month_payout + value
      //新的收入金额
      let month_income = value + result.getDouble(result.getColumnIndex('MONTH_INCOME'))
      let data = {'month_income': month_income, 'balance': balance}
      //执行更新操作
      return this.rdbStore.update(data,predicates)
    }
  }
}
//创建数据模型实例
let mineModel = new MineModel();
//导出模型实例
export default mineModel as MineModel;

3、执行操作的代码

其中预算值的更新修改在mine页面中,在页面刚加载时就进行数据的查询和插入,获得初始数据,通过获取弹出框中的值来修改预算值。

在addAmount页面中,每添加一条明细都需要对总支出或者总收入进行修改,通过弹出框中获取的值,通过回调函数对总支出和总收入进行修改,同时修改预算值。主要代码如下:

三、修改小bug

测试时发现输入小数会自动阶段小数点之后的数,但是数据库中数据类型都是用的double,然后发现是,在弹出框中输入值时是string类型,转换为number类型时使用的是parseInt,是转换为整数类型,改为parseFloat即可。

四、项目效果展示

到目前为止,基本功能均已实现,下面是效果展示。

项目展示效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值