This commit is contained in:
Your Name
2026-04-16 13:05:07 +08:00
commit 32efff1670
99 changed files with 9974 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
<template>
<div class="stack">
<div class="dashboard-toolbar">
<el-button plain :loading="loading" @click="loadOverview">刷新概览</el-button>
<el-switch v-model="autoRefresh" inline-prompt active-text="自动刷新" inactive-text="手动" />
<span class="updated-at">最近刷新{{ lastUpdatedAt || "暂无" }}</span>
</div>
<el-alert
v-if="loadError"
title="读取概览失败,已保留上一次成功结果。"
type="warning"
:closable="false"
show-icon
/>
<div class="stats-grid">
<PageCard v-for="item in stats" :key="item.label">
<div class="stat-item">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
<small>{{ item.note }}</small>
</div>
</PageCard>
</div>
<PageCard title="第一期目标" description="先把运营最需要的控制面搬到 Web再逐步把检测执行和运维能力完全迁到 Linux。">
<el-timeline>
<el-timeline-item timestamp="当前阶段" placement="top">已创建 Web 后台API运行中心和导入/导出/筛选主链路</el-timeline-item>
<el-timeline-item timestamp="下一步" placement="top">继续补 Linux Worker 控制闭环和更完整的运行诊断</el-timeline-item>
<el-timeline-item timestamp="后续阶段" placement="top">把检测端逐步迁为 Linux Worker前后端彻底分离</el-timeline-item>
</el-timeline>
</PageCard>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import PageCard from "@/components/PageCard.vue";
import { dashboardApi } from "@/api/modules";
const loading = ref(false);
const loadError = ref(false);
const autoRefresh = ref(true);
const lastUpdatedAt = ref("");
let timer: number | null = null;
const stats = ref([
{ label: "总域名", value: "--", note: "数据库域名总量" },
{ label: "待检测", value: "--", note: "当前待处理域名" },
{ label: "黑名单", value: "--", note: "命中风险域名" },
{ label: "API", value: "离线", note: "当前 API 运行状态" },
{ label: "Worker", value: "离线", note: "当前 Worker 运行状态" },
{ label: "模式", value: "--", note: "当前 Worker 运行模式" }
]);
const clearRefreshTimer = () => {
if (timer) {
window.clearInterval(timer);
timer = null;
}
};
const ensureRefreshTimer = () => {
clearRefreshTimer();
if (autoRefresh.value) {
timer = window.setInterval(() => {
loadOverview(false);
}, 10000);
}
};
const formatNow = () =>
new Date().toLocaleString("zh-CN", {
hour12: false
});
const loadOverview = async (showError = true) => {
loading.value = true;
try {
const { data } = await dashboardApi.overview();
stats.value = [
{ label: "总域名", value: String(data.domains_total), note: "数据库域名总量" },
{ label: "待检测", value: String(data.pending_total), note: "当前待处理域名" },
{ label: "黑名单", value: String(data.blacklist_total), note: "命中风险域名" },
{ label: "API", value: data.api_status, note: "当前 API 运行状态" },
{ label: "Worker", value: data.worker_status, note: "当前 Worker 运行状态" },
{ label: "模式", value: data.worker_mode, note: "当前 Worker 运行模式" }
];
loadError.value = false;
lastUpdatedAt.value = formatNow();
} catch {
loadError.value = true;
if (showError) {
lastUpdatedAt.value = lastUpdatedAt.value || "读取失败";
}
} finally {
loading.value = false;
}
};
watch(autoRefresh, ensureRefreshTimer);
onMounted(async () => {
await loadOverview();
ensureRefreshTimer();
});
onBeforeUnmount(clearRefreshTimer);
</script>
<style scoped lang="scss">
.stack {
display: grid;
gap: 20px;
}
.dashboard-toolbar {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.updated-at {
color: #64748b;
font-size: 13px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.stat-item span {
color: #64748b;
}
.stat-item strong {
font-size: 32px;
color: #0f172a;
}
.stat-item small {
color: #94a3b8;
}
@media (max-width: 960px) {
.stats-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>