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

4
.lintstagedrc.json Normal file
View File

@ -0,0 +1,4 @@
{
"*.{js,ts,vue}": ["eslint --fix", "prettier --write"],
"*.{css,less,json,md}": ["prettier --write"]
}

7358
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
"private": true, "private": true,
"version": "0.2.3", "version": "0.2.3",
"type": "module", "type": "module",
"packageManager": "pnpm@9.15.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
@ -20,31 +21,25 @@
"lint": "eslint src", "lint": "eslint src",
"lint:fix": "eslint src --fix", "lint:fix": "eslint src --fix",
"commit": "git-cz", "commit": "git-cz",
"preinstall": "npx only-allow pnpm",
"prepare": "husky" "prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1", "@ant-design/icons-vue": "^7.0.1",
"@tauri-apps/api": "^2.10.1", "@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-autostart": "^2.5.1", "@tauri-apps/plugin-autostart": "^2.5.1",
"@tauri-apps/plugin-http": "^2.5.7", "@tauri-apps/plugin-http": "^2.5.8",
"@tauri-apps/plugin-notification": "^2.3.3", "@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-opener": "^2.5.3", "@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-store": "^2.4.2", "@tauri-apps/plugin-store": "^2.4.2",
"@tauri-apps/plugin-updater": "^2.10.0", "@tauri-apps/plugin-updater": "^2.10.1",
"ant-design-vue": "^4.2.6", "ant-design-vue": "^4.2.6",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"lodash-es": "^4.17.23", "lodash-es": "^4.18.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"vue": "^3.5.31" "vue": "^3.5.32",
}, "vue-router": "^4.6.4"
"lint-staged": {
"*.{js,ts,vue}": [
"eslint --fix"
],
"*.{js,ts,vue,css,less,json,md}": [
"prettier --write"
]
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^20.5.0", "@commitlint/cli": "^20.5.0",
@ -52,24 +47,25 @@
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@tauri-apps/cli": "^2.10.1", "@tauri-apps/cli": "^2.10.1",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@vitejs/plugin-vue": "^6.0.5", "@vitejs/plugin-vue": "^6.0.6",
"axios": "^1.14.0", "axios": "^1.15.0",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"cz-git": "^1.12.0", "cz-git": "^1.12.0",
"dotenv": "^17.3.1", "dotenv": "^17.4.2",
"eslint": "^10.1.0", "eslint": "^10.2.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4", "eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import-x": "^4.16.2", "eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-vue": "^10.8.0", "eslint-plugin-vue": "^10.8.0",
"globals": "^17.4.0", "globals": "^17.5.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^16.4.0", "lint-staged": "^16.4.0",
"sass-embedded": "^1.99.0",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.57.2", "typescript-eslint": "^8.58.2",
"vite": "^7.3.1", "vite": "^7.3.2",
"vue-tsc": "^3.2.6" "vue-tsc": "^3.2.6"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,14 @@
<script setup lang="ts"> <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 { getVersion } from "@tauri-apps/api/app";
import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { getCurrentWindow } from "@tauri-apps/api/window"; import { getCurrentWindow } from "@tauri-apps/api/window";
import { isEnabled as isAutostartEnabled } from "@tauri-apps/plugin-autostart"; 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 AppStatusBar, { type StatusBarItem } from "@/components/AppStatusBar.vue";
import CloseDialog from "@/components/CloseDialog.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 StatusHeader from "@/components/StatusHeader.vue";
import UpdateDialog from "@/components/UpdateDialog.vue"; import UpdateDialog from "@/components/UpdateDialog.vue";
import { useTray } from "@/composables/useTray"; import { useTray } from "@/composables/useTray";
@ -21,8 +18,10 @@ import { useMonitorStore } from "@/stores/monitor";
import { useNetworkStore } from "@/stores/network"; import { useNetworkStore } from "@/stores/network";
import { useUpdaterStore } from "@/stores/updater"; import { useUpdaterStore } from "@/stores/updater";
import { migrateFromLocalStorage } from "@/utils/storage"; 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 store = useMonitorStore();
const appStore = useAppStore(); const appStore = useAppStore();
const logStore = useLogStore(); const logStore = useLogStore();
@ -36,6 +35,24 @@ let unlistenNotification: UnlistenFn | null = null;
/** 窗口关闭请求事件的取消监听函数 */ /** 窗口关闭请求事件的取消监听函数 */
let unlistenClose: 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 statusItems = computed<StatusBarItem[]>(() => {
const items: StatusBarItem[] = []; const items: StatusBarItem[] = [];
if (store.isPostponed) { if (store.isPostponed) {
@ -117,35 +134,32 @@ onUnmounted(() => {
</a-layout-header> </a-layout-header>
<a-layout-content class="app-content"> <a-layout-content class="app-content">
<a-tabs v-model:active-key="activeTab" tab-position="left" class="main-tabs"> <div class="main-shell">
<a-tab-pane key="monitor"> <a-menu :selected-keys="[selectedMenuKey]" mode="inline" class="side-menu" @click="handleMenuClick">
<template #tab> <a-menu-item key="monitor">
<span><DashboardOutlined /> 监测</span> <DashboardOutlined />
</template> <span>监测</span>
<MonitorControl /> </a-menu-item>
</a-tab-pane> <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"> <div class="page-content">
<template #tab> <div class="page-content-inner">
<span><SettingOutlined /> 账号</span> <router-view />
</template> </div>
<LoginForm /> </div>
</a-tab-pane> </div>
<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>
</a-layout-content> </a-layout-content>
<AppStatusBar :items="statusItems" /> <AppStatusBar :items="statusItems" />
@ -163,7 +177,7 @@ body {
} }
</style> </style>
<style scoped> <style scoped lang="scss">
.app-layout { .app-layout {
height: 100vh; height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@ -186,44 +200,30 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.main-tabs { .main-shell {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
display: flex;
gap: 16px; gap: 16px;
} }
.main-tabs :deep(.ant-tabs-nav) { .side-menu {
background: rgba(255, 255, 255, 0.12); width: 112px;
backdrop-filter: blur(12px);
border-radius: 12px; border-radius: 12px;
padding: 12px 0; flex-shrink: 0;
min-width: 100px;
} }
.main-tabs :deep(.ant-tabs-nav::before) { .page-content {
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) {
background: #fff; background: #fff;
border-radius: 12px; border-radius: 12px;
padding: 24px;
overflow: auto;
border: none !important; border: none !important;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1); 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> </style>

View File

@ -3,5 +3,6 @@ import { createPinia } from "pinia";
import { createApp } from "vue"; import { createApp } from "vue";
import "ant-design-vue/dist/reset.css"; import "ant-design-vue/dist/reset.css";
import App from "@/App.vue"; 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
src/router/index.ts Normal file
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;

View File

@ -37,61 +37,77 @@ const levelColor: Record<string, string> = {
<a-empty v-if="logStore.logs.length === 0" description="暂无日志记录" /> <a-empty v-if="logStore.logs.length === 0" description="暂无日志记录" />
<div v-else class="log-viewer__list"> <div v-else class="log-viewer__list">
<div v-for="log in reversedLogs" :key="`${log.timestamp}-${log.message}`" class="log-item"> <div v-for="log in reversedLogs" :key="`${log.timestamp}-${log.message}`" class="log-viewer__item">
<span class="log-item__time">{{ log.timestamp }}</span> <span class="log-viewer__time">{{ log.timestamp }}</span>
<a-tag :color="levelColor[log.level] || 'default'" size="small" class="log-item__tag"> <a-tag :color="levelColor[log.level] || 'default'" size="small" class="log-viewer__tag">
{{ log.level }} {{ log.level }}
</a-tag> </a-tag>
<a-tag color="geekblue" size="small" class="log-item__tag"> <a-tag color="geekblue" size="small" class="log-viewer__tag">
{{ log.category }} {{ log.category }}
</a-tag> </a-tag>
<span class="log-item__msg">{{ log.message }}</span> <span class="log-viewer__message">{{ log.message }}</span>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped lang="scss">
.log-viewer__toolbar { .log-viewer {
display: flex; display: flex;
justify-content: space-between; flex-direction: column;
align-items: center; min-height: 0;
margin-bottom: 12px; height: 100%;
}
.log-viewer__count { &__toolbar {
font-size: 13px; display: flex;
color: #999; justify-content: space-between;
} align-items: center;
.log-viewer__list { margin-bottom: 12px;
max-height: 420px; flex-shrink: 0;
overflow-y: auto; }
border: 1px solid #f0f0f0;
border-radius: 6px; &__count {
background: #fafafa; font-size: 13px;
} color: #999;
.log-item { }
display: flex;
align-items: baseline; &__list {
gap: 8px; flex: 1;
padding: 6px 12px; min-height: 0;
font-size: 12px; overflow-y: auto;
border-bottom: 1px solid #f0f0f0; border: 1px solid #f0f0f0;
font-family: "Courier New", Consolas, monospace; border-radius: 6px;
} background: #fafafa;
.log-item:last-child { }
border-bottom: none;
} &__item {
.log-item__time { display: flex;
color: #999; align-items: baseline;
white-space: nowrap; gap: 8px;
flex-shrink: 0; padding: 6px 12px;
} font-size: 12px;
.log-item__tag { border-bottom: 1px solid #f0f0f0;
flex-shrink: 0; font-family: "Courier New", Consolas, monospace;
margin-right: 0 !important;
} &:last-child {
.log-item__msg { border-bottom: none;
word-break: break-word; }
color: #333; }
&__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> </style>