dev
This commit is contained in:
160
domain-web/src/views/dashboard/DashboardView.vue
Normal file
160
domain-web/src/views/dashboard/DashboardView.vue
Normal 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>
|
||||
Reference in New Issue
Block a user