r110/chart.html · 约 7967 字符
1<!DOCTYPE html>
2<html lang="zh-CN">
3<head>
4<meta charset="UTF-8">
5<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
6<meta name="theme-color" content="#0f9d76">
7<title>统计报表 · 家有账家庭记账工具</title>
8<link rel="stylesheet" href="style.css">
9</head>
10<body data-page="chart">
11
12<div class="navbar"><a class="bk" href="index.html">‹</a><div class="tt">统计报表</div><div class="rt" data-tip="演示版:报表导出开发中">导出</div></div>
13
14<div style="padding:12px 13px 0">
15 <div class="seg" id="rangeSeg"><span class="on" data-v="m">本月</span><span data-v="q">本季</span><span data-v="y">本年</span></div>
16</div>
17
18<div class="card">
19 <div class="ttl">📈 近 6 个月收支趋势 <span class="rt">🟩收入 🟥支出</span></div>
20 <div class="bars" id="barBox"></div>
21 <div class="divider"></div>
22 <div class="grid3">
23 <div style="text-align:center"><div class="fz11 muted">月均收入</div><div class="num inc" style="font-weight:800">31,690</div></div>
24 <div style="text-align:center"><div class="fz11 muted">月均支出</div><div class="num pay" style="font-weight:800">19,819</div></div>
25 <div style="text-align:center"><div class="fz11 muted">平均结余率</div><div class="num" style="font-weight:800;color:var(--brand-d)">37.5%</div></div>
26 </div>
27</div>
28
29<div class="card">
30 <div class="ttl">🍩 本月支出构成 <span class="rt">合计 ¥12,387</span></div>
31 <div style="display:flex;align-items:center;gap:14px">
32 <div class="ring" id="pieBox"></div>
33 <div style="flex:1;min-width:0" id="pieLegend"></div>
34 </div>
35</div>
36
37<div class="card">
38 <div class="ttl">🏆 支出分类排行 <span class="rt">环比上月</span></div>
39 <div id="rankBox"></div>
40</div>
41
42<div class="card">
43 <div class="ttl">👨👩👧 成员支出对比</div>
44 <div id="memChart"></div>
45</div>
46
47<div class="card">
48 <div class="ttl">💹 结余走势</div>
49 <div id="lineBox"></div>
50 <div class="fz12 muted" style="margin-top:9px;background:var(--brand-l);border-radius:10px;padding:9px 11px;color:var(--brand-d)">
51 💡 本月结余率 61.8%,为近 6 个月最高。主要因为支出中仅房贷为大额固定项,购物类超支 ¥182 需留意。
52 </div>
53</div>
54<div style="height:6px"></div>
55
56<nav class="tabbar">
57<a href="index.html" class=""><span class="ti">🏠</span>首页</a>
58<a href="bills.html" class=""><span class="ti">📒</span>账单</a>
59<a href="add.html" class="fab"><span class="fi">+</span><span class="fl">记一笔</span></a>
60<a href="chart.html" class="on"><span class="ti">📊</span>图表</a>
61<a href="me.html" class=""><span class="ti">👤</span>我的</a>
62</nav>
63<div class="mask" id="mask"></div>
64<div class="toast" id="toast"></div>
65<script src="data.js"></script>
66<script src="app.js"></script>
67<script>
68
69var mx = 0;
70TREND.forEach(function (t) { mx = Math.max(mx, t.inc, t.pay); });
71$('#barBox').innerHTML = TREND.map(function (t) {
72 return '<div class="g"><div class="pair">' +
73 '<div class="b" style="height:' + (t.inc / mx * 100).toFixed(0) + '%"></div>' +
74 '<div class="b p" style="height:' + (t.pay / mx * 100).toFixed(0) + '%"></div>' +
75 '</div><div class="x">' + t.m + '</div></div>';
76}).join('');
77
78var tot = PIE.reduce(function (s, p) { return s + p.v; }, 0);
79var size = 118, w = 15, r = (size - w) / 2, C = 2 * Math.PI * r, off = 0;
80var svg = '<svg width="' + size + '" height="' + size + '">';
81PIE.forEach(function (p) {
82 var c = catOf(p.cat), len = C * p.v / tot;
83 svg += '<circle cx="' + size / 2 + '" cy="' + size / 2 + '" r="' + r + '" fill="none" stroke="' + c.c +
84 '" stroke-width="' + w + '" stroke-dasharray="' + (len - 1.6).toFixed(1) + ' ' + (C - len + 1.6).toFixed(1) +
85 '" stroke-dashoffset="' + (-off).toFixed(1) + '"/>';
86 off += len;
87});
88svg += '</svg>';
89$('#pieBox').innerHTML = svg + '<div class="in"><div class="p" style="font-size:17px">¥12,387</div><div class="t">本月支出</div></div>';
90$('#pieLegend').innerHTML = PIE.slice(0, 6).map(function (p) {
91 var c = catOf(p.cat);
92 return '<div style="display:flex;align-items:center;gap:6px;font-size:12px;margin-bottom:6px">' +
93 '<i style="width:8px;height:8px;border-radius:2px;background:' + c.c + ';display:inline-block;flex:none"></i>' +
94 '<span style="font-weight:700">' + c.n + '</span>' +
95 '<span class="num muted" style="margin-left:auto">' + (p.v / tot * 100).toFixed(1) + '%</span>' +
96 '<span class="num" style="width:52px;text-align:right;font-weight:700">' + money0(p.v) + '</span></div>';
97}).join('');
98
99var RANK = [
100 { cat: 'house', v: 6820, dt: 0 }, { cat: 'food', v: 2186, dt: -12.4 },
101 { cat: 'shop', v: 1682, dt: 38.6 }, { cat: 'edu', v: 1600, dt: 0 },
102 { cat: 'grocery', v: 1248, dt: -6.2 }, { cat: 'gift', v: 800, dt: 166.7 }
103];
104var rmx = RANK[0].v;
105$('#rankBox').innerHTML = RANK.map(function (x, i) {
106 var c = catOf(x.cat);
107 var d = x.dt === 0 ? '<span class="tag">持平</span>'
108 : x.dt > 0 ? '<span class="tag r">↑' + x.dt + '%</span>' : '<span class="tag g">↓' + Math.abs(x.dt) + '%</span>';
109 return '<div style="display:flex;align-items:center;gap:9px;margin-bottom:11px">' +
110 '<div style="width:15px;font-size:12px;font-weight:800;color:' + (i < 3 ? 'var(--accent-d)' : '#b9c6c1') + '">' + (i + 1) + '</div>' +
111 '<div class="ico sm" style="background:' + c.c + '1a">' + c.e + '</div>' +
112 '<div style="flex:1;min-width:0">' +
113 '<div style="display:flex;align-items:center;font-size:13px;font-weight:700"><span>' + c.n + '</span>' +
114 '<span style="margin-left:auto;display:flex;gap:6px;align-items:center">' + d +
115 '<b class="num">¥' + money0(x.v) + '</b></span></div>' +
116 '<div class="bar thin" style="margin-top:5px"><i style="width:' + (x.v / rmx * 100).toFixed(0) + '%;background:' + c.c + '"></i></div>' +
117 '</div></div>';
118}).join('');
119
120var mmx = Math.max.apply(null, MEMBERS.map(function (m) { return m.month; }));
121$('#memChart').innerHTML = '<div style="display:flex;align-items:flex-end;gap:12px;height:104px">' +
122 MEMBERS.map(function (m) {
123 return '<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;gap:4px">' +
124 '<div class="num fz11" style="font-weight:800">' + money0(m.month) + '</div>' +
125 '<div style="width:26px;border-radius:6px 6px 0 0;background:' + m.c + ';height:' + (m.month / mmx * 62).toFixed(0) + '%"></div>' +
126 '<div class="fz11 muted">' + m.e + ' ' + m.n + '</div></div>';
127 }).join('') + '</div>';
128
129var bal = TREND.map(function (t) { return t.inc - t.pay; });
130var bmx = Math.max.apply(null, bal), bmn = Math.min.apply(null, bal);
131var pts = bal.map(function (v, i) {
132 var x = 14 + i * ((330 - 28) / (bal.length - 1));
133 var y = 78 - (v - bmn) / (bmx - bmn || 1) * 56;
134 return x.toFixed(1) + ',' + y.toFixed(1);
135});
136$('#lineBox').innerHTML = '<svg viewBox="0 0 330 96" style="width:100%;height:96px">' +
137 '<polyline points="' + pts.join(' ') + '" fill="none" stroke="#0f9d76" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>' +
138 '<polyline points="14,90 ' + pts.join(' ') + ' 316,90" fill="rgba(15,157,118,.10)" stroke="none"/>' +
139 pts.map(function (p) { var xy = p.split(','); return '<circle cx="' + xy[0] + '" cy="' + xy[1] + '" r="3.2" fill="#fff" stroke="#0f9d76" stroke-width="2"/>'; }).join('') +
140 TREND.map(function (t, i) {
141 var x = 14 + i * ((330 - 28) / (bal.length - 1));
142 return '<text x="' + x + '" y="94" font-size="9" fill="#7b8b86" text-anchor="middle">' + t.m + '</text>';
143 }).join('') + '</svg>';
144
145segGroup('#rangeSeg', function (v) {
146 toast(v === 'm' ? '已切换到本月视图' : v === 'q' ? '季度视图:Q3 收支汇总' : '年度视图:2026 全年收支汇总');
147});
148
149</script>
150</body>
151</html>
152