feat: redesign admin layout and stats dashboards

This commit is contained in:
2026-02-07 01:59:29 +08:00
parent 9991834ccd
commit 6eb0651e23
70 changed files with 713 additions and 690 deletions

View File

@@ -16,6 +16,7 @@ import {
unbanIp,
unbanUser,
} from '../api/security'
import MetricGrid from '../components/MetricGrid.vue'
const pageSize = 20
@@ -119,9 +120,27 @@ const threatTypeOptions = computed(() => {
const dashboardCards = computed(() => {
const d = dashboard.value || {}
return [
{ key: 'threat_events_24h', label: '最近24小时威胁事件', value: normalizeCount(d.threat_events_24h) },
{ key: 'banned_ip_count', label: '当前封禁IP数', value: normalizeCount(d.banned_ip_count) },
{ key: 'banned_user_count', label: '当前封禁用户数', value: normalizeCount(d.banned_user_count) },
{
key: 'threat_events_24h',
label: '最近24小时威胁事件',
value: normalizeCount(d.threat_events_24h),
tone: 'red',
hint: '用于衡量当前攻击面活跃度',
},
{
key: 'banned_ip_count',
label: '当前封禁 IP 数',
value: normalizeCount(d.banned_ip_count),
tone: 'orange',
hint: '自动与人工封禁总量',
},
{
key: 'banned_user_count',
label: '当前封禁用户数',
value: normalizeCount(d.banned_user_count),
tone: 'purple',
hint: '高风险账户拦截情况',
},
]
})
@@ -452,17 +471,7 @@ onMounted(async () => {
</div>
</div>
<el-row :gutter="12" class="stats-row">
<el-col v-for="it in dashboardCards" :key="it.key" :xs="24" :sm="8" :md="8" :lg="8" :xl="8">
<el-card shadow="never" class="stat-card" :body-style="{ padding: '14px' }">
<div class="stat-value">
<el-skeleton v-if="dashboardLoading" :rows="1" animated />
<template v-else>{{ it.value }}</template>
</div>
<div class="stat-label">{{ it.label }}</div>
</el-card>
</el-col>
</el-row>
<MetricGrid :items="dashboardCards" :loading="dashboardLoading" :min-width="220" />
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card">
<el-tabs v-model="activeTab">
@@ -731,7 +740,8 @@ onMounted(async () => {
.page-stack {
display: flex;
flex-direction: column;
gap: 12px;
gap: 14px;
min-width: 0;
}
.toolbar {
@@ -741,13 +751,12 @@ onMounted(async () => {
flex-wrap: wrap;
}
.stats-row {
margin-bottom: 2px;
}
.card {
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
background: var(--app-card-bg);
box-shadow: var(--app-shadow-soft);
}
.sub-card {
@@ -756,23 +765,6 @@ onMounted(async () => {
border: 1px solid var(--app-border);
}
.stat-card {
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
box-shadow: var(--app-shadow);
}
.stat-value {
font-size: 22px;
font-weight: 800;
line-height: 1.1;
}
.stat-label {
margin-top: 6px;
font-size: 12px;
color: var(--app-muted);
}
.filters {
display: flex;
@@ -784,6 +776,9 @@ onMounted(async () => {
.table-wrap {
overflow-x: auto;
border-radius: 10px;
border: 1px solid var(--app-border);
background: #fff;
}
.ellipsis {