Files
vps-manager/static/js/views/dashboard.js
T
gouki cbb32ea3b0 fix(expiry): 已过期资产不再从到期统计中消失,总览页改为到期日历
get_expiring/get_overview 去掉 expiry_date>=today 下界(仅排除已注销)并新增 overdue_count;renewal_service 续费提醒纳入已过期、文案区分已过期N天/今天到期/N天后;dashboard 用到期日历逐条显示具体到期日与倒计时;api.js 新增 Fmt.countdownText 统一文案。
2026-09-18 08:41:21 +00:00

70 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 总览视图 */
const DashboardView = {
template: `
<div class="space-y-5">
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3">
<div class="bg-white dark:bg-slate-900 rounded-xl p-4 border border-slate-200 dark:border-slate-800">
<div class="text-xs text-slate-500">总资产</div>
<div class="text-2xl font-bold mt-1">{{ store.overview.total || 0 }}</div>
</div>
<div class="bg-white dark:bg-slate-900 rounded-xl p-4 border border-red-200 dark:border-red-900/50">
<div class="text-xs text-slate-500">30天内到期</div>
<div class="text-2xl font-bold mt-1 text-red-600 dark:text-red-400">{{ store.overview.expiring_30 || 0 }}</div>
<div v-if="store.overview.overdue_count" class="text-xs mt-1 font-medium text-red-600 dark:text-red-400">另有 {{ store.overview.overdue_count }} 项已过期</div>
</div>
<div class="bg-white dark:bg-slate-900 rounded-xl p-4 border border-slate-200 dark:border-slate-800">
<div class="text-xs text-slate-500">本年支出</div>
<div class="text-2xl font-bold mt-1">{{ store.overview.year_cost || 0 }}</div>
</div>
<div class="bg-white dark:bg-slate-900 rounded-xl p-4 border border-amber-200 dark:border-amber-900/50">
<div class="text-xs text-slate-500">异常状态</div>
<div class="text-2xl font-bold mt-1 text-amber-600 dark:text-amber-400">{{ store.overview.abnormal_count || 0 }}</div>
</div>
</div>
<!-- 到期日历:不限制在 30 天窗口内,每个有到期日的产品都列出“几号到期 / 还剩多久” -->
<div class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-4">
<div class="flex items-center justify-between mb-3">
<h2 class="text-sm font-semibold">到期日历({{ expiryList.length }})</h2>
<a href="#monitor" class="text-xs text-blue-600 dark:text-blue-400 hover:underline">全部倒计时 →</a>
</div>
<div v-if="expiryList.length" class="space-y-2">
<div v-for="a in expiryList" :key="a.id" class="flex justify-between items-center gap-3 px-3 py-2 rounded-lg bg-slate-50 dark:bg-slate-800/50">
<div class="min-w-0">
<div class="font-medium text-sm truncate">{{ a.name }}</div>
<div class="text-xs text-slate-400 mt-0.5 truncate">{{ meta(a) }}</div>
</div>
<div class="text-right shrink-0">
<div class="text-sm" :class="Fmt.expiryText(a.days_to_expiry)">{{ a.expiry_date }}</div>
<span class="text-xs px-2 py-0.5 rounded-full" :class="Fmt.expiryBadge(a.days_to_expiry)">{{ Fmt.countdownText(a.days_to_expiry) }}</span>
</div>
</div>
</div>
<p v-else class="text-xs text-slate-400 py-4 text-center">没有资产设置了到期日,算不出倒计时</p>
<p v-if="noExpiryCount" class="text-xs text-slate-400 mt-2">另 {{ noExpiryCount }} 个资产未填到期日,不会出现在这里(到资产页编辑补上)</p>
</div>
<div class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 p-4">
<h2 class="text-sm font-semibold mb-3">按类型分布</h2>
<div class="flex flex-wrap gap-2">
<span v-for="(n, t) in store.overview.by_type" :key="t" class="text-xs px-3 py-1.5 rounded-lg" :class="Fmt.typeBadge(t)">{{ Fmt.TYPE_LABELS[t] }} {{ n }}</span>
<span v-if="!store.overview.by_type || !Object.keys(store.overview.by_type).length" class="text-xs text-slate-400">暂无数据</span>
</div>
</div>
</div>`,
setup() {
// 到期日历:只看未注销且有到期日的资产,最紧急(含已过期)排最前
const expiryList = Vue.computed(() => store.assets
.filter(a => a.expiry_date && a.status !== 'cancelled')
.sort((x, y) => (x.days_to_expiry ?? 10 ** 9) - (y.days_to_expiry ?? 10 ** 9)));
const noExpiryCount = Vue.computed(() => store.assets.filter(a => !a.expiry_date && a.status !== 'cancelled').length);
// 副标题:类型 · 平台 · 费用/周期,缺失的字段不占位(避免出现 "VPS · · 3.5USD")
function meta(a) {
const bits = [Fmt.TYPE_LABELS[a.asset_type] || a.asset_type, a.provider_name || a.provider];
if (a.cost) bits.push(a.cost + (a.currency || '') + '/' + (Fmt.CYCLE_LABELS[a.renewal_cycle] || '未设周期'));
return bits.filter(Boolean).join(' · ');
}
return { store, Fmt, expiryList, noExpiryCount, meta };
},
};