一、制作账单页面
该页面用来展示本月至目前为止,每天支出和收入总计,以列表的形式展现,不难分析出,该页面主要是一个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即可。
四、项目效果展示
到目前为止,基本功能均已实现,下面是效果展示。
项目展示效果