A:提交部分样式调整代码
This commit is contained in:
1 parent
329e4f066a
commit
ca73e07323
7 files changed
+1930
-292
No files matched your search
@@ -8,6 +8,7 @@ import { ref, onMounted, provide } from 'vue'
|
||||
import { NButton } from 'naive-ui'
|
||||
import { NConfigProvider, NGlobalStyle } from 'naive-ui'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import MainLayout from '@/layouts/MainLayout.vue'
|
||||
|
||||
const router = useRouter()
|
||||
// 是否展示生成路由
|
||||
@@ -166,7 +167,13 @@ onMounted(() => {
|
||||
|
||||
<!-- 展示主要内容 -->
|
||||
<div class="main-content">
|
||||
<router-view />
|
||||
<MainLayout>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade-slide" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</MainLayout>
|
||||
</div>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
@@ -28,3 +28,359 @@ html, body {
|
||||
li{
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
|
||||
/* ==================== 全局滚动条样式 ==================== */
|
||||
/* ==================== 全局滚动条样式 ==================== */
|
||||
/* 放置在 src/styles/scrollbar.css 或 src/assets/styles/scrollbar.css */
|
||||
|
||||
/* 基础滚动条样式 */
|
||||
* {
|
||||
/* Firefox 滚动条样式 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Webkit 滚动条整体 */
|
||||
*::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
/* 滚动条轨道 */
|
||||
*::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 5px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块 - 增强黑暗模式下的可见性 */
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow: inset 0 0 2px rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 滚动条滑块悬停效果 - 更明显的反馈 */
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
box-shadow: inset 0 0 4px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 滚动条滑块激活效果 */
|
||||
*::-webkit-scrollbar-thumb:active {
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 滚动条角落 */
|
||||
*::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* ==================== 主题色滚动条样式 ==================== */
|
||||
/* ==================== 全局滚动条样式 ==================== */
|
||||
|
||||
/* 基础滚动条配置 */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.25) rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Webkit 滚动条整体配置 */
|
||||
*::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
/* 滚动条轨道 */
|
||||
*::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 4px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块 - 默认状态 */
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.2) 0%,
|
||||
rgba(255, 255, 255, 0.15) 50%,
|
||||
rgba(255, 255, 255, 0.25) 100%
|
||||
);
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
||||
0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 滚动条滑块 - 悬停状态 */
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.35) 0%,
|
||||
rgba(255, 255, 255, 0.25) 50%,
|
||||
rgba(255, 255, 255, 0.4) 100%
|
||||
);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
0 2px 4px rgba(0, 0, 0, 0.15);
|
||||
transform: scaleY(1.05);
|
||||
}
|
||||
|
||||
/* 滚动条滑块 - 激活状态 */
|
||||
*::-webkit-scrollbar-thumb:active {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.45) 0%,
|
||||
rgba(255, 255, 255, 0.35) 50%,
|
||||
rgba(255, 255, 255, 0.5) 100%
|
||||
);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
transform: scaleY(1.1);
|
||||
}
|
||||
|
||||
/* 滚动条角落 */
|
||||
*::-webkit-scrollbar-corner {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
/* ==================== 主题化滚动条变体 ==================== */
|
||||
|
||||
/* 主题色滚动条 - 蓝色系 */
|
||||
.scrollbar-primary::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(99, 102, 241, 0.3) 0%,
|
||||
rgba(99, 102, 241, 0.2) 50%,
|
||||
rgba(99, 102, 241, 0.35) 100%
|
||||
);
|
||||
border-color: rgba(99, 102, 241, 0.2);
|
||||
}
|
||||
|
||||
.scrollbar-primary::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(99, 102, 241, 0.5) 0%,
|
||||
rgba(99, 102, 241, 0.35) 50%,
|
||||
rgba(99, 102, 241, 0.55) 100%
|
||||
);
|
||||
border-color: rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
|
||||
/* 成功色滚动条 - 绿色系 */
|
||||
.scrollbar-success::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(34, 197, 94, 0.3) 0%,
|
||||
rgba(34, 197, 94, 0.2) 50%,
|
||||
rgba(34, 197, 94, 0.35) 100%
|
||||
);
|
||||
border-color: rgba(34, 197, 94, 0.2);
|
||||
}
|
||||
|
||||
.scrollbar-success::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(34, 197, 94, 0.5) 0%,
|
||||
rgba(34, 197, 94, 0.35) 50%,
|
||||
rgba(34, 197, 94, 0.55) 100%
|
||||
);
|
||||
border-color: rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
|
||||
/* 警告色滚动条 - 橙色系 */
|
||||
.scrollbar-warning::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(245, 158, 11, 0.3) 0%,
|
||||
rgba(245, 158, 11, 0.2) 50%,
|
||||
rgba(245, 158, 11, 0.35) 100%
|
||||
);
|
||||
border-color: rgba(245, 158, 11, 0.2);
|
||||
}
|
||||
|
||||
.scrollbar-warning::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(245, 158, 11, 0.5) 0%,
|
||||
rgba(245, 158, 11, 0.35) 50%,
|
||||
rgba(245, 158, 11, 0.55) 100%
|
||||
);
|
||||
border-color: rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
/* 紫色滚动条 */
|
||||
.scrollbar-purple::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(168, 85, 247, 0.3) 0%,
|
||||
rgba(168, 85, 247, 0.2) 50%,
|
||||
rgba(168, 85, 247, 0.35) 100%
|
||||
);
|
||||
border-color: rgba(168, 85, 247, 0.2);
|
||||
}
|
||||
|
||||
.scrollbar-purple::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(168, 85, 247, 0.5) 0%,
|
||||
rgba(168, 85, 247, 0.35) 50%,
|
||||
rgba(168, 85, 247, 0.55) 100%
|
||||
);
|
||||
border-color: rgba(168, 85, 247, 0.3);
|
||||
}
|
||||
|
||||
/* ==================== 尺寸变体 ==================== */
|
||||
|
||||
/* 细滚动条 */
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
/* 宽滚动条 */
|
||||
.scrollbar-wide::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* ==================== 浅色模式适配 ==================== */
|
||||
@media (prefers-color-scheme: light) {
|
||||
* {
|
||||
scrollbar-color: rgba(0, 0, 0, 0.3) rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.15) 0%,
|
||||
rgba(0, 0, 0, 0.1) 50%,
|
||||
rgba(0, 0, 0, 0.2) 100%
|
||||
);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5),
|
||||
0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.25) 0%,
|
||||
rgba(0, 0, 0, 0.2) 50%,
|
||||
rgba(0, 0, 0, 0.3) 100%
|
||||
);
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:active {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.35) 0%,
|
||||
rgba(0, 0, 0, 0.3) 50%,
|
||||
rgba(0, 0, 0, 0.4) 100%
|
||||
);
|
||||
border-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-corner {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 特殊状态 ==================== */
|
||||
|
||||
/* 禁用状态 */
|
||||
.scrollbar-disabled::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border-color: rgba(255, 255, 255, 0.02) !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.scrollbar-hidden {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.scrollbar-hidden::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==================== 动画增强 ==================== */
|
||||
|
||||
/* 滚动时的轨道高亮效果 */
|
||||
@keyframes scrollTrackPulse {
|
||||
0%, 100% {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
50% {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-animated::-webkit-scrollbar-track:hover {
|
||||
animation: scrollTrackPulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 滑块的呼吸效果 */
|
||||
@keyframes thumbBreathe {
|
||||
0%, 100% {
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
||||
0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
||||
0 2px 4px rgba(0, 0, 0, 0.15),
|
||||
0 0 8px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar-glow::-webkit-scrollbar-thumb:hover {
|
||||
animation: thumbBreathe 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ==================== 使用说明(CSS 注释) ==================== */
|
||||
|
||||
/*
|
||||
使用方法:
|
||||
|
||||
1. 基础用法:
|
||||
直接引入此 CSS 文件,所有元素将自动应用默认滚动条样式
|
||||
|
||||
2. 主题化用法:
|
||||
<div class="scrollbar-primary">...</div> // 蓝色主题
|
||||
<div class="scrollbar-success">...</div> // 绿色主题
|
||||
<div class="scrollbar-warning">...</div> // 橙色主题
|
||||
<div class="scrollbar-purple">...</div> // 紫色主题
|
||||
|
||||
3. 尺寸变体:
|
||||
<div class="scrollbar-thin">...</div> // 细滚动条
|
||||
<div class="scrollbar-wide">...</div> // 宽滚动条
|
||||
|
||||
4. 特效增强:
|
||||
<div class="scrollbar-animated">...</div> // 动画效果
|
||||
<div class="scrollbar-glow">...</div> // 发光效果
|
||||
|
||||
5. 组合使用:
|
||||
<div class="scrollbar-primary scrollbar-wide scrollbar-glow">...</div>
|
||||
|
||||
6. 隐藏滚动条:
|
||||
<div class="scrollbar-hidden">...</div>
|
||||
|
||||
注意:此样式自动适配浅色/深色模式
|
||||
*/
|
||||
@@ -273,4 +273,16 @@ const openSettings = () => {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 为特定组件添加过渡效果 */
|
||||
.n-tabs,
|
||||
.n-button,
|
||||
.n-input {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
}
|
||||
|
||||
/* 确保 tabs 的所有子元素都有过渡 */
|
||||
.n-tabs * {
|
||||
transition: inherit !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div ref="appContainer" class="app-container">
|
||||
<!-- 顶部标题栏组件 -->
|
||||
<TitleBar />
|
||||
|
||||
<!-- 路由视图区域 -->
|
||||
<main>
|
||||
<slot /> <!-- 页面内容会被插入到这里 -->
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, nextTick, computed } from 'vue'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import TitleBar from '@/components/TitleBar.vue'
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const isDark = computed(() => themeStore.isDark)
|
||||
|
||||
// 引用app容器元素
|
||||
const appContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
// 监听主题变化并更新CSS类
|
||||
watch(isDark, () => {
|
||||
nextTick(() => {
|
||||
updateThemeClass()
|
||||
})
|
||||
}, { immediate: true })
|
||||
|
||||
// 更新主题CSS类
|
||||
const updateThemeClass = () => {
|
||||
if (appContainer.value) {
|
||||
if (isDark.value) {
|
||||
appContainer.value.classList.add('dark-theme')
|
||||
} else {
|
||||
appContainer.value.classList.remove('dark-theme')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化主题类
|
||||
updateThemeClass()
|
||||
|
||||
// 页面加载完成后启用过渡动画
|
||||
setTimeout(() => {
|
||||
document.body.classList.add('theme-ready')
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 主题色彩变量 */
|
||||
.app-container {
|
||||
/* 暗色主题 */
|
||||
--bg-primary: #0f172a;
|
||||
--bg-secondary: #1e293b;
|
||||
--bg-tertiary: #334155;
|
||||
--bg-quaternary: #475569;
|
||||
--bg-overlay: rgba(15, 23, 42, 0.95);
|
||||
--bg-glass: rgba(30, 41, 59, 0.8);
|
||||
--bg-card: rgba(51, 65, 85, 0.5);
|
||||
--bg-hover: rgba(59, 130, 246, 0.1);
|
||||
--bg-selected: rgba(59, 130, 246, 0.2);
|
||||
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #f1f5f9;
|
||||
--text-tertiary: #e2e8f0;
|
||||
--text-quaternary: #cbd5e1;
|
||||
--text-muted: #94a3b8;
|
||||
--text-disabled: #64748b;
|
||||
|
||||
--border-primary: #334155;
|
||||
--border-secondary: rgba(51, 65, 85, 0.3);
|
||||
--border-tertiary: rgba(71, 85, 105, 0.3);
|
||||
--border-hover: rgba(59, 130, 246, 0.5);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
--accent-primary: #3b82f6;
|
||||
--accent-secondary: #06b6d4;
|
||||
--accent-success: #22c55e;
|
||||
--accent-warning: #f59e0b;
|
||||
--accent-error: #ef4444;
|
||||
--accent-purple: #8b5cf6;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
|
||||
--backdrop-blur: blur(20px);
|
||||
--transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 亮色主题变量 */
|
||||
.app-container:not(.dark-theme) {
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8fafc;
|
||||
--bg-tertiary: #f1f5f9;
|
||||
--bg-quaternary: #e2e8f0;
|
||||
--bg-overlay: rgba(255, 255, 255, 0.95);
|
||||
--bg-glass: rgba(248, 250, 252, 0.8);
|
||||
--bg-card: rgba(241, 245, 249, 0.8);
|
||||
--bg-hover: rgba(59, 130, 246, 0.08);
|
||||
--bg-selected: rgba(59, 130, 246, 0.15);
|
||||
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #1e293b;
|
||||
--text-tertiary: #334155;
|
||||
--text-quaternary: #475569;
|
||||
--text-muted: #64748b;
|
||||
--text-disabled: #94a3b8;
|
||||
|
||||
--border-primary: #e2e8f0;
|
||||
--border-secondary: rgba(226, 232, 240, 0.8);
|
||||
--border-tertiary: rgba(203, 213, 225, 0.6);
|
||||
--border-hover: rgba(59, 130, 246, 0.3);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 根容器样式 */
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
||||
color: var(--text-primary);
|
||||
user-select: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-tertiary);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--border-hover);
|
||||
}
|
||||
|
||||
/* 自定义Naive UI组件样式 */
|
||||
:deep(.n-input .n-input__input-el) {
|
||||
background: var(--bg-card);
|
||||
border-color: var(--border-tertiary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
:deep(.n-input:hover .n-input__input-el) {
|
||||
border-color: var(--border-hover);
|
||||
}
|
||||
|
||||
:deep(.n-input.n-input--focus .n-input__input-el) {
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
border-color: var(--border-tertiary);
|
||||
color: var(--text-quaternary);
|
||||
}
|
||||
|
||||
:deep(.n-button:hover) {
|
||||
border-color: var(--border-hover);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
:deep(.n-button--primary-type) {
|
||||
background: var(--accent-primary);
|
||||
border-color: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.n-button--primary-type:hover) {
|
||||
background: #1d4ed8;
|
||||
border-color: #1d4ed8;
|
||||
}
|
||||
|
||||
:deep(.n-button--info-type) {
|
||||
background: var(--accent-secondary);
|
||||
border-color: var(--accent-secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.n-button--info-type:hover) {
|
||||
background: #0891b2;
|
||||
border-color: #0891b2;
|
||||
}
|
||||
|
||||
:deep(.n-button--warning-type) {
|
||||
background: var(--accent-warning);
|
||||
border-color: var(--accent-warning);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.n-button--warning-type:hover) {
|
||||
background: #d97706;
|
||||
border-color: #d97706;
|
||||
}
|
||||
|
||||
/* 主题切换过渡效果 - 只在页面加载完成后启用 */
|
||||
body.theme-ready * {
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease,
|
||||
color 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* 初始加载时禁用所有过渡 */
|
||||
body:not(.theme-ready) * {
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,756 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>网络分析</h1>
|
||||
<div class="analyse-view">
|
||||
<div class="analyse-container">
|
||||
<!-- 顶部概览 -->
|
||||
<div class="analyse-header">
|
||||
<div class="header-info">
|
||||
<h2 class="view-title">流量分析</h2>
|
||||
<p class="view-subtitle">深入分析网络流量模式和安全威胁</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<n-date-picker
|
||||
v-model:value="dateRange"
|
||||
type="daterange"
|
||||
clearable
|
||||
size="medium"
|
||||
/>
|
||||
<n-button type="primary" size="medium">
|
||||
<template #icon>
|
||||
<n-icon :component="RefreshOutline" />
|
||||
</template>
|
||||
刷新数据
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关键指标卡片 -->
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<div class="metric-header">
|
||||
<span class="metric-title">总流量</span>
|
||||
<n-icon :component="TrendingUpOutline" class="metric-icon up" />
|
||||
</div>
|
||||
<div class="metric-value">2.45 TB</div>
|
||||
<div class="metric-trend">
|
||||
<span class="trend up">+12.5%</span>
|
||||
<span class="trend-text">较上周</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-header">
|
||||
<span class="metric-title">威胁检测</span>
|
||||
<n-icon :component="WarningOutline" class="metric-icon warning" />
|
||||
</div>
|
||||
<div class="metric-value">156</div>
|
||||
<div class="metric-trend">
|
||||
<span class="trend down">-8.2%</span>
|
||||
<span class="trend-text">较上周</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-header">
|
||||
<span class="metric-title">平均延迟</span>
|
||||
<n-icon :component="SpeedometerOutline" class="metric-icon" />
|
||||
</div>
|
||||
<div class="metric-value">12.3 ms</div>
|
||||
<div class="metric-trend">
|
||||
<span class="trend stable">0.0%</span>
|
||||
<span class="trend-text">稳定</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-card">
|
||||
<div class="metric-header">
|
||||
<span class="metric-title">数据包丢失</span>
|
||||
<n-icon :component="AlertCircleOutline" class="metric-icon error" />
|
||||
</div>
|
||||
<div class="metric-value">0.02%</div>
|
||||
<div class="metric-trend">
|
||||
<span class="trend up">+0.01%</span>
|
||||
<span class="trend-text">轻微上升</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<div class="charts-grid">
|
||||
<!-- 流量趋势图 -->
|
||||
<div class="chart-card large">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">流量趋势</h3>
|
||||
<div class="chart-controls">
|
||||
<n-button-group size="tiny">
|
||||
<n-button>1小时</n-button>
|
||||
<n-button type="primary">24小时</n-button>
|
||||
<n-button>7天</n-button>
|
||||
<n-button>30天</n-button>
|
||||
</n-button-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-body">
|
||||
<div class="chart-placeholder">
|
||||
<div class="chart-line"></div>
|
||||
<svg viewBox="0 0 400 200" class="chart-svg">
|
||||
<path
|
||||
d="M 0 180 Q 100 100 200 120 T 400 80"
|
||||
fill="none"
|
||||
stroke="var(--accent-primary)"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<path
|
||||
d="M 0 180 Q 100 100 200 120 T 400 80 L 400 200 L 0 200 Z"
|
||||
fill="url(#gradient)"
|
||||
opacity="0.2"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:var(--accent-primary);stop-opacity:0.8" />
|
||||
<stop offset="100%" style="stop-color:var(--accent-primary);stop-opacity:0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 协议分布饼图 -->
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">协议分布</h3>
|
||||
</div>
|
||||
<div class="chart-body">
|
||||
<div class="pie-chart">
|
||||
<div class="pie-center">
|
||||
<svg viewBox="0 0 100 100" class="pie-svg">
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
fill="none"
|
||||
stroke="var(--accent-primary)"
|
||||
stroke-width="20"
|
||||
stroke-dasharray="75 125"
|
||||
transform="rotate(-90 50 50)"
|
||||
/>
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
fill="none"
|
||||
stroke="var(--accent-secondary)"
|
||||
stroke-width="20"
|
||||
stroke-dasharray="50 125"
|
||||
stroke-dashoffset="-75"
|
||||
transform="rotate(-90 50 50)"
|
||||
/>
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
fill="none"
|
||||
stroke="var(--accent-warning)"
|
||||
stroke-width="20"
|
||||
stroke-dasharray="35 125"
|
||||
stroke-dashoffset="-125"
|
||||
transform="rotate(-90 50 50)"
|
||||
/>
|
||||
<circle
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40"
|
||||
fill="none"
|
||||
stroke="var(--accent-error)"
|
||||
stroke-width="20"
|
||||
stroke-dasharray="40 125"
|
||||
stroke-dashoffset="-160"
|
||||
transform="rotate(-90 50 50)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="pie-legend">
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background: var(--accent-primary)"></span>
|
||||
<span>HTTPS (37.5%)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background: var(--accent-secondary)"></span>
|
||||
<span>HTTP (25%)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background: var(--accent-warning)"></span>
|
||||
<span>DNS (17.5%)</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-dot" style="background: var(--accent-error)"></span>
|
||||
<span>其他 (20%)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top 应用 -->
|
||||
<div class="chart-card">
|
||||
<div class="chart-header">
|
||||
<h3 class="chart-title">Top 应用流量</h3>
|
||||
</div>
|
||||
<div class="chart-body">
|
||||
<div class="app-list">
|
||||
<div v-for="(app, index) in topApps" :key="index" class="app-item">
|
||||
<div class="app-info">
|
||||
<span class="app-rank">{{ index + 1 }}</span>
|
||||
<span class="app-name">{{ app.name }}</span>
|
||||
</div>
|
||||
<div class="app-traffic">
|
||||
<div class="traffic-bar">
|
||||
<div
|
||||
class="traffic-fill"
|
||||
:style="{ width: app.percentage + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<span class="traffic-text">{{ app.traffic }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细表格 -->
|
||||
<div class="detail-panel">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">异常流量检测</h3>
|
||||
<div class="panel-controls">
|
||||
<n-tag type="error" size="small">
|
||||
{{ anomalies.length }} 个异常
|
||||
</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="anomaly-list">
|
||||
<div v-for="anomaly in anomalies" :key="anomaly.id" class="anomaly-item">
|
||||
<div class="anomaly-severity" :class="'severity-' + anomaly.severity">
|
||||
<n-icon
|
||||
:component="anomaly.severity === 'high' ? AlertOutline :
|
||||
anomaly.severity === 'medium' ? WarningOutline :
|
||||
InformationCircleOutline"
|
||||
size="16"
|
||||
/>
|
||||
</div>
|
||||
<div class="anomaly-info">
|
||||
<div class="anomaly-title">{{ anomaly.title }}</div>
|
||||
<div class="anomaly-details">
|
||||
<span>{{ anomaly.time }}</span>
|
||||
<span>•</span>
|
||||
<span>{{ anomaly.source }}</span>
|
||||
<span>→</span>
|
||||
<span>{{ anomaly.destination }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="anomaly-action">
|
||||
<n-button size="tiny" quaternary>查看详情</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { NButton, NButtonGroup, NIcon, NDatePicker, NTag } from 'naive-ui'
|
||||
import {
|
||||
RefreshOutline,
|
||||
TrendingUpOutline,
|
||||
WarningOutline,
|
||||
SpeedometerOutline,
|
||||
AlertCircleOutline,
|
||||
AlertOutline,
|
||||
InformationCircleOutline,
|
||||
} from '@vicons/ionicons5'
|
||||
|
||||
// 日期范围
|
||||
const dateRange = ref<[number, number] | null>(null)
|
||||
|
||||
// Top 应用数据
|
||||
const topApps = ref([
|
||||
{ name: 'Chrome', traffic: '523 GB', percentage: 85 },
|
||||
{ name: 'Microsoft Teams', traffic: '387 GB', percentage: 63 },
|
||||
{ name: 'Spotify', traffic: '256 GB', percentage: 42 },
|
||||
{ name: 'Visual Studio Code', traffic: '198 GB', percentage: 32 },
|
||||
{ name: 'Slack', traffic: '142 GB', percentage: 23 },
|
||||
])
|
||||
|
||||
// 异常数据
|
||||
const anomalies = ref([
|
||||
{
|
||||
id: 1,
|
||||
severity: 'high',
|
||||
title: '异常大量数据传输',
|
||||
time: '10:23:45',
|
||||
source: '192.168.1.105',
|
||||
destination: '104.21.58.93',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
severity: 'medium',
|
||||
title: '可疑端口扫描活动',
|
||||
time: '10:15:32',
|
||||
source: '10.0.0.158',
|
||||
destination: '192.168.1.1',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
severity: 'low',
|
||||
title: '非标准协议使用',
|
||||
time: '09:58:21',
|
||||
source: '192.168.1.201',
|
||||
destination: '172.16.0.1',
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.analyse-view {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.analyse-container {
|
||||
padding: 24px;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.analyse-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.view-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.view-subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 指标卡片 */
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.metric-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.metric-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.metric-title {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.metric-icon {
|
||||
font-size: 20px;
|
||||
color: var(--text-quaternary);
|
||||
}
|
||||
|
||||
.metric-icon.up {
|
||||
color: var(--accent-success);
|
||||
}
|
||||
|
||||
.metric-icon.warning {
|
||||
color: var(--accent-warning);
|
||||
}
|
||||
|
||||
.metric-icon.error {
|
||||
color: var(--accent-error);
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.metric-trend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.trend {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.trend.up {
|
||||
color: var(--accent-success);
|
||||
}
|
||||
|
||||
.trend.down {
|
||||
color: var(--accent-error);
|
||||
}
|
||||
|
||||
.trend.stable {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.trend-text {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* 图表网格 */
|
||||
.charts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.chart-card {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chart-card.large {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chart-body {
|
||||
height: 240px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 流量趋势图 */
|
||||
.chart-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 饼图 */
|
||||
.pie-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.pie-center {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.pie-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pie-legend {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 应用列表 */
|
||||
.app-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.app-rank {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.app-traffic {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.traffic-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.traffic-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
||||
border-radius: 3px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.traffic-text {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
min-width: 60px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 详细面板 */
|
||||
.detail-panel {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 异常列表 */
|
||||
.anomaly-list {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.anomaly-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.anomaly-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.anomaly-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.anomaly-severity {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.severity-high {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--accent-error);
|
||||
}
|
||||
|
||||
.severity-medium {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: var(--accent-warning);
|
||||
}
|
||||
|
||||
.severity-low {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.anomaly-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.anomaly-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.anomaly-details {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 1200px) {
|
||||
.charts-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.chart-card.large {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.analyse-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.charts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-card.large {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.pie-chart {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Naive UI 样式覆盖 */
|
||||
:deep(.n-date-picker) {
|
||||
--n-border: 1px solid var(--border-tertiary);
|
||||
--n-border-hover: 1px solid var(--border-hover);
|
||||
--n-border-focus: 1px solid var(--accent-primary);
|
||||
--n-color: var(--bg-card);
|
||||
--n-text-color: var(--text-primary);
|
||||
}
|
||||
|
||||
:deep(.n-button-group .n-button) {
|
||||
border-color: var(--border-tertiary);
|
||||
}
|
||||
|
||||
:deep(.n-button-group .n-button--primary-type) {
|
||||
background: var(--accent-primary);
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,521 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>防火墙</h1>
|
||||
<div class="firewall-view">
|
||||
<div class="firewall-container">
|
||||
<!-- 顶部工具栏 -->
|
||||
<div class="firewall-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<h2 class="view-title">防火墙规则管理</h2>
|
||||
<p class="view-subtitle">配置和管理网络防护规则</p>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<n-button type="primary" size="medium">
|
||||
<template #icon>
|
||||
<n-icon :component="AddOutline" />
|
||||
</template>
|
||||
添加规则
|
||||
</n-button>
|
||||
<n-button size="medium" ghost>导入规则</n-button>
|
||||
<n-button size="medium" ghost>导出规则</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 规则统计卡片 -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon-wrapper success">
|
||||
<n-icon :component="CheckmarkCircleOutline" size="24" />
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.active }}</div>
|
||||
<div class="stat-label">启用规则</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon-wrapper warning">
|
||||
<n-icon :component="PauseCircleOutline" size="24" />
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.inactive }}</div>
|
||||
<div class="stat-label">禁用规则</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon-wrapper error">
|
||||
<n-icon :component="CloseCircleOutline" size="24" />
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.blocked }}</div>
|
||||
<div class="stat-label">已拦截</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon-wrapper info">
|
||||
<n-icon :component="ShieldCheckmarkOutline" size="24" />
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.total }}</div>
|
||||
<div class="stat-label">总规则数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 规则列表 -->
|
||||
<div class="rules-panel">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">防火墙规则列表</h3>
|
||||
<div class="panel-controls">
|
||||
<n-input
|
||||
v-model:value="searchQuery"
|
||||
placeholder="搜索规则..."
|
||||
size="small"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon :component="SearchOutline" />
|
||||
</template>
|
||||
</n-input>
|
||||
<n-select
|
||||
v-model:value="filterType"
|
||||
size="small"
|
||||
style="width: 120px"
|
||||
:options="filterOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rules-list">
|
||||
<div v-for="rule in mockRules" :key="rule.id" class="rule-item">
|
||||
<div class="rule-status">
|
||||
<n-switch v-model:value="rule.enabled" size="small" />
|
||||
</div>
|
||||
|
||||
<div class="rule-main">
|
||||
<div class="rule-header">
|
||||
<h4 class="rule-name">{{ rule.name }}</h4>
|
||||
<n-tag :type="rule.action === 'allow' ? 'success' : 'error'" size="small">
|
||||
{{ rule.action === 'allow' ? '允许' : '拒绝' }}
|
||||
</n-tag>
|
||||
</div>
|
||||
<div class="rule-details">
|
||||
<span class="rule-detail">
|
||||
<n-icon :component="LocationOutline" size="14" />
|
||||
{{ rule.source }}
|
||||
</span>
|
||||
<span class="rule-arrow">→</span>
|
||||
<span class="rule-detail">
|
||||
<n-icon :component="LocationOutline" size="14" />
|
||||
{{ rule.destination }}
|
||||
</span>
|
||||
<span class="rule-detail">
|
||||
<n-icon :component="LayersOutline" size="14" />
|
||||
{{ rule.protocol }}
|
||||
</span>
|
||||
<span class="rule-detail">
|
||||
<n-icon :component="ServerOutline" size="14" />
|
||||
端口: {{ rule.port }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rule-actions">
|
||||
<n-button size="tiny" circle quaternary>
|
||||
<template #icon>
|
||||
<n-icon :component="CreateOutline" />
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button size="tiny" circle quaternary>
|
||||
<template #icon>
|
||||
<n-icon :component="CopyOutline" />
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button size="tiny" circle quaternary type="error">
|
||||
<template #icon>
|
||||
<n-icon :component="TrashOutline" />
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { NButton, NIcon, NInput, NSelect, NSwitch, NTag } from 'naive-ui'
|
||||
import {
|
||||
AddOutline,
|
||||
SearchOutline,
|
||||
CheckmarkCircleOutline,
|
||||
PauseCircleOutline,
|
||||
CloseCircleOutline,
|
||||
ShieldCheckmarkOutline,
|
||||
LocationOutline,
|
||||
LayersOutline,
|
||||
ServerOutline,
|
||||
CreateOutline,
|
||||
CopyOutline,
|
||||
TrashOutline,
|
||||
} from '@vicons/ionicons5'
|
||||
|
||||
// 搜索和过滤
|
||||
const searchQuery = ref('')
|
||||
const filterType = ref('all')
|
||||
|
||||
const filterOptions = [
|
||||
{ label: '全部规则', value: 'all' },
|
||||
{ label: '已启用', value: 'active' },
|
||||
{ label: '已禁用', value: 'inactive' },
|
||||
{ label: '入站规则', value: 'inbound' },
|
||||
{ label: '出站规则', value: 'outbound' },
|
||||
]
|
||||
|
||||
// 统计数据
|
||||
const stats = ref({
|
||||
active: 128,
|
||||
inactive: 24,
|
||||
blocked: 3456,
|
||||
total: 152,
|
||||
})
|
||||
|
||||
// 模拟规则数据
|
||||
const mockRules = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '阻止恶意IP访问',
|
||||
enabled: true,
|
||||
action: 'deny',
|
||||
source: '10.0.0.0/8',
|
||||
destination: '任意',
|
||||
protocol: 'TCP/UDP',
|
||||
port: '全部',
|
||||
priority: 100,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '允许HTTPS流量',
|
||||
enabled: true,
|
||||
action: 'allow',
|
||||
source: '任意',
|
||||
destination: '任意',
|
||||
protocol: 'TCP',
|
||||
port: '443',
|
||||
priority: 200,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '允许DNS查询',
|
||||
enabled: true,
|
||||
action: 'allow',
|
||||
source: '192.168.1.0/24',
|
||||
destination: '8.8.8.8',
|
||||
protocol: 'UDP',
|
||||
port: '53',
|
||||
priority: 300,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '阻止Telnet访问',
|
||||
enabled: false,
|
||||
action: 'deny',
|
||||
source: '任意',
|
||||
destination: '任意',
|
||||
protocol: 'TCP',
|
||||
port: '23',
|
||||
priority: 400,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '允许SSH管理',
|
||||
enabled: true,
|
||||
action: 'allow',
|
||||
source: '192.168.1.100',
|
||||
destination: '192.168.1.1',
|
||||
protocol: 'TCP',
|
||||
port: '22',
|
||||
priority: 500,
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.firewall-view {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.firewall-container {
|
||||
padding: 24px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 工具栏 */
|
||||
.firewall-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.view-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.view-subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.stat-icon-wrapper {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stat-icon-wrapper.success {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
color: var(--accent-success);
|
||||
}
|
||||
|
||||
.stat-icon-wrapper.warning {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: var(--accent-warning);
|
||||
}
|
||||
|
||||
.stat-icon-wrapper.error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--accent-error);
|
||||
}
|
||||
|
||||
.stat-icon-wrapper.info {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 规则面板 */
|
||||
.rules-panel {
|
||||
background: var(--bg-card);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel-controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 规则列表 */
|
||||
.rules-list {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.rule-item {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.rule-item:hover {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--border-hover);
|
||||
}
|
||||
|
||||
.rule-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rule-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rule-main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.rule-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.rule-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rule-details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.rule-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.rule-arrow {
|
||||
color: var(--text-quaternary);
|
||||
}
|
||||
|
||||
.rule-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.firewall-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.panel-controls {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rule-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.rule-details {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* Naive UI 样式覆盖 */
|
||||
:deep(.n-input) {
|
||||
--n-border: 1px solid var(--border-tertiary);
|
||||
--n-border-hover: 1px solid var(--border-hover);
|
||||
--n-border-focus: 1px solid var(--accent-primary);
|
||||
--n-color: var(--bg-card);
|
||||
--n-text-color: var(--text-primary);
|
||||
}
|
||||
|
||||
:deep(.n-select) {
|
||||
--n-border: 1px solid var(--border-tertiary);
|
||||
--n-border-hover: 1px solid var(--border-hover);
|
||||
--n-border-focus: 1px solid var(--accent-primary);
|
||||
--n-color: var(--bg-card);
|
||||
--n-text-color: var(--text-primary);
|
||||
}
|
||||
|
||||
:deep(.n-switch) {
|
||||
--n-rail-color-active: var(--accent-success);
|
||||
}
|
||||
|
||||
:deep(.n-tag) {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,9 @@
|
||||
<template>
|
||||
<div ref="appContainer" class="app-container">
|
||||
<!-- 顶部标题栏组件 -->
|
||||
<TitleBar />
|
||||
|
||||
<div class="monitor-view">
|
||||
<!-- 主内容区域 - 可拖拽布局 -->
|
||||
<div class="main-content">
|
||||
<!-- 左侧边栏 -->
|
||||
<div class="sidebar" :style="{ width: sidebarWidth + 'px' }">
|
||||
<div class="sidebar scrollbar-primary scrollbar-thin" :style="{ width: sidebarWidth + 'px' }">
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-header">
|
||||
<h3 class="sidebar-title">系统概览</h3>
|
||||
@@ -110,7 +107,7 @@
|
||||
<div class="table-column">流量</div>
|
||||
</div>
|
||||
|
||||
<div class="table-body">
|
||||
<div class="table-body scrollbar-success scrollbar-glow">
|
||||
<div
|
||||
v-for="i in 15"
|
||||
:key="i"
|
||||
@@ -157,7 +154,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-content scrollbar-warning scrollbar-animated">
|
||||
<div class="events-container">
|
||||
<div
|
||||
v-for="i in 12"
|
||||
@@ -183,7 +180,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 右侧检查器面板 -->
|
||||
<div class="inspector" :style="{ width: inspectorWidth + 'px' }">
|
||||
<div class="inspector scrollbar-purple scrollbar-glow" :style="{ width: inspectorWidth + 'px' }">
|
||||
<div class="inspector-content">
|
||||
<div class="inspector-header">
|
||||
<h3 class="inspector-title">
|
||||
@@ -289,38 +286,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { NButton, NInput, NIcon } from 'naive-ui'
|
||||
import { SearchOutline } from '@vicons/ionicons5'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import TitleBar from '@/components/TitleBar.vue'
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const isDark = computed({
|
||||
get: () => themeStore.isDark,
|
||||
set: (val) => (themeStore.isDark = val),
|
||||
})
|
||||
|
||||
// 引用app容器元素
|
||||
const appContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
// 响应式数据
|
||||
const currentView = ref('monitor')
|
||||
const searchQuery = ref('')
|
||||
|
||||
// 布局尺寸控制
|
||||
const sidebarWidth = ref(300)
|
||||
const inspectorWidth = ref(350)
|
||||
const timelineHeight = ref(200)
|
||||
const timelineHeight = ref(500)
|
||||
const mainHeaderHeight = ref(80)
|
||||
|
||||
// 最小尺寸限制
|
||||
const MIN_SIDEBAR_WIDTH = 250
|
||||
const MAX_SIDEBAR_WIDTH = 450
|
||||
const MIN_INSPECTOR_WIDTH = 300
|
||||
const MAX_INSPECTOR_WIDTH = 500
|
||||
const MIN_TIMELINE_HEIGHT = 150
|
||||
const MAX_TIMELINE_HEIGHT = 350
|
||||
// 调整分割条范围限制 - 放宽范围
|
||||
const MIN_SIDEBAR_WIDTH = 200
|
||||
const MAX_SIDEBAR_WIDTH = 600
|
||||
const MIN_INSPECTOR_WIDTH = 250
|
||||
const MAX_INSPECTOR_WIDTH = 650
|
||||
const MIN_TIMELINE_HEIGHT = 100
|
||||
const MAX_TIMELINE_HEIGHT = 800
|
||||
|
||||
// 模拟数据
|
||||
const mockData = ref({
|
||||
@@ -403,31 +388,15 @@ const stopResize = () => {
|
||||
document.body.style.userSelect = ''
|
||||
}
|
||||
|
||||
// 方法
|
||||
const openSettings = () => {
|
||||
console.log('打开设置')
|
||||
}
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
isDark.value = !isDark.value
|
||||
console.log('切换暗色模式:', isDark.value)
|
||||
}
|
||||
|
||||
// 键盘快捷键 - 重置布局
|
||||
// 重置布局
|
||||
const resetLayout = () => {
|
||||
sidebarWidth.value = 300
|
||||
inspectorWidth.value = 350
|
||||
inspectorWidth.value = 450
|
||||
timelineHeight.value = 200
|
||||
}
|
||||
|
||||
// 监听键盘事件和主题切换
|
||||
// 键盘快捷键
|
||||
onMounted(() => {
|
||||
// 获取app容器元素引用
|
||||
appContainer.value = document.querySelector('.app-container')
|
||||
|
||||
// 初始化主题类
|
||||
updateThemeClass()
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.ctrlKey && event.key === 'r') {
|
||||
event.preventDefault()
|
||||
@@ -443,119 +412,27 @@ onMounted(() => {
|
||||
document.removeEventListener('mouseup', stopResize)
|
||||
})
|
||||
})
|
||||
|
||||
// 监听主题变化并更新CSS类
|
||||
watch(isDark, () => {
|
||||
nextTick(() => {
|
||||
updateThemeClass()
|
||||
})
|
||||
}, { immediate: true })
|
||||
|
||||
// 更新主题CSS类
|
||||
const updateThemeClass = () => {
|
||||
if (appContainer.value) {
|
||||
if (isDark.value) {
|
||||
appContainer.value.classList.add('dark-theme')
|
||||
} else {
|
||||
appContainer.value.classList.remove('dark-theme')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 主题色彩变量 */
|
||||
.app-container {
|
||||
/* 暗色主题 */
|
||||
--bg-primary: #0f172a;
|
||||
--bg-secondary: #1e293b;
|
||||
--bg-tertiary: #334155;
|
||||
--bg-quaternary: #475569;
|
||||
--bg-overlay: rgba(15, 23, 42, 0.95);
|
||||
--bg-glass: rgba(30, 41, 59, 0.8);
|
||||
--bg-card: rgba(51, 65, 85, 0.5);
|
||||
--bg-hover: rgba(59, 130, 246, 0.1);
|
||||
--bg-selected: rgba(59, 130, 246, 0.2);
|
||||
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #f1f5f9;
|
||||
--text-tertiary: #e2e8f0;
|
||||
--text-quaternary: #cbd5e1;
|
||||
--text-muted: #94a3b8;
|
||||
--text-disabled: #64748b;
|
||||
|
||||
--border-primary: #334155;
|
||||
--border-secondary: rgba(51, 65, 85, 0.3);
|
||||
--border-tertiary: rgba(71, 85, 105, 0.3);
|
||||
--border-hover: rgba(59, 130, 246, 0.5);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
--accent-primary: #3b82f6;
|
||||
--accent-secondary: #06b6d4;
|
||||
--accent-success: #22c55e;
|
||||
--accent-warning: #f59e0b;
|
||||
--accent-error: #ef4444;
|
||||
--accent-purple: #8b5cf6;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
|
||||
--backdrop-blur: blur(20px);
|
||||
--transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 亮色主题变量 */
|
||||
.app-container:not(.dark-theme) {
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8fafc;
|
||||
--bg-tertiary: #f1f5f9;
|
||||
--bg-quaternary: #e2e8f0;
|
||||
--bg-overlay: rgba(255, 255, 255, 0.95);
|
||||
--bg-glass: rgba(248, 250, 252, 0.8);
|
||||
--bg-card: rgba(241, 245, 249, 0.8);
|
||||
--bg-hover: rgba(59, 130, 246, 0.08);
|
||||
--bg-selected: rgba(59, 130, 246, 0.15);
|
||||
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #1e293b;
|
||||
--text-tertiary: #334155;
|
||||
--text-quaternary: #475569;
|
||||
--text-muted: #64748b;
|
||||
--text-disabled: #94a3b8;
|
||||
|
||||
--border-primary: #e2e8f0;
|
||||
--border-secondary: rgba(226, 232, 240, 0.8);
|
||||
--border-tertiary: rgba(203, 213, 225, 0.6);
|
||||
--border-hover: rgba(59, 130, 246, 0.3);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 根据主题状态添加类 */
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
||||
color: var(--text-primary);
|
||||
user-select: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
/* 当暗色主题时添加 dark-theme 类 */
|
||||
.app-container:global(.dark-theme) {
|
||||
/* 暗色主题的额外样式 */
|
||||
/* 监控视图容器 - 防止整体滚动 */
|
||||
.monitor-view {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
height: calc(100vh - 60px);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 侧边栏 */
|
||||
@@ -566,12 +443,14 @@ const updateThemeClass = () => {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
padding: 24px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
@@ -684,6 +563,7 @@ const updateThemeClass = () => {
|
||||
transition: background-color 0.2s;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.resize-handle-vertical:hover {
|
||||
@@ -702,6 +582,7 @@ const updateThemeClass = () => {
|
||||
transition: background-color 0.2s;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.resize-handle-horizontal:hover {
|
||||
@@ -719,7 +600,9 @@ const updateThemeClass = () => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
background: var(--bg-card);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-header {
|
||||
@@ -727,6 +610,7 @@ const updateThemeClass = () => {
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
padding: 0 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -776,6 +660,7 @@ const updateThemeClass = () => {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.connections-header {
|
||||
@@ -785,6 +670,7 @@ const updateThemeClass = () => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: var(--bg-card);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.connections-title {
|
||||
@@ -812,12 +698,14 @@ const updateThemeClass = () => {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
@@ -830,6 +718,7 @@ const updateThemeClass = () => {
|
||||
color: var(--text-quaternary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-column {
|
||||
@@ -844,6 +733,7 @@ const updateThemeClass = () => {
|
||||
.table-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
@@ -914,6 +804,7 @@ const updateThemeClass = () => {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
@@ -923,6 +814,7 @@ const updateThemeClass = () => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: var(--bg-card);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
@@ -944,6 +836,7 @@ const updateThemeClass = () => {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px 24px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.events-container {
|
||||
@@ -1029,6 +922,7 @@ const updateThemeClass = () => {
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border-left: 1px solid var(--border-primary);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.inspector-content {
|
||||
@@ -1042,6 +936,7 @@ const updateThemeClass = () => {
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--border-secondary);
|
||||
background: var(--bg-card);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.inspector-title {
|
||||
@@ -1057,6 +952,8 @@ const updateThemeClass = () => {
|
||||
.inspector-body {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
@@ -1244,51 +1141,6 @@ const updateThemeClass = () => {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-tertiary);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--border-hover);
|
||||
}
|
||||
|
||||
/* 拖拽时的视觉反馈 */
|
||||
.resize-handle-vertical:active,
|
||||
.resize-handle-horizontal:active {
|
||||
background-color: var(--accent-primary) !important;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 1200px) {
|
||||
.search-area {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保内容不会溢出 */
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.min-w-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 自定义Naive UI组件样式 */
|
||||
:deep(.n-input .n-input__input-el) {
|
||||
background: var(--bg-card);
|
||||
@@ -1314,91 +1166,20 @@ const updateThemeClass = () => {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
:deep(.n-button--primary-type) {
|
||||
background: var(--accent-primary);
|
||||
border-color: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.n-button--primary-type:hover) {
|
||||
background: #1d4ed8;
|
||||
border-color: #1d4ed8;
|
||||
}
|
||||
|
||||
:deep(.n-button--info-type) {
|
||||
background: var(--accent-secondary);
|
||||
border-color: var(--accent-secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.n-button--info-type:hover) {
|
||||
background: #0891b2;
|
||||
border-color: #0891b2;
|
||||
}
|
||||
|
||||
:deep(.n-button--warning-type) {
|
||||
background: var(--accent-warning);
|
||||
border-color: var(--accent-warning);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.n-button--warning-type:hover) {
|
||||
background: #d97706;
|
||||
border-color: #d97706;
|
||||
}
|
||||
|
||||
/* 主题切换过渡效果 */
|
||||
* {
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease,
|
||||
color 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* 动态主题类切换 */
|
||||
.app-container {
|
||||
&.dark-theme {
|
||||
/* 暗色主题时的额外样式 */
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 1200px) {
|
||||
.search-area {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
&:not(.dark-theme) {
|
||||
/* 亮色主题时的额外样式 */
|
||||
.chart-bar {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
box-shadow: 0 0 8px rgba(34, 197, 94, 0.4);
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
.detail-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 主题切换过渡效果 - 只在页面加载完成后启用 */
|
||||
body.theme-ready * {
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease,
|
||||
color 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* 初始加载时禁用所有过渡 */
|
||||
body:not(.theme-ready) * {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* 针对拖拽操作的过渡保持独立 */
|
||||
.resize-handle-vertical,
|
||||
.resize-handle-horizontal,
|
||||
.table-row,
|
||||
.stat-card,
|
||||
.action-item,
|
||||
.event-item {
|
||||
transition: var(--transition) !important;
|
||||
/* 拖拽时的视觉反馈 */
|
||||
.resize-handle-vertical:active,
|
||||
.resize-handle-horizontal:active {
|
||||
background-color: var(--accent-primary) !important;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user