鸿蒙app 交易记录可视化(分类统计/账单导出)【玩转华为云】

举报
鱼弦 发表于 2025/12/30 10:05:50 2025/12/30
【摘要】 引言在金融与个人理财类鸿蒙应用中,交易记录可视化帮助用户直观了解收支结构与趋势,支持分类统计、图表展示与账单导出,提升财务透明度与管理效率。鸿蒙系统提供关系型数据库、图表绘制与文件操作能力,可高效实现该功能。技术背景鸿蒙框架:Stage 模型,@Component构建 UI,RelationalStore存储交易数据,@ohos.chart绘制统计图,@ohos.file.fs实现账单导出。...

引言

在金融与个人理财类鸿蒙应用中,交易记录可视化帮助用户直观了解收支结构与趋势,支持分类统计、图表展示与账单导出,提升财务透明度与管理效率。鸿蒙系统提供关系型数据库、图表绘制与文件操作能力,可高效实现该功能。

技术背景

  • 鸿蒙框架:Stage 模型,@Component构建 UI,RelationalStore存储交易数据,@ohos.chart绘制统计图,@ohos.file.fs实现账单导出。
  • 数据处理:按类别、时间聚合交易金额,计算占比与趋势。
  • 可视化:饼图(分类占比)、柱状图(月度收支)、折线图(趋势)。
  • 导出格式:CSV/Excel,便于用户在三方软件中分析。

应用使用场景

  1. 个人记账:分类查看餐饮、交通、娱乐等支出占比。
  2. 企业账务:按部门、项目统计交易流水。
  3. 消费分析:月度收支柱状图发现超支类别。
  4. 数据备份:导出 CSV 存档或报税。

核心特性

  • 多维度统计:支持按类别、时间、支付方式分组。
  • 图表交互:点击图例筛选数据,缩放时间轴。
  • 离线可用:数据本地存储,无网亦可分析。
  • 安全导出:文件加密或权限控制,保护隐私。

原理流程图与原理解释

流程图

graph TD  
    A[交易记录数据] --> B[数据库查询与聚合]  
    B --> C[分类统计/时间分组]  
    C --> D[生成图表数据集]  
    D --> E[渲染饼图/柱状图/折线图]  
    B --> F[生成账单文件(CSV)]  
    E & F --> G[UI 展示与导出]

原理解释

  1. 数据查询:使用 SQL 按 categorydate分组求和。
  2. 分类统计:计算每个类别总金额及占总支出比例。
  3. 图表渲染:将数据转为图表库所需结构,驱动绘制。
  4. 账单导出:将查询结果写入 CSV 文件,保存至公共目录或沙箱。

环境准备

  • DevEco Studio 4.0+
  • SDK API 9+(支持 chartfile.fsrelationalStore
  • 权限
"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)  
  }  
}

实际详细应用代码示例实现

见以上 TransactionDBChartService与 UI 完整代码。

运行结果

  • 页面显示分类支出与月度收支数据。
  • 点击“导出账单CSV”生成文件,路径弹窗提示。

测试步骤以及详细代码

  1. 初始化数据库并插入测试数据。
  2. 调用 loadData()检查图表数据非空。
  3. 执行 exportBill()验证文件生成与内容正确性。

部署场景

  • 个人理财 App:日常收支分析。
  • 企业 ERP:部门费用统计与导出。
  • 银行客户端:客户账单可视化与下载。

疑难解答

  • 数据库空指针:确保 init在 UI 加载前调用。
  • 导出失败:检查文件路径权限与目录存在性。
  • 图表无数据:确认数据插入成功且查询条件正确。

未来展望

  • 实时同步:云端数据同步后实时刷新图表。
  • 多维度钻取:点击饼图区块查看该类明细。
  • PDF 导出:支持富文本报表。

技术趋势与挑战

  • 趋势:可视化组件与 AI 分析结合,预测支出趋势。
  • 挑战:大数据量下图表性能优化,导出文件加密。

总结

本文基于鸿蒙系统实现交易记录可视化功能,涵盖分类统计、图表展示与账单导出,数据库、服务与 UI 代码完整可运行,支持离线分析和安全导出,为鸿蒙金融类应用提供完整解决方案。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。