回复
#星光不负 码向未来# 记账本应用开发指南 原创
一路向北545
发布于 2025-10-23 22:49
浏览
0收藏
下面我将为您创建一个基于HarmonyOS NEXT和ArkTS的记账本应用,使用relationalStore进行本地数据存储。
1. 数据模型设计
首先,我们设计记账本的数据模型:
export class Record {
id?: number; // 主键,自增
type: number; // 0-支出 1-收入
amount: number; // 金额
category: string; // 分类
description: string; // 描述
date: string; // 日期 YYYY-MM-DD
time: string; // 时间 HH:mm:ss
createTime: number; // 创建时间戳
constructor(
type: number,
amount: number,
category: string,
description: string,
date: string,
time: string
) {
this.type = type;
this.amount = amount;
this.category = category;
this.description = description;
this.date = date;
this.time = time;
this.createTime = new Date().getTime();
}
}2. 数据库管理
创建数据库管理类:
// database/RecordStore.ts
import relationalStore from '@ohos.data.relationalStore';
import { Record } from '../model/Record';
import { TotalInfo } from '../model/TotalInfo';
export class RecordStore {
private static instance: RecordStore;
private rdbStore: relationalStore.RdbStore | null = null;
// 数据库配置
private readonly STORE_CONFIG: relationalStore.StoreConfig = {
name: 'FinanceRecord.db',
securityLevel: relationalStore.SecurityLevel.S1
};
// 表结构
private readonly TABLE_NAME = 'records';
private readonly SQL_CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS ${this.TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type INTEGER NOT NULL,
amount REAL NOT NULL,
category TEXT NOT NULL,
description TEXT,
date TEXT NOT NULL,
time TEXT NOT NULL,
createTime INTEGER NOT NULL
)
`;
private constructor() {
}
public static getInstance(): RecordStore {
if (!RecordStore.instance) {
RecordStore.instance = new RecordStore();
}
return RecordStore.instance;
}
// 初始化数据库
async initialize(context: Context): Promise<void> {
try {
this.rdbStore = await relationalStore.getRdbStore(context, this.STORE_CONFIG);
await this.rdbStore.executeSql(this.SQL_CREATE_TABLE);
console.info('Database initialized successfully');
} catch (error) {
console.error(`Failed to initialize database: ${error}`);
}
}
// 添加记录
async addRecord(record: Record): Promise<number> {
if (!this.rdbStore) {
throw new Error('Database not initialized');
}
const valueBucket: relationalStore.ValuesBucket = {
'type': record.type,
'amount': record.amount,
'category': record.category,
'description': record.description,
'date': record.date,
'time': record.time,
'createTime': record.createTime
};
try {
const id = await this.rdbStore.insert(this.TABLE_NAME, valueBucket);
console.info(`Record added with id: ${id}`);
return id;
} catch (error) {
throw new Error(`Failed to add record: ${error}`);
}
}
// 查询所有记录
async getAllRecords(): Promise<Record[]> {
if (!this.rdbStore) {
throw new Error('Database not initialized');
}
const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
predicates.orderByDesc('date').orderByDesc('time');
try {
const resultSet = await this.rdbStore.query(predicates,
['id', 'type', 'amount', 'category', 'description', 'date', 'time', 'createTime']);
const records: Record[] = [];
while (resultSet.goToNextRow()) {
const record = new Record(
resultSet.getDouble(resultSet.getColumnIndex('type')),
resultSet.getDouble(resultSet.getColumnIndex('amount')),
resultSet.getString(resultSet.getColumnIndex('category')),
resultSet.getString(resultSet.getColumnIndex('description')),
resultSet.getString(resultSet.getColumnIndex('date')),
resultSet.getString(resultSet.getColumnIndex('time'))
);
record.id = resultSet.getLong(resultSet.getColumnIndex('id'));
record.createTime = resultSet.getLong(resultSet.getColumnIndex('createTime'));
records.push(record);
}
resultSet.close();
return records;
} catch (error) {
throw new Error(`Failed to query records: ${error}`);
}
}
// 根据ID删除记录
async deleteRecordById(id: number): Promise<number> {
if (!this.rdbStore) {
throw new Error('Database not initialized');
}
const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
predicates.equalTo('id', id);
try {
const deletedRows = await this.rdbStore.delete(predicates);
console.info(`Deleted ${deletedRows} record(s)`);
return deletedRows;
} catch (error) {
throw new Error(`Failed to delete record: ${error}`);
}
}
// 更新记录
async updateRecord(record: Record): Promise<number> {
if (!this.rdbStore || !record.id) {
throw new Error('Database not initialized or record id is missing');
}
const valueBucket: relationalStore.ValuesBucket = {
'type': record.type,
'amount': record.amount,
'category': record.category,
'description': record.description,
'date': record.date,
'time': record.time
};
const predicates = new relationalStore.RdbPredicates(this.TABLE_NAME);
predicates.equalTo('id', record.id);
try {
const updatedRows = await this.rdbStore.update(valueBucket, predicates);
console.info(`Updated ${updatedRows} record(s)`);
return updatedRows;
} catch (error) {
throw new Error(`Failed to update record: ${error}`);
}
}
// 获取统计信息
async getStatistics(): Promise<TotalInfo> {
const records = await this.getAllRecords();
let totalIncome = 0;
let totalExpense = 0;
records.forEach(record => {
if (record.type === 1) { // 收入
totalIncome += record.amount;
} else { // 支出
totalExpense += record.amount;
}
});
return new TotalInfo(totalIncome, totalExpense, totalIncome - totalExpense)
}
}export class TotalInfo {
totalIncome: number = 0
totalExpense: number = 0
balance: number = 0
constructor(totalIncome: number, totalExpense: number, balance: number) {
this.totalIncome = totalIncome
this.totalExpense = totalExpense
this.balance = balance
}
}3. 主页面实现

import { Record } from '../model/Record';
import { RecordStore } from '../database/RecordStore';
import { router } from '@kit.ArkUI';
@Entry
@Component
struct HomePage {
@State records: Record[] = [];
@State totalIncome: number = 0;
@State totalExpense: number = 0;
@State balance: number = 0;
private recordStore = RecordStore.getInstance();
aboutToAppear() {
this.loadData();
}
async loadData() {
try {
this.records = await this.recordStore.getAllRecords();
const stats = await this.recordStore.getStatistics();
this.totalIncome = stats.totalIncome;
this.totalExpense = stats.totalExpense;
this.balance = stats.balance;
} catch (error) {
console.error(`Failed to load data: ${error}`);
}
}
build() {
Column() {
// 顶部统计区域
this.buildHeader()
// 记录列表
List({ space: 10 }) {
ForEach(this.records, (record: Record) => {
ListItem() {
this.buildRecordItem(record)
}
}, (record: Record) => record.id?.toString() ?? '')
}
.layoutWeight(1)
.width('100%')
// 底部添加按钮
Button('添加记录')
.width('90%')
.height(50)
.fontSize(18)
.backgroundColor('#007DFF')
.onClick(() => {
router.pushUrl({ url: 'pages/AddRecordPage' });
})
.margin(20)
}
.width('100%')
.height('100%')
.backgroundColor('#F1F3F5')
}
@Builder buildHeader() {
Column() {
Text('总余额')
.fontSize(16)
.fontColor('#666')
Text(`¥${this.balance.toFixed(2)}`)
.fontSize(32)
.fontColor(this.balance >= 0 ? '#FF6B35' : '#FF4757')
.fontWeight(FontWeight.Bold)
.margin({ top: 5, bottom: 20 })
Row() {
Column() {
Text('收入')
.fontSize(14)
.fontColor('#666')
Text(`¥${this.totalIncome.toFixed(2)}`)
.fontSize(18)
.fontColor('#10B981')
.fontWeight(FontWeight.Medium)
}
.layoutWeight(1)
Column() {
Text('支出')
.fontSize(14)
.fontColor('#666')
Text(`¥${this.totalExpense.toFixed(2)}`)
.fontSize(18)
.fontColor('#EF4444')
.fontWeight(FontWeight.Medium)
}
.layoutWeight(1)
}
.width('80%')
}
.width('100%')
.padding(20)
.backgroundColor('#FFFFFF')
}
@Builder buildRecordItem(record: Record) {
Row() {
Column() {
Text(record.category)
.fontSize(16)
.fontColor('#1F2937')
Text(record.description)
.fontSize(12)
.fontColor('#6B7280')
.margin({ top: 2 })
Text(`${record.date} ${record.time}`)
.fontSize(10)
.fontColor('#9CA3AF')
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Text(`${record.type === 1 ? '+' : '-'}¥${record.amount.toFixed(2)}`)
.fontSize(16)
.fontColor(record.type === 1 ? '#10B981' : '#EF4444')
.fontWeight(FontWeight.Medium)
}
.width('95%')
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(10)
.margin({ left: 10, right: 10, top: 5 })
}
}4. 添加记录页面

// pages/AddRecordPage.ets
import { Record } from '../model/Record';
import { RecordStore } from '../database/RecordStore';
import { promptAction, router } from '@kit.ArkUI';
@Entry
@Component
struct AddRecordPage {
@State recordType: number = 0; // 0-支出, 1-收入
@State amount: string = '';
@State category: string = '';
@State description: string = '';
@State date: string = this.getCurrentDate();
@State time: string = this.getCurrentTime();
private recordStore = RecordStore.getInstance();
// 分类选项
private expenseCategories: string[] = ['餐饮', '购物', '交通', '娱乐', '医疗', '教育', '其他'];
private incomeCategories: string[] = ['工资', '奖金', '投资', '兼职', '其他'];
getCurrentDate(): string {
const now = new Date();
return `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate()
.toString()
.padStart(2, '0')}`;
}
getCurrentTime(): string {
const now = new Date();
return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes()
.toString()
.padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
}
async saveRecord() {
if (!this.amount || !this.category) {
promptAction.showToast({ message: '请填写金额和分类', duration: 2000 });
return;
}
const record = new Record(
this.recordType,
parseFloat(this.amount),
this.category,
this.description,
this.date,
this.time
);
try {
await this.recordStore.addRecord(record);
promptAction.showToast({ message: '记录添加成功', duration: 2000 });
router.back();
} catch (error) {
promptAction.showToast({ message: '添加失败,请重试', duration: 2000 });
console.error(`Failed to save record: ${error}`);
}
}
build() {
Column({ space: 20 }) {
// 类型选择
Row() {
Button('支出')
.layoutWeight(1)
.backgroundColor(this.recordType === 0 ? '#EF4444' : '#F1F5F9')
.fontColor(this.recordType === 0 ? '#FFFFFF' : '#64748B')
.onClick(() => {
this.recordType = 0;
this.category = '';
})
Button('收入')
.layoutWeight(1)
.backgroundColor(this.recordType === 1 ? '#10B981' : '#F1F5F9')
.fontColor(this.recordType === 1 ? '#FFFFFF' : '#64748B')
.onClick(() => {
this.recordType = 1;
this.category = '';
})
}
.width('90%')
.height(50)
.borderRadius(25)
// 金额输入
TextInput({ placeholder: '输入金额' })
.width('90%')
.height(50)
.type(InputType.Number)
.onChange((value: string) => {
this.amount = value;
})
// 分类选择
Text('选择分类')
.width('90%')
.fontSize(16)
.fontColor('#374151')
.textAlign(TextAlign.Start)
List({ space: 10 }) {
ForEach(this.recordType === 0 ? this.expenseCategories : this.incomeCategories,
(item: string) => {
ListItem() {
Button(item)
.backgroundColor(this.category === item ?
(this.recordType === 0 ? '#EF4444' : '#10B981') : '#F1F5F9')
.fontColor(this.category === item ? '#FFFFFF' : '#64748B')
.onClick(() => {
this.category = item;
})
}
})
}
.width('90%')
// 描述输入
TextInput({ placeholder: '添加描述(可选)' })
.width('90%')
.height(50)
.onChange((value: string) => {
this.description = value;
})
// 日期时间
Row() {
Text('日期:')
.fontSize(14)
.fontColor('#6B7280')
Text(this.date)
.fontSize(14)
.fontColor('#374151')
.margin({ left: 10 })
Text('时间:')
.fontSize(14)
.fontColor('#6B7280')
.margin({ left: 20 })
Text(this.time)
.fontSize(14)
.fontColor('#374151')
.margin({ left: 10 })
}
.width('90%')
.justifyContent(FlexAlign.Start)
// 保存按钮
Button('保存记录')
.width('90%')
.height(50)
.backgroundColor('#007DFF')
.fontColor('#FFFFFF')
.onClick(() => this.saveRecord())
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F8FAFC')
}
}5. 应用入口和权限配置
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import { RecordStore } from './database/RecordStore';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
console.info('EntryAbility onCreate');
// 初始化数据库
const recordStore = RecordStore.getInstance();
recordStore.initialize(this.context).then(() => {
console.info('Database initialized in ability');
}).catch((error) => {
console.error(`Failed to initialize database: ${error}`);
});
}
onWindowStageCreate(windowStage: window.WindowStage): void {
console.info('EntryAbility onWindowStageCreate');
windowStage.loadContent('pages/HomePage', (err, data) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data));
});
}
}6. 模块配置文件
// module.json5
{
"module": {
"name": "finance",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet"
],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"action.system.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
}
}使用说明
- 初始化数据库:应用启动时自动初始化数据库和表结构
- 添加记录:点击"添加记录"按钮,选择类型(支出/收入),输入金额,选择分类,添加描述
- 查看记录:主页显示所有记录列表,按时间倒序排列
- 统计信息:主页顶部显示总收入、总支出和余额
- 数据持久化:所有数据使用relationalStore本地存储
功能特点
- ✅ 完整的CRUD操作(创建、读取、更新、删除)
- ✅ 收入和支出分类管理
- ✅ 实时统计计算
- ✅ 本地数据持久化
- ✅ 响应式UI设计
- ✅ 错误处理和用户提示
这个记账本应用提供了完整的财务管理功能,使用了HarmonyOS NEXT的relationalStore进行数据存储,具有良好的用户体验和数据安全性。您可以根据需要进一步扩展功能,如添加数据导出、图表分析、预算设置等。
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
标签
赞
收藏
回复
相关推荐



















