A:Demo 基本完成

This commit is contained in:
jimmy committed 2025-08-17 17:55:16 +08:00
1 parent 307df36d20
commit 54a2b0edb8
13 files changed
+475 -116

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@
# 用于GitHub Pages等在线演示部署
# 启用演示模式
VITE_DEMO_MODE=true
VITE_DEMO_MODE=false
# 演示模式下不需要API地址
VITE_APP_API_URL=
-3
View File
@@ -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"
+176 -43
View File
@@ -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) => {