refactor: ♻️ 使用router跳转页面

This commit is contained in:
tsl
2026-04-17 10:11:17 +08:00
parent 13f92f79b6
commit 305fcfae06
11 changed files with 941 additions and 7782 deletions
+65 -65
View File
@@ -1,17 +1,14 @@
<script setup lang="ts">
import { SettingOutlined, DashboardOutlined, ToolOutlined, FileTextOutlined } from "@ant-design/icons-vue";
import { DashboardOutlined, FileTextOutlined, SettingOutlined, ToolOutlined } from "@ant-design/icons-vue";
import { getVersion } from "@tauri-apps/api/app";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { isEnabled as isAutostartEnabled } from "@tauri-apps/plugin-autostart";
import { ref, computed, onMounted, onUnmounted } from "vue";
import { computed, onMounted, onUnmounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import AppStatusBar, { type StatusBarItem } from "@/components/AppStatusBar.vue";
import CloseDialog from "@/components/CloseDialog.vue";
import LogViewer from "@/components/LogViewer.vue";
import LoginForm from "@/components/LoginForm.vue";
import MonitorControl from "@/components/MonitorControl.vue";
import SettingsPanel from "@/components/SettingsPanel.vue";
import StatusHeader from "@/components/StatusHeader.vue";
import UpdateDialog from "@/components/UpdateDialog.vue";
import { useTray } from "@/composables/useTray";
@@ -21,8 +18,10 @@ import { useMonitorStore } from "@/stores/monitor";
import { useNetworkStore } from "@/stores/network";
import { useUpdaterStore } from "@/stores/updater";
import { migrateFromLocalStorage } from "@/utils/storage";
import type { MenuProps } from "ant-design-vue";
const activeTab = ref("monitor");
const router = useRouter();
const route = useRoute();
const store = useMonitorStore();
const appStore = useAppStore();
const logStore = useLogStore();
@@ -36,6 +35,24 @@ let unlistenNotification: UnlistenFn | null = null;
/** 窗口关闭请求事件的取消监听函数 */
let unlistenClose: UnlistenFn | null = null;
const menuRouteMap = {
monitor: "/monitor",
account: "/account",
settings: "/settings",
logs: "/logs",
} as const;
const selectedMenuKey = computed(() => {
const matchedKey = Object.entries(menuRouteMap).find(([, path]) => path === route.path)?.[0];
return matchedKey ?? "monitor";
});
const handleMenuClick: MenuProps["onClick"] = ({ key }) => {
const targetPath = menuRouteMap[key as keyof typeof menuRouteMap];
if (!targetPath || targetPath === route.path) return;
void router.push(targetPath);
};
const statusItems = computed<StatusBarItem[]>(() => {
const items: StatusBarItem[] = [];
if (store.isPostponed) {
@@ -117,35 +134,32 @@ onUnmounted(() => {
</a-layout-header>
<a-layout-content class="app-content">
<a-tabs v-model:active-key="activeTab" tab-position="left" class="main-tabs">
<a-tab-pane key="monitor">
<template #tab>
<span><DashboardOutlined /> 监测</span>
</template>
<MonitorControl />
</a-tab-pane>
<div class="main-shell">
<a-menu :selected-keys="[selectedMenuKey]" mode="inline" class="side-menu" @click="handleMenuClick">
<a-menu-item key="monitor">
<DashboardOutlined />
<span>监测</span>
</a-menu-item>
<a-menu-item key="account">
<SettingOutlined />
<span>账号</span>
</a-menu-item>
<a-menu-item key="settings">
<ToolOutlined />
<span>配置</span>
</a-menu-item>
<a-menu-item key="logs">
<FileTextOutlined />
<span>日志</span>
</a-menu-item>
</a-menu>
<a-tab-pane key="login">
<template #tab>
<span><SettingOutlined /> 账号</span>
</template>
<LoginForm />
</a-tab-pane>
<a-tab-pane key="settings">
<template #tab>
<span><ToolOutlined /> 配置</span>
</template>
<SettingsPanel />
</a-tab-pane>
<a-tab-pane key="logs">
<template #tab>
<span><FileTextOutlined /> 日志</span>
</template>
<LogViewer />
</a-tab-pane>
</a-tabs>
<div class="page-content">
<div class="page-content-inner">
<router-view />
</div>
</div>
</div>
</a-layout-content>
<AppStatusBar :items="statusItems" />
@@ -163,7 +177,7 @@ body {
}
</style>
<style scoped>
<style scoped lang="scss">
.app-layout {
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@@ -186,44 +200,30 @@ body {
display: flex;
flex-direction: column;
}
.main-tabs {
.main-shell {
flex: 1;
min-height: 0;
display: flex;
gap: 16px;
}
.main-tabs :deep(.ant-tabs-nav) {
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(12px);
.side-menu {
width: 112px;
border-radius: 12px;
padding: 12px 0;
min-width: 100px;
flex-shrink: 0;
}
.main-tabs :deep(.ant-tabs-nav::before) {
border: none;
}
.main-tabs :deep(.ant-tabs-tab) {
color: rgba(255, 255, 255, 0.7) !important;
padding: 10px 20px !important;
margin: 0 !important;
transition: all 0.2s;
}
.main-tabs :deep(.ant-tabs-tab:hover) {
color: #fff !important;
}
.main-tabs :deep(.ant-tabs-tab-active .ant-tabs-tab-btn) {
color: #fff !important;
font-weight: 600;
}
.main-tabs :deep(.ant-tabs-ink-bar) {
background: #fff;
border-radius: 2px;
}
.main-tabs :deep(.ant-tabs-content-holder) {
.page-content {
background: #fff;
border-radius: 12px;
padding: 24px;
overflow: auto;
border: none !important;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
flex: 1;
min-height: 0;
overflow: hidden;
}
.page-content-inner {
overflow: auto;
padding: 24px;
height: 100%;
box-sizing: border-box;
}
</style>
+2 -1
View File
@@ -3,5 +3,6 @@ import { createPinia } from "pinia";
import { createApp } from "vue";
import "ant-design-vue/dist/reset.css";
import App from "@/App.vue";
import router from "@/router";
createApp(App).use(createPinia()).use(Antd).mount("#app");
createApp(App).use(createPinia()).use(Antd).use(router).mount("#app");
+39
View File
@@ -0,0 +1,39 @@
import {
createRouter,
createWebHashHistory,
type RouteRecordRaw,
} from "vue-router";
const routes: RouteRecordRaw[] = [
{
path: "/",
redirect: "/monitor",
},
{
path: "/monitor",
name: "monitor",
component: () => import("@/views/MonitorPage.vue"),
},
{
path: "/account",
name: "account",
component: () => import("@/views/AccountPage.vue"),
},
{
path: "/settings",
name: "settings",
component: () => import("@/views/SettingsPage.vue"),
},
{
path: "/logs",
name: "logs",
component: () => import("@/views/LogsPage.vue"),
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
@@ -37,61 +37,77 @@ const levelColor: Record<string, string> = {
<a-empty v-if="logStore.logs.length === 0" description="暂无日志记录" />
<div v-else class="log-viewer__list">
<div v-for="log in reversedLogs" :key="`${log.timestamp}-${log.message}`" class="log-item">
<span class="log-item__time">{{ log.timestamp }}</span>
<a-tag :color="levelColor[log.level] || 'default'" size="small" class="log-item__tag">
<div v-for="log in reversedLogs" :key="`${log.timestamp}-${log.message}`" class="log-viewer__item">
<span class="log-viewer__time">{{ log.timestamp }}</span>
<a-tag :color="levelColor[log.level] || 'default'" size="small" class="log-viewer__tag">
{{ log.level }}
</a-tag>
<a-tag color="geekblue" size="small" class="log-item__tag">
<a-tag color="geekblue" size="small" class="log-viewer__tag">
{{ log.category }}
</a-tag>
<span class="log-item__msg">{{ log.message }}</span>
<span class="log-viewer__message">{{ log.message }}</span>
</div>
</div>
</div>
</template>
<style scoped>
.log-viewer__toolbar {
<style scoped lang="scss">
.log-viewer {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.log-viewer__count {
font-size: 13px;
color: #999;
}
.log-viewer__list {
max-height: 420px;
overflow-y: auto;
border: 1px solid #f0f0f0;
border-radius: 6px;
background: #fafafa;
}
.log-item {
display: flex;
align-items: baseline;
gap: 8px;
padding: 6px 12px;
font-size: 12px;
border-bottom: 1px solid #f0f0f0;
font-family: "Courier New", Consolas, monospace;
}
.log-item:last-child {
border-bottom: none;
}
.log-item__time {
color: #999;
white-space: nowrap;
flex-shrink: 0;
}
.log-item__tag {
flex-shrink: 0;
margin-right: 0 !important;
}
.log-item__msg {
word-break: break-word;
color: #333;
flex-direction: column;
min-height: 0;
height: 100%;
&__toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
flex-shrink: 0;
}
&__count {
font-size: 13px;
color: #999;
}
&__list {
flex: 1;
min-height: 0;
overflow-y: auto;
border: 1px solid #f0f0f0;
border-radius: 6px;
background: #fafafa;
}
&__item {
display: flex;
align-items: baseline;
gap: 8px;
padding: 6px 12px;
font-size: 12px;
border-bottom: 1px solid #f0f0f0;
font-family: "Courier New", Consolas, monospace;
&:last-child {
border-bottom: none;
}
}
&__time {
color: #999;
white-space: nowrap;
flex-shrink: 0;
}
&__tag {
flex-shrink: 0;
margin-right: 0 !important;
}
&__message {
word-break: break-word;
color: #333;
}
}
</style>