鸿蒙app 交易记录可视化(分类统计/账单导出)【玩转华为云】
【摘要】 引言在金融与个人理财类鸿蒙应用中,交易记录可视化帮助用户直观了解收支结构与趋势,支持分类统计、图表展示与账单导出,提升财务透明度与管理效率。鸿蒙系统提供关系型数据库、图表绘制与文件操作能力,可高效实现该功能。技术背景鸿蒙框架:Stage 模型,@Component构建 UI,RelationalStore存储交易数据,@ohos.chart绘制统计图,@ohos.file.fs实现账单导出。...
引言
在金融与个人理财类鸿蒙应用中,交易记录可视化帮助用户直观了解收支结构与趋势,支持分类统计、图表展示与账单导出,提升财务透明度与管理效率。鸿蒙系统提供关系型数据库、图表绘制与文件操作能力,可高效实现该功能。
技术背景
-
鸿蒙框架:Stage 模型,
@Component构建 UI,RelationalStore存储交易数据,@ohos.chart绘制统计图,@ohos.file.fs实现账单导出。 -
数据处理:按类别、时间聚合交易金额,计算占比与趋势。
-
可视化:饼图(分类占比)、柱状图(月度收支)、折线图(趋势)。
-
导出格式:CSV/Excel,便于用户在三方软件中分析。
应用使用场景
-
个人记账:分类查看餐饮、交通、娱乐等支出占比。
-
企业账务:按部门、项目统计交易流水。
-
消费分析:月度收支柱状图发现超支类别。
-
数据备份:导出 CSV 存档或报税。
核心特性
-
多维度统计:支持按类别、时间、支付方式分组。
-
图表交互:点击图例筛选数据,缩放时间轴。
-
离线可用:数据本地存储,无网亦可分析。
-
安全导出:文件加密或权限控制,保护隐私。
原理流程图与原理解释
流程图
graph TD
A[交易记录数据] --> B[数据库查询与聚合]
B --> C[分类统计/时间分组]
C --> D[生成图表数据集]
D --> E[渲染饼图/柱状图/折线图]
B --> F[生成账单文件(CSV)]
E & F --> G[UI 展示与导出]
原理解释
-
数据查询:使用 SQL 按
category、date分组求和。 -
分类统计:计算每个类别总金额及占总支出比例。
-
图表渲染:将数据转为图表库所需结构,驱动绘制。
-
账单导出:将查询结果写入 CSV 文件,保存至公共目录或沙箱。
环境准备
-
DevEco Studio 4.0+
-
SDK API 9+(支持
chart、file.fs、relationalStore) -
权限:
"requestPermissions": [
{ "name": "ohos.permission.READ_USER_STORAGE" },
{ "name": "ohos.permission.WRITE_USER_STORAGE" }
]
不同场景下详细代码实现
1. 数据模型(Model/Transaction.ts)
export class Transaction {
id: number;
category: string;
amount: number;
date: string; // YYYY-MM-DD
type: 'income' | 'expense';
note: string;
constructor(id: number, category: string, amount: number, date: string, type: 'income' | 'expense', note: string) {
this.id = id;
this.category = category;
this.amount = amount;
this.date = date;
this.type = type;
this.note = note;
}
}
2. 数据库服务(Database/TransactionDB.ts)
import relationalStore from '@ohos.data.relationalStore';
import { Transaction } from '../Model/Transaction';
export class TransactionDB {
private rdbStore: relationalStore.RdbStore | null = null;
async init(context: Context) {
this.rdbStore = await relationalStore.getRdbStore(context, {
name: 'trans.db',
securityLevel: relationalStore.SecurityLevel.S1
});
const sql = `CREATE TABLE IF NOT EXISTS trans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT,
amount REAL,
date TEXT,
type TEXT,
note TEXT
)`;
await this.rdbStore.executeSql(sql);
}
async insert(tx: Omit<Transaction, 'id'>): Promise<number> {
const valueBucket = {
'category': tx.category,
'amount': tx.amount,
'date': tx.date,
'type': tx.type,
'note': tx.note
};
return await this.rdbStore!.insert('trans', valueBucket);
}
async getAll(): Promise<Transaction[]> {
const resultSet = await this.rdbStore!.query(new relationalStore.RdbPredicates('trans'), ['*']);
const list: Transaction[] = [];
while (resultSet.goToNextRow()) {
list.push(new Transaction(
resultSet.getLong(resultSet.getColumnIndex('id')),
resultSet.getString(resultSet.getColumnIndex('category')),
resultSet.getDouble(resultSet.getColumnIndex('amount')),
resultSet.getString(resultSet.getColumnIndex('date')),
resultSet.getString(resultSet.getColumnIndex('type')) as 'income' | 'expense',
resultSet.getString(resultSet.getColumnIndex('note'))
));
}
resultSet.close();
return list;
}
async getByCategory(): Promise<Map<string, number>> {
const resultSet = await this.rdbStore!.query(new relationalStore.RdbPredicates('trans'),
['category', 'SUM(amount) as total']);
const map = new Map<string, number>();
while (resultSet.goToNextRow()) {
map.set(resultSet.getString(resultSet.getColumnIndex('category')),
resultSet.getDouble(resultSet.getColumnIndex('total')));
}
resultSet.close();
return map;
}
async exportToCSV(path: string): Promise<boolean> {
const data = await this.getAll();
const csvHeader = 'ID,Category,Amount,Date,Type,Note\n';
let csvContent = csvHeader;
data.forEach(tx => {
csvContent += `${tx.id},${tx.category},${tx.amount},${tx.date},${tx.type},"${tx.note}"\n`;
});
try {
const file = fs.openSync(path, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
fs.writeSync(file.fd, csvContent);
fs.closeSync(file);
return true;
} catch (e) {
console.error('Export CSV failed:', e);
return false;
}
}
}
3. 图表数据服务(Service/ChartService.ts)
import { TransactionDB } from '../Database/TransactionDB';
export class ChartService {
private db: TransactionDB = new TransactionDB();
async getCategoryPieData(): Promise<Array<{ category: string; value: number }>> {
const map = await this.db.getByCategory();
return Array.from(map.entries()).map(([category, value]) => ({ category, value }));
}
async getMonthlyBarData(): Promise<Array<{ month: string; income: number; expense: number }>> {
const data = await this.db.getAll();
const monthMap = new Map<string, { income: number; expense: number }>();
data.forEach(tx => {
const m = tx.date.substring(0, 7);
if (!monthMap.has(m)) monthMap.set(m, { income: 0, expense: 0 });
const item = monthMap.get(m)!;
if (tx.type === 'income') item.income += tx.amount;
else item.expense += tx.amount;
});
return Array.from(monthMap.entries()).map(([month, vals]) => ({ month, ...vals }));
}
}
4. UI 界面(pages/Index.ets)
import { TransactionDB } from '../Database/TransactionDB';
import { ChartService } from '../Service/ChartService';
@Entry
@Component
struct TransactionVisualPage {
@State pieData: Array<any> = [];
@State barData: Array<any> = [];
@State exportPath: string = '';
private db: TransactionDB = new TransactionDB();
private chartService: ChartService = new ChartService();
aboutToAppear() {
this.loadData();
}
async loadData() {
this.pieData = await this.chartService.getCategoryPieData();
this.barData = await this.chartService.getMonthlyBarData();
}
async exportBill() {
const fileName = `bill_${new Date().getTime()}.csv`;
const path = `/data/storage/el2/base/haps/entry/files/${fileName}`;
const ok = await this.db.exportToCSV(path);
if (ok) {
this.exportPath = path;
AlertDialog.show({ message: `导出成功: ${path}` });
} else {
AlertDialog.show({ message: '导出失败' });
}
}
build() {
Column({ space: 20 }) {
Text('交易记录可视化').fontSize(24).fontWeight(FontWeight.Bold);
// 分类饼图
Text('支出分类占比').fontSize(18);
// 此处用 Text 模拟图表,实际可用 @ohos.chart 绘制
ForEach(this.pieData, item => {
Row() {
Text(item.category).fontSize(14);
Text(`${item.value.toFixed(2)}`).fontSize(14);
}
})
// 月度收支柱状图
Text('月度收支').fontSize(18);
ForEach(this.barData, item => {
Row() {
Text(item.month).fontSize(14);
Text(`收入:${item.income.toFixed(2)} 支出:${item.expense.toFixed(2)}`).fontSize(14);
}
})
Button('导出账单CSV').onClick(() => this.exportBill())
Text(this.exportPath).fontSize(12).fontColor(Color.Gray);
}.width('100%').padding(16)
}
}
实际详细应用代码示例实现
见以上
TransactionDB、ChartService与 UI 完整代码。运行结果
-
页面显示分类支出与月度收支数据。
-
点击“导出账单CSV”生成文件,路径弹窗提示。
测试步骤以及详细代码
-
初始化数据库并插入测试数据。
-
调用
loadData()检查图表数据非空。 -
执行
exportBill()验证文件生成与内容正确性。
部署场景
-
个人理财 App:日常收支分析。
-
企业 ERP:部门费用统计与导出。
-
银行客户端:客户账单可视化与下载。
疑难解答
-
数据库空指针:确保
init在 UI 加载前调用。 -
导出失败:检查文件路径权限与目录存在性。
-
图表无数据:确认数据插入成功且查询条件正确。
未来展望
-
实时同步:云端数据同步后实时刷新图表。
-
多维度钻取:点击饼图区块查看该类明细。
-
PDF 导出:支持富文本报表。
技术趋势与挑战
-
趋势:可视化组件与 AI 分析结合,预测支出趋势。
-
挑战:大数据量下图表性能优化,导出文件加密。
总结
本文基于鸿蒙系统实现交易记录可视化功能,涵盖分类统计、图表展示与账单导出,数据库、服务与 UI 代码完整可运行,支持离线分析和安全导出,为鸿蒙金融类应用提供完整解决方案。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)