鸿蒙交易记录可视化(分类统计/账单导出)
【摘要】 一、引言在移动支付与个人财务管理日益普及的今天,用户对交易记录的 可视化分析 和 灵活导出 需求愈发强烈。传统的交易记录仅以列表形式展示,缺乏直观的分类统计(如餐饮、交通、购物等消费占比)和便捷的账单导出功能(如导出为Excel/CSV供记账软件使用),导致用户难以快速掌握消费规律或进行财务对账。鸿蒙操作系统(HarmonyOS)凭借其 分布式数据管理 和 UI组...
一、引言
二、技术背景
1. 交易记录可视化的核心需求
-
分类统计:按消费类型(如餐饮、交通、购物)自动分类,通过图表(饼图/柱状图)展示各类别的消费金额占比与趋势。 -
账单导出:将交易记录导出为通用格式(如CSV/Excel),支持分享至其他应用(如微信、邮箱)或本地保存,便于记账软件导入或财务对账。 -
数据安全:交易记录包含敏感信息(如交易金额、商户名称),需确保本地存储加密与用户授权访问。 -
交互体验:支持按时间筛选(如本月/本年)、搜索商户名称,提升数据查询效率。
2. 鸿蒙的技术能力支撑
-
UI组件库(ArkUI):通过 @ohos.agp.components
提供图表组件(如PieChart
、ColumnChart
,需结合第三方库或自定义绘制)和列表组件(List
、Grid
),支持动态数据绑定与交互事件。 -
分布式数据管理:通过 @ohos.data.preferences
或@ohos.data.rdb
(关系型数据库)存储交易记录,支持本地加密存储与多设备同步(可选)。 -
文件操作与分享:通过 @ohos.file.fs
管理本地文件(如生成CSV文件),通过@ohos.app.ability.Share
实现账单文件的分享(如发送至微信、邮箱)。 -
数据统计与分析:通过JavaScript/TypeScript的数组方法(如 reduce
、filter
)对交易记录进行分类汇总,计算各类别的总金额与占比。
3. 常见交易记录可视化形式
|
|
|
---|---|---|
|
|
|
|
|
|
|
|
|
三、应用使用场景
1. 个人日常财务管理
2. 家庭共同账本
3. 企业差旅报销
4. 商户销售数据分析
四、不同场景下详细代码实现
场景1:分类统计图表(饼图展示消费占比)
4.1 交易记录数据模型(models/Transaction.ets)
// src/main/ets/models/Transaction.ets
export interface Transaction {
id: string; // 交易唯一ID
time: string; // 交易时间(如 "2025-01-20 14:30")
merchant: string; // 商户名称(如 "星巴克")
amount: number; // 交易金额(单位:元)
category: string; // 消费类别(如 "餐饮"、"交通")
}
4.2 分类统计逻辑(utils/StatisticsUtil.ets)
// src/main/ets/utils/StatisticsUtil.ets
import { Transaction } from '../models/Transaction';
// 按类别统计总金额
export function getCategoryStatistics(transactions: Transaction[]): Map<string, number> {
const categoryMap = new Map<string, number>();
transactions.forEach(transaction => {
const currentAmount = categoryMap.get(transaction.category) || 0;
categoryMap.set(transaction.category, currentAmount + transaction.amount);
});
return categoryMap;
}
// 获取所有消费类别(去重)
export function getCategories(transactions: Transaction[]): string[] {
return [...new Set(transactions.map(t => t.category))];
}
4.3 饼图组件(components/PieChart.ets)
// src/main/ets/components/PieChart.ets
import { View, Canvas, CanvasRenderingContext2D } from '@ohos.agp.components';
import { getCategoryStatistics } from '../utils/StatisticsUtil';
@Component
export struct PieChart {
@Prop transactions: Transaction[] = []; // 接收父组件传递的交易记录
private readonly centerX = 150; // 饼图中心X坐标
private readonly centerY = 150; // 饼图中心Y坐标
private readonly radius = 100; // 饼图半径
build() {
Canvas(this.centerX * 2, this.centerY * 2)
.width('100%')
.height('300px')
.onReady(() => {
this.drawPieChart();
})
}
private drawPieChart() {
const ctx = getContext(this) as CanvasRenderingContext2D;
const stats = getCategoryStatistics(this.transactions);
const categories = Array.from(stats.keys());
const amounts = categories.map(cat => stats.get(cat)!);
const total = amounts.reduce((sum, a) => sum + a, 0);
let currentAngle = -Math.PI / 2; // 起始角度(12点方向)
categories.forEach((category, index) => {
const amount = stats.get(category)!;
const percentage = amount / total;
const sliceAngle = percentage * 2 * Math.PI;
// 设置颜色(可根据类别动态分配)
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'];
ctx.fillStyle = colors[index % colors.length];
// 绘制扇形
ctx.beginPath();
ctx.moveTo(this.centerX, this.centerY);
ctx.arc(this.centerX, this.centerY, this.radius, currentAngle, currentAngle + sliceAngle);
ctx.closePath();
ctx.fill();
// 绘制类别标签(可选)
const labelAngle = currentAngle + sliceAngle / 2;
const labelX = this.centerX + Math.cos(labelAngle) * (this.radius * 0.7);
const labelY = this.centerY + Math.sin(labelAngle) * (this.radius * 0.7);
ctx.fillStyle = '#000';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText(`${category} (${(percentage * 100).toFixed(1)}%)`, labelX, labelY);
currentAngle += sliceAngle;
});
}
}
4.4 交易记录页面(pages/TransactionPage.ets)
// src/main/ets/pages/TransactionPage.ets
import { Transaction } from '../models/Transaction';
import { PieChart } from '../components/PieChart';
@Entry
@Component
struct TransactionPage {
@State transactions: Transaction[] = [
{ id: '1', time: '2025-01-20 14:30', merchant: '星巴克', amount: 35.0, category: '餐饮' },
{ id: '2', time: '2025-01-20 08:00', merchant: '地铁站', amount: 6.0, category: '交通' },
{ id: '3', time: '2025-01-19 20:00', merchant: '万达广场', amount: 299.0, category: '购物' },
{ id: '4', time: '2025-01-19 12:30', merchant: '肯德基', amount: 45.0, category: '餐饮' },
];
build() {
Column() {
Text('交易记录分类统计')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 });
// 饼图展示分类统计
PieChart({ transactions: this.transactions })
// 交易列表(可选)
List() {
ForEach(this.transactions, (transaction: Transaction) => {
ListItem() {
Row() {
Text(transaction.time.substring(5, 10)) // 显示日期(如01-20)
.fontSize(14)
.width('15%');
Text(transaction.merchant)
.fontSize(14)
.width('30%');
Text(`¥${transaction.amount.toFixed(2)}`)
.fontSize(14)
.fontColor(Color.Red)
.width('20%');
Text(transaction.category)
.fontSize(14)
.width('25%');
}
.width('100%')
.padding(10);
}
})
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.padding(20);
}
}
4.5 原理解释
-
数据模型: Transaction
接口定义了交易记录的核心字段(时间、商户、金额、类别),用于存储和传递交易数据。 -
分类统计逻辑: getCategoryStatistics
函数遍历交易记录,按category
字段分组并累加金额,返回一个Map<string, number>
(键为类别,值为该类别总金额)。 -
饼图绘制: PieChart
组件通过Canvas API自定义绘制饼图,根据各类别的金额占比计算扇形角度(sliceAngle = (amount / total) * 2π
),并使用不同颜色区分类别,同时在扇形中心标注类别名称和占比。 -
组件集成:交易记录页面( TransactionPage
)将交易数据通过@State
管理,并将transactions
数组传递给PieChart
组件进行可视化展示,同时通过List
组件展示原始交易列表(可选)。
场景2:账单导出(生成CSV文件并分享)
4.6 CSV生成工具(utils/CsvUtil.ets)
// src/main/ets/utils/CsvUtil.ets
import { Transaction } from '../models/Transaction';
// 生成CSV内容(首行为表头,后续为交易记录)
export function generateCsvContent(transactions: Transaction[]): string {
const headers = ['时间', '商户', '金额(元)', '类别'];
const rows = transactions.map(t => [t.time, t.merchant, t.amount.toFixed(2), t.category]);
return [headers, ...rows].map(row => row.join(',')).join('\n');
}
// 保存CSV文件到本地并返回文件路径
export async function saveCsvFile(content: string, fileName: string = '交易账单.csv'): Promise<string> {
try {
// 获取应用文档目录(鸿蒙提供的全局存储路径)
const documentDir = await getDocumentDir();
const filePath = `${documentDir}/${fileName}`;
// 写入文件内容
await writeFile(filePath, content, { encoding: 'utf-8' });
return filePath;
} catch (error) {
console.error('保存CSV文件失败:', error);
throw new Error('文件保存失败');
}
}
// 鸿蒙文件操作辅助函数(简化示例,实际需使用 @ohos.file.fs)
async function getDocumentDir(): Promise<string> {
// 实际项目中应调用鸿蒙的文件API获取文档目录(如 context.getFilesDir())
return '/data/accounts/appdata/documents'; // 示例路径(需替换为真实路径)
}
async function writeFile(path: string, content: string, options: { encoding: string }): Promise<void> {
// 实际项目中应使用 @ohos.file.fs.writeFile 实现文件写入
console.log(`模拟写入文件: ${path}, 内容: ${content.substring(0, 50)}...`);
// 此处为模拟逻辑,真实环境需调用鸿蒙文件API
}
4.7 账单导出页面(pages/ExportPage.ets)
// src/main/ets/pages/ExportPage.ets
import { Transaction } from '../models/Transaction';
import { generateCsvContent, saveCsvFile } from '../utils/CsvUtil';
import { BusinessError } from '@ohos.base';
@Entry
@Component
struct ExportPage {
@State transactions: Transaction[] = [
{ id: '1', time: '2025-01-20 14:30', merchant: '星巴克', amount: 35.0, category: '餐饮' },
{ id: '2', time: '2025-01-20 08:00', merchant: '地铁站', amount: 6.0, category: '交通' },
{ id: '3', time: '2025-01-19 20:00', merchant: '万达广场', amount: 299.0, category: '购物' },
];
async handleExport() {
try {
// 1. 生成CSV内容
const csvContent = generateCsvContent(this.transactions);
// 2. 保存CSV文件到本地
const filePath = await saveCsvFile(csvContent, '我的交易账单.csv');
console.log(`CSV文件已保存至: ${filePath}`);
// 3. 调用鸿蒙分享功能(分享文件或直接打开)
await this.shareFile(filePath);
// 提示用户
alert('账单导出成功!文件已保存并可分享。');
} catch (error) {
console.error('导出失败:', error);
alert('导出失败,请重试。');
}
}
// 鸿蒙文件分享功能(简化示例,实际需使用 @ohos.app.ability.Share)
private async shareFile(filePath: string) {
// 实际项目中应调用鸿蒙的分享API(如 Share.shareFile(filePath))
console.log(`模拟分享文件: ${filePath}`);
// 此处为模拟逻辑,真实环境需调用鸿蒙分享API
}
build() {
Column() {
Text('账单导出')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 30 });
Button('导出CSV账单')
.width('80%')
.height(50)
.fontSize(16)
.onClick(() => {
this.handleExport();
})
.margin({ bottom: 20 });
Text('导出内容包含:交易时间、商户、金额、类别')
.fontSize(14)
.fontColor(Color.Gray);
}
.width('100%')
.height('100%')
.padding(20)
.justifyContent(FlexAlign.Center);
}
}
4.8 原理解释
-
CSV生成: generateCsvContent
函数将交易记录数组转换为CSV格式字符串(首行为表头时间,商户,金额(元),类别
,后续每行为一条交易的字段值,用逗号分隔)。 -
文件保存: saveCsvFile
函数模拟将CSV内容写入本地文件(实际需调用鸿蒙的@ohos.file.fs.writeFile
API),文件默认保存至应用的文档目录(如/data/accounts/appdata/documents/我的交易账单.csv
)。 -
文件分享: shareFile
函数模拟调用鸿蒙的分享能力(实际需使用@ohos.app.ability.Share
模块),用户可选择将文件保存至本地相册/文件管理器,或直接分享至微信、邮箱等应用。
五、原理解释
1. 交易记录可视化的核心流程
-
数据管理:交易记录以结构化数据( Transaction
对象数组)的形式存储在应用内存或本地数据库(如@ohos.data.rdb
),包含时间、商户、金额、类别等字段。 -
分类统计:通过JavaScript的数组方法(如 reduce
、filter
)或自定义工具函数(如getCategoryStatistics
),按category
字段分组并计算各类别的总金额,生成统计结果(如{ 餐饮: 80.0, 交通: 6.0, 购物: 299.0 }
)。 -
可视化展示:分类统计结果通过自定义Canvas绘制(如饼图)或第三方图表库(如ECharts for HarmonyOS)呈现,直观展示各类别的消费占比;交易列表通过 List
组件展示原始数据,支持筛选与搜索。 -
账单导出:将交易记录转换为通用格式(如CSV),通过文件操作API(如 @ohos.file.fs
)生成本地文件,并调用鸿蒙的分享能力(如@ohos.app.ability.Share
)实现文件保存或跨应用分享。
2. 关键技术点
-
Canvas绘图:通过 Canvas
组件和CanvasRenderingContext2D
API自定义绘制饼图/柱状图,灵活控制图表样式与交互(如颜色分配、标签显示)。 -
文件操作:使用鸿蒙的文件系统API(如 writeFile
)管理本地文件,确保交易记录数据的安全存储与导出。 -
数据转换:将结构化数据(JSON/对象数组)转换为通用格式(CSV/Excel),兼容外部记账软件或财务工具的数据导入需求。
六、核心特性
|
|
---|---|
|
|
|
|
|
|
|
|
|
|
七、原理流程图及原理解释
原理流程图(交易记录可视化执行流程)
+-----------------------+ +-----------------------+ +-----------------------+
| 用户查看交易记录 | | 分类统计计算 | | 可视化展示(饼图) |
| (点击“账单”页面) | ----> | (按类别分组求和) | ----> | (Canvas绘制占比) |
+-----------------------+ +-----------------------+ +-----------------------+
| | |
| 数据源:交易记录 | 生成统计结果 | 显示各类别消费占比 |
| (时间/商户/金额) | (如餐饮:80元) | (饼图扇形+标签) |
v v v
+-----------------------+ +-----------------------+ +-----------------------+
| 用户点击“导出账单” | | 生成CSV文件 | | 文件分享/保存 |
| (点击导出按钮) | ----> | (时间/商户/金额/类别)| ----> | (本地存储/微信分享) |
+-----------------------+ +-----------------------+ +-----------------------+
原理解释
-
数据准备:用户打开交易记录页面,应用从本地存储(如数据库或内存数组)加载交易记录数据(包含时间、商户、金额、类别)。 -
分类统计:通过工具函数(如 getCategoryStatistics
)对交易记录按category
字段分组,计算每个类别的总金额(如餐饮类总和为80元),生成统计结果(Map<string, number>
)。 -
可视化渲染:分类统计结果传递给自定义的 PieChart
组件,通过Canvas API绘制饼图——根据各类别金额占比计算扇形角度(如餐饮占比20%,则扇形角度为20%×360°),并用不同颜色区分类别,同时在扇形中心标注类别名称和百分比。 -
账单导出:用户点击“导出账单”后,应用将交易记录转换为CSV格式字符串(包含表头和每行交易数据),通过文件操作API将内容写入本地文件(如 /documents/我的交易账单.csv
),并调用鸿蒙的分享能力(如Share.shareFile
),支持用户将文件保存至本地或分享至其他应用(如微信、邮箱)。
八、环境准备
1. 开发环境
-
工具:DevEco Studio(鸿蒙官方IDE,基于IntelliJ IDEA),Node.js(≥14),HarmonyOS SDK(版本 ≥ 3.2,支持Canvas和文件API)。 -
设备:搭载鸿蒙OS 3.0及以上的设备(如华为P50、Mate 50),用于真机测试账单导出和分享功能。
2. 项目初始化
# 使用DevEco Studio创建新项目(选择“Empty Ability”模板)
# 配置交易记录数据模型(models/Transaction.ets)和工具函数(utils/StatisticsUtil.ets、utils/CsvUtil.ets)
3. 权限配置
config.json
中声明以下权限(根据实际需求调整):{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.READ_MEDIA",
"reason": "用于读取交易记录相关媒体文件(如有图片凭证)"
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "用于保存导出的账单文件"
}
]
}
}
九、实际详细应用代码示例实现
完整代码结构(基于场景1~2)
-
数据模型( models/Transaction.ets
):定义交易记录的结构(时间、商户、金额、类别)。 -
统计工具( utils/StatisticsUtil.ets
):提供分类统计和类别提取的逻辑。 -
图表组件( components/PieChart.ets
):自定义Canvas绘制饼图,展示消费占比。 -
导出工具( utils/CsvUtil.ets
):生成CSV文件内容并保存至本地。 -
页面集成( pages/TransactionPage.ets
、pages/ExportPage.ets
):展示交易列表与分类统计图表,提供账单导出功能。
-
使用DevEco Studio创建鸿蒙项目,按照代码示例实现各模块。 -
在模拟器或真机上运行应用,添加测试交易记录,查看分类统计饼图和交易列表。 -
点击“导出账单”按钮,验证CSV文件是否生成并支持分享/保存。
十、运行结果
正常情况(功能生效)
-
分类统计:饼图清晰展示各消费类别的占比(如餐饮30%、交通15%、购物55%),鼠标悬停(或点击)可查看具体金额。 -
账单导出:点击“导出账单”后,生成包含交易时间、商户、金额、类别的CSV文件,支持保存至本地或分享至微信/邮箱。 -
交互体验:交易列表支持滚动查看,分类统计图表响应式适配不同屏幕尺寸。
异常情况(功能未生效)
-
图表未渲染:检查 PieChart
组件是否正确接收交易数据,或Canvas绘制逻辑是否有误(如坐标计算错误)。 -
文件保存失败:确认文件路径权限(如应用是否有写入文档目录的权限),或 writeFile
模拟逻辑是否替换为真实API。 -
分享无响应:检查鸿蒙分享API(如 @ohos.app.ability.Share
)是否正确调用,或设备是否支持文件分享功能。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)