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

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
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,39 +37,50 @@ 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;
flex-direction: column;
min-height: 0;
height: 100%;
&__toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.log-viewer__count {
flex-shrink: 0;
}
&__count {
font-size: 13px;
color: #999;
}
.log-viewer__list {
max-height: 420px;
}
&__list {
flex: 1;
min-height: 0;
overflow-y: auto;
border: 1px solid #f0f0f0;
border-radius: 6px;
background: #fafafa;
}
.log-item {
}
&__item {
display: flex;
align-items: baseline;
gap: 8px;
@ -77,21 +88,26 @@ const levelColor: Record<string, string> = {
font-size: 12px;
border-bottom: 1px solid #f0f0f0;
font-family: "Courier New", Consolas, monospace;
}
.log-item:last-child {
&:last-child {
border-bottom: none;
}
.log-item__time {
}
}
&__time {
color: #999;
white-space: nowrap;
flex-shrink: 0;
}
.log-item__tag {
}
&__tag {
flex-shrink: 0;
margin-right: 0 !important;
}
.log-item__msg {
}
&__message {
word-break: break-word;
color: #333;
}
}
</style>