refactor: ♻️ 使用router跳转页面
This commit is contained in:
parent
13f92f79b6
commit
305fcfae06
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"*.{js,ts,vue}": ["eslint --fix", "prettier --write"],
|
||||||
|
"*.{css,less,json,md}": ["prettier --write"]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1047
pnpm-lock.yaml
1047
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
130
src/App.vue
130
src/App.vue
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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,39 +37,50 @@ 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;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&__toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
flex-shrink: 0;
|
||||||
.log-viewer__count {
|
}
|
||||||
|
|
||||||
|
&__count {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
.log-viewer__list {
|
|
||||||
max-height: 420px;
|
&__list {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border: 1px solid #f0f0f0;
|
border: 1px solid #f0f0f0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
.log-item {
|
|
||||||
|
&__item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
@ -77,21 +88,26 @@ const levelColor: Record<string, string> = {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
font-family: "Courier New", Consolas, monospace;
|
font-family: "Courier New", Consolas, monospace;
|
||||||
}
|
|
||||||
.log-item:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
.log-item__time {
|
}
|
||||||
|
|
||||||
|
&__time {
|
||||||
color: #999;
|
color: #999;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.log-item__tag {
|
|
||||||
|
&__tag {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
}
|
}
|
||||||
.log-item__msg {
|
|
||||||
|
&__message {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
Reference in New Issue