A:提交部分样式调整代码

This commit is contained in:
jimmy committed 2025-06-23 18:51:59 +08:00
1 parent 329e4f066a
commit ca73e07323
7 files changed
+1930 -292

No files matched your search

+8 -1
View File
@@ -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>
+356
View File
@@ -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>
+751 -6
View File
@@ -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>
+60 -279
View File
@@ -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>