前端可视化家庭账单:用 ECharts 实现支出统计与趋势分析

作者:fruge365日期:2025/11/21

前端可视化家庭账单:用 ECharts 实现支出统计与趋势分析

在家庭财务管理中,直观地看懂钱花到了哪里、花得是否稳定,是提高消费意识与优化预算的关键。本文以 ECharts 为核心,构建一个可视化的家庭账单分析:包括支出分类统计、月度趋势分析、交互筛选与性能优化建议,帮助你在浏览器端快速落地一个实用的可视化面板。

适用场景

  • 需要按类别统计支出占比并快速定位高频支出项
  • 需要观察月度支出变化趋势并识别异常波动
  • 希望在不引入后端的前提下,完成本地或前端的数据分析与展示

数据模型设计

为后续统计与可视化,建议将每笔账单设计为结构化数据:

1[
2  {
3    "date": "2025-01-03",
4    "category": "餐饮",
5    "amount": 56.5,
6    "paymentMethod": "信用卡",
7    "note": "外卖"
8  }
9]
10

关键字段说明:

  • dateYYYY-MM-DD 字符串,便于按月聚合
  • category:分类名称,例如餐饮、交通、居住、教育、医疗、娱乐等
  • amount:支出金额,统一为正数
  • paymentMethod:支付方式,按需筛选或做子维度统计

基础搭建

选择纯前端页面即可运行,使用 CDN 引入 ECharts:

1<!doctype html>
2<html>
3  <head>
4    <meta charset="utf-8" />
5    <meta name="viewport" content="width=device-width, initial-scale=1" />
6    <title>家庭账单可视化</title>
7    <script src="https://cdn.jsdelivr.net/npm/echarts@5"></script>
8    <style>
9      body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; }
10      .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
11      .card { background: #fff; border: 1px solid #eee; border-radius: 8px; padding: 8px; }
12      .title { font-weight: 600; margin: 8px 0; }
13      .chart { height: 320px; }
14    </style>
15  </head>
16  <body>
17    <div class="grid">
18      <div class="card">
19        <div class="title">支出分类占比</div>
20        <div id="chart-pie" class="chart"></div>
21      </div>
22      <div class="card">
23        <div class="title">月度支出趋势</div>
24        <div id="chart-line" class="chart"></div>
25      </div>
26    </div>
27    <script>
28      const bills = [
29        { date: '2025-01-03', category: '餐饮', amount: 56.5, paymentMethod: '信用卡' },
30        { date: '2025-01-05', category: '交通', amount: 18, paymentMethod: '现金' },
31        { date: '2025-01-08', category: '居住', amount: 2200, paymentMethod: '转账' },
32        { date: '2025-02-01', category: '餐饮', amount: 78.2, paymentMethod: '信用卡' },
33        { date: '2025-02-06', category: '娱乐', amount: 120, paymentMethod: '信用卡' },
34        { date: '2025-02-09', category: '交通', amount: 16, paymentMethod: '现金' },
35        { date: '2025-03-02', category: '餐饮', amount: 65.1, paymentMethod: '信用卡' },
36        { date: '2025-03-17', category: '教育', amount: 320, paymentMethod: '转账' },
37        { date: '2025-03-26', category: '医疗', amount: 180, paymentMethod: '信用卡' },
38        { date: '2025-03-28', category: '居住', amount: 2200, paymentMethod: '转账' }
39      ];
40
41      function parseMonth(dateStr) {
42        const d = new Date(dateStr);
43        const y = d.getFullYear();
44        const m = String(d.getMonth() + 1).padStart(2, '0');
45        return `${y}-${m}`;
46      }
47
48      function sumByCategory(list) {
49        const map = new Map();
50        for (const b of list) {
51          map.set(b.category, (map.get(b.category) || 0) + b.amount);
52        }
53        return Array.from(map, ([category, total]) => ({ category, total }));
54      }
55
56      function sumByMonth(list) {
57        const map = new Map();
58        for (const b of list) {
59          const key = parseMonth(b.date);
60          map.set(key, (map.get(key) || 0) + b.amount);
61        }
62        return Array.from(map, ([month, total]) => ({ month, total })).sort((a, b) => a.month.localeCompare(b.month));
63      }
64
65      const pieChart = echarts.init(document.getElementById('chart-pie'));
66      const lineChart = echarts.init(document.getElementById('chart-line'));
67
68      const categoryTotals = sumByCategory(bills);
69      const pieOption = {
70        tooltip: {},
71        legend: { top: 'bottom' },
72        series: [
73          {
74            type: 'pie',
75            radius: ['40%', '70%'],
76            itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
77            data: categoryTotals.map(o => ({ name: o.category, value: Number(o.total.toFixed(2)) }))
78          }
79        ]
80      };
81
82      const monthTotals = sumByMonth(bills);
83      const lineOption = {
84        tooltip: { trigger: 'axis' },
85        xAxis: { type: 'category', data: monthTotals.map(o => o.month) },
86        yAxis: { type: 'value' },
87        dataZoom: [{ type: 'inside' }, { type: 'slider' }],
88        series: [
89          {
90            name: '月支出',
91            type: 'line',
92            smooth: true,
93            showSymbol: false,
94            areaStyle: { opacity: 0.2 },
95            data: monthTotals.map(o => Number(o.total.toFixed(2)))
96          }
97        ]
98      };
99
100      pieChart.setOption(pieOption);
101      lineChart.setOption(lineOption);
102
103      window.addEventListener('resize', function () {
104        pieChart.resize();
105        lineChart.resize();
106      });
107    </script>
108  </body>
109</html>
110

要点:

  • 使用 Map 做聚合,减少中间对象的开销
  • 饼图展示分类占比,折线图展示月度趋势
  • 开启 dataZoom,兼顾短期与长期数据的浏览体验

支出统计:类别分布

  • 将所有账单按 category 聚合求和,并按需排序
  • 饼图适合看比例结构,若类别较多可切换为水平条形图以增强可读性
  • 可配合 legendselected 实现类别筛选

趋势分析:月度变化

  • 依据 date 转换成 YYYY-MM 进行月度聚合
  • 折线图的 smooth 能提升趋势观感,搭配 areaStyle 强化视觉层次
  • 可在异常峰值处使用 markPointvisualMap 进行突出标记

交互增强

  • 时间维度筛选:按年、按月或自定义区间筛选并重新渲染
  • 类别筛选:使用图例勾选或下拉框控制类别数据是否参与统计
  • 多图联动:点击饼图某分类时,联动折线图仅展示该分类在各月的趋势

性能与数据质量

  • 数据量较大时,尽量在聚合前做去噪与无效记录过滤
  • 前端聚合建议使用原生结构与一次遍历完成,避免多次 map/reduce 叠加
  • dataset 统一数据源可降低多图表的重复数据转换成本

扩展建议

  • 叠加预算线:在折线图上叠加每月预算阈值,超出则高亮
  • 子维度细分:同一类别按 paymentMethod 分组,观察支付方式的偏好
  • 导出报表:将聚合结果导出为 CSV,便于长期归档

完整示例(含类别联动)

1<!doctype html>
2<html>
3  <head>
4    <meta charset="utf-8" />
5    <script src="https://cdn.jsdelivr.net/npm/echarts@5"></script>
6    <style>
7      .toolbar { margin-bottom: 12px; }
8      .chart { height: 300px; }
9    </style>
10  </head>
11  <body>
12    <div class="toolbar">
13      <select id="categoryFilter">
14        <option value="all">全部类别</option>
15        <option>餐饮</option>
16        <option>交通</option>
17        <option>居住</option>
18        <option>娱乐</option>
19        <option>教育</option>
20        <option>医疗</option>
21      </select>
22    </div>
23    <div id="pie" class="chart"></div>
24    <div id="line" class="chart"></div>
25    <script>
26      const bills = [
27        { date: '2025-01-03', category: '餐饮', amount: 56.5 },
28        { date: '2025-01-05', category: '交通', amount: 18 },
29        { date: '2025-01-08', category: '居住', amount: 2200 },
30        { date: '2025-02-01', category: '餐饮', amount: 78.2 },
31        { date: '2025-02-06', category: '娱乐', amount: 120 },
32        { date: '2025-02-09', category: '交通', amount: 16 },
33        { date: '2025-03-02', category: '餐饮', amount: 65.1 },
34        { date: '2025-03-17', category: '教育', amount: 320 },
35        { date: '2025-03-26', category: '医疗', amount: 180 },
36        { date: '2025-03-28', category: '居住', amount: 2200 }
37      ];
38
39      function parseMonth(s) {
40        const d = new Date(s);
41        const y = d.getFullYear();
42        const m = String(d.getMonth() + 1).padStart(2, '0');
43        return `${y}-${m}`;
44      }
45
46      function sumByCategory(list) {
47        const map = new Map();
48        for (const b of list) map.set(b.category, (map.get(b.category) || 0) + b.amount);
49        return Array.from(map, ([category, total]) => ({ category, total }));
50      }
51
52      function sumByMonth(list) {
53        const map = new Map();
54        for (const b of list) {
55          const k = parseMonth(b.date);
56          map.set(k, (map.get(k) || 0) + b.amount);
57        }
58        return Array.from(map, ([month, total]) => ({ month, total })).sort((a, b) => a.month.localeCompare(b.month));
59      }
60
61      const pie = echarts.init(document.getElementById('pie'));
62      const line = echarts.init(document.getElementById('line'));
63
64      function renderAll(filteredBills) {
65        const catTotals = sumByCategory(filteredBills);
66        const pieOption = {
67          tooltip: {},
68          legend: { top: 'bottom' },
69          series: [
70            { type: 'pie', radius: ['40%', '70%'], data: catTotals.map(o => ({ name: o.category, value: Number(o.total.toFixed(2)) })) }
71          ]
72        };
73
74        const monthTotals = sumByMonth(filteredBills);
75        const lineOption = {
76          tooltip: { trigger: 'axis' },
77          xAxis: { type: 'category', data: monthTotals.map(o => o.month) },
78          yAxis: { type: 'value' },
79          series: [
80            { name: '月支出', type: 'line', smooth: true, showSymbol: false, data: monthTotals.map(o => Number(o.total.toFixed(2))) }
81          ],
82          dataZoom: [{ type: 'inside' }, { type: 'slider' }]
83        };
84
85        pie.setOption(pieOption);
86        line.setOption(lineOption);
87      }
88
89      renderAll(bills);
90
91      document.getElementById('categoryFilter').addEventListener('change', function (e) {
92        const value = e.target.value;
93        const next = value === 'all' ? bills : bills.filter(b => b.category === value);
94        renderAll(next);
95      });
96
97      window.addEventListener('resize', function () {
98        pie.resize();
99        line.resize();
100      });
101    </script>
102  </body>
103</html>
104

总结

  • 数据结构化是基础,聚合策略决定统计的可靠性与性能
  • ECharts 提供丰富图形与交互能力,覆盖占比与趋势两大核心需求
  • 可视化不是终点,结合预算线、异常提醒与导出能力,才能形成闭环的家庭财务管理工具

前端可视化家庭账单:用 ECharts 实现支出统计与趋势分析》 是转载文章,点击查看原文


相关推荐


分布式专题——56 微服务日志采集与分析系统实战
失散132025/11/19

1 为什么要使用 ELK 随着企业信息化进程加速,日志数据呈现量急剧增加、来源多样、格式复杂的特点,传统日志管理方式已难以满足需求,这是引入ELK的核心背景; ELK(ElasticSearch、Logstash、Kibana)的由三个组件构成,各自承担关键功能,形成高效的日志管理方案: Elasticsearch:提供强大的分布式搜索能力,支撑日志的快速检索; Logstash:具备灵活的数据采集与处理功能,负责日志的收集和预处理; Kibana:提供直观的数据可视化界面,将


如果让我从头再来学习并发编程
桦说编程2025/11/18

大学时,我学习了一本国外的教科书,书名叫做《计算机网络——自顶向下方法》,这本书改变了我看待学习的角度。学习的顺序不是一成不变的,常规的路线通常从底层学习,这本书从应用层面入手,逐步讲解到底层,以一种对常规学习路线相反的方向学习,在我看来恰恰学习计算机网络最轻松上手的路径。很多时候我们追求一步到位,鞭辟入里的理解,反而忽略对于初学者,最佳的学习路线往往是兴趣与试错、感性和求索交织的,认识是逐渐深刻的。 知之者往往陷入知识的诅咒,知道的内容很难遗忘,甚至忘记了自己也是从不理解到懂的。有些书籍和文章


Python 的内置函数 setattr
IMPYLH2025/11/17

Python 内建函数列表 > Python 的内置函数 setattr Python 的内置函数 setattr() 用于动态设置对象的属性值。该函数接受三个参数:对象、属性名称字符串和属性值。当我们需要在运行时为对象添加或修改属性时,setattr() 提供了灵活的操作方式。 基本语法: setattr(object, attribute_name, value) 详细说明: 参数解析: object:需要设置属性的目标对象attribute_name:字符串形式的属性名


Bash 的 chown 命令
hubenchang05152025/11/16

#Bash 的 chown 命令 chown [OPTION]... [OWNER][:[GROUP]] FILE... 功能 修改文件的所有者和所属组。 类型 可执行文件(/usr/bin/chown),属于 coreutils。 参数 OPTION 选项: -c, --changes - 仅对发生变化的文件打印详细信息 -f, --silent, --quiet - 忽略大部分错误信息 -v, --verbose - 打印详细信息 --dereference - 影响符号链接引用的源文


【软件测试】《集成测试全攻略:Mock/Stub 原理 + Postman/JUnit/TestNG 实战》
云知谷2025/11/15

集成测试:一场“团队协作”的精彩大戏! 想象一下,你正在筹备一场超级英雄电影的首映礼!每个超级英雄(比如钢铁侠、美国队长、雷神)都是独立的组件,他们各自的能力(功能)都经过了严格测试(单元测试),证明他们“单兵作战”很强。 但是!电影上映时,他们必须一起合作——钢铁侠开战甲,美国队长指挥战术,雷神召唤闪电,才能打败灭霸(系统级问题)。如果他们配合不好(比如钢铁侠的战甲和美国队长的盾牌不兼容,或者雷神的闪电把战甲炸了),那电影就砸了! 这时候,集成测试(Integration Testing


DeepSeek-OCR实战(01):基础运行环境搭建-Ubuntu
paopao_wu2025/11/13

DeepSeek-OCR实战是一个系列文章,包含了从基础运行环境搭建到应用接入全过程。本章为:基础运行环境搭建,操作系统采用 Ubuntu Server 24 环境版本ubuntu-24.04.3 Serverrelease 10.0Cuda11.8显卡 RTX 2080 Ti 22G驱动 NVIDIA-Linux-x86_64-580.105.08conda25.9.1git2.47.3 1.操作系统基础安装 安装 Ubuntu 24 Server 版本后(全部默认安装),查看一下磁盘


圆桌论坛精华实录 | AI是重构运维逻辑的颠覆性革命?博睿数据与行业大咖亲授“AI+可观测性”的破局之道
Bonree博睿数据2025/11/12

全文约6500字  阅读时间约15分钟。 当前,人工智能正处于高速发展阶段,以前所未有的深度与广度重塑商业规则,推动企业数字化转型从规模化扩张迈入精细化深耕。面对这场汹涌而来的智能变革,运维领域正面临一道核心命题:AI究竟是提升效率的辅助工具,还是重构运维逻辑的颠覆性革命? 国内金融、制造等关键行业已步入数字化深水区,却普遍陷入运维复杂度激增、故障定位滞后、数据价值难以转化等行业焦虑。如何让AI技术真正落地运维场景?如何通过可观测性打通全链路数据孤岛?如何平衡技术创新与业务实用价值?


对于数据结构:堆的超详细保姆级解析——下(堆排序以及TOP-K问题)
ShineWinsu2025/11/10

开篇介绍: hello 大家,我们又见面了,在上一篇博客中,我们共同探索了如何实现堆这么一个数据结构,相信大家经过上篇博客的学习,对堆的了解程度以及掌握程度,都有了极大的水平提升。 那么堆,有什么用呢?首先,作为一个数据结构,它肯定具有存储数据的功能,这是毋庸置疑的,但是呢,我们知道,堆有大堆和小堆之分,那么那么,这一个知识点,蕴含着什么秘密呢? 诶,不错,就是我们标题所说的——堆排序,我们之前学过了冒泡排序以及qsort函数排序,但是呢说实话,这两种排序方式,效率都不高,说难听一点就是在


【Linux】进程初阶(1)——基本进程理解
终焉代码2025/11/8

目录 前言 1.1进程基本理解 1.2进程描述 1.3查看进程 1.4通过系统调用的基本进程操作 1.4.1通过系统调用获取pid 1.4.2通过系统调用创建进程 前言 操作系统对计算机软件与硬件进行管理的方式是先描述再组织,而PCB就是那个"描述",那到底什么是PCB?PCB又在描述什么呢?更多Linux学习内容看准Linux专栏 1.1进程基本理解 在操作系统中,我们运行的一个个软件本质上都是程序。例如我们在windows上打开浏览器时,本质是


Bash 的 cd 命令
hubenchang05152025/11/6

#Bash 的 cd 命令 cd [-L|-P] [DIRECTORY] 功能 切换工作目录。 类型 Bash 内置命令。 参数 OPTION 选项: -L - 逻辑路径;在跟踪符号链接之前解析 ..(默认) -P - 物理路径;在跟踪符号链接之后解析 .. DIRECTORY - 要切换到的目录路径;省略表示切换到用户目录,- 表示切换到上次的工作目录 #示例 基本示例 $ pwd # 查看当前路径 /home/user/primer

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读