A:增加体验预览
This commit is contained in:
1 parent
c0f64c3f07
commit
fbdf211e2f
22 files changed
+3206
-36
No files matched your search
@@ -0,0 +1,17 @@
|
||||
# 演示模式环境配置
|
||||
# 用于GitHub Pages等在线演示部署
|
||||
|
||||
# 启用演示模式
|
||||
VITE_DEMO_MODE=true
|
||||
|
||||
# 演示模式下不需要API地址
|
||||
VITE_APP_API_URL=
|
||||
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=NetVizor - 网络监控系统演示
|
||||
|
||||
# 演示版本标识
|
||||
VITE_APP_VERSION=demo
|
||||
|
||||
# 基础路径 (GitHub Pages可能需要)
|
||||
VITE_BASE_URL=/NetVizor/
|
||||
@@ -1,2 +1,3 @@
|
||||
VITE_MODE='development'
|
||||
VITE_APP_API_URL='http://localhost:8268/api'
|
||||
VITE_APP_API_URL='http://localhost:8268/api'
|
||||
VITE_DEMO_MODE=false
|
||||
@@ -0,0 +1,83 @@
|
||||
name: Deploy Demo to GitHub Pages
|
||||
|
||||
on:
|
||||
# 当推送到main分支时触发
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
# 允许手动触发
|
||||
workflow_dispatch:
|
||||
|
||||
# 设置GITHUB_TOKEN的权限,以允许部署到GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# 确保只有一个部署同时运行,跳过排队运行的部分
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# 构建作业
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 检测包管理器
|
||||
id: detect-package-manager
|
||||
run: |
|
||||
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
|
||||
echo "manager=yarn" >> $GITHUB_OUTPUT
|
||||
echo "command=install" >> $GITHUB_OUTPUT
|
||||
echo "runner=yarn" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
elif [ -f "${{ github.workspace }}/package.json" ]; then
|
||||
echo "manager=npm" >> $GITHUB_OUTPUT
|
||||
echo "command=ci" >> $GITHUB_OUTPUT
|
||||
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
else
|
||||
echo "找不到可锁定的包管理器依赖文件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: 设置 Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
cache: ${{ steps.detect-package-manager.outputs.manager }}
|
||||
|
||||
- name: 配置 GitHub Pages
|
||||
uses: actions/configure-pages@v4
|
||||
with:
|
||||
# 自动注入base_path到静态站点生成器的配置中,
|
||||
# 这可能会覆盖现有的值
|
||||
static_site_generator: vite
|
||||
|
||||
- name: 安装依赖
|
||||
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
|
||||
|
||||
- name: 构建演示版本
|
||||
run: ${{ steps.detect-package-manager.outputs.manager }} run build:demo
|
||||
|
||||
- name: 上传构建产物
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./dist
|
||||
|
||||
# 部署作业
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
steps:
|
||||
- name: 部署到 GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -131,10 +131,42 @@
|
||||
- `npm run lint`: 代码检查
|
||||
- `npm run format`: 代码格式化
|
||||
|
||||
## 演示模式系统
|
||||
|
||||
### 环境自动检测
|
||||
项目具备智能环境检测功能,自动切换数据源:
|
||||
- **生产环境**: 检测到WebView2时使用真实数据和桥接通信
|
||||
- **演示模式**: `VITE_DEMO_MODE=true`时使用模拟数据
|
||||
- **开发模式**: `VITE_DEMO_MODE=false`时连接开发API
|
||||
|
||||
### 演示模式特性
|
||||
- **完整UI功能**: 保留所有界面和交互功能
|
||||
- **逼真模拟数据**: 包含25个进程、40条防火墙规则、实时流量图表
|
||||
- **智能数据生成**: 模拟工作时间流量模式、地理分布、协议统计
|
||||
- **实时更新模拟**: WebSocket数据推送、图表动画、状态变化
|
||||
- **用户提示界面**: 演示横幅、状态指示器、下载引导
|
||||
|
||||
### 核心组件
|
||||
- `environmentDetector.ts`: 环境检测和数据源选择
|
||||
- `mockDataService.ts`: 模拟数据生成器
|
||||
- `dataSourceAdapter.ts`: 数据源适配器统一接口
|
||||
- `DemoBanner.vue`: 演示模式横幅组件
|
||||
- `DemoModeIndicator.vue`: 演示状态指示器
|
||||
|
||||
### 部署配置
|
||||
- **开发模式**: `npm run dev` (连接真实API)
|
||||
- **演示开发**: `npm run dev:demo` (使用模拟数据)
|
||||
- **演示构建**: `npm run build:demo` (用于GitHub Pages)
|
||||
- **自动部署**: GitHub Actions工作流自动部署到Pages
|
||||
|
||||
### 测试工具
|
||||
内置演示模式测试工具,控制台运行 `testDemoMode()` 验证功能。
|
||||
|
||||
## 开发注意事项
|
||||
- 主要使用虚拟列表组件处理大数据量
|
||||
- WebView2环境下运行,注意桥接通信
|
||||
- 支持开发时显示调试导航(IS_SHOW_GENERATE_ROUTER控制)
|
||||
- 演示模式完全独立,不影响生产功能
|
||||
- 使用TODO.md作为开发任务管理工具
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# NetVizor 演示模式部署指南
|
||||
|
||||
这个文档介绍如何部署 NetVizor 的演示版本到 GitHub Pages。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 本地演示模式开发
|
||||
|
||||
```bash
|
||||
# 启动演示模式开发服务器
|
||||
npm run dev:demo
|
||||
|
||||
# 构建演示版本
|
||||
npm run build:demo
|
||||
|
||||
# 预览演示版本
|
||||
npm run preview:demo
|
||||
```
|
||||
|
||||
### GitHub Pages 自动部署
|
||||
|
||||
1. **推送代码到 GitHub**
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Add demo mode support"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
2. **配置 GitHub Pages**
|
||||
- 前往项目的 GitHub 页面
|
||||
- 进入 Settings > Pages
|
||||
- Source 选择 "GitHub Actions"
|
||||
- 保存设置
|
||||
|
||||
3. **自动部署**
|
||||
- 推送到 `main` 分支会自动触发部署
|
||||
- 也可以在 Actions 页面手动触发部署
|
||||
|
||||
## 环境配置
|
||||
|
||||
### 演示模式环境变量
|
||||
|
||||
演示模式使用 `.env.demo` 配置文件:
|
||||
|
||||
```env
|
||||
# 启用演示模式
|
||||
VITE_DEMO_MODE=true
|
||||
|
||||
# 演示模式下不需要API地址
|
||||
VITE_APP_API_URL=
|
||||
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=NetVizor - 网络监控系统演示
|
||||
|
||||
# 演示版本标识
|
||||
VITE_APP_VERSION=demo
|
||||
|
||||
# 基础路径 (GitHub Pages可能需要)
|
||||
VITE_BASE_URL=/NetVizor/
|
||||
```
|
||||
|
||||
### 开发环境配置
|
||||
|
||||
开发模式使用 `.env.development` 配置文件:
|
||||
|
||||
```env
|
||||
VITE_MODE='development'
|
||||
VITE_APP_API_URL='http://localhost:8268/api'
|
||||
VITE_DEMO_MODE=false
|
||||
```
|
||||
|
||||
## 演示模式特性
|
||||
|
||||
### 自动环境检测
|
||||
|
||||
项目会自动检测运行环境:
|
||||
|
||||
- **WebView2 环境**: 使用真实数据和桥接通信
|
||||
- **演示模式** (`VITE_DEMO_MODE=true`): 使用模拟数据
|
||||
- **开发模式** (`VITE_DEMO_MODE=false`): 尝试连接开发API
|
||||
|
||||
### 模拟数据功能
|
||||
|
||||
演示模式提供以下模拟数据:
|
||||
|
||||
- ✅ 实时网络连接监控数据
|
||||
- ✅ 防火墙规则和状态
|
||||
- ✅ 流量统计与分析图表
|
||||
- ✅ 协议分布可视化
|
||||
- ✅ 安全事件和威胁检测
|
||||
- ✅ 应用程序排行和统计
|
||||
- ✅ 地理位置分布数据
|
||||
|
||||
### 用户界面增强
|
||||
|
||||
演示模式包含特殊的UI组件:
|
||||
|
||||
- **演示横幅**: 告知用户这是演示版本
|
||||
- **状态指示器**: 显示当前运行在演示模式
|
||||
- **数据活动指示器**: 模拟实时数据更新
|
||||
|
||||
## 构建配置
|
||||
|
||||
### Vite 配置
|
||||
|
||||
项目使用 Vite 作为构建工具,支持多环境配置:
|
||||
|
||||
```typescript
|
||||
// vite.config.ts
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd())
|
||||
|
||||
return {
|
||||
// 根据mode加载对应的.env文件
|
||||
// demo模式会自动加载.env.demo
|
||||
// development模式会加载.env.development
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 包管理器脚本
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "vite", // 开发模式
|
||||
"dev:demo": "vite --mode demo", // 演示开发模式
|
||||
"build": "vite build", // 生产构建
|
||||
"build:demo": "vite build --mode demo", // 演示构建
|
||||
"preview": "vite preview", // 预览生产版本
|
||||
"preview:demo": "vite preview --mode demo" // 预览演示版本
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 部署目标
|
||||
|
||||
### GitHub Pages
|
||||
|
||||
自动部署到 GitHub Pages:
|
||||
|
||||
- **URL 格式**: `https://username.github.io/repository-name/`
|
||||
- **自动触发**: 推送到 main 分支
|
||||
- **手动触发**: GitHub Actions 页面
|
||||
|
||||
### 其他平台
|
||||
|
||||
演示版本也可以部署到其他静态托管平台:
|
||||
|
||||
- **Netlify**: 拖拽 `dist` 文件夹或连接 GitHub
|
||||
- **Vercel**: 导入 GitHub 项目,设置构建命令为 `npm run build:demo`
|
||||
- **Surge.sh**: `npm install -g surge && surge dist`
|
||||
|
||||
## 测试和验证
|
||||
|
||||
### 本地测试
|
||||
|
||||
```bash
|
||||
# 构建演示版本
|
||||
npm run build:demo
|
||||
|
||||
# 启动本地服务器测试
|
||||
npm run preview:demo
|
||||
|
||||
# 或使用其他静态服务器
|
||||
npx serve dist
|
||||
```
|
||||
|
||||
### 演示模式测试工具
|
||||
|
||||
项目包含内置的测试工具,在浏览器控制台运行:
|
||||
|
||||
```javascript
|
||||
// 运行演示模式完整测试
|
||||
await testDemoMode()
|
||||
```
|
||||
|
||||
### 验证清单
|
||||
|
||||
部署前确认以下项目:
|
||||
|
||||
- [ ] 演示横幅正常显示
|
||||
- [ ] 状态指示器显示"演示模式"
|
||||
- [ ] 所有页面可以正常访问
|
||||
- [ ] 模拟数据正常加载和更新
|
||||
- [ ] 图表和可视化组件正常渲染
|
||||
- [ ] 响应式设计在移动设备上正常
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **页面空白**: 检查控制台错误,通常是路径配置问题
|
||||
2. **资源404**: 确认 `VITE_BASE_URL` 配置正确
|
||||
3. **演示模式未激活**: 验证 `VITE_DEMO_MODE=true` 设置
|
||||
|
||||
### 调试信息
|
||||
|
||||
演示模式会在控制台输出详细的调试信息:
|
||||
|
||||
```
|
||||
[EnvironmentDetector] WebView2 detected: false
|
||||
[EnvironmentDetector] Demo mode enabled: true
|
||||
[EnvironmentDetector] Environment type: DEMO
|
||||
[DataAdapter] 使用模拟进程数据
|
||||
[WebSocketStore] 演示模式:跳过真实WebSocket连接
|
||||
```
|
||||
|
||||
### 支持联系
|
||||
|
||||
如果遇到部署问题,请:
|
||||
|
||||
1. 检查 GitHub Actions 日志
|
||||
2. 查看浏览器控制台错误信息
|
||||
3. 在项目 Issues 页面报告问题
|
||||
|
||||
## 维护说明
|
||||
|
||||
### 更新模拟数据
|
||||
|
||||
模拟数据生成器位于 `src/utils/mockDataService.ts`,可以:
|
||||
|
||||
- 添加新的数据类型
|
||||
- 优化数据生成逻辑
|
||||
- 调整数据量和频率
|
||||
|
||||
### 更新演示UI
|
||||
|
||||
演示模式UI组件位于:
|
||||
|
||||
- `src/components/DemoBanner.vue`: 演示横幅
|
||||
- `src/components/DemoModeIndicator.vue`: 状态指示器
|
||||
|
||||
### 环境配置更新
|
||||
|
||||
需要更新环境配置时:
|
||||
|
||||
1. 修改对应的 `.env.*` 文件
|
||||
2. 更新 `environmentDetector.ts` 逻辑
|
||||
3. 测试不同环境下的行为
|
||||
@@ -0,0 +1,122 @@
|
||||
# NetVizor 演示模式快速指南
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 本地运行演示模式
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 启动演示模式开发服务器
|
||||
npm run dev:demo
|
||||
|
||||
# 访问 http://localhost:5173
|
||||
```
|
||||
|
||||
### 构建演示版本
|
||||
|
||||
```bash
|
||||
# 构建演示版本
|
||||
npm run build:demo
|
||||
|
||||
# 预览构建结果
|
||||
npm run preview:demo
|
||||
```
|
||||
|
||||
## 🎯 演示模式特性
|
||||
|
||||
### ✅ 完整功能演示
|
||||
- **实时网络监控**: 显示25个模拟进程和网络连接
|
||||
- **防火墙管理**: 展示40条防火墙规则和管理界面
|
||||
- **流量分析**: 包含48小时流量统计和趋势图表
|
||||
- **协议分析**: TCP/UDP/ICMP协议分布可视化
|
||||
- **安全事件**: 威胁检测、端口扫描等安全事件展示
|
||||
- **地理分布**: 全球主要城市连接分布图
|
||||
|
||||
### 🎨 界面增强
|
||||
- **演示横幅**: 顶部显示"在线演示版本"提示
|
||||
- **状态指示器**: 右上角显示"演示模式"标识
|
||||
- **实时活动**: 模拟数据更新指示器
|
||||
- **下载引导**: 引导用户下载完整版本
|
||||
|
||||
### 📊 智能数据模拟
|
||||
- **时间模式**: 工作时间流量更高,深夜较低
|
||||
- **真实应用**: Chrome、Steam、Discord等常见应用
|
||||
- **网络协议**: 真实的协议分布比例
|
||||
- **安全威胁**: 各种威胁类型和严重级别
|
||||
|
||||
## 🔧 开发说明
|
||||
|
||||
### 环境切换
|
||||
|
||||
项目会自动检测运行环境:
|
||||
|
||||
1. **WebView2环境**: 自动使用真实数据
|
||||
2. **演示模式**: `VITE_DEMO_MODE=true` 时使用模拟数据
|
||||
3. **开发模式**: `VITE_DEMO_MODE=false` 时连接开发API
|
||||
|
||||
### 手动切换演示模式
|
||||
|
||||
修改 `.env.development` 文件:
|
||||
```env
|
||||
# 启用演示模式
|
||||
VITE_DEMO_MODE=true
|
||||
|
||||
# 或禁用演示模式
|
||||
VITE_DEMO_MODE=false
|
||||
```
|
||||
|
||||
### 测试演示功能
|
||||
|
||||
在浏览器控制台运行:
|
||||
```javascript
|
||||
// 运行完整的演示模式测试
|
||||
await testDemoMode()
|
||||
```
|
||||
|
||||
## 🌐 在线演示
|
||||
|
||||
演示版本将自动部署到 GitHub Pages:
|
||||
- 推送代码到 `main` 分支自动触发部署
|
||||
- 或在 GitHub Actions 页面手动触发
|
||||
|
||||
## 📝 技术实现
|
||||
|
||||
### 核心组件
|
||||
- `environmentDetector.ts`: 智能环境检测
|
||||
- `mockDataService.ts`: 逼真数据生成器
|
||||
- `dataSourceAdapter.ts`: 数据源统一接口
|
||||
- `DemoBanner.vue`: 演示横幅组件
|
||||
- `DemoModeIndicator.vue`: 状态指示器
|
||||
|
||||
### 数据流程
|
||||
```
|
||||
环境检测 → 数据源选择 → 模拟数据生成 → UI渲染
|
||||
```
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
**Q: 为什么看到"Not running in WebView2 environment"警告?**
|
||||
A: 这是正常的,演示模式下不需要WebView2环境。
|
||||
|
||||
**Q: 如何验证演示模式是否正常工作?**
|
||||
A: 查看右上角是否显示"演示模式"标识,顶部是否有演示横幅。
|
||||
|
||||
**Q: 演示数据会影响真实环境吗?**
|
||||
A: 不会,演示模式完全独立,不会影响生产环境的数据和功能。
|
||||
|
||||
**Q: 如何自定义演示数据?**
|
||||
A: 修改 `src/utils/mockDataService.ts` 中的数据生成逻辑。
|
||||
|
||||
## 🎉 体验建议
|
||||
|
||||
1. **访问所有主要页面**: 监控、防火墙、分析
|
||||
2. **观察实时更新**: 数据会定期刷新模拟实时效果
|
||||
3. **检查响应式**: 在不同屏幕尺寸下测试
|
||||
4. **测试交互功能**: 点击、筛选、排序等操作
|
||||
5. **查看图表动画**: ECharts图表的动态效果
|
||||
|
||||
---
|
||||
|
||||
💡 **提示**: 这是NetVizor网络监控系统的在线演示版本,所有数据均为模拟生成。下载完整版本可获得真实的网络监控功能!
|
||||
+5
@@ -9,15 +9,20 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
CommandManager: typeof import('./src/components/CommandManager.vue')['default']
|
||||
DemoBanner: typeof import('./src/components/DemoBanner.vue')['default']
|
||||
DemoModeIndicator: typeof import('./src/components/DemoModeIndicator.vue')['default']
|
||||
FirewallRuleForm: typeof import('./src/components/FirewallRuleForm.vue')['default']
|
||||
IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
|
||||
IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
|
||||
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
|
||||
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
|
||||
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NetworkConnectionPanel2: typeof import('./src/components/NetworkConnectionPanel2.vue')['default']
|
||||
NHighlight: typeof import('naive-ui')['NHighlight']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
RuleItem: typeof import('./src/components/RuleItem.vue')['default']
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:demo": "vite --mode demo",
|
||||
"build": "vite build",
|
||||
"build:demo": "vite build --mode demo",
|
||||
"build-with-types": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"preview:demo": "vite preview --mode demo",
|
||||
"test:unit": "vitest",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
|
||||
@@ -10,6 +10,9 @@ import { NConfigProvider, NGlobalStyle, NMessageProvider, NLoadingBarProvider, N
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import NetworkConnectionPanel2 from '@/components/NetworkConnectionPanel2.vue'
|
||||
import MainLayout from './layouts/MainLayout.vue'
|
||||
import DemoBanner from '@/components/DemoBanner.vue'
|
||||
import DemoModeIndicator from '@/components/DemoModeIndicator.vue'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
|
||||
const router = useRouter()
|
||||
// 是否展示生成路由
|
||||
@@ -75,11 +78,18 @@ if (nodeEnv !== undefined && !StringUtils.isBlank(nodeEnv) && nodeEnv === app.DE
|
||||
const themeStore = useThemeStore()
|
||||
// 提供主题给子组件
|
||||
provide('theme', themeStore.theme)
|
||||
// 演示模式检测
|
||||
const isDemoMode = ref(environmentDetector.shouldUseMockData())
|
||||
|
||||
// 初始化主题
|
||||
onMounted(() => {
|
||||
themeStore.initTheme()
|
||||
// 标记页面已准备好,启用过渡动画
|
||||
document.body.classList.add('theme-ready')
|
||||
|
||||
// 打印环境信息
|
||||
console.log('[App] 应用初始化完成')
|
||||
console.log('[App] 环境信息:', environmentDetector.getEnvironmentInfo())
|
||||
})
|
||||
// region 拖拽 tab
|
||||
// 添加拖拽相关状态
|
||||
@@ -151,6 +161,18 @@ const getLayout = (route) => {
|
||||
<n-loading-bar-provider>
|
||||
<n-global-style />
|
||||
|
||||
<!-- 演示模式横幅 -->
|
||||
<DemoBanner v-if="isDemoMode" position="top" :persistent="true" />
|
||||
|
||||
<!-- 演示模式指示器 -->
|
||||
<DemoModeIndicator
|
||||
v-if="isDemoMode"
|
||||
position="top-right"
|
||||
variant="badge"
|
||||
:show-details="true"
|
||||
:show-activity="true"
|
||||
/>
|
||||
|
||||
<!-- 可拖动的导航栏 -->
|
||||
<div
|
||||
v-if="isShowGenerateRouter"
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
<template>
|
||||
<div v-if="showBanner" class="demo-banner">
|
||||
<div class="demo-banner-content">
|
||||
<div class="demo-info">
|
||||
<div class="demo-icon">
|
||||
<n-icon :component="InformationCircleOutline" size="24" />
|
||||
</div>
|
||||
<div class="demo-text">
|
||||
<h4 class="demo-title">在线演示版本</h4>
|
||||
<p class="demo-description">
|
||||
您正在体验 NetVizor 网络监控系统的演示版本,所有数据均为模拟数据。
|
||||
<span class="download-hint">下载完整版本获得真实的网络监控功能。</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-actions">
|
||||
<n-button
|
||||
type="primary"
|
||||
size="medium"
|
||||
@click="handleDownload"
|
||||
class="download-btn"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="DownloadOutline" />
|
||||
</template>
|
||||
下载完整版
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
quaternary
|
||||
size="medium"
|
||||
@click="handleClose"
|
||||
class="close-btn"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="CloseOutline" />
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 演示模式详细信息(可折叠) -->
|
||||
<div v-if="showDetails" class="demo-details">
|
||||
<div class="demo-features">
|
||||
<h5>演示功能包括:</h5>
|
||||
<ul class="feature-list">
|
||||
<li>✅ 实时网络连接监控</li>
|
||||
<li>✅ 防火墙规则管理界面</li>
|
||||
<li>✅ 流量统计与分析</li>
|
||||
<li>✅ 协议分布可视化</li>
|
||||
<li>✅ 安全事件展示</li>
|
||||
<li>⚠️ 所有数据均为模拟生成</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="demo-stats" v-if="demoInfo">
|
||||
<h5>演示环境信息:</h5>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">数据源:</span>
|
||||
<span class="stat-value">{{ demoInfo.dataSourceType }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">活跃订阅:</span>
|
||||
<span class="stat-value">{{ demoInfo.activeSubscriptions }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">环境类型:</span>
|
||||
<span class="stat-value">{{ demoInfo.environmentType }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 展开/收起按钮 -->
|
||||
<div class="demo-toggle">
|
||||
<n-button
|
||||
text
|
||||
size="small"
|
||||
@click="showDetails = !showDetails"
|
||||
class="toggle-btn"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="showDetails ? ChevronUpOutline : ChevronDownOutline" />
|
||||
</template>
|
||||
{{ showDetails ? '收起' : '详细信息' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import {
|
||||
InformationCircleOutline,
|
||||
DownloadOutline,
|
||||
CloseOutline,
|
||||
ChevronDownOutline,
|
||||
ChevronUpOutline
|
||||
} from '@vicons/ionicons5'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
position?: 'top' | 'bottom'
|
||||
persistent?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
position: 'top',
|
||||
persistent: false
|
||||
})
|
||||
|
||||
// 响应式数据
|
||||
const showBanner = ref(true)
|
||||
const showDetails = ref(false)
|
||||
const message = useMessage()
|
||||
const websocketStore = useWebSocketStore()
|
||||
|
||||
// 计算属性
|
||||
const isDemoMode = computed(() => environmentDetector.shouldUseMockData())
|
||||
const demoInfo = computed(() => websocketStore.getDemoModeInfo())
|
||||
|
||||
// 如果不是演示模式,隐藏横幅
|
||||
const shouldShow = computed(() => {
|
||||
return isDemoMode.value && showBanner.value
|
||||
})
|
||||
|
||||
// 方法
|
||||
const handleDownload = () => {
|
||||
// 这里可以跳转到下载页面或显示下载链接
|
||||
message.info('完整版本下载功能即将开放,敬请期待!')
|
||||
|
||||
// 示例:打开新页面
|
||||
// window.open('https://github.com/your-repo/releases', '_blank')
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
if (props.persistent) {
|
||||
message.warning('演示模式横幅无法关闭')
|
||||
return
|
||||
}
|
||||
|
||||
showBanner.value = false
|
||||
|
||||
// 可以将关闭状态保存到localStorage
|
||||
localStorage.setItem('demo-banner-closed', 'true')
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 检查用户是否曾经关闭过横幅
|
||||
if (!props.persistent) {
|
||||
const wasClosed = localStorage.getItem('demo-banner-closed')
|
||||
if (wasClosed === 'true') {
|
||||
showBanner.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 打印演示模式信息
|
||||
if (isDemoMode.value) {
|
||||
console.log('[DemoBanner] 演示模式已激活')
|
||||
console.log('[DemoBanner] 环境信息:', environmentDetector.getEnvironmentInfo())
|
||||
}
|
||||
})
|
||||
|
||||
// 对外暴露
|
||||
defineExpose({
|
||||
show: () => { showBanner.value = true },
|
||||
hide: () => { showBanner.value = false },
|
||||
toggle: () => { showBanner.value = !showBanner.value }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-banner {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
.demo-banner-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.demo-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.demo-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.demo-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 4px 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.demo-description {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.download-hint {
|
||||
font-weight: 500;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.demo-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.download-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
min-width: auto;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #ffffff;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.demo-details {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
padding: 16px 20px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.demo-features h5,
|
||||
.demo-stats h5 {
|
||||
margin: 0 0 12px 0;
|
||||
color: #ffd700;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: #ffd700;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.demo-toggle {
|
||||
text-align: center;
|
||||
padding: 8px 20px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.toggle-btn:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.demo-banner-content {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.demo-info {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.demo-details {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.demo-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 主题变体 */
|
||||
.demo-banner.position-bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.demo-banner.position-top {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<div v-if="isDemoMode" :class="indicatorClass">
|
||||
<div class="indicator-content">
|
||||
<div class="indicator-icon">
|
||||
<n-icon :component="PlayCircleOutline" size="16" />
|
||||
</div>
|
||||
<span class="indicator-text">演示模式</span>
|
||||
<div v-if="showDetails" class="indicator-details">
|
||||
<n-tooltip placement="bottom-start">
|
||||
<template #trigger>
|
||||
<n-icon :component="InformationCircleOutline" size="14" class="info-icon" />
|
||||
</template>
|
||||
<div class="tooltip-content">
|
||||
<div class="tooltip-item">
|
||||
<strong>数据源:</strong>{{ demoInfo?.dataSourceType || '模拟数据' }}
|
||||
</div>
|
||||
<div class="tooltip-item">
|
||||
<strong>环境:</strong>{{ demoInfo?.environmentType || 'DEMO' }}
|
||||
</div>
|
||||
<div class="tooltip-item">
|
||||
<strong>活跃连接:</strong>{{ demoInfo?.activeSubscriptions || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
</n-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据更新动画指示器 -->
|
||||
<div v-if="showActivityIndicator" class="activity-indicator">
|
||||
<div class="activity-dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { PlayCircleOutline, InformationCircleOutline } from '@vicons/ionicons5'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'inline'
|
||||
variant?: 'badge' | 'pill' | 'minimal'
|
||||
showDetails?: boolean
|
||||
showActivity?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
position: 'top-right',
|
||||
variant: 'badge',
|
||||
showDetails: true,
|
||||
showActivity: true
|
||||
})
|
||||
|
||||
// 响应式数据
|
||||
const showActivityIndicator = ref(false)
|
||||
const websocketStore = useWebSocketStore()
|
||||
|
||||
// 计算属性
|
||||
const isDemoMode = computed(() => environmentDetector.shouldUseMockData())
|
||||
const demoInfo = computed(() => websocketStore.getDemoModeInfo())
|
||||
|
||||
const indicatorClass = computed(() => [
|
||||
'demo-indicator',
|
||||
`position-${props.position}`,
|
||||
`variant-${props.variant}`
|
||||
])
|
||||
|
||||
// 模拟数据更新活动指示器
|
||||
let activityTimer: NodeJS.Timeout | null = null
|
||||
|
||||
const startActivityIndicator = () => {
|
||||
if (!props.showActivity || !isDemoMode.value) return
|
||||
|
||||
const showActivity = () => {
|
||||
showActivityIndicator.value = true
|
||||
setTimeout(() => {
|
||||
showActivityIndicator.value = false
|
||||
}, 800)
|
||||
}
|
||||
|
||||
// 随机间隔显示活动指示器
|
||||
const scheduleNext = () => {
|
||||
const delay = 2000 + Math.random() * 3000 // 2-5秒随机间隔
|
||||
activityTimer = setTimeout(() => {
|
||||
showActivity()
|
||||
scheduleNext()
|
||||
}, delay)
|
||||
}
|
||||
|
||||
scheduleNext()
|
||||
}
|
||||
|
||||
const stopActivityIndicator = () => {
|
||||
if (activityTimer) {
|
||||
clearTimeout(activityTimer)
|
||||
activityTimer = null
|
||||
}
|
||||
showActivityIndicator.value = false
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
if (isDemoMode.value) {
|
||||
console.log('[DemoModeIndicator] 演示模式指示器已激活')
|
||||
startActivityIndicator()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopActivityIndicator()
|
||||
})
|
||||
|
||||
// 对外暴露
|
||||
defineExpose({
|
||||
triggerActivity: () => {
|
||||
if (props.showActivity) {
|
||||
showActivityIndicator.value = true
|
||||
setTimeout(() => {
|
||||
showActivityIndicator.value = false
|
||||
}, 800)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 位置样式 */
|
||||
.position-top-left {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.position-top-right {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.position-bottom-left {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.position-bottom-right {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.position-inline {
|
||||
position: relative;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
/* 变体样式 */
|
||||
.variant-badge {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 6px 12px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.variant-pill {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
color: #667eea;
|
||||
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.variant-minimal {
|
||||
background: transparent;
|
||||
color: #667eea;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.indicator-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.indicator-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.indicator-text {
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.info-icon:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.activity-indicator {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.activity-dot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #52c41a;
|
||||
border-radius: 50%;
|
||||
animation: activityPulse 0.8s ease-out;
|
||||
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.7);
|
||||
}
|
||||
|
||||
/* Tooltip 样式 */
|
||||
.tooltip-content {
|
||||
padding: 8px 0;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.tooltip-item {
|
||||
padding: 2px 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tooltip-item strong {
|
||||
color: #333;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 动画 */
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes activityPulse {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.7);
|
||||
}
|
||||
70% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 8px rgba(82, 196, 26, 0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.8);
|
||||
box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗色主题适配 */
|
||||
.dark .variant-pill {
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
border-color: rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.dark .variant-minimal {
|
||||
color: #8b9be6;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 768px) {
|
||||
.position-top-left,
|
||||
.position-top-right {
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.position-top-left,
|
||||
.position-bottom-left {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.position-top-right,
|
||||
.position-bottom-right {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.position-bottom-left,
|
||||
.position-bottom-right {
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.variant-badge,
|
||||
.variant-pill {
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { MessagePayload } from '@/correspond/IBridge'
|
||||
import { CSharpBridge } from '@/correspond/CSharpBridge'
|
||||
import { TauriBridge } from '@/correspond/TauriBridge'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
|
||||
interface BridgeMessagePayload<T = any> {
|
||||
channel: string
|
||||
@@ -19,7 +20,10 @@ class CSharpBridgeV2 {
|
||||
;(window as any).externalFunctions.__BRIDGE_LISTEN__ = CSharpBridgeV2.CSBridge.bridgeInvoke
|
||||
;(window as any).externalFunctions.__BRIDGE_LISTEN__FUNCTIONS__ = new Map()
|
||||
} else {
|
||||
console.error('Not running in WebView2 environment')
|
||||
// 只在非演示模式下显示此警告
|
||||
if (!environmentDetector.shouldUseMockData()) {
|
||||
console.warn('[CSharpBridgeV2] WebView2环境未检测到,运行在浏览器模式')
|
||||
}
|
||||
}
|
||||
}
|
||||
return CSharpBridgeV2.CSBridge
|
||||
|
||||
@@ -1,35 +1,11 @@
|
||||
import CSharpBridgeV2 from '@/correspond/CSharpBridgeV2'
|
||||
import websocketPlugin from '@/plugins/websocketPlugin'
|
||||
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||
import { httpConfig } from '@/config/httpConfig'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
|
||||
// 注册 C# 函数
|
||||
window.externalFunctions = {}
|
||||
|
||||
// 监听WebSocket地址
|
||||
const bridge = CSharpBridgeV2?.getBridge()
|
||||
if (bridge) {
|
||||
const intervalId = setInterval(() => {
|
||||
if (webSocketStore.isInitialized) {
|
||||
clearInterval(intervalId)
|
||||
return
|
||||
}
|
||||
if (bridge) {
|
||||
bridge.send('GetWebSocketPath', {}, (data) => {
|
||||
webSocketStore.initialize(data)
|
||||
console.log(data, '======')
|
||||
})
|
||||
bridge.send('GetHttpPath', {}, (data) => {
|
||||
httpConfig.setUrl(data + '/api')
|
||||
console.log(data, 'HTTP 路径')
|
||||
})
|
||||
}
|
||||
}, 100)
|
||||
} else {
|
||||
// todo: 2025/6/25 08:32 开发阶段直接使用固定节点
|
||||
webSocketStore.initialize('ws://127.0.0.1:8267')
|
||||
}
|
||||
|
||||
import './assets/main.css'
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
@@ -76,5 +52,57 @@ app.use(httpPlugin)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
// 监听获取 WebSocket 链接
|
||||
const webSocketStore = useWebSocketStore()
|
||||
// 监听获取 WebSocket 链接 - 需要在 Pinia 初始化后进行
|
||||
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||
|
||||
// 初始化WebSocket连接
|
||||
const initializeWebSocket = () => {
|
||||
const webSocketStore = useWebSocketStore()
|
||||
|
||||
console.log('[Main] 初始化WebSocket连接')
|
||||
console.log('[Main] 环境信息:', environmentDetector.getEnvironmentInfo())
|
||||
|
||||
// 检查是否为演示模式
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
console.log('[Main] 演示模式:跳过WebSocket初始化')
|
||||
return
|
||||
}
|
||||
|
||||
const bridge = CSharpBridgeV2?.getBridge()
|
||||
if (bridge) {
|
||||
console.log('[Main] 检测到WebView2环境,使用桥接获取WebSocket路径')
|
||||
const intervalId = setInterval(() => {
|
||||
if (webSocketStore.isInitialized) {
|
||||
clearInterval(intervalId)
|
||||
return
|
||||
}
|
||||
if (bridge) {
|
||||
bridge.send('GetWebSocketPath', {}, (data) => {
|
||||
webSocketStore.initialize(data)
|
||||
console.log('[Main] WebSocket路径已设置:', data)
|
||||
})
|
||||
bridge.send('GetHttpPath', {}, (data) => {
|
||||
httpConfig.setUrl(data + '/api')
|
||||
console.log('[Main] HTTP路径已设置:', data)
|
||||
})
|
||||
}
|
||||
}, 100)
|
||||
} else {
|
||||
console.log('[Main] 开发环境:使用默认WebSocket路径')
|
||||
// 开发阶段直接使用固定节点
|
||||
webSocketStore.initialize('ws://127.0.0.1:8267')
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟初始化,确保所有store都已准备好
|
||||
setTimeout(initializeWebSocket, 100)
|
||||
|
||||
// 演示模式测试工具
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
import('@/utils/demoModeTest').then(({ demoModeTest }) => {
|
||||
console.log('[Main] 演示模式测试工具已加载')
|
||||
console.log('[Main] 在控制台运行 testDemoMode() 开始测试')
|
||||
}).catch(error => {
|
||||
console.warn('[Main] 演示模式测试工具加载失败:', error)
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { WebSocketResponse } from '@/types/websocket'
|
||||
import { ApplicationType } from '@/types/infoModel'
|
||||
import { httpClient } from '@/utils/http'
|
||||
import { ResponseModel, SubscriptionInfo } from '@/types/response'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
import { dataSourceAdapter } from '@/utils/dataSourceAdapter'
|
||||
|
||||
export const useApplicationStore = defineStore('applicationInfoSub', () => {
|
||||
const appInfos: Ref<ApplicationType[]> = ref([])
|
||||
@@ -123,6 +125,17 @@ export const useApplicationStore = defineStore('applicationInfoSub', () => {
|
||||
* 订阅信息
|
||||
*/
|
||||
function subscribe() {
|
||||
console.log('[ApplicationStore] 订阅应用程序信息')
|
||||
|
||||
// 检查演示模式
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
console.log('[ApplicationStore] 演示模式:使用模拟数据')
|
||||
loadDemoData()
|
||||
startDemoDataUpdates()
|
||||
return
|
||||
}
|
||||
|
||||
// 真实模式:使用WebSocket
|
||||
const webSocketStore = useWebSocketStore()
|
||||
const { isOpen } = storeToRefs(webSocketStore)
|
||||
watch(
|
||||
@@ -142,6 +155,34 @@ export const useApplicationStore = defineStore('applicationInfoSub', () => {
|
||||
)
|
||||
}
|
||||
|
||||
// 加载演示数据
|
||||
async function loadDemoData() {
|
||||
try {
|
||||
const applications = await dataSourceAdapter.getApplicationList()
|
||||
console.log('[ApplicationStore] 加载演示数据:', applications.length, '个应用程序')
|
||||
appInfos.value = applications as ApplicationType[]
|
||||
} catch (error) {
|
||||
console.error('[ApplicationStore] 加载演示数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 启动演示数据更新
|
||||
function startDemoDataUpdates() {
|
||||
const subscriptionId = dataSourceAdapter.subscribeRealtimeData((data) => {
|
||||
if (data.type === 'application_update' && data.data) {
|
||||
// 随机更新一个应用的数据
|
||||
const appData = data.data as ApplicationType
|
||||
const index = appInfos.value.findIndex(a => a.id === appData.id)
|
||||
if (index !== -1) {
|
||||
appInfos.value[index] = appData
|
||||
}
|
||||
console.log('[ApplicationStore] 实时更新应用数据:', appData.name)
|
||||
}
|
||||
}, 5000) // 每5秒更新一次
|
||||
|
||||
console.log('[ApplicationStore] 演示模式实时更新已启动:', subscriptionId)
|
||||
}
|
||||
|
||||
function unsubscribe() {
|
||||
const subAppInfo: SubscriptionInfo = {
|
||||
subscriptionType: 'AppDetailInfo',
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ref, Ref, watch } from 'vue'
|
||||
import { ProcessType } from '@/types/process'
|
||||
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||
import { WebSocketResponse } from '@/types/websocket'
|
||||
import { environmentDetector } from '@/utils/environmentDetector'
|
||||
import { dataSourceAdapter } from '@/utils/dataSourceAdapter'
|
||||
|
||||
export const useProcessStore = defineStore('processInfoSub', () => {
|
||||
// 进程信息
|
||||
@@ -14,6 +16,17 @@ export const useProcessStore = defineStore('processInfoSub', () => {
|
||||
}
|
||||
|
||||
function subscribe() {
|
||||
console.log('[ProcessStore] 订阅进程信息')
|
||||
|
||||
// 检查演示模式
|
||||
if (environmentDetector.shouldUseMockData()) {
|
||||
console.log('[ProcessStore] 演示模式:使用模拟数据')
|
||||
loadDemoData()
|
||||
startDemoDataUpdates()
|
||||
return
|
||||
}
|
||||
|
||||
// 真实模式:使用WebSocket
|
||||
const webSocketStore = useWebSocketStore()
|
||||
const { isOpen } = storeToRefs(webSocketStore)
|
||||
watch(
|
||||
@@ -23,7 +36,7 @@ export const useProcessStore = defineStore('processInfoSub', () => {
|
||||
webSocketStore.registerHandler('ProcessInfo', (data: WebSocketResponse<string>) => {
|
||||
// console.log(data.data)
|
||||
const parse: ProcessType[] = JSON.parse(data.data)
|
||||
console.log(parse,Date.now());
|
||||
console.log('[ProcessStore] 接收到进程数据:', parse.length, '个进程')
|
||||
processInfos.value = parse
|
||||
})
|
||||
}
|
||||
@@ -32,6 +45,37 @@ export const useProcessStore = defineStore('processInfoSub', () => {
|
||||
)
|
||||
}
|
||||
|
||||
// 加载演示数据
|
||||
async function loadDemoData() {
|
||||
try {
|
||||
const processes = await dataSourceAdapter.getProcessList()
|
||||
console.log('[ProcessStore] 加载演示数据:', processes.length, '个进程')
|
||||
processInfos.value = processes
|
||||
} catch (error) {
|
||||
console.error('[ProcessStore] 加载演示数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 启动演示数据更新
|
||||
function startDemoDataUpdates() {
|
||||
const subscriptionId = dataSourceAdapter.subscribeRealtimeData((data) => {
|
||||
if (data.type === 'process_update' && data.data) {
|
||||
// 随机更新一个进程的数据
|
||||
const processData = data.data as ProcessType
|
||||
const index = processInfos.value.findIndex(p => p.processId === processData.processId)
|
||||
if (index !== -1) {
|
||||
processInfos.value[index] = processData
|
||||
} else {
|
||||
// 如果进程不存在,可能是新进程,添加到列表
|
||||
processInfos.value.push(processData)
|
||||
}
|
||||
console.log('[ProcessStore] 实时更新进程数据:', processData.processName)
|
||||
}
|
||||
}, 3000) // 每3秒更新一次
|
||||
|
||||
console.log('[ProcessStore] 演示模式实时更新已启动:', subscriptionId)
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe,processInfos,clear
|
||||
}
|
||||
|
||||
@@ -1,20 +1,51 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { WebSocketManager } from '@/utils/websocket'
|
||||
import { ref, computed,shallowRef } from 'vue'
|
||||
import { ref, computed, shallowRef } from 'vue'
|
||||
import { WebSocketState } from '@/types/websocket'
|
||||
import { environmentDetector, shouldUseMockData } from '@/utils/environmentDetector'
|
||||
import { dataSourceAdapter } from '@/utils/dataSourceAdapter'
|
||||
|
||||
export const useWebSocketStore = defineStore('websocket', () => {
|
||||
const manager = shallowRef<WebSocketManager | null>(null)
|
||||
|
||||
// 演示模式相关状态
|
||||
const isDemoMode = ref<boolean>(shouldUseMockData())
|
||||
const mockSubscriptions = ref<Map<string, string>>(new Map())
|
||||
|
||||
// 计算属性,自动响应WebSocketManager内部状态变化
|
||||
const isInitialized = ref<boolean>(false)
|
||||
const isOpen = ref<boolean>(false)
|
||||
const isConnected = computed(() => manager.value?.isConnected.value ?? false)
|
||||
const isReconnecting = computed(() => manager.value?.isReconnecting.value ?? false)
|
||||
const connectionState = computed(() => manager.value?.state.value ?? WebSocketState.DISCONNECTED)
|
||||
const isConnected = computed(() => {
|
||||
if (isDemoMode.value) {
|
||||
return true // 演示模式下总是显示已连接
|
||||
}
|
||||
return manager.value?.isConnected.value ?? false
|
||||
})
|
||||
const isReconnecting = computed(() => {
|
||||
if (isDemoMode.value) {
|
||||
return false // 演示模式下不显示重连状态
|
||||
}
|
||||
return manager.value?.isReconnecting.value ?? false
|
||||
})
|
||||
const connectionState = computed(() => {
|
||||
if (isDemoMode.value) {
|
||||
return WebSocketState.CONNECTED
|
||||
}
|
||||
return manager.value?.state.value ?? WebSocketState.DISCONNECTED
|
||||
})
|
||||
|
||||
// 初始化WebSocket
|
||||
const initialize = (url: string) => {
|
||||
console.log('[WebSocketStore] 初始化WebSocket连接')
|
||||
console.log('[WebSocketStore] 环境信息:', environmentDetector.getEnvironmentInfo())
|
||||
|
||||
if (isDemoMode.value) {
|
||||
console.log('[WebSocketStore] 演示模式:跳过真实WebSocket连接')
|
||||
isInitialized.value = true
|
||||
isOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
if (url.trim() === '') {
|
||||
console.warn('WebSocket URL is empty')
|
||||
return
|
||||
@@ -33,6 +64,12 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
|
||||
// 发送消息
|
||||
const send = <T>(command: string, data?: T) => {
|
||||
if (isDemoMode.value) {
|
||||
console.log(`[WebSocketStore] 演示模式:模拟发送消息 ${command}`, data)
|
||||
// 在演示模式下,可以模拟一些响应
|
||||
return
|
||||
}
|
||||
|
||||
if (!manager.value) {
|
||||
throw new Error('WebSocket未初始化')
|
||||
}
|
||||
@@ -44,6 +81,18 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
|
||||
// 注册处理器
|
||||
const registerHandler = (command: string, handler: (data: unknown) => void) => {
|
||||
if (isDemoMode.value) {
|
||||
console.log(`[WebSocketStore] 演示模式:注册消息处理器 ${command}`)
|
||||
|
||||
// 对于实时数据订阅,启动模拟数据推送
|
||||
if (command.includes('subscribe') || command.includes('realtime')) {
|
||||
const subscriptionId = dataSourceAdapter.subscribeRealtimeData(handler, 2000)
|
||||
mockSubscriptions.value.set(command, subscriptionId)
|
||||
console.log(`[WebSocketStore] 启动模拟数据推送: ${command} -> ${subscriptionId}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!manager.value) {
|
||||
console.warn('WebSocket未初始化,处理器将在初始化后注册')
|
||||
return
|
||||
@@ -53,6 +102,19 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
|
||||
// 注销处理器
|
||||
const unregisterHandler = (command: string, handler: (data: unknown) => void) => {
|
||||
if (isDemoMode.value) {
|
||||
console.log(`[WebSocketStore] 演示模式:注销消息处理器 ${command}`)
|
||||
|
||||
// 停止模拟数据推送
|
||||
const subscriptionId = mockSubscriptions.value.get(command)
|
||||
if (subscriptionId) {
|
||||
dataSourceAdapter.unsubscribeRealtimeData(subscriptionId)
|
||||
mockSubscriptions.value.delete(command)
|
||||
console.log(`[WebSocketStore] 停止模拟数据推送: ${command}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!manager.value) {
|
||||
return
|
||||
}
|
||||
@@ -61,6 +123,21 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
|
||||
// 断开连接
|
||||
const disconnect = () => {
|
||||
if (isDemoMode.value) {
|
||||
console.log('[WebSocketStore] 演示模式:断开连接(清理模拟订阅)')
|
||||
|
||||
// 清理所有模拟订阅
|
||||
mockSubscriptions.value.forEach((subscriptionId, command) => {
|
||||
dataSourceAdapter.unsubscribeRealtimeData(subscriptionId)
|
||||
console.log(`[WebSocketStore] 清理模拟订阅: ${command}`)
|
||||
})
|
||||
mockSubscriptions.value.clear()
|
||||
|
||||
isInitialized.value = false
|
||||
isOpen.value = false
|
||||
return
|
||||
}
|
||||
|
||||
if (manager.value) {
|
||||
manager.value.disconnect()
|
||||
manager.value = null
|
||||
@@ -69,6 +146,28 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 新增方法:获取演示模式状态和信息
|
||||
const getDemoModeInfo = () => {
|
||||
return {
|
||||
isDemoMode: isDemoMode.value,
|
||||
environmentInfo: environmentDetector.getEnvironmentInfo(),
|
||||
dataSourceInfo: dataSourceAdapter.getDataSourceInfo(),
|
||||
activeSubscriptions: mockSubscriptions.value.size,
|
||||
subscriptions: Array.from(mockSubscriptions.value.keys())
|
||||
}
|
||||
}
|
||||
|
||||
// 添加便捷方法用于数据订阅
|
||||
const subscribe = (topic: string, callback: (data: unknown) => void) => {
|
||||
const command = `subscribe_${topic}`
|
||||
registerHandler(command, callback)
|
||||
return command
|
||||
}
|
||||
|
||||
const unsubscribe = (command: string, callback: (data: unknown) => void) => {
|
||||
unregisterHandler(command, callback)
|
||||
}
|
||||
|
||||
return {
|
||||
// 响应式状态
|
||||
manager,
|
||||
@@ -77,6 +176,7 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
isConnected,
|
||||
isReconnecting,
|
||||
connectionState,
|
||||
isDemoMode,
|
||||
|
||||
// 方法
|
||||
initialize,
|
||||
@@ -84,5 +184,10 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
registerHandler,
|
||||
unregisterHandler,
|
||||
disconnect,
|
||||
|
||||
// 新增方法
|
||||
getDemoModeInfo,
|
||||
subscribe,
|
||||
unsubscribe,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* 数据源适配器
|
||||
* 根据环境自动切换真实数据源和模拟数据源
|
||||
*/
|
||||
|
||||
import { environmentDetector, DataSourceType } from './environmentDetector'
|
||||
import { mockDataService } from './mockDataService'
|
||||
import type { ProcessType, ConnectionInfo } from '@/types/process'
|
||||
import type { FirewallRule, FirewallStatus } from '@/types/firewall'
|
||||
import type { WebSocketResponse } from '@/types/websocket'
|
||||
|
||||
class DataSourceAdapter {
|
||||
private static instance: DataSourceAdapter | null = null
|
||||
|
||||
// 模拟实时数据的定时器
|
||||
private realtimeTimers: Map<string, NodeJS.Timeout> = new Map()
|
||||
|
||||
static getInstance(): DataSourceAdapter {
|
||||
if (!DataSourceAdapter.instance) {
|
||||
DataSourceAdapter.instance = new DataSourceAdapter()
|
||||
}
|
||||
return DataSourceAdapter.instance
|
||||
}
|
||||
|
||||
// ================= 进程和连接数据获取 =================
|
||||
|
||||
/**
|
||||
* 获取进程列表
|
||||
*/
|
||||
async getProcessList(): Promise<ProcessType[]> {
|
||||
const dataSourceType = environmentDetector.getDataSourceType()
|
||||
|
||||
switch (dataSourceType) {
|
||||
case DataSourceType.MOCK_DATA:
|
||||
console.log('[DataAdapter] 使用模拟进程数据')
|
||||
return await this.getMockProcessList()
|
||||
|
||||
case DataSourceType.REAL_DATA:
|
||||
console.log('[DataAdapter] 使用WebView2桥接获取进程数据')
|
||||
return await this.getRealProcessList()
|
||||
|
||||
case DataSourceType.DEV_API:
|
||||
console.log('[DataAdapter] 使用开发API获取进程数据')
|
||||
return await this.getApiProcessList()
|
||||
|
||||
default:
|
||||
console.warn('[DataAdapter] 未知数据源类型,使用模拟数据')
|
||||
return await this.getMockProcessList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接列表
|
||||
*/
|
||||
async getConnectionList(): Promise<ConnectionInfo[]> {
|
||||
const dataSourceType = environmentDetector.getDataSourceType()
|
||||
|
||||
switch (dataSourceType) {
|
||||
case DataSourceType.MOCK_DATA:
|
||||
return await this.getMockConnectionList()
|
||||
|
||||
case DataSourceType.REAL_DATA:
|
||||
return await this.getRealConnectionList()
|
||||
|
||||
case DataSourceType.DEV_API:
|
||||
return await this.getApiConnectionList()
|
||||
|
||||
default:
|
||||
return await this.getMockConnectionList()
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 防火墙数据获取 =================
|
||||
|
||||
/**
|
||||
* 获取防火墙状态
|
||||
*/
|
||||
async getFirewallStatus(): Promise<FirewallStatus> {
|
||||
const dataSourceType = environmentDetector.getDataSourceType()
|
||||
|
||||
switch (dataSourceType) {
|
||||
case DataSourceType.MOCK_DATA:
|
||||
return await this.getMockFirewallStatus()
|
||||
|
||||
case DataSourceType.REAL_DATA:
|
||||
return await this.getRealFirewallStatus()
|
||||
|
||||
case DataSourceType.DEV_API:
|
||||
return await this.getApiFirewallStatus()
|
||||
|
||||
default:
|
||||
return await this.getMockFirewallStatus()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取防火墙规则列表
|
||||
*/
|
||||
async getFirewallRules(): Promise<FirewallRule[]> {
|
||||
const dataSourceType = environmentDetector.getDataSourceType()
|
||||
|
||||
switch (dataSourceType) {
|
||||
case DataSourceType.MOCK_DATA:
|
||||
return await this.getMockFirewallRules()
|
||||
|
||||
case DataSourceType.REAL_DATA:
|
||||
return await this.getRealFirewallRules()
|
||||
|
||||
case DataSourceType.DEV_API:
|
||||
return await this.getApiFirewallRules()
|
||||
|
||||
default:
|
||||
return await this.getMockFirewallRules()
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 实时数据订阅 =================
|
||||
|
||||
/**
|
||||
* 获取应用程序列表
|
||||
*/
|
||||
async getApplicationList(): Promise<any[]> {
|
||||
const dataSourceType = environmentDetector.getDataSourceType()
|
||||
|
||||
switch (dataSourceType) {
|
||||
case DataSourceType.MOCK_DATA:
|
||||
console.log('[DataAdapter] 使用模拟应用程序数据')
|
||||
return await this.getMockApplicationList()
|
||||
|
||||
case DataSourceType.REAL_DATA:
|
||||
console.log('[DataAdapter] 使用WebView2桥接获取应用程序数据')
|
||||
return await this.getRealApplicationList()
|
||||
|
||||
case DataSourceType.DEV_API:
|
||||
console.log('[DataAdapter] 使用开发API获取应用程序数据')
|
||||
return await this.getApiApplicationList()
|
||||
|
||||
default:
|
||||
console.warn('[DataAdapter] 未知数据源类型,使用模拟应用程序数据')
|
||||
return await this.getMockApplicationList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅实时数据更新
|
||||
*/
|
||||
subscribeRealtimeData(
|
||||
callback: (data: WebSocketResponse) => void,
|
||||
interval = 2000
|
||||
): string {
|
||||
const subscriptionId = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
const dataSourceType = environmentDetector.getDataSourceType()
|
||||
|
||||
switch (dataSourceType) {
|
||||
case DataSourceType.MOCK_DATA:
|
||||
this.subscribeMockRealtimeData(subscriptionId, callback, interval)
|
||||
break
|
||||
|
||||
case DataSourceType.REAL_DATA:
|
||||
this.subscribeRealRealtimeData(subscriptionId, callback)
|
||||
break
|
||||
|
||||
case DataSourceType.DEV_API:
|
||||
this.subscribeApiRealtimeData(subscriptionId, callback)
|
||||
break
|
||||
}
|
||||
|
||||
console.log(`[DataAdapter] 已订阅实时数据更新: ${subscriptionId}`)
|
||||
return subscriptionId
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅实时数据
|
||||
*/
|
||||
unsubscribeRealtimeData(subscriptionId: string): void {
|
||||
const timer = this.realtimeTimers.get(subscriptionId)
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
this.realtimeTimers.delete(subscriptionId)
|
||||
console.log(`[DataAdapter] 已取消订阅: ${subscriptionId}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 模拟数据实现 =================
|
||||
|
||||
private async getMockProcessList(): Promise<ProcessType[]> {
|
||||
// 模拟网络延迟
|
||||
await this.delay(100, 300)
|
||||
return mockDataService.generateProcessList(25)
|
||||
}
|
||||
|
||||
private async getMockConnectionList(): Promise<ConnectionInfo[]> {
|
||||
await this.delay(50, 150)
|
||||
const processes = await this.getMockProcessList()
|
||||
const connections: ConnectionInfo[] = []
|
||||
|
||||
processes.forEach(process => {
|
||||
connections.push(...process.connections)
|
||||
})
|
||||
|
||||
return connections
|
||||
}
|
||||
|
||||
private async getMockApplicationList(): Promise<any[]> {
|
||||
await this.delay(100, 200)
|
||||
return mockDataService.generateApplicationList(15)
|
||||
}
|
||||
|
||||
private async getMockFirewallStatus(): Promise<FirewallStatus> {
|
||||
await this.delay(50, 100)
|
||||
return mockDataService.generateFirewallStatus()
|
||||
}
|
||||
|
||||
private async getMockFirewallRules(): Promise<FirewallRule[]> {
|
||||
await this.delay(100, 200)
|
||||
return mockDataService.generateFirewallRules(35)
|
||||
}
|
||||
|
||||
private subscribeMockRealtimeData(
|
||||
subscriptionId: string,
|
||||
callback: (data: WebSocketResponse) => void,
|
||||
interval: number
|
||||
): void {
|
||||
const timer = setInterval(() => {
|
||||
const update = mockDataService.generateRealtimeUpdate()
|
||||
callback(update)
|
||||
}, interval)
|
||||
|
||||
this.realtimeTimers.set(subscriptionId, timer)
|
||||
}
|
||||
|
||||
// ================= 真实数据实现 (WebView2桥接) =================
|
||||
|
||||
private async getRealProcessList(): Promise<ProcessType[]> {
|
||||
// TODO: 实现WebView2桥接调用
|
||||
console.log('[DataAdapter] 调用WebView2桥接获取进程数据')
|
||||
// 临时返回空数组,待后端桥接实现
|
||||
return []
|
||||
}
|
||||
|
||||
private async getRealConnectionList(): Promise<ConnectionInfo[]> {
|
||||
// TODO: 实现WebView2桥接调用
|
||||
console.log('[DataAdapter] 调用WebView2桥接获取连接数据')
|
||||
return []
|
||||
}
|
||||
|
||||
private async getRealApplicationList(): Promise<any[]> {
|
||||
// TODO: 实现WebView2桥接调用
|
||||
console.log('[DataAdapter] 调用WebView2桥接获取应用程序数据')
|
||||
return []
|
||||
}
|
||||
|
||||
private async getRealFirewallStatus(): Promise<FirewallStatus> {
|
||||
// TODO: 实现WebView2桥接调用
|
||||
console.log('[DataAdapter] 调用WebView2桥接获取防火墙状态')
|
||||
return mockDataService.generateFirewallStatus() // 临时使用模拟数据
|
||||
}
|
||||
|
||||
private async getRealFirewallRules(): Promise<FirewallRule[]> {
|
||||
// TODO: 实现WebView2桥接调用
|
||||
console.log('[DataAdapter] 调用WebView2桥接获取防火墙规则')
|
||||
return []
|
||||
}
|
||||
|
||||
private subscribeRealRealtimeData(
|
||||
subscriptionId: string,
|
||||
callback: (data: WebSocketResponse) => void
|
||||
): void {
|
||||
// TODO: 实现WebView2桥接实时数据订阅
|
||||
console.log('[DataAdapter] 订阅WebView2桥接实时数据')
|
||||
}
|
||||
|
||||
// ================= API数据实现 (开发环境) =================
|
||||
|
||||
private async getApiProcessList(): Promise<ProcessType[]> {
|
||||
try {
|
||||
const apiUrl = environmentDetector.getApiBaseUrl()
|
||||
console.log(`[DataAdapter] 调用API获取进程数据: ${apiUrl}/processes`)
|
||||
|
||||
// TODO: 实现真实API调用
|
||||
// const response = await fetch(`${apiUrl}/processes`)
|
||||
// return await response.json()
|
||||
|
||||
// 临时使用模拟数据
|
||||
return await this.getMockProcessList()
|
||||
} catch (error) {
|
||||
console.warn('[DataAdapter] API调用失败,使用模拟数据', error)
|
||||
return await this.getMockProcessList()
|
||||
}
|
||||
}
|
||||
|
||||
private async getApiConnectionList(): Promise<ConnectionInfo[]> {
|
||||
try {
|
||||
const apiUrl = environmentDetector.getApiBaseUrl()
|
||||
console.log(`[DataAdapter] 调用API获取连接数据: ${apiUrl}/connections`)
|
||||
return await this.getMockConnectionList()
|
||||
} catch (error) {
|
||||
console.warn('[DataAdapter] API调用失败,使用模拟数据', error)
|
||||
return await this.getMockConnectionList()
|
||||
}
|
||||
}
|
||||
|
||||
private async getApiApplicationList(): Promise<any[]> {
|
||||
try {
|
||||
const apiUrl = environmentDetector.getApiBaseUrl()
|
||||
console.log(`[DataAdapter] 调用API获取应用程序数据: ${apiUrl}/applications`)
|
||||
return await this.getMockApplicationList()
|
||||
} catch (error) {
|
||||
console.warn('[DataAdapter] API调用失败,使用模拟应用程序数据', error)
|
||||
return await this.getMockApplicationList()
|
||||
}
|
||||
}
|
||||
|
||||
private async getApiFirewallStatus(): Promise<FirewallStatus> {
|
||||
try {
|
||||
const apiUrl = environmentDetector.getApiBaseUrl()
|
||||
console.log(`[DataAdapter] 调用API获取防火墙状态: ${apiUrl}/firewall/status`)
|
||||
return await this.getMockFirewallStatus()
|
||||
} catch (error) {
|
||||
console.warn('[DataAdapter] API调用失败,使用模拟数据', error)
|
||||
return await this.getMockFirewallStatus()
|
||||
}
|
||||
}
|
||||
|
||||
private async getApiFirewallRules(): Promise<FirewallRule[]> {
|
||||
try {
|
||||
const apiUrl = environmentDetector.getApiBaseUrl()
|
||||
console.log(`[DataAdapter] 调用API获取防火墙规则: ${apiUrl}/firewall/rules`)
|
||||
return await this.getMockFirewallRules()
|
||||
} catch (error) {
|
||||
console.warn('[DataAdapter] API调用失败,使用模拟数据', error)
|
||||
return await this.getMockFirewallRules()
|
||||
}
|
||||
}
|
||||
|
||||
private subscribeApiRealtimeData(
|
||||
subscriptionId: string,
|
||||
callback: (data: WebSocketResponse) => void
|
||||
): void {
|
||||
// TODO: 实现WebSocket API订阅
|
||||
console.log('[DataAdapter] 订阅API WebSocket实时数据')
|
||||
|
||||
// 临时使用模拟数据
|
||||
this.subscribeMockRealtimeData(subscriptionId, callback, 3000)
|
||||
}
|
||||
|
||||
// ================= 工具方法 =================
|
||||
|
||||
/**
|
||||
* 模拟网络延迟
|
||||
*/
|
||||
private delay(min: number, max: number): Promise<void> {
|
||||
const delay = Math.floor(Math.random() * (max - min + 1)) + min
|
||||
return new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有订阅
|
||||
*/
|
||||
cleanup(): void {
|
||||
this.realtimeTimers.forEach((timer, id) => {
|
||||
clearInterval(timer)
|
||||
console.log(`[DataAdapter] 清理订阅: ${id}`)
|
||||
})
|
||||
this.realtimeTimers.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源信息
|
||||
*/
|
||||
getDataSourceInfo() {
|
||||
return {
|
||||
dataSourceType: environmentDetector.getDataSourceType(),
|
||||
environmentType: environmentDetector.getEnvironmentType(),
|
||||
activeSubscriptions: this.realtimeTimers.size,
|
||||
subscriptionIds: Array.from(this.realtimeTimers.keys())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const dataSourceAdapter = DataSourceAdapter.getInstance()
|
||||
|
||||
// 在页面卸载时清理资源
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
dataSourceAdapter.cleanup()
|
||||
})
|
||||
}
|
||||
|
||||
export default dataSourceAdapter
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* 演示模式测试工具
|
||||
* 用于验证演示模式的各项功能
|
||||
*/
|
||||
|
||||
import { environmentDetector } from './environmentDetector'
|
||||
import { dataSourceAdapter } from './dataSourceAdapter'
|
||||
import { mockDataService } from './mockDataService'
|
||||
import { httpClient } from './http'
|
||||
import { useWebSocketStore } from '@/stores/websocketStore'
|
||||
|
||||
export class DemoModeTest {
|
||||
private results: { [key: string]: any } = {}
|
||||
|
||||
/**
|
||||
* 运行所有测试
|
||||
*/
|
||||
async runAllTests(): Promise<void> {
|
||||
console.log('=== 开始演示模式测试 ===')
|
||||
|
||||
try {
|
||||
await this.testEnvironmentDetection()
|
||||
await this.testDataSourceAdapter()
|
||||
await this.testMockDataGeneration()
|
||||
await this.testHttpClientDemoMode()
|
||||
await this.testWebSocketStoreDemoMode()
|
||||
|
||||
console.log('=== 演示模式测试完成 ===')
|
||||
console.log('测试结果:', this.results)
|
||||
|
||||
return this.results
|
||||
} catch (error) {
|
||||
console.error('演示模式测试失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试环境检测
|
||||
*/
|
||||
private async testEnvironmentDetection(): Promise<void> {
|
||||
console.log('测试 1: 环境检测')
|
||||
|
||||
const envInfo = environmentDetector.getEnvironmentInfo()
|
||||
this.results.environmentDetection = {
|
||||
passed: true,
|
||||
info: envInfo
|
||||
}
|
||||
|
||||
console.log('✅ 环境检测测试通过:', envInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试数据源适配器
|
||||
*/
|
||||
private async testDataSourceAdapter(): Promise<void> {
|
||||
console.log('测试 2: 数据源适配器')
|
||||
|
||||
try {
|
||||
// 测试进程列表获取
|
||||
const processes = await dataSourceAdapter.getProcessList()
|
||||
console.log(`✅ 获取进程列表成功,共 ${processes.length} 个进程`)
|
||||
|
||||
// 测试连接列表获取
|
||||
const connections = await dataSourceAdapter.getConnectionList()
|
||||
console.log(`✅ 获取连接列表成功,共 ${connections.length} 个连接`)
|
||||
|
||||
// 测试防火墙状态
|
||||
const firewallStatus = await dataSourceAdapter.getFirewallStatus()
|
||||
console.log(`✅ 获取防火墙状态成功,状态: ${firewallStatus.isEnabled ? '启用' : '禁用'}`)
|
||||
|
||||
// 测试防火墙规则
|
||||
const firewallRules = await dataSourceAdapter.getFirewallRules()
|
||||
console.log(`✅ 获取防火墙规则成功,共 ${firewallRules.length} 条规则`)
|
||||
|
||||
this.results.dataSourceAdapter = {
|
||||
passed: true,
|
||||
processCount: processes.length,
|
||||
connectionCount: connections.length,
|
||||
firewallEnabled: firewallStatus.isEnabled,
|
||||
ruleCount: firewallRules.length
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 数据源适配器测试失败:', error)
|
||||
this.results.dataSourceAdapter = {
|
||||
passed: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试模拟数据生成
|
||||
*/
|
||||
private async testMockDataGeneration(): Promise<void> {
|
||||
console.log('测试 3: 模拟数据生成')
|
||||
|
||||
try {
|
||||
// 测试各种数据生成
|
||||
const trafficStats = mockDataService.generateTrafficStats(12)
|
||||
const topApps = mockDataService.generateTopApplications(5)
|
||||
const protocolDist = mockDataService.generateProtocolDistribution()
|
||||
const securityEvents = mockDataService.generateSecurityEvents(5)
|
||||
const networkMetrics = mockDataService.generateNetworkMetrics()
|
||||
|
||||
console.log(`✅ 生成流量统计数据: ${trafficStats.length} 个数据点`)
|
||||
console.log(`✅ 生成应用排行数据: ${topApps.length} 个应用`)
|
||||
console.log(`✅ 生成协议分布数据: TCP=${protocolDist.TCP}, UDP=${protocolDist.UDP}`)
|
||||
console.log(`✅ 生成安全事件数据: ${securityEvents.length} 个事件`)
|
||||
console.log(`✅ 生成网络指标数据: 延迟=${networkMetrics.latency}ms`)
|
||||
|
||||
this.results.mockDataGeneration = {
|
||||
passed: true,
|
||||
trafficStatsCount: trafficStats.length,
|
||||
topAppsCount: topApps.length,
|
||||
protocolDistribution: protocolDist,
|
||||
securityEventsCount: securityEvents.length,
|
||||
networkMetrics
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 模拟数据生成测试失败:', error)
|
||||
this.results.mockDataGeneration = {
|
||||
passed: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试HTTP客户端演示模式
|
||||
*/
|
||||
private async testHttpClientDemoMode(): Promise<void> {
|
||||
console.log('测试 4: HTTP客户端演示模式')
|
||||
|
||||
try {
|
||||
// 测试各种API调用
|
||||
const processResponse = await httpClient.get('/api/processes')
|
||||
console.log('✅ 模拟进程API调用成功:', processResponse.success)
|
||||
|
||||
const firewallResponse = await httpClient.get('/api/firewall/status')
|
||||
console.log('✅ 模拟防火墙状态API调用成功:', firewallResponse.success)
|
||||
|
||||
const statsResponse = await httpClient.get('/api/stats')
|
||||
console.log('✅ 模拟统计API调用成功:', statsResponse.success)
|
||||
|
||||
// 测试POST请求
|
||||
const createResponse = await httpClient.post('/api/firewall/rules', {
|
||||
name: 'Test Rule',
|
||||
action: 'allow'
|
||||
})
|
||||
console.log('✅ 模拟创建规则API调用成功:', createResponse.success)
|
||||
|
||||
this.results.httpClientDemoMode = {
|
||||
passed: true,
|
||||
isDemoMode: httpClient.isDemoModeEnabled(),
|
||||
clientInfo: httpClient.getClientInfo()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ HTTP客户端演示模式测试失败:', error)
|
||||
this.results.httpClientDemoMode = {
|
||||
passed: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试WebSocket Store演示模式
|
||||
*/
|
||||
private async testWebSocketStoreDemoMode(): Promise<void> {
|
||||
console.log('测试 5: WebSocket Store演示模式')
|
||||
|
||||
try {
|
||||
// 这个测试需要在Vue组件环境中运行
|
||||
// 这里只做基本验证
|
||||
|
||||
this.results.websocketStoreDemoMode = {
|
||||
passed: true,
|
||||
note: '需要在Vue组件环境中进行完整测试'
|
||||
}
|
||||
|
||||
console.log('✅ WebSocket Store演示模式测试标记为通过(需要组件环境)')
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ WebSocket Store演示模式测试失败:', error)
|
||||
this.results.websocketStoreDemoMode = {
|
||||
passed: false,
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测试结果
|
||||
*/
|
||||
getResults() {
|
||||
return this.results
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试报告
|
||||
*/
|
||||
generateReport(): string {
|
||||
const passedTests = Object.values(this.results).filter(r => r.passed).length
|
||||
const totalTests = Object.keys(this.results).length
|
||||
|
||||
let report = `
|
||||
=== 演示模式测试报告 ===
|
||||
通过测试: ${passedTests}/${totalTests}
|
||||
环境信息: ${JSON.stringify(environmentDetector.getEnvironmentInfo(), null, 2)}
|
||||
|
||||
详细结果:
|
||||
`
|
||||
|
||||
Object.entries(this.results).forEach(([testName, result]) => {
|
||||
report += `
|
||||
${testName}: ${result.passed ? '✅ 通过' : '❌ 失败'}
|
||||
`
|
||||
if (!result.passed && result.error) {
|
||||
report += ` 错误: ${result.error}\n`
|
||||
}
|
||||
})
|
||||
|
||||
return report
|
||||
}
|
||||
}
|
||||
|
||||
// 导出测试实例
|
||||
export const demoModeTest = new DemoModeTest()
|
||||
|
||||
// 全局测试函数(可在控制台调用)
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).testDemoMode = async () => {
|
||||
await demoModeTest.runAllTests()
|
||||
console.log(demoModeTest.generateReport())
|
||||
return demoModeTest.getResults()
|
||||
}
|
||||
|
||||
console.log('💡 演示模式测试工具已加载,在控制台运行 testDemoMode() 开始测试')
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* 环境检测工具类
|
||||
* 用于检测当前运行环境并决定数据源类型
|
||||
*/
|
||||
|
||||
export enum DataSourceType {
|
||||
REAL_DATA = 'REAL_DATA', // 真实数据 (WebView2生产环境)
|
||||
MOCK_DATA = 'MOCK_DATA', // 模拟数据 (演示模式)
|
||||
DEV_API = 'DEV_API' // 开发API (开发环境)
|
||||
}
|
||||
|
||||
export enum EnvironmentType {
|
||||
PRODUCTION = 'PRODUCTION', // 生产环境 (WebView2)
|
||||
DEMO = 'DEMO', // 演示环境 (GitHub Pages)
|
||||
DEVELOPMENT = 'DEVELOPMENT' // 开发环境
|
||||
}
|
||||
|
||||
class EnvironmentDetector {
|
||||
private static instance: EnvironmentDetector | null = null
|
||||
|
||||
private _isWebView2: boolean | null = null
|
||||
private _isDemoMode: boolean | null = null
|
||||
private _dataSourceType: DataSourceType | null = null
|
||||
private _environmentType: EnvironmentType | null = null
|
||||
|
||||
static getInstance(): EnvironmentDetector {
|
||||
if (!EnvironmentDetector.instance) {
|
||||
EnvironmentDetector.instance = new EnvironmentDetector()
|
||||
}
|
||||
return EnvironmentDetector.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否运行在WebView2环境中
|
||||
*/
|
||||
isWebView2(): boolean {
|
||||
if (this._isWebView2 === null) {
|
||||
this._isWebView2 = !!(window.chrome && window.chrome.webview)
|
||||
console.log('[EnvironmentDetector] WebView2 detected:', this._isWebView2)
|
||||
}
|
||||
return this._isWebView2
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否开启了演示模式
|
||||
*/
|
||||
isDemoMode(): boolean {
|
||||
if (this._isDemoMode === null) {
|
||||
this._isDemoMode = import.meta.env.VITE_DEMO_MODE === 'true'
|
||||
console.log('[EnvironmentDetector] Demo mode enabled:', this._isDemoMode)
|
||||
}
|
||||
return this._isDemoMode
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前环境类型
|
||||
*/
|
||||
getEnvironmentType(): EnvironmentType {
|
||||
if (this._environmentType === null) {
|
||||
if (this.isWebView2()) {
|
||||
this._environmentType = EnvironmentType.PRODUCTION
|
||||
} else if (this.isDemoMode()) {
|
||||
this._environmentType = EnvironmentType.DEMO
|
||||
} else {
|
||||
this._environmentType = EnvironmentType.DEVELOPMENT
|
||||
}
|
||||
console.log('[EnvironmentDetector] Environment type:', this._environmentType)
|
||||
}
|
||||
return this._environmentType
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源类型
|
||||
*/
|
||||
getDataSourceType(): DataSourceType {
|
||||
if (this._dataSourceType === null) {
|
||||
if (this.isWebView2()) {
|
||||
// WebView2环境:使用真实数据(桥接通信)
|
||||
this._dataSourceType = DataSourceType.REAL_DATA
|
||||
} else if (this.isDemoMode()) {
|
||||
// 演示模式:使用模拟数据
|
||||
this._dataSourceType = DataSourceType.MOCK_DATA
|
||||
} else {
|
||||
// 开发环境:尝试连接真实API
|
||||
this._dataSourceType = DataSourceType.DEV_API
|
||||
}
|
||||
console.log('[EnvironmentDetector] Data source type:', this._dataSourceType)
|
||||
}
|
||||
return this._dataSourceType
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否应该使用模拟数据
|
||||
*/
|
||||
shouldUseMockData(): boolean {
|
||||
return this.getDataSourceType() === DataSourceType.MOCK_DATA
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否应该使用桥接通信
|
||||
*/
|
||||
shouldUseBridge(): boolean {
|
||||
return this.getDataSourceType() === DataSourceType.REAL_DATA
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否应该使用HTTP API
|
||||
*/
|
||||
shouldUseAPI(): boolean {
|
||||
return this.getDataSourceType() === DataSourceType.DEV_API
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API基础URL
|
||||
*/
|
||||
getApiBaseUrl(): string {
|
||||
return import.meta.env.VITE_APP_API_URL || 'http://localhost:8268/api'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取环境信息摘要
|
||||
*/
|
||||
getEnvironmentInfo() {
|
||||
return {
|
||||
isWebView2: this.isWebView2(),
|
||||
isDemoMode: this.isDemoMode(),
|
||||
environmentType: this.getEnvironmentType(),
|
||||
dataSourceType: this.getDataSourceType(),
|
||||
apiBaseUrl: this.getApiBaseUrl(),
|
||||
shouldUseMockData: this.shouldUseMockData(),
|
||||
shouldUseBridge: this.shouldUseBridge(),
|
||||
shouldUseAPI: this.shouldUseAPI()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置缓存(主要用于测试)
|
||||
*/
|
||||
reset(): void {
|
||||
this._isWebView2 = null
|
||||
this._isDemoMode = null
|
||||
this._dataSourceType = null
|
||||
this._environmentType = null
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const environmentDetector = EnvironmentDetector.getInstance()
|
||||
|
||||
// 导出便捷方法
|
||||
export const isWebView2 = () => environmentDetector.isWebView2()
|
||||
export const isDemoMode = () => environmentDetector.isDemoMode()
|
||||
export const shouldUseMockData = () => environmentDetector.shouldUseMockData()
|
||||
export const shouldUseBridge = () => environmentDetector.shouldUseBridge()
|
||||
export const shouldUseAPI = () => environmentDetector.shouldUseAPI()
|
||||
export const getDataSourceType = () => environmentDetector.getDataSourceType()
|
||||
export const getEnvironmentType = () => environmentDetector.getEnvironmentType()
|
||||
export const getEnvironmentInfo = () => environmentDetector.getEnvironmentInfo()
|
||||
|
||||
export default environmentDetector
|
||||
@@ -8,14 +8,21 @@ import axios, {
|
||||
// 或者混合导入(如果同时需要导入值和类型)
|
||||
import type { ApiResponse, HttpConfig, RequestConfig } from '@/types/http.ts'
|
||||
import { disLog, logB, logN, logS } from '@/utils/logHelper/logUtils'
|
||||
import { environmentDetector, shouldUseMockData } from '@/utils/environmentDetector'
|
||||
import { dataSourceAdapter } from '@/utils/dataSourceAdapter'
|
||||
|
||||
class HttpClient {
|
||||
private axiosInstance: AxiosInstance
|
||||
private pending: Map<string, AbortController> = new Map()
|
||||
private isDemoMode: boolean = shouldUseMockData()
|
||||
|
||||
constructor() {
|
||||
this.axiosInstance = axios.create()
|
||||
this.setupInterceptors()
|
||||
|
||||
// 打印环境信息
|
||||
console.log('[HttpClient] 初始化HTTP客户端')
|
||||
console.log('[HttpClient] 环境信息:', environmentDetector.getEnvironmentInfo())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,10 +186,240 @@ class HttpClient {
|
||||
logB.error(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理演示模式的API请求
|
||||
*/
|
||||
private async handleDemoRequest<T = unknown>(url: string, method: string, data?: unknown): Promise<ApiResponse<T>> {
|
||||
console.log(`[HttpClient] 演示模式:模拟API请求 ${method} ${url}`, data)
|
||||
|
||||
// 模拟网络延迟
|
||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 500 + 100))
|
||||
|
||||
try {
|
||||
// 根据URL路径返回相应的模拟数据
|
||||
let responseData: unknown
|
||||
|
||||
if (url.includes('/processes') || url.includes('/process')) {
|
||||
responseData = await dataSourceAdapter.getProcessList()
|
||||
} else if (url.includes('/connections') || url.includes('/connection')) {
|
||||
responseData = await dataSourceAdapter.getConnectionList()
|
||||
} else if (url.includes('/firewall/status')) {
|
||||
responseData = await dataSourceAdapter.getFirewallStatus()
|
||||
} else if (url.includes('/firewall/rules') || url.includes('/firewall/rule')) {
|
||||
// 防火墙规则需要返回特定格式的数据
|
||||
const rules = await dataSourceAdapter.getFirewallRules()
|
||||
responseData = {
|
||||
rules: rules,
|
||||
totalCount: rules.length,
|
||||
currentPage: 0,
|
||||
pageSize: rules.length
|
||||
}
|
||||
} else if (url.includes('/stats') || url.includes('/statistics/available-ranges')) {
|
||||
// 时间范围可用性
|
||||
responseData = [
|
||||
{ type: 'hour', available: true },
|
||||
{ type: 'day', available: true },
|
||||
{ type: 'week', available: true },
|
||||
{ type: 'month', available: false }
|
||||
]
|
||||
} else if (url.includes('/statistics/interfaces')) {
|
||||
// 网络接口列表
|
||||
responseData = [
|
||||
{
|
||||
id: 'eth0',
|
||||
name: 'Ethernet',
|
||||
displayName: '以太网',
|
||||
isActive: true,
|
||||
macAddress: '00:1B:44:11:3A:B7'
|
||||
},
|
||||
{
|
||||
id: 'wifi0',
|
||||
name: 'WiFi',
|
||||
displayName: 'WiFi适配器',
|
||||
isActive: true,
|
||||
macAddress: '00:1C:42:00:00:08'
|
||||
}
|
||||
]
|
||||
} else if (url.includes('/traffic/trends')) {
|
||||
// 流量趋势数据
|
||||
const now = Date.now()
|
||||
const points = []
|
||||
for (let i = 0; i < 60; i++) {
|
||||
points.push({
|
||||
timestamp: Math.floor((now - (59-i) * 60000) / 1000).toString(),
|
||||
uploadSpeed: Math.floor(Math.random() * 1000000) + 100000,
|
||||
downloadSpeed: Math.floor(Math.random() * 2000000) + 500000
|
||||
})
|
||||
}
|
||||
responseData = {
|
||||
interface: 'all',
|
||||
timeRange: '1hour',
|
||||
points: points
|
||||
}
|
||||
} else if (url.includes('/traffic/top-apps')) {
|
||||
// Top应用流量数据
|
||||
const topApps = [
|
||||
{ processName: 'chrome.exe', displayName: 'Google Chrome', icon: '', totalBytes: 2500000000 },
|
||||
{ processName: 'firefox.exe', displayName: 'Mozilla Firefox', icon: '', totalBytes: 1800000000 },
|
||||
{ processName: 'steam.exe', displayName: 'Steam', icon: '', totalBytes: 1200000000 },
|
||||
{ processName: 'discord.exe', displayName: 'Discord', icon: '', totalBytes: 800000000 },
|
||||
{ processName: 'spotify.exe', displayName: 'Spotify', icon: '', totalBytes: 600000000 },
|
||||
{ processName: 'skype.exe', displayName: 'Skype', icon: '', totalBytes: 400000000 },
|
||||
{ processName: 'zoom.exe', displayName: 'Zoom', icon: '', totalBytes: 350000000 },
|
||||
{ processName: 'teams.exe', displayName: 'Microsoft Teams', icon: '', totalBytes: 300000000 },
|
||||
{ processName: 'telegram.exe', displayName: 'Telegram', icon: '', totalBytes: 250000000 },
|
||||
{ processName: 'qbittorrent.exe', displayName: 'qBittorrent', icon: '', totalBytes: 200000000 }
|
||||
]
|
||||
responseData = topApps
|
||||
} else if (url.includes('/apps/top-traffic')) {
|
||||
// 软件流量排行数据
|
||||
const topTrafficApps = [
|
||||
{
|
||||
rank: 1,
|
||||
appId: 'chrome_001',
|
||||
processName: 'chrome.exe',
|
||||
displayName: 'Google Chrome',
|
||||
processPath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
||||
icon: '',
|
||||
version: '120.0.6099.109',
|
||||
company: 'Google LLC',
|
||||
totalBytes: 2500000000,
|
||||
uploadBytes: 250000000,
|
||||
connectionCount: 45
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
appId: 'firefox_001',
|
||||
processName: 'firefox.exe',
|
||||
displayName: 'Mozilla Firefox',
|
||||
processPath: 'C:\\Program Files\\Mozilla Firefox\\firefox.exe',
|
||||
icon: '',
|
||||
version: '121.0',
|
||||
company: 'Mozilla Corporation',
|
||||
totalBytes: 1800000000,
|
||||
uploadBytes: 180000000,
|
||||
connectionCount: 28
|
||||
},
|
||||
{
|
||||
rank: 3,
|
||||
appId: 'steam_001',
|
||||
processName: 'steam.exe',
|
||||
displayName: 'Steam',
|
||||
processPath: 'C:\\Program Files (x86)\\Steam\\steam.exe',
|
||||
icon: '',
|
||||
version: '3.4.15.7',
|
||||
company: 'Valve Corporation',
|
||||
totalBytes: 1200000000,
|
||||
uploadBytes: 120000000,
|
||||
connectionCount: 12
|
||||
},
|
||||
{
|
||||
rank: 4,
|
||||
appId: 'discord_001',
|
||||
processName: 'discord.exe',
|
||||
displayName: 'Discord',
|
||||
processPath: 'C:\\Users\\User\\AppData\\Local\\Discord\\app-1.0.9016\\Discord.exe',
|
||||
icon: '',
|
||||
version: '1.0.9016',
|
||||
company: 'Discord Inc.',
|
||||
totalBytes: 800000000,
|
||||
uploadBytes: 80000000,
|
||||
connectionCount: 8
|
||||
},
|
||||
{
|
||||
rank: 5,
|
||||
appId: 'spotify_001',
|
||||
processName: 'spotify.exe',
|
||||
displayName: 'Spotify',
|
||||
processPath: 'C:\\Users\\User\\AppData\\Roaming\\Spotify\\Spotify.exe',
|
||||
icon: '',
|
||||
version: '1.2.25.1011',
|
||||
company: 'Spotify AB',
|
||||
totalBytes: 600000000,
|
||||
uploadBytes: 60000000,
|
||||
connectionCount: 15
|
||||
}
|
||||
]
|
||||
|
||||
responseData = {
|
||||
total: topTrafficApps.length,
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
items: topTrafficApps
|
||||
}
|
||||
} else if (url.includes('/statistics')) {
|
||||
responseData = {
|
||||
timestamp: Date.now(),
|
||||
totalConnections: Math.floor(Math.random() * 100) + 50,
|
||||
activeConnections: Math.floor(Math.random() * 50) + 20,
|
||||
totalBandwidth: Math.floor(Math.random() * 1000000) + 100000,
|
||||
currentBandwidth: Math.floor(Math.random() * 100000) + 10000
|
||||
}
|
||||
} else if (url.includes('/unsubscribe')) {
|
||||
// 取消订阅请求
|
||||
responseData = {
|
||||
message: '演示模式:取消订阅成功',
|
||||
success: true,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
} else {
|
||||
// 根据HTTP方法提供不同的响应
|
||||
if (method === 'POST') {
|
||||
responseData = {
|
||||
message: '演示模式:创建成功',
|
||||
id: Date.now(),
|
||||
timestamp: Date.now()
|
||||
}
|
||||
} else if (method === 'PUT' || method === 'PATCH') {
|
||||
responseData = {
|
||||
message: '演示模式:更新成功',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
} else if (method === 'DELETE') {
|
||||
responseData = {
|
||||
message: '演示模式:删除成功',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
} else {
|
||||
responseData = {
|
||||
message: '演示模式:操作成功',
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
code: 200,
|
||||
message: '请求成功',
|
||||
data: responseData as T,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[HttpClient] 演示模式请求处理错误:', error)
|
||||
return {
|
||||
success: false,
|
||||
code: 500,
|
||||
message: '演示模式:请求处理失败',
|
||||
data: null as T,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求方法
|
||||
*/
|
||||
async request<T = unknown>(config: RequestConfig): Promise<ApiResponse<T>> {
|
||||
// 演示模式下使用模拟数据
|
||||
if (this.isDemoMode) {
|
||||
return this.handleDemoRequest<T>(
|
||||
config.url || '',
|
||||
config.method?.toUpperCase() || 'GET',
|
||||
config.data || config.params
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.axiosInstance.request<ApiResponse<T>>(config)
|
||||
return response.data
|
||||
@@ -287,6 +524,34 @@ class HttpClient {
|
||||
getAxiosInstance(): AxiosInstance {
|
||||
return this.axiosInstance
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取演示模式状态
|
||||
*/
|
||||
isDemoModeEnabled(): boolean {
|
||||
return this.isDemoMode
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取HTTP客户端信息
|
||||
*/
|
||||
getClientInfo() {
|
||||
return {
|
||||
isDemoMode: this.isDemoMode,
|
||||
environmentInfo: environmentDetector.getEnvironmentInfo(),
|
||||
pendingRequests: this.pending.size,
|
||||
baseURL: this.axiosInstance.defaults.baseURL,
|
||||
timeout: this.axiosInstance.defaults.timeout
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制切换演示模式(用于测试)
|
||||
*/
|
||||
setDemoMode(enabled: boolean): void {
|
||||
console.log(`[HttpClient] 切换演示模式: ${enabled}`)
|
||||
this.isDemoMode = enabled
|
||||
}
|
||||
}
|
||||
|
||||
export const httpClient = new HttpClient()
|
||||
@@ -0,0 +1,660 @@
|
||||
/**
|
||||
* 模拟数据生成服务
|
||||
* 为演示模式提供逼真的网络监控数据
|
||||
*/
|
||||
|
||||
import type { ProcessType, ConnectionInfo, IPEndPoint } from '@/types/process'
|
||||
import type { FirewallRule, FirewallStatus, FirewallStatistics, DisplayRule } from '@/types/firewall'
|
||||
import type { WebSocketResponse } from '@/types/websocket'
|
||||
import { ConnectionState, ProtocolType, TrafficDirection } from '@/constants/enums'
|
||||
import { FirewallProfile, RuleDirection, RuleAction } from '@/types/firewall'
|
||||
|
||||
class MockDataService {
|
||||
private static instance: MockDataService | null = null
|
||||
private processId = 1000
|
||||
private connectionId = 10000
|
||||
private ruleId = 1
|
||||
|
||||
static getInstance(): MockDataService {
|
||||
if (!MockDataService.instance) {
|
||||
MockDataService.instance = new MockDataService()
|
||||
}
|
||||
return MockDataService.instance
|
||||
}
|
||||
|
||||
// ================= 随机数据生成工具 =================
|
||||
|
||||
private randomInt(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
private randomFloat(min: number, max: number): number {
|
||||
return Math.random() * (max - min) + min
|
||||
}
|
||||
|
||||
private randomChoice<T>(array: T[]): T {
|
||||
return array[Math.floor(Math.random() * array.length)]
|
||||
}
|
||||
|
||||
private randomBoolean(probability = 0.5): boolean {
|
||||
return Math.random() < probability
|
||||
}
|
||||
|
||||
private generateIPAddress(): string {
|
||||
// 生成常见的内网和外网IP
|
||||
const types = [
|
||||
() => `192.168.${this.randomInt(1, 255)}.${this.randomInt(1, 254)}`, // 内网
|
||||
() => `10.${this.randomInt(0, 255)}.${this.randomInt(1, 255)}.${this.randomInt(1, 254)}`, // 内网
|
||||
() => `172.${this.randomInt(16, 31)}.${this.randomInt(1, 255)}.${this.randomInt(1, 254)}`, // 内网
|
||||
() => `${this.randomInt(1, 223)}.${this.randomInt(0, 255)}.${this.randomInt(0, 255)}.${this.randomInt(1, 254)}`, // 公网
|
||||
]
|
||||
return this.randomChoice(types)()
|
||||
}
|
||||
|
||||
private generateProcessName(): string {
|
||||
const processes = [
|
||||
'chrome.exe', 'firefox.exe', 'edge.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'
|
||||
]
|
||||
return this.randomChoice(processes)
|
||||
}
|
||||
|
||||
private generateDomainName(): string {
|
||||
const domains = [
|
||||
'www.google.com', 'www.baidu.com', 'api.github.com', 'cdn.jsdelivr.net',
|
||||
'www.microsoft.com', 'discord.com', 'open.spotify.com', 'www.youtube.com',
|
||||
'api.twitter.com', 'www.facebook.com', 'cdn.cloudflare.com', 'amazonaws.com',
|
||||
'azure.microsoft.com', 'googleapis.com', 'www.apple.com', 'developer.mozilla.org'
|
||||
]
|
||||
return this.randomChoice(domains)
|
||||
}
|
||||
|
||||
// ================= 网络连接数据生成 =================
|
||||
|
||||
generateIPEndPoint(isLocal = false): IPEndPoint {
|
||||
return {
|
||||
address: isLocal ? '127.0.0.1' : this.generateIPAddress(),
|
||||
port: isLocal ? this.randomInt(1024, 65535) : this.randomInt(80, 65535),
|
||||
addressFamily: 2 // IPv4
|
||||
}
|
||||
}
|
||||
|
||||
generateConnectionInfo(processId: number): ConnectionInfo {
|
||||
const protocol = this.randomChoice([ProtocolType.TCP, ProtocolType.UDP])
|
||||
const state = protocol === ProtocolType.TCP
|
||||
? this.randomChoice([ConnectionState.ESTABLISHED, ConnectionState.LISTEN, ConnectionState.TIME_WAIT, ConnectionState.CLOSE_WAIT])
|
||||
: ConnectionState.Connected
|
||||
|
||||
const startTime = new Date(Date.now() - this.randomInt(0, 3600000)) // 过去1小时内
|
||||
const lastActiveTime = new Date(Date.now() - this.randomInt(0, 300000)) // 过去5分钟内
|
||||
|
||||
return {
|
||||
connectionKey: `${processId}_${this.connectionId++}`,
|
||||
processId,
|
||||
protocol,
|
||||
localEndpoint: this.generateIPEndPoint(true),
|
||||
remoteEndpoint: this.generateIPEndPoint(false),
|
||||
state,
|
||||
direction: this.randomChoice([TrafficDirection.Inbound, TrafficDirection.Outbound]),
|
||||
startTime,
|
||||
lastActiveTime,
|
||||
bytesSent: this.randomInt(1024, 1024 * 1024 * 100), // 1KB - 100MB
|
||||
bytesReceived: this.randomInt(1024, 1024 * 1024 * 100),
|
||||
currentSendSpeed: this.randomInt(0, 1024 * 100), // 0 - 100KB/s
|
||||
currentReceiveSpeed: this.randomInt(0, 1024 * 100),
|
||||
isActive: this.randomBoolean(0.7)
|
||||
}
|
||||
}
|
||||
|
||||
generateProcessInfo(): ProcessType {
|
||||
const processName = this.generateProcessName()
|
||||
const startTime = new Date(Date.now() - this.randomInt(0, 86400000)) // 过去24小时内
|
||||
const connectionCount = this.randomInt(0, 20)
|
||||
const connections: ConnectionInfo[] = []
|
||||
|
||||
const pid = this.processId++
|
||||
|
||||
// 生成连接
|
||||
for (let i = 0; i < connectionCount; i++) {
|
||||
connections.push(this.generateConnectionInfo(pid))
|
||||
}
|
||||
|
||||
const totalUploaded = this.randomInt(1024, 1024 * 1024 * 1024) // 1KB - 1GB
|
||||
const totalDownloaded = this.randomInt(1024, 1024 * 1024 * 1024)
|
||||
|
||||
return {
|
||||
processName,
|
||||
processId: pid,
|
||||
startTime,
|
||||
hasExited: this.randomBoolean(0.1),
|
||||
useMemory: this.randomInt(1024 * 1024, 1024 * 1024 * 512), // 1MB - 512MB
|
||||
threadCount: this.randomInt(1, 50),
|
||||
mainModulePath: `C:\\Program Files\\${processName.replace('.exe', '')}\\${processName}`,
|
||||
mainModuleName: processName,
|
||||
totalUploaded,
|
||||
totalDownloaded,
|
||||
uploadSpeed: this.randomInt(0, 1024 * 100), // 0 - 100KB/s
|
||||
downloadSpeed: this.randomInt(0, 1024 * 100),
|
||||
connections
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 防火墙数据生成 =================
|
||||
|
||||
generateFirewallRule(): FirewallRule {
|
||||
const appNames = [
|
||||
'Google Chrome', 'Mozilla Firefox', 'Microsoft Edge', 'Visual Studio Code',
|
||||
'Steam', 'Discord', 'Spotify', 'WeChat', 'QQ', 'TeamViewer'
|
||||
]
|
||||
|
||||
const directions = [RuleDirection.Inbound, RuleDirection.Outbound]
|
||||
const protocols = [ProtocolType.TCP, ProtocolType.UDP, ProtocolType.Any]
|
||||
const actions = [RuleAction.Allow, RuleAction.Block]
|
||||
|
||||
const app = this.randomChoice(appNames)
|
||||
const direction = this.randomChoice(directions)
|
||||
const protocol = this.randomChoice(protocols)
|
||||
const action = this.randomChoice(actions)
|
||||
const enabled = this.randomBoolean(0.8)
|
||||
|
||||
const ruleName = `${app} - ${direction === RuleDirection.Inbound ? '入站' : '出站'} - ${action === RuleAction.Allow ? '允许' : '阻止'}`
|
||||
|
||||
return {
|
||||
name: ruleName,
|
||||
description: `${app}的${direction === RuleDirection.Inbound ? '入站' : '出站'}连接规则`,
|
||||
applicationName: `C:\\Program Files\\${app}\\${app.replace(' ', '')}.exe`,
|
||||
serviceName: '',
|
||||
protocol,
|
||||
localPorts: protocol === ProtocolType.TCP ? this.randomChoice(['80', '443', '8080', '3000', 'Any']) : 'Any',
|
||||
remotePorts: 'Any',
|
||||
localAddresses: 'Any',
|
||||
remoteAddresses: 'Any',
|
||||
icmpTypesAndCodes: '',
|
||||
direction,
|
||||
enabled,
|
||||
profiles: FirewallProfile.All,
|
||||
edgeTraversal: false,
|
||||
action,
|
||||
grouping: `${app} 应用程序`,
|
||||
interfaceTypes: 'All',
|
||||
interfaces: [],
|
||||
creationDate: new Date(Date.now() - this.randomInt(0, 30 * 24 * 3600000)).toISOString(),
|
||||
modificationDate: new Date(Date.now() - this.randomInt(0, 7 * 24 * 3600000)).toISOString(),
|
||||
edgeTraversalAllowed: false,
|
||||
looseSourceMapping: false,
|
||||
localOnlyMapping: false,
|
||||
remoteMachineAuthorizationList: '',
|
||||
remoteUserAuthorizationList: '',
|
||||
embeddedContext: '',
|
||||
flags: 0,
|
||||
secureFlags: false
|
||||
}
|
||||
}
|
||||
|
||||
generateFirewallStatus(): FirewallStatus {
|
||||
const totalRules = this.randomInt(50, 200)
|
||||
const enabledRules = Math.floor(totalRules * this.randomFloat(0.6, 0.9))
|
||||
|
||||
return {
|
||||
isEnabled: true,
|
||||
profileStatuses: {
|
||||
[FirewallProfile.Domain]: {
|
||||
profile: FirewallProfile.Domain,
|
||||
isEnabled: true,
|
||||
blockAllInboundTraffic: false,
|
||||
notifyOnListen: true,
|
||||
unicastResponsesDisabled: false,
|
||||
defaultInboundAction: RuleAction.Block,
|
||||
defaultOutboundAction: RuleAction.Allow
|
||||
},
|
||||
[FirewallProfile.Private]: {
|
||||
profile: FirewallProfile.Private,
|
||||
isEnabled: true,
|
||||
blockAllInboundTraffic: false,
|
||||
notifyOnListen: true,
|
||||
unicastResponsesDisabled: false,
|
||||
defaultInboundAction: RuleAction.Block,
|
||||
defaultOutboundAction: RuleAction.Allow
|
||||
},
|
||||
[FirewallProfile.Public]: {
|
||||
profile: FirewallProfile.Public,
|
||||
isEnabled: true,
|
||||
blockAllInboundTraffic: true,
|
||||
notifyOnListen: true,
|
||||
unicastResponsesDisabled: false,
|
||||
defaultInboundAction: RuleAction.Block,
|
||||
defaultOutboundAction: RuleAction.Allow
|
||||
},
|
||||
[FirewallProfile.All]: {
|
||||
profile: FirewallProfile.All,
|
||||
isEnabled: true,
|
||||
blockAllInboundTraffic: false,
|
||||
notifyOnListen: true,
|
||||
unicastResponsesDisabled: false,
|
||||
defaultInboundAction: RuleAction.Block,
|
||||
defaultOutboundAction: RuleAction.Allow
|
||||
}
|
||||
},
|
||||
totalRules,
|
||||
enabledRules,
|
||||
inboundRules: Math.floor(totalRules * 0.4),
|
||||
outboundRules: Math.floor(totalRules * 0.6),
|
||||
lastModified: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 批量数据生成 =================
|
||||
|
||||
generateProcessList(count = 20): ProcessType[] {
|
||||
const processes: ProcessType[] = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
processes.push(this.generateProcessInfo())
|
||||
}
|
||||
return processes
|
||||
}
|
||||
|
||||
generateFirewallRules(count = 30): FirewallRule[] {
|
||||
const rules: FirewallRule[] = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
rules.push(this.generateFirewallRule())
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
// ================= 实时数据更新模拟 =================
|
||||
|
||||
generateRealtimeUpdate(): WebSocketResponse {
|
||||
const updateTypes = [
|
||||
'process_update',
|
||||
'connection_update',
|
||||
'traffic_update',
|
||||
'firewall_event'
|
||||
]
|
||||
|
||||
const type = this.randomChoice(updateTypes)
|
||||
const timestamp = new Date().toISOString()
|
||||
|
||||
switch (type) {
|
||||
case 'process_update':
|
||||
return {
|
||||
type,
|
||||
data: this.generateProcessInfo(),
|
||||
timestamp
|
||||
}
|
||||
|
||||
case 'connection_update':
|
||||
return {
|
||||
type,
|
||||
data: this.generateConnectionInfo(this.randomInt(1000, 2000)),
|
||||
timestamp
|
||||
}
|
||||
|
||||
case 'traffic_update':
|
||||
return {
|
||||
type,
|
||||
data: {
|
||||
totalBytesIn: this.randomInt(1024 * 1024, 1024 * 1024 * 1024),
|
||||
totalBytesOut: this.randomInt(1024 * 1024, 1024 * 1024 * 1024),
|
||||
currentSpeedIn: this.randomInt(0, 1024 * 1024), // 0 - 1MB/s
|
||||
currentSpeedOut: this.randomInt(0, 1024 * 1024),
|
||||
timestamp: Date.now()
|
||||
},
|
||||
timestamp
|
||||
}
|
||||
|
||||
case 'firewall_event':
|
||||
return {
|
||||
type,
|
||||
data: {
|
||||
action: this.randomChoice(['blocked', 'allowed']),
|
||||
protocol: this.randomChoice(['TCP', 'UDP']),
|
||||
sourceIP: this.generateIPAddress(),
|
||||
destPort: this.randomInt(80, 65535),
|
||||
process: this.generateProcessName(),
|
||||
timestamp: Date.now()
|
||||
},
|
||||
timestamp
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
type: 'unknown',
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 统计数据生成 =================
|
||||
|
||||
generateTrafficStats(hours = 24) {
|
||||
const stats = []
|
||||
const now = Date.now()
|
||||
|
||||
for (let i = hours; i >= 0; i--) {
|
||||
const timestamp = now - (i * 3600000) // 每小时
|
||||
|
||||
// 模拟一天中的流量变化模式(工作时间流量更高)
|
||||
const hour = new Date(timestamp).getHours()
|
||||
let trafficMultiplier = 1
|
||||
|
||||
if (hour >= 9 && hour <= 18) {
|
||||
trafficMultiplier = this.randomFloat(1.5, 2.5) // 工作时间流量更高
|
||||
} else if (hour >= 19 && hour <= 23) {
|
||||
trafficMultiplier = this.randomFloat(1.2, 1.8) // 晚上娱乐时间
|
||||
} else {
|
||||
trafficMultiplier = this.randomFloat(0.3, 0.8) // 深夜和凌晨较低
|
||||
}
|
||||
|
||||
const baseBytesIn = this.randomInt(1024 * 1024 * 10, 1024 * 1024 * 50)
|
||||
const baseBytesOut = this.randomInt(1024 * 1024 * 5, 1024 * 1024 * 25)
|
||||
|
||||
stats.push({
|
||||
timestamp,
|
||||
bytesIn: Math.floor(baseBytesIn * trafficMultiplier),
|
||||
bytesOut: Math.floor(baseBytesOut * trafficMultiplier),
|
||||
packetsIn: this.randomInt(1000 * trafficMultiplier, 10000 * trafficMultiplier),
|
||||
packetsOut: this.randomInt(500 * trafficMultiplier, 5000 * trafficMultiplier),
|
||||
connections: this.randomInt(10, Math.floor(100 * trafficMultiplier)),
|
||||
hour
|
||||
})
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
generateTopApplications(count = 10) {
|
||||
const apps = [
|
||||
{ name: 'Google Chrome', bytes: this.randomInt(1024 * 1024 * 100, 1024 * 1024 * 1024) },
|
||||
{ name: 'Steam', bytes: this.randomInt(1024 * 1024 * 50, 1024 * 1024 * 500) },
|
||||
{ name: 'Discord', bytes: this.randomInt(1024 * 1024 * 10, 1024 * 1024 * 100) },
|
||||
{ name: 'Visual Studio Code', bytes: this.randomInt(1024 * 1024 * 5, 1024 * 1024 * 50) },
|
||||
{ name: 'WeChat', bytes: this.randomInt(1024 * 1024 * 5, 1024 * 1024 * 30) },
|
||||
{ name: 'Spotify', bytes: this.randomInt(1024 * 1024 * 10, 1024 * 1024 * 80) },
|
||||
{ name: 'Microsoft Edge', bytes: this.randomInt(1024 * 1024 * 20, 1024 * 1024 * 200) },
|
||||
{ name: 'QQ', bytes: this.randomInt(1024 * 1024 * 3, 1024 * 1024 * 20) },
|
||||
{ name: 'TeamViewer', bytes: this.randomInt(1024 * 1024 * 1, 1024 * 1024 * 10) },
|
||||
{ name: 'Node.js', bytes: this.randomInt(1024 * 1024 * 2, 1024 * 1024 * 15) }
|
||||
]
|
||||
|
||||
return apps
|
||||
.sort((a, b) => b.bytes - a.bytes)
|
||||
.slice(0, count)
|
||||
}
|
||||
|
||||
// ================= 专门为网络分析生成的数据 =================
|
||||
|
||||
/**
|
||||
* 生成网络协议分布数据
|
||||
*/
|
||||
generateProtocolDistribution() {
|
||||
const total = this.randomInt(1000, 5000)
|
||||
const tcpPercent = this.randomFloat(0.6, 0.8)
|
||||
const udpPercent = this.randomFloat(0.15, 0.3)
|
||||
const otherPercent = 1 - tcpPercent - udpPercent
|
||||
|
||||
return {
|
||||
TCP: Math.floor(total * tcpPercent),
|
||||
UDP: Math.floor(total * udpPercent),
|
||||
ICMP: Math.floor(total * otherPercent * 0.8),
|
||||
Other: Math.floor(total * otherPercent * 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成端口使用统计
|
||||
*/
|
||||
generatePortStats() {
|
||||
const commonPorts = [
|
||||
{ port: 80, name: 'HTTP', connections: this.randomInt(50, 200) },
|
||||
{ port: 443, name: 'HTTPS', connections: this.randomInt(100, 300) },
|
||||
{ port: 53, name: 'DNS', connections: this.randomInt(20, 80) },
|
||||
{ port: 22, name: 'SSH', connections: this.randomInt(1, 10) },
|
||||
{ port: 21, name: 'FTP', connections: this.randomInt(0, 5) },
|
||||
{ port: 25, name: 'SMTP', connections: this.randomInt(5, 20) },
|
||||
{ port: 993, name: 'IMAPS', connections: this.randomInt(3, 15) },
|
||||
{ port: 3389, name: 'RDP', connections: this.randomInt(0, 3) },
|
||||
{ port: 8080, name: 'HTTP-Alt', connections: this.randomInt(10, 50) },
|
||||
{ port: 3001, name: 'Dev-Server', connections: this.randomInt(1, 10) }
|
||||
]
|
||||
|
||||
return commonPorts.sort((a, b) => b.connections - a.connections)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成地理位置分布数据
|
||||
*/
|
||||
generateGeoDistribution() {
|
||||
const locations = [
|
||||
{ country: '中国', city: '北京', connections: this.randomInt(100, 300), flag: '🇨🇳' },
|
||||
{ country: '美国', city: '洛杉矶', connections: this.randomInt(50, 150), flag: '🇺🇸' },
|
||||
{ country: '日本', city: '东京', connections: this.randomInt(20, 80), flag: '🇯🇵' },
|
||||
{ country: '德国', city: '法兰克福', connections: this.randomInt(15, 60), flag: '🇩🇪' },
|
||||
{ country: '新加坡', city: '新加坡', connections: this.randomInt(10, 40), flag: '🇸🇬' },
|
||||
{ country: '英国', city: '伦敦', connections: this.randomInt(8, 35), flag: '🇬🇧' },
|
||||
{ country: '法国', city: '巴黎', connections: this.randomInt(5, 25), flag: '🇫🇷' },
|
||||
{ country: '韩国', city: '首尔', connections: this.randomInt(12, 45), flag: '🇰🇷' }
|
||||
]
|
||||
|
||||
return locations.sort((a, b) => b.connections - a.connections)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成网络威胁事件
|
||||
*/
|
||||
generateSecurityEvents(count = 10) {
|
||||
const threatTypes = [
|
||||
'端口扫描', '暴力破解', '恶意软件通信', 'DDoS攻击',
|
||||
'异常流量', '可疑连接', '数据泄露尝试', '未授权访问'
|
||||
]
|
||||
|
||||
const severityLevels = ['低', '中', '高', '严重']
|
||||
const events = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const timestamp = Date.now() - this.randomInt(0, 86400000) // 过去24小时
|
||||
events.push({
|
||||
id: `event_${timestamp}_${i}`,
|
||||
type: this.randomChoice(threatTypes),
|
||||
severity: this.randomChoice(severityLevels),
|
||||
sourceIP: this.generateIPAddress(),
|
||||
targetPort: this.randomInt(1, 65535),
|
||||
timestamp,
|
||||
blocked: this.randomBoolean(0.7),
|
||||
description: `检测到${this.randomChoice(threatTypes)}行为`
|
||||
})
|
||||
}
|
||||
|
||||
return events.sort((a, b) => b.timestamp - a.timestamp)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成网络性能指标
|
||||
*/
|
||||
generateNetworkMetrics() {
|
||||
return {
|
||||
latency: this.randomInt(10, 100), // ms
|
||||
packetLoss: this.randomFloat(0, 2), // %
|
||||
jitter: this.randomInt(1, 20), // ms
|
||||
throughput: this.randomInt(50, 1000), // Mbps
|
||||
bandwidth: this.randomInt(100, 1000), // Mbps
|
||||
uptime: this.randomFloat(99.5, 99.99), // %
|
||||
errorRate: this.randomFloat(0, 0.5) // %
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成实时网络流量图表数据
|
||||
*/
|
||||
generateRealtimeTrafficChart(points = 60) {
|
||||
const data = []
|
||||
const now = Date.now()
|
||||
|
||||
for (let i = points; i >= 0; i--) {
|
||||
const timestamp = now - (i * 1000) // 每秒一个点
|
||||
const hour = new Date(timestamp).getHours()
|
||||
|
||||
// 根据时间调整基础流量
|
||||
let baseTraffic = 1
|
||||
if (hour >= 9 && hour <= 18) baseTraffic = 2
|
||||
else if (hour >= 19 && hour <= 23) baseTraffic = 1.5
|
||||
else baseTraffic = 0.5
|
||||
|
||||
// 添加一些随机波动
|
||||
const variation = this.randomFloat(0.7, 1.3)
|
||||
|
||||
data.push({
|
||||
timestamp,
|
||||
uploadSpeed: Math.floor(this.randomInt(1024 * 10, 1024 * 100) * baseTraffic * variation), // KB/s
|
||||
downloadSpeed: Math.floor(this.randomInt(1024 * 50, 1024 * 500) * baseTraffic * variation), // KB/s
|
||||
totalConnections: this.randomInt(10, Math.floor(50 * baseTraffic)),
|
||||
activeConnections: this.randomInt(5, Math.floor(25 * baseTraffic))
|
||||
})
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成应用程序列表
|
||||
*/
|
||||
generateApplicationList(count = 15) {
|
||||
const applications = []
|
||||
|
||||
const sampleApps = [
|
||||
{
|
||||
name: 'Google Chrome',
|
||||
process: 'chrome.exe',
|
||||
company: 'Google LLC',
|
||||
version: '120.0.6099.109',
|
||||
description: 'Google Chrome'
|
||||
},
|
||||
{
|
||||
name: 'Visual Studio Code',
|
||||
process: 'Code.exe',
|
||||
company: 'Microsoft Corporation',
|
||||
version: '1.85.1',
|
||||
description: 'Visual Studio Code'
|
||||
},
|
||||
{
|
||||
name: 'Steam',
|
||||
process: 'steam.exe',
|
||||
company: 'Valve Corporation',
|
||||
version: '3.4.15.7',
|
||||
description: 'Steam'
|
||||
},
|
||||
{
|
||||
name: 'Discord',
|
||||
process: 'Discord.exe',
|
||||
company: 'Discord Inc.',
|
||||
version: '1.0.9016',
|
||||
description: 'Discord'
|
||||
},
|
||||
{
|
||||
name: 'Spotify',
|
||||
process: 'Spotify.exe',
|
||||
company: 'Spotify AB',
|
||||
version: '1.2.25.1011',
|
||||
description: 'Spotify'
|
||||
},
|
||||
{
|
||||
name: 'Microsoft Edge',
|
||||
process: 'msedge.exe',
|
||||
company: 'Microsoft Corporation',
|
||||
version: '120.0.2210.89',
|
||||
description: 'Microsoft Edge'
|
||||
},
|
||||
{
|
||||
name: 'Windows Explorer',
|
||||
process: 'explorer.exe',
|
||||
company: 'Microsoft Corporation',
|
||||
version: '10.0.19041.3636',
|
||||
description: 'Windows Explorer'
|
||||
},
|
||||
{
|
||||
name: 'Notepad++',
|
||||
process: 'notepad++.exe',
|
||||
company: 'Notepad++ Team',
|
||||
version: '8.5.8',
|
||||
description: 'Notepad++'
|
||||
}
|
||||
]
|
||||
|
||||
for (let i = 0; i < Math.min(count, sampleApps.length); i++) {
|
||||
const app = sampleApps[i]
|
||||
const processCount = Math.floor(Math.random() * 5) + 1
|
||||
const processIds = Array.from({length: processCount}, () => Math.floor(Math.random() * 9999) + 1000)
|
||||
|
||||
applications.push({
|
||||
id: `app_${i + 1}_${Date.now()}`,
|
||||
productName: app.name,
|
||||
processName: app.process,
|
||||
processIds: processIds,
|
||||
startTime: new Date(Date.now() - Math.random() * 86400000).toISOString(),
|
||||
hasExited: false,
|
||||
exitTime: undefined,
|
||||
exitCode: undefined,
|
||||
useMemory: Math.floor(Math.random() * 500000000) + 50000000, // 50MB - 550MB
|
||||
threadCount: Math.floor(Math.random() * 50) + 5,
|
||||
mainModulePath: `C:\\Program Files\\${app.name}\\${app.process}`,
|
||||
mainModuleName: app.process,
|
||||
fileDescription: app.description,
|
||||
companyName: app.company,
|
||||
version: app.version,
|
||||
legalCopyright: `© ${app.company}`,
|
||||
iconBase64: '', // 空字符串,会使用fallback图标
|
||||
name: app.name // 添加name属性用于显示
|
||||
})
|
||||
}
|
||||
|
||||
return applications
|
||||
}
|
||||
|
||||
// ================= 组合数据生成(为各个模块提供完整数据集) =================
|
||||
|
||||
/**
|
||||
* 生成监控模块完整数据
|
||||
*/
|
||||
generateMonitorModuleData() {
|
||||
return {
|
||||
processes: this.generateProcessList(25),
|
||||
connections: [], // 会从processes中提取
|
||||
realtimeChart: this.generateRealtimeTrafficChart(),
|
||||
networkMetrics: this.generateNetworkMetrics(),
|
||||
protocolStats: this.generateProtocolDistribution()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成防火墙模块完整数据
|
||||
*/
|
||||
generateFirewallModuleData() {
|
||||
return {
|
||||
status: this.generateFirewallStatus(),
|
||||
rules: this.generateFirewallRules(40),
|
||||
securityEvents: this.generateSecurityEvents(15),
|
||||
portStats: this.generatePortStats()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成分析模块完整数据
|
||||
*/
|
||||
generateAnalysisModuleData() {
|
||||
return {
|
||||
trafficStats: this.generateTrafficStats(48), // 48小时数据
|
||||
topApplications: this.generateTopApplications(15),
|
||||
protocolDistribution: this.generateProtocolDistribution(),
|
||||
geoDistribution: this.generateGeoDistribution(),
|
||||
portStats: this.generatePortStats(),
|
||||
securityEvents: this.generateSecurityEvents(20),
|
||||
networkMetrics: this.generateNetworkMetrics()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const mockDataService = MockDataService.getInstance()
|
||||
export default mockDataService
|
||||
@@ -39,8 +39,8 @@ export default defineConfig(({ mode }) => {
|
||||
// 防止 Vite 掩盖 Rust 错误
|
||||
clearScreen: false,
|
||||
server: {
|
||||
port: 3000,
|
||||
strictPort: true,
|
||||
port: 0,
|
||||
strictPort: false,
|
||||
host: "0.0.0.0",
|
||||
watch: {
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user