feat: 账单支出图表(按平台月度支出柱状图)
This commit is contained in:
+35
-1
@@ -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 };
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user