/* 订阅视图 */ const SubscriptionsView = { template: `
💸 这里是「付费资产」汇总:自动收集所有设置了费用或到期日的资产(跨 VPS / 域名 / AI 账号),统一看每个月花多少钱、哪些该续费。
付费资产数
{{ subs.length }}
月度支出({{ cur }})
{{ v.toFixed(2) }}
年度约 {{ (v*12).toFixed(2) }}

按平台月度支出(换算为月均)

项目 平台 费用 到期 管理
暂无付费资产(给资产设置费用和到期日即可出现)
{{ a.name }} {{ a.provider_name || a.provider }} {{ a.cost }} {{ a.currency }}/{{ Fmt.CYCLE_LABELS[a.renewal_cycle] || a.renewal_cycle }} {{ a.expiry_date || '—' }}({{ a.days_to_expiry }}天) 续费 取消
`, setup() { const subs = Vue.computed(() => store.assets .filter(a => (a.cost > 0 || a.expiry_date) && a.status !== 'cancelled') .sort((x, y) => (x.days_to_expiry ?? 9999) - (y.days_to_expiry ?? 9999))); const monthlyByCurrency = Vue.computed(() => { const m = {}; for (const a of subs.value) { const cur = a.currency || 'USD'; m[cur] = (m[cur] || 0) + normalizedMonthly(a.cost, a.renewal_cycle); } return m; }); const byProviderMonthly = Vue.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 } }, }, }); } Vue.watch([subs, byProviderMonthly], () => { Vue.nextTick(renderBillingChart); }, { deep: true }); Vue.onMounted(() => { Vue.nextTick(renderBillingChart); }); Vue.onUnmounted(() => { if (billingChart) { billingChart.destroy(); billingChart = null; } }); return { store, Fmt, subs, monthlyByCurrency, byProviderMonthly }; }, };