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,259 @@
<template>
<div class="layout-shell">
<aside class="sidebar">
<div class="brand">
<strong>domainCheck</strong>
<span>轻量管理后台</span>
</div>
<nav class="menu">
<RouterLink v-for="item in menuItems" :key="item.path" :to="item.path" class="menu-item">
<span>{{ item.label }}</span>
</RouterLink>
</nav>
</aside>
<div class="main-area">
<header class="topbar">
<div>
<h1>{{ title }}</h1>
<p>当前保持桌面版不变Web 管理后台进入可持续运维阶段</p>
</div>
<div class="topbar-actions">
<div class="status-pills">
<span class="status-pill" :class="runtime.apiOnline ? 'online' : 'offline'">
API {{ runtime.apiOnline ? "在线" : "离线" }}
</span>
<span class="status-pill" :class="runtime.workerOnline ? 'online' : 'offline'">
Worker {{ runtime.workerOnline ? "在线" : "离线" }}
</span>
<span class="status-pill neutral">{{ runtime.workerMode }}</span>
</div>
<span class="user">{{ authStore.user.display_name }}</span>
<el-button type="primary" plain @click="logout">退出</el-button>
</div>
</header>
<main class="content">
<router-view />
</main>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { runtimeApi } from "@/api/modules";
import { useAuthStore } from "@/stores/auth";
const route = useRoute();
const router = useRouter();
const authStore = useAuthStore();
const runtime = ref({
apiOnline: false,
workerOnline: false,
workerMode: "windows-local"
});
let timer: number | null = null;
const menuItems = [
{ path: "/dashboard", label: "概览" },
{ path: "/runtime", label: "运行中心" },
{ path: "/settings", label: "系统设置" },
{ path: "/imports", label: "域名导入" },
{ path: "/detect", label: "检测控制" },
{ path: "/domains", label: "域名筛选" },
{ path: "/exports", label: "导出中心" },
{ path: "/logs", label: "日志诊断" }
];
const title = computed(() => String(route.meta?.title || "概览"));
const loadRuntimeSummary = async () => {
try {
const response = await runtimeApi.status();
runtime.value = {
apiOnline: Boolean(response.data?.api?.pid),
workerOnline: Boolean(response.data?.worker?.running),
workerMode: response.data?.worker?.mode || "windows-local"
};
} catch {
runtime.value = {
apiOnline: false,
workerOnline: false,
workerMode: "unknown"
};
}
};
const startPolling = () => {
if (timer) {
window.clearInterval(timer);
}
timer = window.setInterval(() => {
loadRuntimeSummary();
}, 10000);
};
const logout = () => {
authStore.clearToken();
router.push("/login");
};
onMounted(async () => {
await loadRuntimeSummary();
startPolling();
});
onBeforeUnmount(() => {
if (timer) {
window.clearInterval(timer);
timer = null;
}
});
</script>
<style scoped lang="scss">
.layout-shell {
display: grid;
grid-template-columns: 240px 1fr;
min-height: 100vh;
}
.sidebar {
background: linear-gradient(180deg, #0f172a, #172554);
color: #e5eefc;
padding: 24px 18px;
display: flex;
flex-direction: column;
gap: 28px;
}
.brand {
display: flex;
flex-direction: column;
gap: 6px;
}
.brand strong {
font-size: 24px;
}
.brand span {
color: rgba(229, 238, 252, 0.78);
font-size: 13px;
}
.menu {
display: flex;
flex-direction: column;
gap: 10px;
}
.menu-item {
padding: 12px 14px;
border-radius: 12px;
color: rgba(229, 238, 252, 0.86);
transition: 0.2s ease;
}
.menu-item:hover,
.menu-item.router-link-active {
background: rgba(255, 255, 255, 0.12);
color: #ffffff;
}
.main-area {
display: flex;
flex-direction: column;
min-width: 0;
}
.topbar {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 20px;
padding: 24px 28px 12px;
}
.topbar h1 {
margin: 0;
font-size: 28px;
}
.topbar p {
margin: 8px 0 0;
color: #64748b;
}
.topbar-actions {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
justify-content: flex-end;
}
.status-pills {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.status-pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 30px;
padding: 0 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
}
.status-pill.online {
background: #dcfce7;
color: #166534;
}
.status-pill.offline {
background: #fee2e2;
color: #991b1b;
}
.status-pill.neutral {
background: #e2e8f0;
color: #334155;
}
.user {
color: #334155;
font-weight: 600;
}
.content {
padding: 0 28px 28px;
}
@media (max-width: 960px) {
.layout-shell {
grid-template-columns: 1fr;
}
.sidebar {
gap: 16px;
}
.menu {
flex-direction: row;
flex-wrap: wrap;
}
.topbar {
flex-direction: column;
}
}
</style>