A:Demo 基本完成
This commit is contained in:
1 parent
307df36d20
commit
54a2b0edb8
13 files changed
+475
-116
No files matched your search
@@ -2,7 +2,7 @@
|
||||
# 用于GitHub Pages等在线演示部署
|
||||
|
||||
# 启用演示模式
|
||||
VITE_DEMO_MODE=true
|
||||
VITE_DEMO_MODE=false
|
||||
|
||||
# 演示模式下不需要API地址
|
||||
VITE_APP_API_URL=
|
||||
|
||||
@@ -161,9 +161,6 @@ const getLayout = (route) => {
|
||||
<n-loading-bar-provider>
|
||||
<n-global-style />
|
||||
|
||||
<!-- 演示模式横幅 -->
|
||||
<DemoBanner v-if="isDemoMode" position="top" :persistent="true" />
|
||||
|
||||
<!-- 演示模式指示器 -->
|
||||
<DemoModeIndicator
|
||||
v-if="isDemoMode"
|
||||
|
||||
@@ -1,60 +1,83 @@
|
||||
<template>
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-left">
|
||||
<div class="app-title">
|
||||
<div class="app-icon">
|
||||
<span>N</span>
|
||||
<div class="title-bar-wrapper">
|
||||
<!-- 演示模式横幅 -->
|
||||
<div v-if="isDemoMode" class="demo-banner-compact">
|
||||
<div class="demo-content">
|
||||
<div class="demo-info">
|
||||
<n-icon :component="InformationCircleOutline" size="18" class="demo-icon" />
|
||||
<span class="demo-text">🎯 在线演示版本 - 体验完整功能</span>
|
||||
<span class="demo-badge">DEMO</span>
|
||||
</div>
|
||||
<div class="demo-actions">
|
||||
<n-button size="tiny" type="primary" @click="handleDownload">
|
||||
<template #icon>
|
||||
<n-icon :component="DownloadOutline" />
|
||||
</template>
|
||||
下载
|
||||
</n-button>
|
||||
</div>
|
||||
<span class="app-name">NetVisor</span>
|
||||
</div>
|
||||
|
||||
<div class="view-switcher">
|
||||
<n-tabs
|
||||
type="segment"
|
||||
class="n-tab-group-set"
|
||||
animated
|
||||
:value="activeTab"
|
||||
@update:value="handleTabChange"
|
||||
>
|
||||
<n-tab
|
||||
class="n-tab-item-set"
|
||||
v-for="tab in tabConfig"
|
||||
:key="tab.name"
|
||||
:name="tab.name"
|
||||
:tab="tab.label"
|
||||
/>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="title-bar-right">
|
||||
<div class="status-indicator" :class="statusClass">
|
||||
<div class="status-dot"></div>
|
||||
<span>{{ statusText }}</span>
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-left">
|
||||
<div class="app-title">
|
||||
<div class="app-icon">
|
||||
<span>N</span>
|
||||
</div>
|
||||
<span class="app-name">NetVisor</span>
|
||||
</div>
|
||||
|
||||
<div class="view-switcher">
|
||||
<n-tabs
|
||||
type="segment"
|
||||
class="n-tab-group-set"
|
||||
animated
|
||||
:value="activeTab"
|
||||
@update:value="handleTabChange"
|
||||
>
|
||||
<n-tab
|
||||
class="n-tab-item-set"
|
||||
v-for="tab in tabConfig"
|
||||
:key="tab.name"
|
||||
:name="tab.name"
|
||||
:tab="tab.label"
|
||||
/>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-button circle size="small" quaternary @click="openSettings">
|
||||
<template #icon>
|
||||
<n-icon :component="SettingsOutline" />
|
||||
</template>
|
||||
</n-button>
|
||||
<div class="title-bar-right">
|
||||
<div class="status-indicator" :class="statusClass">
|
||||
<div class="status-dot"></div>
|
||||
<span>{{ statusText }}</span>
|
||||
</div>
|
||||
|
||||
<n-button circle size="small" quaternary @click="toggleDarkMode">
|
||||
<template #icon>
|
||||
<n-icon :component="isDark ? SunnyOutline : MoonOutline" />
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button circle size="small" quaternary @click="openSettings">
|
||||
<template #icon>
|
||||
<n-icon :component="SettingsOutline" />
|
||||
</template>
|
||||
</n-button>
|
||||
|
||||
<n-button circle size="small" quaternary @click="toggleDarkMode">
|
||||
<template #icon>
|
||||
<n-icon :component="isDark ? SunnyOutline : MoonOutline" />
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { NButton, NIcon, NTabs, NTab } from 'naive-ui'
|
||||
import { SettingsOutline, MoonOutline, SunnyOutline } from '@vicons/ionicons5'
|
||||
import { SettingsOutline, MoonOutline, SunnyOutline, InformationCircleOutline, DownloadOutline } from '@vicons/ionicons5'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
import { WebSocketState } from '@/types/websocket'
|
||||
|
||||
// region 路由的激活和绑定
|
||||
@@ -134,6 +157,15 @@ const openSettings = () => {
|
||||
console.log('打开设置')
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region 演示模式
|
||||
const message = useMessage()
|
||||
const isDemoMode = computed(() => environmentDetector.shouldUseMockData())
|
||||
|
||||
const handleDownload = () => {
|
||||
message.info('完整版本下载功能即将开放,敬请期待!')
|
||||
}
|
||||
// endregion
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -184,9 +216,113 @@ const openSettings = () => {
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
/* 标题栏包装器 */
|
||||
.title-bar-wrapper {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 演示模式紧凑横条 */
|
||||
.demo-banner-compact {
|
||||
background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #ec4899 100%);
|
||||
color: white;
|
||||
padding: 8px 24px;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 添加动态背景效果 */
|
||||
.demo-banner-compact::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
||||
animation: shine 3s infinite;
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
0% { left: -100%; }
|
||||
50% { left: 100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.demo-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.demo-icon {
|
||||
color: #fbbf24;
|
||||
animation: pulse-icon 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-icon {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
.demo-text {
|
||||
color: rgba(255, 255, 255, 0.98);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.demo-badge {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
animation: glow 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
from { box-shadow: 0 0 5px rgba(255, 255, 255, 0.3); }
|
||||
to { box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); }
|
||||
}
|
||||
|
||||
.demo-actions .n-button {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.demo-actions .n-button:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 顶部标题栏 */
|
||||
.title-bar {
|
||||
height: 100%;
|
||||
height: 60px;
|
||||
min-height: 60px;
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
background: var(--bg-overlay);
|
||||
@@ -195,9 +331,6 @@ const openSettings = () => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.title-bar-left {
|
||||
|
||||
@@ -30,9 +30,11 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 顶部 bar 高度 */
|
||||
/* 顶部 bar 高度 - 根据演示模式动态调整 */
|
||||
.title-bar-style {
|
||||
height: 60px;
|
||||
/* 标题栏本身60px,演示模式横幅约32px */
|
||||
height: auto;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
/* 主容器样式 */
|
||||
@@ -42,16 +44,15 @@ onMounted(() => {
|
||||
color: var(--text-primary);
|
||||
user-select: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main {
|
||||
min-height: calc(100vh - 60px); /* 减去标题栏高度 */
|
||||
height: calc(100vh - 60px);
|
||||
width: 100vw;
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
flex: 1;
|
||||
min-height: 0; /* 允许弹性收缩 */
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 滚动条样式 - 使用 CSS 变量 */
|
||||
|
||||
@@ -124,13 +124,13 @@ export const useApplicationStore = defineStore('applicationInfoSub', () => {
|
||||
/**
|
||||
* 订阅信息
|
||||
*/
|
||||
function subscribe() {
|
||||
console.log('[ApplicationStore] 订阅应用程序信息')
|
||||
async function subscribe() {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ApplicationStore] 订阅应用程序信息`)
|
||||
|
||||
// 检查演示模式
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
console.log('[ApplicationStore] 演示模式:使用模拟数据')
|
||||
loadDemoData()
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ApplicationStore] 演示模式:使用模拟数据`)
|
||||
await loadDemoData() // 等待初始数据加载完成
|
||||
startDemoDataUpdates()
|
||||
return
|
||||
}
|
||||
@@ -159,7 +159,7 @@ export const useApplicationStore = defineStore('applicationInfoSub', () => {
|
||||
async function loadDemoData() {
|
||||
try {
|
||||
const applications = await dataSourceAdapter.getApplicationList()
|
||||
console.log('[ApplicationStore] 加载演示数据:', applications.length, '个应用程序')
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ApplicationStore] 加载演示数据:`, applications.length, '个应用程序')
|
||||
appInfos.value = applications as ApplicationType[]
|
||||
} catch (error) {
|
||||
console.error('[ApplicationStore] 加载演示数据失败:', error)
|
||||
|
||||
@@ -15,14 +15,23 @@ export const useProcessStore = defineStore('processInfoSub', () => {
|
||||
|
||||
}
|
||||
|
||||
function subscribe() {
|
||||
console.log('[ProcessStore] 订阅进程信息')
|
||||
async function subscribe() {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 🚀 开始订阅进程信息`)
|
||||
|
||||
// 检查演示模式
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
console.log('[ProcessStore] 演示模式:使用模拟数据')
|
||||
loadDemoData()
|
||||
startDemoDataUpdates()
|
||||
const shouldUseMockData = environmentDetector.shouldUseMockData()
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 🔍 环境检测结果:`, shouldUseMockData)
|
||||
|
||||
if (shouldUseMockData) {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 🎭 演示模式:使用模拟数据`)
|
||||
try {
|
||||
await loadDemoData() // 等待初始数据加载完成
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] ✅ 初始数据加载完成`)
|
||||
startDemoDataUpdates()
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] ⚡ 实时更新已启动`)
|
||||
} catch (error) {
|
||||
console.error(`[${new Date().toLocaleTimeString()}] [ProcessStore] ❌ 演示数据加载失败:`, error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,8 +58,14 @@ export const useProcessStore = defineStore('processInfoSub', () => {
|
||||
async function loadDemoData() {
|
||||
try {
|
||||
const processes = await dataSourceAdapter.getProcessList()
|
||||
console.log('[ProcessStore] 加载演示数据:', processes.length, '个进程')
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 加载演示数据:`, processes.length, '个进程')
|
||||
|
||||
// 立即设置数据,不等待
|
||||
processInfos.value = processes
|
||||
|
||||
// 验证连接数据
|
||||
const totalConnections = processes.reduce((sum, p) => sum + (p.connections?.length || 0), 0)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 总连接数:`, totalConnections)
|
||||
} catch (error) {
|
||||
console.error('[ProcessStore] 加载演示数据失败:', error)
|
||||
}
|
||||
@@ -58,22 +73,30 @@ export const useProcessStore = defineStore('processInfoSub', () => {
|
||||
|
||||
// 启动演示数据更新
|
||||
function startDemoDataUpdates() {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 🎬 启动实时更新,当前进程数:`, processInfos.value.length)
|
||||
|
||||
const subscriptionId = dataSourceAdapter.subscribeRealtimeData((data) => {
|
||||
if (data.type === 'process_update' && data.data) {
|
||||
// 检查当前数据状态
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 📊 实时更新前进程数:`, processInfos.value.length)
|
||||
|
||||
// 随机更新一个进程的数据
|
||||
const processData = data.data as ProcessType
|
||||
const index = processInfos.value.findIndex(p => p.processId === processData.processId)
|
||||
if (index !== -1) {
|
||||
processInfos.value[index] = processData
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 🔄 更新现有进程:`, processData.processName)
|
||||
} else {
|
||||
// 如果进程不存在,可能是新进程,添加到列表
|
||||
processInfos.value.push(processData)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] ➕ 添加新进程:`, processData.processName)
|
||||
}
|
||||
console.log('[ProcessStore] 实时更新进程数据:', processData.processName)
|
||||
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] 📊 实时更新后进程数:`, processInfos.value.length)
|
||||
}
|
||||
}, 3000) // 每3秒更新一次
|
||||
|
||||
console.log('[ProcessStore] 演示模式实时更新已启动:', subscriptionId)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [ProcessStore] ⚡ 演示模式实时更新已启动:`, subscriptionId)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -184,9 +184,10 @@ class DataSourceAdapter {
|
||||
// ================= 模拟数据实现 =================
|
||||
|
||||
private async getMockProcessList(): Promise<ProcessType[]> {
|
||||
// 模拟网络延迟
|
||||
await this.delay(100, 300)
|
||||
return mockDataService.generateProcessList(25)
|
||||
// 演示模式立即返回数据,不模拟延迟
|
||||
const processes = mockDataService.generateProcessList(8) // 减少到8个进程,更符合实际
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [DataAdapter] 生成演示进程数据:`, processes.length, '个进程')
|
||||
return processes
|
||||
}
|
||||
|
||||
private async getMockConnectionList(): Promise<ConnectionInfo[]> {
|
||||
@@ -202,8 +203,9 @@ class DataSourceAdapter {
|
||||
}
|
||||
|
||||
private async getMockApplicationList(): Promise<any[]> {
|
||||
await this.delay(100, 200)
|
||||
return mockDataService.generateApplicationList(15)
|
||||
const applications = mockDataService.generateApplicationList(15)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [DataAdapter] 生成演示应用数据:`, applications.length, '个应用')
|
||||
return applications
|
||||
}
|
||||
|
||||
private async getMockFirewallStatus(): Promise<FirewallStatus> {
|
||||
|
||||
@@ -53,15 +53,39 @@ class MockDataService {
|
||||
|
||||
private generateProcessName(): string {
|
||||
const processes = [
|
||||
'chrome.exe', 'firefox.exe', 'edge.exe', 'notepad.exe', 'explorer.exe',
|
||||
'chrome.exe', 'firefox.exe', 'msedge.exe', 'notepad.exe', 'explorer.exe',
|
||||
'code.exe', 'WeChat.exe', 'QQ.exe', 'Spotify.exe', 'Discord.exe',
|
||||
'steam.exe', 'obs64.exe', 'Photoshop.exe', 'winrar.exe', 'TeamViewer.exe',
|
||||
'node.exe', 'python.exe', 'java.exe', 'nginx.exe', 'httpd.exe',
|
||||
'svchost.exe', 'system', 'csrss.exe', 'dwm.exe', 'conhost.exe'
|
||||
'svchost.exe', 'csrss.exe', 'dwm.exe', 'conhost.exe'
|
||||
]
|
||||
return this.randomChoice(processes)
|
||||
}
|
||||
|
||||
// 根据应用名称生成相关的进程名
|
||||
getRelatedProcessName(appName: string): string {
|
||||
const appProcessMap: { [key: string]: string[] } = {
|
||||
'chrome': ['chrome.exe', 'chrome_crashpad_handler.exe'],
|
||||
'google chrome': ['chrome.exe', 'chrome_crashpad_handler.exe'],
|
||||
'firefox': ['firefox.exe', 'firefox_helper.exe'],
|
||||
'mozilla firefox': ['firefox.exe', 'firefox_helper.exe'],
|
||||
'edge': ['msedge.exe', 'msedge_proxy.exe'],
|
||||
'microsoft edge': ['msedge.exe', 'msedge_proxy.exe'],
|
||||
'code': ['code.exe', 'code_helper.exe'],
|
||||
'visual studio code': ['code.exe', 'code_helper.exe'],
|
||||
'wechat': ['WeChat.exe', 'WeChatAppEx.exe'],
|
||||
'qq': ['QQ.exe', 'QQExternal.exe'],
|
||||
'discord': ['Discord.exe', 'discord_voice.exe'],
|
||||
'steam': ['steam.exe', 'steamwebhelper.exe'],
|
||||
'spotify': ['Spotify.exe', 'SpotifyWebHelper.exe']
|
||||
}
|
||||
|
||||
const normalizedAppName = appName.toLowerCase().replace(/\s+/g, ' ')
|
||||
const relatedProcesses = appProcessMap[normalizedAppName] || [this.generateProcessName()]
|
||||
|
||||
return this.randomChoice(relatedProcesses)
|
||||
}
|
||||
|
||||
private generateDomainName(): string {
|
||||
const domains = [
|
||||
'www.google.com', 'www.baidu.com', 'api.github.com', 'cdn.jsdelivr.net',
|
||||
@@ -112,7 +136,7 @@ class MockDataService {
|
||||
generateProcessInfo(): ProcessType {
|
||||
const processName = this.generateProcessName()
|
||||
const startTime = new Date(Date.now() - this.randomInt(0, 86400000)) // 过去24小时内
|
||||
const connectionCount = this.randomInt(0, 20)
|
||||
const connectionCount = this.randomInt(2, 15) // 确保每个进程至少有2个连接
|
||||
const connections: ConnectionInfo[] = []
|
||||
|
||||
const pid = this.processId++
|
||||
|
||||
@@ -206,7 +206,7 @@ defineExpose({
|
||||
|
||||
// 计算图表配置
|
||||
const chartOption = computed<EChartsOption>(() => {
|
||||
if (!props.data || props.loading) {
|
||||
if (!props.data || props.loading || !props.data.appInfo || !props.data.topConnections) {
|
||||
return {
|
||||
title: {
|
||||
text: '暂无数据',
|
||||
@@ -222,6 +222,21 @@ const chartOption = computed<EChartsOption>(() => {
|
||||
|
||||
const { appInfo, topConnections } = props.data
|
||||
|
||||
// 验证必要数据
|
||||
if (!appInfo?.appId) {
|
||||
return {
|
||||
title: {
|
||||
text: '应用信息缺失',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
textStyle: {
|
||||
color: 'var(--text-muted)',
|
||||
fontSize: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建节点和边
|
||||
const nodes: any[] = []
|
||||
const links: any[] = []
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="modal-header">
|
||||
<div class="header-left">
|
||||
<img
|
||||
v-if="analysisData?.appInfo.icon"
|
||||
v-if="analysisData?.appInfo?.icon"
|
||||
:src="'data:image/png;base64,' + analysisData.appInfo.icon"
|
||||
class="app-icon"
|
||||
@error="handleIconError"
|
||||
@@ -13,10 +13,10 @@
|
||||
<n-icon v-else :component="DesktopOutline" class="app-icon-fallback" />
|
||||
|
||||
<div class="header-info">
|
||||
<h3 class="app-name">{{ analysisData?.appInfo.name || 'Unknown Application' }}</h3>
|
||||
<h3 class="app-name">{{ analysisData?.appInfo?.name || 'Unknown Application' }}</h3>
|
||||
<p class="app-details">
|
||||
{{ analysisData?.appInfo.company || 'Unknown Company' }}
|
||||
<span v-if="analysisData?.appInfo.version"
|
||||
{{ analysisData?.appInfo?.company || 'Unknown Company' }}
|
||||
<span v-if="analysisData?.appInfo?.version"
|
||||
>• v{{ analysisData.appInfo.version }}</span
|
||||
>
|
||||
</p>
|
||||
@@ -40,30 +40,30 @@
|
||||
<!-- 统计概览卡片 -->
|
||||
<div class="stats-overview">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ formatBytes(analysisData?.summary.totalTraffic || 0) }}</div>
|
||||
<div class="stat-value">{{ formatBytes(analysisData?.summary?.totalTraffic || 0) }}</div>
|
||||
<div class="stat-label">总流量</div>
|
||||
<div class="stat-trend">
|
||||
<span class="upload">↑ {{ formatBytes(analysisData?.summary.totalUpload || 0) }}</span>
|
||||
<span class="upload">↑ {{ formatBytes(analysisData?.summary?.totalUpload || 0) }}</span>
|
||||
<span class="download"
|
||||
>↓ {{ formatBytes(analysisData?.summary.totalDownload || 0) }}</span
|
||||
>↓ {{ formatBytes(analysisData?.summary?.totalDownload || 0) }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ analysisData?.summary.totalConnections || 0 }}</div>
|
||||
<div class="stat-value">{{ analysisData?.summary?.totalConnections || 0 }}</div>
|
||||
<div class="stat-label">总连接数</div>
|
||||
<div class="stat-extra">{{ analysisData?.summary.uniqueRemoteIPs || 0 }} 个远程IP</div>
|
||||
<div class="stat-extra">{{ analysisData?.summary?.uniqueRemoteIPs || 0 }} 个远程IP</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ analysisData?.summary.uniqueRemotePorts || 0 }}</div>
|
||||
<div class="stat-value">{{ analysisData?.summary?.uniqueRemotePorts || 0 }}</div>
|
||||
<div class="stat-label">使用端口</div>
|
||||
<div class="stat-extra">{{ timeRangeText }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{{ analysisData?.portAnalysis.length || 0 }}</div>
|
||||
<div class="stat-value">{{ analysisData?.portAnalysis?.length || 0 }}</div>
|
||||
<div class="stat-label">活跃端口</div>
|
||||
<div class="stat-extra">多协议通信</div>
|
||||
</div>
|
||||
@@ -116,7 +116,7 @@
|
||||
</h4>
|
||||
<div class="section-controls">
|
||||
<span class="connection-count"
|
||||
>{{ analysisData?.topConnections.length || 0 }} 个连接</span
|
||||
>{{ analysisData?.topConnections?.length || 0 }} 个连接</span
|
||||
>
|
||||
<n-button size="small" @click="refreshData">
|
||||
<template #icon>
|
||||
@@ -209,7 +209,7 @@
|
||||
</h4>
|
||||
<div class="section-controls">
|
||||
<span class="data-count"
|
||||
>共 {{ analysisData?.topConnections.length || 0 }} 条记录</span
|
||||
>共 {{ analysisData?.topConnections?.length || 0 }} 条记录</span
|
||||
>
|
||||
<n-button size="small" @click="refreshData">
|
||||
<template #icon>
|
||||
|
||||
@@ -102,6 +102,7 @@ import { httpClient } from '@/utils/http'
|
||||
import { SubscriptionInfo } from '@/types/response'
|
||||
import { convertFileSize } from '@/utils/fileUtil'
|
||||
import { FILE_SIZE_UNIT_ENUM } from '@/constants/enums'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
|
||||
// Props
|
||||
defineProps<{
|
||||
@@ -118,6 +119,18 @@ const { trafficHistory } = storeToRefs(trafficStore)
|
||||
|
||||
// 订阅应用详情
|
||||
const subscribeToAppDetails = (appPath: string) => {
|
||||
// 演示模式:直接使用选中的应用作为详情
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [演示模式] 显示应用详情: ${appPath}`)
|
||||
if (selectedApp.value) {
|
||||
// 立即显示详情,不模拟延迟
|
||||
applicationStore.setInspectingAppDetails(selectedApp.value)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [演示模式] 应用详情已设置:`, selectedApp.value.name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 真实模式:使用WebSocket订阅
|
||||
if (!isOpen.value) return
|
||||
console.log(`Subscribing to app details for: ${appPath}`)
|
||||
const subInfo = {
|
||||
@@ -132,6 +145,14 @@ const subscribeToAppDetails = (appPath: string) => {
|
||||
|
||||
// 取消订阅应用详情
|
||||
const unsubscribeFromAppDetails = () => {
|
||||
// 演示模式:直接清空详情
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
console.log('[演示模式] 清空应用详情')
|
||||
applicationStore.setInspectingAppDetails(null)
|
||||
return
|
||||
}
|
||||
|
||||
// 真实模式:使用WebSocket取消订阅
|
||||
if (!isOpen.value) return
|
||||
console.log('Unsubscribing from app details')
|
||||
const subInfo: SubscriptionInfo = {
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
</div>
|
||||
<div class="filter-stats">
|
||||
<span class="stats-text">
|
||||
已选择 {{ selectedProcessIds.size }} / {{ filteredProcesses.length }} 个进程
|
||||
已选择 {{ selectedProcessIds.size }} / {{ displayProcesses.length }} 个进程
|
||||
{{ selectedProcessIds.size > 0 ? `· ${displayConnections.length} 个连接` : '' }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -367,6 +367,8 @@ import { useTrafficStore } from '@/stores/trafficStore'
|
||||
import type { ProcessType, ConnectionInfo, IPEndPoint } from '@/types/process'
|
||||
import { ConnectionState, FILE_SIZE_UNIT_ENUM } from '@/constants/enums'
|
||||
import { convertFileSize } from '@/utils/fileUtil'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
import { mockDataService } from '@/utils/mockDataService'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
|
||||
const webSocketStore = useWebSocketStore()
|
||||
@@ -383,8 +385,32 @@ const { filterText, isFiltering, filteredProcesses } = storeToRefs(filterStore)
|
||||
|
||||
const trafficStore = useTrafficStore()
|
||||
|
||||
// 订阅进程信息
|
||||
processStore.subscribe()
|
||||
// 立即输出基础日志
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🔥 组件脚本开始执行`)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🌍 当前模式:`, import.meta.env.MODE)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🎭 DEMO模式:`, import.meta.env.VITE_DEMO_MODE)
|
||||
|
||||
// 为应用生成相关的进程数据
|
||||
const generateRelatedProcesses = async (appName: string): Promise<ProcessType[]> => {
|
||||
const processes: ProcessType[] = []
|
||||
|
||||
// 生成2-3个与应用相关的进程
|
||||
const processCount = Math.random() > 0.5 ? 2 : 3
|
||||
|
||||
for (let i = 0; i < processCount; i++) {
|
||||
const relatedProcessName = mockDataService.getRelatedProcessName(appName)
|
||||
const process = mockDataService.generateProcessInfo()
|
||||
|
||||
// 修改进程名为相关名称
|
||||
process.processName = relatedProcessName
|
||||
process.mainModuleName = relatedProcessName
|
||||
process.mainModulePath = `C:\\Program Files\\${appName}\\${relatedProcessName}`
|
||||
|
||||
processes.push(process)
|
||||
}
|
||||
|
||||
return processes
|
||||
}
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
@@ -423,9 +449,9 @@ const virtualListHeight = computed(() => {
|
||||
|
||||
// 计算统计数据
|
||||
const stats = computed(() => {
|
||||
const activeProcesses = filteredProcesses.value.filter((p) => !p.hasExited)
|
||||
const totalConnections = filteredProcesses.value.reduce((sum, p) => sum + p.connections.length, 0)
|
||||
const activeConnections = filteredProcesses.value.reduce(
|
||||
const activeProcesses = displayProcesses.value.filter((p) => !p.hasExited)
|
||||
const totalConnections = displayProcesses.value.reduce((sum, p) => sum + p.connections.length, 0)
|
||||
const activeConnections = displayProcesses.value.reduce(
|
||||
(sum, p) => sum + p.connections.filter((c) => c.isActive).length,
|
||||
0,
|
||||
)
|
||||
@@ -434,7 +460,7 @@ const stats = computed(() => {
|
||||
|
||||
return {
|
||||
activeProcesses: activeProcesses.length,
|
||||
totalProcesses: filteredProcesses.value.length,
|
||||
totalProcesses: displayProcesses.value.length,
|
||||
totalConnections,
|
||||
activeConnections,
|
||||
totalUploadSpeed,
|
||||
@@ -473,22 +499,53 @@ const stopDataCollection = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 演示模式下根据选中应用过滤进程
|
||||
const displayProcesses = computed(() => {
|
||||
if (environmentDetector.shouldUseMockData() && selectedApp.value) {
|
||||
// 演示模式:根据选中应用过滤相关进程
|
||||
const appName = selectedApp.value.name || selectedApp.value.processName
|
||||
const relatedProcesses = filteredProcesses.value.filter(process => {
|
||||
// 进程名包含应用名,或者是常见的系统进程
|
||||
const processName = process.processName.toLowerCase()
|
||||
const targetApp = appName.toLowerCase()
|
||||
|
||||
return processName.includes(targetApp) ||
|
||||
processName.includes(targetApp.replace('.exe', '')) ||
|
||||
['svchost.exe', 'system'].includes(processName) // 保留一些系统进程
|
||||
})
|
||||
|
||||
// 如果没有找到相关进程,返回前2个进程作为示例
|
||||
if (relatedProcesses.length === 0) {
|
||||
return filteredProcesses.value.slice(0, 2)
|
||||
}
|
||||
|
||||
// 最多显示3个相关进程
|
||||
return relatedProcesses.slice(0, 3)
|
||||
}
|
||||
|
||||
// 非演示模式:使用所有过滤后的进程
|
||||
return filteredProcesses.value
|
||||
})
|
||||
|
||||
// 获取所有连接列表(统一显示)
|
||||
const allConnections = computed(() => {
|
||||
const connections: unknown[] = []
|
||||
|
||||
filteredProcesses.value.forEach((process) => {
|
||||
process.connections.forEach((connection, index) => {
|
||||
connections.push({
|
||||
key: `${process.processId}-${index}`,
|
||||
...connection,
|
||||
processId: process.processId,
|
||||
processName: process.processName,
|
||||
processInfo: process, // 添加完整的进程信息
|
||||
displayProcesses.value.forEach((process) => {
|
||||
if (process.connections && Array.isArray(process.connections)) {
|
||||
process.connections.forEach((connection, index) => {
|
||||
connections.push({
|
||||
key: `${process.processId}-${index}`,
|
||||
...connection,
|
||||
processId: process.processId,
|
||||
processName: process.processName,
|
||||
processInfo: process, // 添加完整的进程信息
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 计算连接列表: ${displayProcesses.value.length} 个进程, ${connections.length} 个连接`)
|
||||
return connections
|
||||
})
|
||||
|
||||
@@ -524,7 +581,7 @@ const displayConnections = computed(() => {
|
||||
|
||||
// 获取进程选择器显示的进程信息
|
||||
const processSelectors = computed(() => {
|
||||
return filteredProcesses.value.map(process => ({
|
||||
return displayProcesses.value.map(process => ({
|
||||
...process,
|
||||
isSelected: selectedProcessIds.value.has(process.processId),
|
||||
connectionCount: process.connections.length,
|
||||
@@ -652,7 +709,7 @@ const formatUptime = (startTime: Date): string => {
|
||||
// 全选/清空进程
|
||||
const toggleAllProcesses = () => {
|
||||
if (selectedProcessIds.value.size === 0) {
|
||||
filteredProcesses.value.forEach(p => selectedProcessIds.value.add(p.processId))
|
||||
displayProcesses.value.forEach(p => selectedProcessIds.value.add(p.processId))
|
||||
} else {
|
||||
selectedProcessIds.value.clear()
|
||||
}
|
||||
@@ -816,23 +873,107 @@ const updateMainViewHeight = () => {
|
||||
}
|
||||
|
||||
// 监听选中应用的变化
|
||||
watch(selectedApp, (newVal, oldVal) => {
|
||||
processStore.clear()
|
||||
watch(selectedApp, async (newVal, oldVal) => {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🔄 应用切换:`, newVal?.name)
|
||||
|
||||
if (!environmentDetector.shouldUseMockData()) {
|
||||
// 非演示模式:清空进程数据
|
||||
processStore.clear()
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🗑️ 清空进程数据(非演示模式)`)
|
||||
} else if (newVal) {
|
||||
// 演示模式:为新选中的应用生成相关进程数据
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🎭 演示模式为应用生成相关进程:`, newVal.name)
|
||||
|
||||
try {
|
||||
// 生成与应用相关的进程数据
|
||||
const relatedProcesses = await generateRelatedProcesses(newVal.name)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] ✅ 生成了${relatedProcesses.length}个相关进程`)
|
||||
|
||||
// 替换进程数据
|
||||
processStore.clear()
|
||||
processInfos.value.push(...relatedProcesses)
|
||||
} catch (error) {
|
||||
console.error(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] ❌ 生成相关进程失败:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
filterStore.clearFilter()
|
||||
selectedProcessIds.value.clear()
|
||||
console.log(newVal.id, '==========')
|
||||
|
||||
// 演示模式下应用切换后重新自动选择进程
|
||||
if (environmentDetector.shouldUseMockData() && newVal) {
|
||||
// 等待下一个tick确保数据更新完成
|
||||
await nextTick()
|
||||
|
||||
// 自动选择新生成的进程
|
||||
const newProcesses = processInfos.value.filter(p => p.connections && p.connections.length > 0)
|
||||
if (newProcesses.length > 0) {
|
||||
newProcesses.forEach(p => selectedProcessIds.value.add(p.processId))
|
||||
selectedProcessIds.value = new Set(selectedProcessIds.value)
|
||||
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🎯 应用切换后自动选择进程:`, {
|
||||
进程数: newProcesses.length,
|
||||
进程名: newProcesses.map(p => p.processName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🎯 应用切换完成:`, newVal?.id)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 🚀 组件已挂载,开始初始化`)
|
||||
|
||||
nextTick(() => {
|
||||
updateMainViewHeight()
|
||||
})
|
||||
|
||||
window.addEventListener('resize', updateMainViewHeight)
|
||||
|
||||
// 订阅进程信息 - 在组件挂载后执行
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 💥 开始订阅进程信息`)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 📊 当前进程数量:`, processInfos.value.length)
|
||||
|
||||
try {
|
||||
await processStore.subscribe()
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] ✅ 进程信息订阅完成,当前数量:`, processInfos.value.length)
|
||||
} catch (error) {
|
||||
console.error(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] ❌ 进程信息订阅失败:`, error)
|
||||
}
|
||||
|
||||
// 启动数据收集
|
||||
startDataCollection()
|
||||
|
||||
// 演示模式下自动选择进程
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
// 监听显示的进程数据变化,自动选择进程
|
||||
let stopWatcher: (() => void) | null = null
|
||||
|
||||
stopWatcher = watch(
|
||||
displayProcesses,
|
||||
(newProcesses) => {
|
||||
if (newProcesses.length > 0 && selectedProcessIds.value.size === 0) {
|
||||
// 自动选择所有显示的进程(已经过滤过,数量不多)
|
||||
const processesToSelect = newProcesses.filter(p => p.connections && p.connections.length > 0)
|
||||
|
||||
processesToSelect.forEach(p => selectedProcessIds.value.add(p.processId))
|
||||
selectedProcessIds.value = new Set(selectedProcessIds.value)
|
||||
|
||||
const totalConnections = processesToSelect.reduce((sum, p) => sum + p.connections.length, 0)
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorMainPanel] 演示模式自动选择进程:`, {
|
||||
进程数: processesToSelect.length,
|
||||
进程名: processesToSelect.map(p => p.processName),
|
||||
总连接数: totalConnections
|
||||
})
|
||||
|
||||
// 只执行一次,然后停止监听
|
||||
if (stopWatcher) stopWatcher()
|
||||
}
|
||||
},
|
||||
{ immediate: true, flush: 'sync' }
|
||||
)
|
||||
}
|
||||
|
||||
watch(
|
||||
[isOpen, selectedApp],
|
||||
([newValue, newValue1]) => {
|
||||
|
||||
@@ -123,9 +123,11 @@ const resetLayout = () => {
|
||||
const useApplicationStore1 = useApplicationStore()
|
||||
|
||||
// 键盘快捷键
|
||||
onMounted(() => {
|
||||
// 创建事件绑定
|
||||
useApplicationStore1.subscribe()
|
||||
onMounted(async () => {
|
||||
// 创建事件绑定 - 确保按正确顺序加载数据
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorView] 开始订阅应用程序信息`)
|
||||
await useApplicationStore1.subscribe()
|
||||
console.log(`[${new Date().toLocaleTimeString()}] [MonitorView] 应用程序信息订阅完成`)
|
||||
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
|
||||
Reference in new issue
Block a user