feat: 账单支出图表(按平台月度支出柱状图)

This commit is contained in:
gouki
2026-08-02 21:57:17 +00:00
parent d15fe834b3
commit eca886c6cc
+35 -1
View File
@@ -332,6 +332,10 @@ const SubscriptionsView = {
<div class="text-xs text-slate-400 mt-1">年度约 {{ (v*12).toFixed(2) }}</div>
</div>
</div>
<div v-if="subs.length" class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-4">
<h3 class="font-semibold mb-2 text-sm">按平台月度支出(换算为月均)</h3>
<canvas id="billingChart" height="80"></canvas>
</div>
<div class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-slate-50 dark:bg-slate-800/50 text-slate-500 text-left text-xs">
@@ -372,7 +376,37 @@ const SubscriptionsView = {
}
return m;
});
return { store, Fmt, subs, monthlyByCurrency };
const byProviderMonthly = computed(() => {
const m = {};
for (const a of subs.value) {
const prov = a.provider_name || a.provider;
m[prov] = (m[prov] || 0) + normalizedMonthly(a.cost, a.renewal_cycle);
}
return m;
});
let billingChart = null;
function renderBillingChart() {
const canvas = document.getElementById('billingChart');
if (!canvas || !window.Chart) return;
const entries = Object.entries(byProviderMonthly.value).sort((x, y) => y[1] - x[1]);
if (!entries.length) return;
if (billingChart) { billingChart.destroy(); billingChart = null; }
billingChart = new Chart(canvas, {
type: 'bar',
data: {
labels: entries.map(e => e[0]),
datasets: [{ label: '月度支出', data: entries.map(e => e[1].toFixed(2)), backgroundColor: '#6366f1', borderRadius: 6 }],
},
options: {
responsive: true,
scales: { y: { beginAtZero: true } },
plugins: { legend: { display: false } },
},
});
}
watch([subs, byProviderMonthly], () => { nextTick(renderBillingChart); }, { deep: true });
onMounted(() => { nextTick(renderBillingChart); });
return { store, Fmt, subs, monthlyByCurrency, byProviderMonthly };
},
};