Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68a6d48e71 |
@@ -1,271 +0,0 @@
|
||||
# 网络设备管理平台
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是网络设备管理平台的前端项目,用于在地铁线路运管中心检测和查看各车站网络设备的详细数据、运行情况、异常告警,并提供分析、统计、日志和权限管理能力。
|
||||
|
||||
主要设备类型:
|
||||
|
||||
- 摄像机
|
||||
- 网络录像机
|
||||
- 交换机
|
||||
- 解码器
|
||||
- 智能安防箱
|
||||
- 媒体服务器
|
||||
- 视频服务器
|
||||
- 网络键盘
|
||||
- 报警主机
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 包管理:pnpm
|
||||
- 构建工具:Vite
|
||||
- 前端框架:Vue 3
|
||||
- 语言:TypeScript
|
||||
- 路由:Vue Router
|
||||
- 组件库:Naive UI
|
||||
- 状态管理:Pinia + pinia-plugin-persistedstate
|
||||
- 服务端状态/轮询:TanStack Vue Query
|
||||
- 本地持久化:localStorage、sessionStorage、IndexedDB/localforage
|
||||
- 网络请求:axios
|
||||
- 实时消息:STOMP/WebSocket(`@stomp/stompjs`)
|
||||
- 图表:ECharts
|
||||
- 图标:lucide-vue-next
|
||||
- 样式:Sass
|
||||
|
||||
## 环境与脚本
|
||||
|
||||
### 运行环境
|
||||
|
||||
- Node.js:`^20.19.0 || >=22.12.0`
|
||||
- pnpm:以 `package.json` 中 `packageManager` 为准
|
||||
|
||||
### 常用命令
|
||||
|
||||
```bash
|
||||
pnpm install # 安装依赖
|
||||
pnpm dev # 启动开发服务,默认端口 9763
|
||||
pnpm build # 类型检查 + Vite 构建 + 构建产物压缩
|
||||
pnpm preview # 预览构建产物
|
||||
pnpm build-only # 仅执行 Vite 构建
|
||||
pnpm type-check # 执行 vue-tsc 类型检查
|
||||
pnpm lint # 执行 ESLint,并带 --fix
|
||||
pnpm format # 使用 Prettier 格式化 src/
|
||||
```
|
||||
|
||||
当前项目未配置测试脚本,也未发现测试文件;不要声称已运行单元测试。需要验证改动时,优先运行 `pnpm type-check`、`pnpm lint`、`pnpm build` 中与改动相关的命令。
|
||||
|
||||
### 构建流程
|
||||
|
||||
`pnpm build` 的实际流程为:
|
||||
|
||||
1. `tsx build/pre-build.ts`:根据 `package.json` 的版本和构建时间写入 `public/manifest.json`。
|
||||
2. `vue-tsc --build`:类型检查。
|
||||
3. `vite build`:生成 `dist/`。
|
||||
4. `tsx build/post-build.ts`:基于 `dist/` 生成 `.zip`、`.tar`、`.tar.gz` 压缩包。
|
||||
|
||||
压缩包命名格式为:`ndm-web-platform_v<version>_<YYMMDD-HHmmss>`。
|
||||
|
||||
## 目录结构
|
||||
|
||||
项目源码集中在 `src/`:
|
||||
|
||||
```text
|
||||
src/
|
||||
apis/ # 接口客户端、接口模型、业务请求封装
|
||||
components/ # 业务组件与全局组件
|
||||
device/
|
||||
global/
|
||||
permission/
|
||||
station/
|
||||
composables/ # 组合式函数
|
||||
alarm/
|
||||
common/
|
||||
device/
|
||||
permission/
|
||||
query/ # TanStack Query 轮询与请求编排
|
||||
station/
|
||||
stomp/ # STOMP/WebSocket 客户端
|
||||
constants/ # 常量
|
||||
enums/ # 枚举
|
||||
helpers/ # 辅助逻辑
|
||||
layouts/
|
||||
app-layout.vue # 登录后主布局
|
||||
pages/ # 页面
|
||||
plugins/ # Pinia 持久化等插件
|
||||
router/
|
||||
index.ts # 路由配置与守卫
|
||||
stores/ # Pinia stores
|
||||
styles/ # 全局样式
|
||||
types/ # 全局/工具类型
|
||||
utils/ # 通用工具函数
|
||||
```
|
||||
|
||||
路径别名:`@/*` 指向 `src/*`。
|
||||
|
||||
## 页面与路由
|
||||
|
||||
路由配置在 `src/router/index.ts`。登录页独立于主布局;除 `/login` 外,其余页面都作为 `src/layouts/app-layout.vue` 的子路由。
|
||||
|
||||
```text
|
||||
src/
|
||||
router/
|
||||
index.ts
|
||||
layouts/
|
||||
app-layout.vue
|
||||
pages/
|
||||
login/
|
||||
login-page.vue # 登录页,对应 /login
|
||||
station/
|
||||
station-page.vue # 车站状态页/首页,对应 /station
|
||||
device/
|
||||
device-page.vue # 设备诊断页,对应 /device
|
||||
alarm/
|
||||
alarm-log-page.vue # 设备告警记录,对应 /alarm/alarm-log
|
||||
alarm-ignore-page.vue # 告警忽略管理,对应 /alarm/alarm-ignore
|
||||
log/
|
||||
vimp-log-page.vue # 视频平台日志,对应 /log/vimp-log
|
||||
call-log-page.vue # 上级调用日志,对应 /log/call-log
|
||||
permission/
|
||||
permission-page.vue # 权限管理,对应 /permission
|
||||
system/
|
||||
changelog/
|
||||
changelog-page.vue # 更新记录,对应 /changelog
|
||||
error/
|
||||
not-found-page.vue # 404 页面,对应 catch-all
|
||||
```
|
||||
|
||||
路由守卫规则:
|
||||
|
||||
- 未登录访问非 `/login` 页面会跳转到 `/login`。
|
||||
- 已登录访问 `/login` 会跳转到 `/`。
|
||||
- `/` 默认重定向到 `/station`。
|
||||
- `/permission` 需要 `useUserStore().isLamp` 为真,否则跳转到 404。
|
||||
|
||||
## 数据轮询与状态管理
|
||||
|
||||
由于后端服务按车站分布,前端需要向各车站服务依次请求数据。项目采用“单点驱动 + 变更监听 + 级联触发”的轮询模式。
|
||||
|
||||
核心文件位于 `src/composables/query/`:
|
||||
|
||||
- `use-line-stations-query.ts`:查询所有车站,是业务轮询入口。
|
||||
- `use-user-permission-query.ts`:查询并计算当前用户在各车站的权限,负责调度后续设备/告警查询。
|
||||
- `use-line-devices-query.ts`:查询设备数据。
|
||||
- `use-line-alarms-query.ts`:查询告警数据。
|
||||
- `use-verify-user-query.ts`:用户登录/校验相关请求。
|
||||
- `use-version-check-query.ts`:版本检查,依赖构建阶段生成的 `/manifest.json`。
|
||||
|
||||
关键 Pinia stores 位于 `src/stores/`:
|
||||
|
||||
- `user.ts`:用户登录态、Token、用户类型等。
|
||||
- `station.ts`:车站数据。
|
||||
- `device.ts`:设备数据。
|
||||
- `alarm.ts`:告警数据。
|
||||
- `permission.ts`:权限数据。
|
||||
- `setting.ts`:系统设置、调试开关、网络开关等。
|
||||
- `polling.ts`:轮询状态控制。
|
||||
- `unread.ts`:未读状态。
|
||||
|
||||
持久化注意事项:
|
||||
|
||||
- 大体量业务数据会使用 IndexedDB/localforage。
|
||||
- 普通设置类状态会使用 localStorage/sessionStorage。
|
||||
- `src/main.ts` 会比较 `VITE_STORAGE_VERSION` 与本地 `ndm-storage-version`,不一致时清空 localStorage 并清空 localforage。
|
||||
|
||||
## 接口与代理
|
||||
|
||||
接口相关代码位于 `src/apis/`:
|
||||
|
||||
- `client/`:HTTP 客户端基础封装。
|
||||
- `domain/`:领域相关类型/逻辑。
|
||||
- `model/`:接口模型。
|
||||
- `request/`:按业务拆分的请求函数。
|
||||
|
||||
开发代理配置在 `vite.config.ts`:
|
||||
|
||||
- 开发服务端口:`9763`。
|
||||
- 已配置多个线路/站点前缀代理,包括 01、02、04、10、21 相关站点。
|
||||
- 当前配置包含 `/api`、`/minio`、`/ws` 等代理项。
|
||||
- `ProxyItem.rewrite` 用于将本地请求前缀改写为后端真实路径,例如将 `/1001/api` 改写为 `/api`。
|
||||
|
||||
修改代理时,应同步检查 `key`、`target`、`rewrite`、`ws` 是否匹配实际环境。
|
||||
|
||||
## 环境变量
|
||||
|
||||
项目使用 Vite 环境变量,当前变量集中在 `.env`。文档或回答中只列变量名和用途,不要复述真实密钥、密码或授权值。
|
||||
|
||||
常见变量:
|
||||
|
||||
- `VITE_APP_TITLE`:页面标题。
|
||||
- `VITE_REQUEST_INTERVAL`:轮询间隔,单位秒。
|
||||
- `VITE_NDM_APP_KEY`:网管 appKey。
|
||||
- `VITE_LAMP_CLIENT_ID`:LAMP clientId。
|
||||
- `VITE_LAMP_CLIENT_SECRET`:LAMP clientSecret。
|
||||
- `VITE_LAMP_USERNAME`:LAMP 登录用户名。
|
||||
- `VITE_LAMP_PASSWORD`:LAMP 登录密码。
|
||||
- `VITE_LAMP_AUTHORIZATION`:已有 Authorization 时直接使用,否则由 clientId/clientSecret 生成。
|
||||
- `VITE_STORAGE_VERSION`:本地缓存版本,用于触发缓存清理。
|
||||
- `VITE_DEBUG_CODE`:调试模式授权码。
|
||||
|
||||
## 调试模式与离线开发
|
||||
|
||||
调试模式默认隐藏,用于开发、联调和故障排查。
|
||||
|
||||
开启方式:
|
||||
|
||||
1. 使用快捷键 `Ctrl + Alt + D` 唤起验证弹窗。
|
||||
2. 输入 `VITE_DEBUG_CODE` 对应授权码。
|
||||
3. 验证通过后,“系统设置”中会显示调试分组。
|
||||
|
||||
调试相关能力:
|
||||
|
||||
- 显示设备原始数据。
|
||||
- 控制是否轮询车站。
|
||||
- 控制是否主动请求。
|
||||
- 控制是否订阅 STOMP/WebSocket 消息。
|
||||
- 启用模拟用户。
|
||||
- 允许在特定场景下直接操作本地 IndexedDB。
|
||||
|
||||
离线开发:
|
||||
|
||||
- 如果浏览器已有现场缓存,可在调试模式中关闭轮询、主动请求和消息订阅,直接查看本地缓存。
|
||||
- 全新环境可在登录页控制台设置 `window.$mockUser.value = true` 进入模拟登录,再通过调试面板导入 `docs/data/` 下的数据:
|
||||
- `ndm-station-store.json`
|
||||
- `ndm-device-store.json`
|
||||
- `ndm-alarm-store.json`
|
||||
|
||||
## 代码风格与约定
|
||||
|
||||
- 遵循现有 Vue SFC、TypeScript、组合式函数和 Pinia 写法。
|
||||
- 新增业务请求时优先放入 `src/apis/request/` 对应业务分类。
|
||||
- 新增页面时同步更新 `src/router/index.ts`,并保持 `src/pages/` 目录与路由语义一致。
|
||||
- 新增共享逻辑优先放入 `src/composables/`、`src/helpers/` 或 `src/utils/`,不要在页面中堆积重复逻辑。
|
||||
- 新增全局/业务组件时优先放入 `src/components/` 对应分类。
|
||||
- 使用 `@/` 引用 `src/` 下模块。
|
||||
- 不要在代码、文档或回复中泄露真实密码、Token、Authorization 或现场地址以外的敏感信息。
|
||||
|
||||
格式化配置来自 `.prettierrc.json`:
|
||||
|
||||
- LF 换行
|
||||
- 2 空格缩进
|
||||
- 单引号
|
||||
- 使用分号
|
||||
- `trailingComma: all`
|
||||
- `printWidth: 200`
|
||||
|
||||
ESLint 重点规则:
|
||||
|
||||
- `@typescript-eslint/no-unused-vars` 为 warn。
|
||||
- `@typescript-eslint/no-explicit-any` 当前关闭,但新增代码仍应尽量保持类型清晰。
|
||||
- `vue/multi-word-component-names` 当前关闭。
|
||||
|
||||
## 协作规则
|
||||
|
||||
1. 默认不要直接修改项目代码;当用户明确授权修改时,只修改授权范围内的文件。
|
||||
2. 修改前先阅读相关文件和既有实现,避免凭空猜测目录、接口或状态结构。
|
||||
3. 修改后说明改了哪些文件、为什么改、如何验证。
|
||||
4. 文档类修改至少重新读取目标文件确认内容正确。
|
||||
5. 代码类修改应按影响范围运行 `pnpm type-check`、`pnpm lint`、`pnpm build` 或更小范围的可用验证命令。
|
||||
6. 不要提交 Git commit,除非用户明确要求。
|
||||
7. 不要删除失败测试或通过压制类型错误来规避问题。
|
||||
8. 不要在最终说明中声称执行了未实际执行的命令。
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ndm-web-platform",
|
||||
"version": "0.0.0",
|
||||
"version": "0.41.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
[
|
||||
{
|
||||
"version": "0.42.0",
|
||||
"date": "2026-05-20",
|
||||
"changes": {
|
||||
"fixes": [
|
||||
{ "content": "优化安防箱环境数据卡片,避免空标签渲染并改进风扇信息展示" },
|
||||
{ "content": "修复设备硬件卡片进度条异常值显示问题,并完善状态判断逻辑" }
|
||||
],
|
||||
"feats": [
|
||||
{ "content": "新增服务器网卡信息展示" },
|
||||
{ "content": "新增安防箱网卡信息展示,并优化相关卡片标题" },
|
||||
{ "content": "为交换机诊断信息新增温度字段" },
|
||||
{ "content": "新增录像机环境状态卡片和网卡信息展示" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "0.41.0",
|
||||
"date": "2026-05-19",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "0.42.0",
|
||||
"buildTime": "2026-05-20 13:52:07"
|
||||
"version": "0.41.0",
|
||||
"buildTime": "2026-05-19 19:40:37"
|
||||
}
|
||||
|
||||
@@ -10,37 +10,6 @@ export interface NdmNvrDiagInfo {
|
||||
totalSize?: number;
|
||||
}[];
|
||||
};
|
||||
ethInfo: {
|
||||
adminStatus?: string; // '1'
|
||||
desc?: string; // 'bond1'
|
||||
ifType?: string; // '6'
|
||||
inDiscards?: string; // '17931688'
|
||||
inErrors?: string; // '0'
|
||||
inNUcastPkts?: string; // '40945433'
|
||||
inOctets?: string; // '3453544614'
|
||||
inUcastPkts?: string; // '3375411816'
|
||||
inUnknownProtos?: string; // '0'
|
||||
index?: string; // '8'
|
||||
lastChange?: string; // '0:05:42.15'
|
||||
mTU?: string; // '1500'
|
||||
macAddress?: string; // '04:7b:cb:69:92:58'
|
||||
operStatus?: string; // '1'
|
||||
outDiscards?: string; // '0'
|
||||
outErrors?: string; // '0'
|
||||
outNUcastPkts?: string; // '0'
|
||||
outOctets?: string; // '3443476717'
|
||||
outQLen?: string; // '0'
|
||||
outUcastPkts?: string; // '415381735'
|
||||
specific?: string; // '0.0'
|
||||
speed?: string; // '2000000000'
|
||||
};
|
||||
ipInfo: {
|
||||
broadcastAddress?: string; // '1'
|
||||
iPAddress?: string; // '10.14.1.22'
|
||||
index?: string; // '8'
|
||||
mASK?: string; // '255.255.255.0'
|
||||
reasmMaxSize?: string; // '0'
|
||||
};
|
||||
stCommonInfo?: {
|
||||
设备ID?: string;
|
||||
软件版本?: string;
|
||||
@@ -51,16 +20,12 @@ export interface NdmNvrDiagInfo {
|
||||
内存使用率?: string;
|
||||
CPU使用率?: string;
|
||||
};
|
||||
cdFanInfo?: NdmNvrFanInfo[];
|
||||
cdPowerSupplyInfo?: NdmNvrPowerSupplyInfo[];
|
||||
}
|
||||
|
||||
export interface NdmNvrFanInfo {
|
||||
索引号?: string;
|
||||
'风扇转速(rpm)'?: string;
|
||||
}
|
||||
|
||||
export interface NdmNvrPowerSupplyInfo {
|
||||
索引号?: string;
|
||||
电源状态?: string;
|
||||
cdFanInfo?: {
|
||||
索引号?: string;
|
||||
'风扇转速(rpm)'?: string;
|
||||
}[];
|
||||
cdPowerSupplyInfo?: {
|
||||
索引号?: string;
|
||||
电源状态?: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
@@ -1,36 +1,5 @@
|
||||
export interface NdmSecurityBoxDiagInfo {
|
||||
[key: string]: any;
|
||||
ethInfo?: {
|
||||
adminStatus?: string; // '1'
|
||||
desc?: string; // 'br-lan'
|
||||
ifType?: string; // '6'
|
||||
inDiscards?: string; // '84977'
|
||||
inErrors?: string; // '0'
|
||||
inNUcastPkts?: string; // '233473'
|
||||
inOctets?: string; // '92355850'
|
||||
inUcastPkts?: string; // '899970'
|
||||
inUnknownProtos?: string; // '0'
|
||||
index?: string; // '8'
|
||||
lastChange?: string; // '0:00:00.00'
|
||||
mTU?: string; // '1500'
|
||||
macAddress?: string; // '56:62:bc:d3:9e:37'
|
||||
operStatus?: string; // '1'
|
||||
outDiscards?: string; // '0'
|
||||
outErrors?: string; // '0'
|
||||
outNUcastPkts?: string; // '0'
|
||||
outOctets?: string; // '68175904'
|
||||
outQLen?: string; // '0'
|
||||
outUcastPkts?: string; // '748100'
|
||||
specific?: string; // '0.0'
|
||||
speed?: string; // '0'
|
||||
};
|
||||
ipInfo?: {
|
||||
broadcastAddress?: string; // '1'
|
||||
iPAddress?: string; // '10.24.18.101'
|
||||
index?: string; // '8'
|
||||
mASK?: string; // '255.255.255.0'
|
||||
reasmMaxSize?: string; // '0'
|
||||
};
|
||||
info?: [
|
||||
{
|
||||
addrCode?: number;
|
||||
|
||||
@@ -6,35 +6,4 @@ export interface NdmServerDiagInfo {
|
||||
磁盘使用率?: string;
|
||||
系统运行时间?: string;
|
||||
};
|
||||
ethInfo?: {
|
||||
adminStatus?: string; // '1'
|
||||
desc?: string; // 'Intel Corporation I350 Gigabit Network Connection'
|
||||
ifType?: string; // '6'
|
||||
inDiscards?: string; // '6707634'
|
||||
inErrors?: string; // '0'
|
||||
inNUcastPkts?: string; // '8991944'
|
||||
inOctets?: string; // '4220524983'
|
||||
inUcastPkts?: string; // '2342740610'
|
||||
inUnknownProtos?: string; // '0'
|
||||
index?: string; // '2'
|
||||
lastChange?: string; // '0:03:15.26'
|
||||
mTU?: string; // '1500'
|
||||
macAddress?: string; // 'e8:78:ee:f6:8d:98'
|
||||
operStatus?: string; // '1'
|
||||
outDiscards?: string; // '0'
|
||||
outErrors?: string; // '0'
|
||||
outNUcastPkts?: string; // '0'
|
||||
outOctets?: string; // '1415770066'
|
||||
outQLen?: string; // '0'
|
||||
outUcastPkts?: string; // '3335494729'
|
||||
specific?: string; // '0.0'
|
||||
speed?: string; // '1000000000'
|
||||
};
|
||||
ipInfo?: {
|
||||
broadcastAddress?: string; // '1'
|
||||
iPAddress?: string; // '10.14.1.8'
|
||||
index?: string; // '2'
|
||||
mASK?: string; // '255.255.255.0'
|
||||
reasmMaxSize?: string; // '0'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ export interface NdmSwitchDiagInfo {
|
||||
[key: string]: any;
|
||||
cpuRatio?: string; // 因环境不同可能不存在
|
||||
memoryRatio?: string; // 因环境不同可能不存在
|
||||
temperature?: number;
|
||||
logTime?: string;
|
||||
info?: {
|
||||
overFlowPorts?: string[];
|
||||
|
||||
@@ -51,13 +51,8 @@ const formattedRunningTime = computed(() => {
|
||||
return (runningTime?.value ?? '-').replace('days', '天');
|
||||
});
|
||||
|
||||
const getProgressPercentage = (percent: number) => {
|
||||
if (percent < 0) return 0;
|
||||
if (percent > 100) return 100;
|
||||
return percent;
|
||||
};
|
||||
|
||||
const getProgressStatus = (percent: number): ProgressStatus => {
|
||||
const getProgressStatus = (percent?: number): ProgressStatus | undefined => {
|
||||
if (!percent) return undefined;
|
||||
if (percent >= 90) return 'error';
|
||||
if (percent >= 70) return 'warning';
|
||||
return 'success';
|
||||
@@ -71,20 +66,20 @@ const getProgressStatus = (percent: number): ProgressStatus => {
|
||||
</template>
|
||||
<template #default>
|
||||
<NFlex vertical>
|
||||
<NFlex v-if="cpuPercent" style="width: 100%" align="center" :wrap="false">
|
||||
<NFlex v-if="cpuUsage" style="width: 100%" align="center" :wrap="false">
|
||||
<NIcon :component="CpuIcon" />
|
||||
<span style="word-break: keep-all">{{ cpuUsageLabel || 'CPU' }}</span>
|
||||
<NProgress :percentage="getProgressPercentage(cpuPercent)" :status="getProgressStatus(cpuPercent)">{{ cpuPercent }}%</NProgress>
|
||||
<NProgress :percentage="cpuPercent" :status="getProgressStatus(cpuPercent)">{{ cpuPercent ?? '-' }}%</NProgress>
|
||||
</NFlex>
|
||||
<NFlex v-if="memPercent" style="width: 100%" align="center" :wrap="false">
|
||||
<NFlex v-if="memUsage" style="width: 100%" align="center" :wrap="false">
|
||||
<NIcon :component="MemoryStickIcon" />
|
||||
<span style="word-break: keep-all">{{ memUsageLabel || '内存' }}</span>
|
||||
<NProgress :percentage="getProgressPercentage(memPercent)" :status="getProgressStatus(memPercent)">{{ memPercent }}%</NProgress>
|
||||
<NProgress :percentage="memPercent" :status="getProgressStatus(memPercent)">{{ memPercent ?? '-' }}%</NProgress>
|
||||
</NFlex>
|
||||
<NFlex v-if="diskPercent" style="width: 100%" align="center" :wrap="false">
|
||||
<NFlex v-if="diskUsage" style="width: 100%" align="center" :wrap="false">
|
||||
<NIcon :component="HardDriveIcon" />
|
||||
<span style="word-break: keep-all">{{ diskUsageLabel || '磁盘' }}</span>
|
||||
<NProgress :percentage="getProgressPercentage(diskPercent)" :status="getProgressStatus(diskPercent)">{{ diskPercent }}%</NProgress>
|
||||
<NProgress :percentage="diskPercent" :status="getProgressStatus(diskPercent)">{{ diskPercent ?? '-' }}%</NProgress>
|
||||
</NFlex>
|
||||
<NFlex v-if="runningTime" style="width: 100%" align="center" :wrap="false">
|
||||
<NIcon :component="ClockCheckIcon" />
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { ComponentInstance } from 'vue';
|
||||
import DeviceCommonCard from './device-common-card.vue';
|
||||
import DeviceHardwareCard from './device-hardware-card.vue';
|
||||
import DeviceHeaderCard from './device-header-card.vue';
|
||||
import NvrEnvCard from './nvr-env-card.vue';
|
||||
import NvrDiskCard from './nvr-disk-card.vue';
|
||||
import NvrRecordCheckCard from './nvr-record-check-card.vue';
|
||||
import SecurityBoxCircuitCard from './security-box-circuit-card.vue';
|
||||
@@ -17,7 +16,6 @@ export {
|
||||
DeviceCommonCard,
|
||||
DeviceHardwareCard,
|
||||
DeviceHeaderCard,
|
||||
NvrEnvCard,
|
||||
NvrDiskCard,
|
||||
NvrRecordCheckCard,
|
||||
SecurityBoxCircuitCard,
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmNvrFanInfo, NdmNvrPowerSupplyInfo } from '@/apis';
|
||||
import { FanIcon, PlugIcon } from 'lucide-vue-next';
|
||||
import { NCard, NFlex, NIcon, NTag } from 'naive-ui';
|
||||
import { computed, toRefs } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
fanInfo?: NdmNvrFanInfo[];
|
||||
powerSupplyInfo?: NdmNvrPowerSupplyInfo[];
|
||||
}>();
|
||||
|
||||
const { fanInfo, powerSupplyInfo } = toRefs(props);
|
||||
|
||||
const showCard = computed(() => {
|
||||
return Object.values(props).some((value) => !!value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard v-if="showCard" hoverable size="small">
|
||||
<template #header>
|
||||
<span>录像机环境状态</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<NFlex vertical>
|
||||
<NTag v-for="info in fanInfo ?? []" :key="info['索引号']">
|
||||
<template #icon>
|
||||
<NIcon :component="FanIcon" />
|
||||
</template>
|
||||
<template #default>
|
||||
<span>风扇{{ info['索引号'] }}: {{ info['风扇转速(rpm)'] }} RPM</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag v-for="info in powerSupplyInfo ?? []" :key="info['索引号']">
|
||||
<template #icon>
|
||||
<NIcon :component="PlugIcon" />
|
||||
</template>
|
||||
<template #default>
|
||||
<span>电源{{ info['索引号'] }}: {{ info['电源状态'] }}</span>
|
||||
</template>
|
||||
</NTag>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
+12
-7
@@ -35,16 +35,21 @@ const getStatusTagType = (status: string | null) => {
|
||||
if (['失效', '开门'].includes(status ?? '')) return 'error';
|
||||
return 'default';
|
||||
};
|
||||
|
||||
const formattedFanSpeeds = computed(() => {
|
||||
if (!fanSpeeds?.value || fanSpeeds.value.length === 0) return null;
|
||||
return fanSpeeds.value.map((speed, index) => `风扇${index + 1}: ${speed} RPM`).join(', ');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard v-if="showCard" hoverable size="small">
|
||||
<template #header>
|
||||
<span>安防箱环境状态</span>
|
||||
<span>安防箱状态</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<NFlex vertical>
|
||||
<NTag v-if="!!temperature">
|
||||
<NTag>
|
||||
<template #icon>
|
||||
<NIcon :component="ThermometerIcon" />
|
||||
</template>
|
||||
@@ -52,7 +57,7 @@ const getStatusTagType = (status: string | null) => {
|
||||
<span>温度: {{ temperature }}℃</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag v-if="!!humidity">
|
||||
<NTag>
|
||||
<template #icon>
|
||||
<NIcon :component="DropletIcon" />
|
||||
</template>
|
||||
@@ -60,15 +65,15 @@ const getStatusTagType = (status: string | null) => {
|
||||
<span>湿度: {{ humidity }}%</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag v-for="(speed, index) in fanSpeeds ?? []" :key="index">
|
||||
<NTag>
|
||||
<template #icon>
|
||||
<NIcon :component="FanIcon" />
|
||||
</template>
|
||||
<template #default>
|
||||
<span>风扇{{ index + 1 }}: {{ speed }} RPM</span>
|
||||
<span>风扇: {{ formattedFanSpeeds }}</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag v-if="!!accessControlStatus" :type="getStatusTagType(accessControlStatus)">
|
||||
<NTag :type="getStatusTagType(accessControlStatus)">
|
||||
<template #icon>
|
||||
<NIcon :component="ShieldIcon" />
|
||||
</template>
|
||||
@@ -76,7 +81,7 @@ const getStatusTagType = (status: string | null) => {
|
||||
<span>门禁: {{ accessControlStatus }}</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<NTag v-if="!!lightningProtectionStatus" :type="getStatusTagType(lightningProtectionStatus)">
|
||||
<NTag :type="getStatusTagType(lightningProtectionStatus)">
|
||||
<template #icon>
|
||||
<NIcon :component="ZapIcon" />
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmNvrDiagInfo, NdmNvrResultVO, Station } from '@/apis';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, NvrDiskCard, NvrEnvCard, NvrRecordCheckCard, type DeviceCommonCardProps } from '@/components';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, NvrDiskCard, NvrRecordCheckCard, type DeviceCommonCardProps } from '@/components';
|
||||
import { isNvrCluster } from '@/helpers';
|
||||
import destr from 'destr';
|
||||
import { NFlex } from 'naive-ui';
|
||||
@@ -21,14 +21,9 @@ const lastDiagInfo = computed(() => {
|
||||
});
|
||||
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { ipAddress } = ndmDevice.value;
|
||||
const { ethInfo, ipInfo, stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
const { 设备ID, 软件版本, 生产厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
||||
|
||||
let operStatus = '-';
|
||||
if (!!ethInfo?.operStatus) {
|
||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
||||
}
|
||||
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
if (!stCommonInfo) return undefined;
|
||||
const { 设备ID, 软件版本, 生产厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo;
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -42,17 +37,6 @@ const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '设备网卡信息',
|
||||
items: [
|
||||
{ label: 'IP地址', value: ipAddress || '-' },
|
||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
||||
{ label: '运行状态', value: operStatus },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -61,9 +45,6 @@ const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.内存使用
|
||||
|
||||
const diskHealth = computed(() => lastDiagInfo.value?.info?.diskHealth);
|
||||
const diskArray = computed(() => lastDiagInfo.value?.info?.groupInfoList);
|
||||
|
||||
const fanInfo = computed(() => lastDiagInfo.value?.cdFanInfo);
|
||||
const powerSupplyInfo = computed(() => lastDiagInfo.value?.cdPowerSupplyInfo);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -71,7 +52,6 @@ const powerSupplyInfo = computed(() => lastDiagInfo.value?.cdPowerSupplyInfo);
|
||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||
<DeviceCommonCard :common-info="commonInfo" />
|
||||
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
||||
<NvrEnvCard :fan-info="fanInfo" :power-supply-info="powerSupplyInfo" />
|
||||
<NvrDiskCard :disk-health="diskHealth" :disk-array="diskArray" />
|
||||
<template v-if="isNvrCluster(ndmDevice)">
|
||||
<NvrRecordCheckCard :ndm-device="ndmDevice" :station="station" />
|
||||
|
||||
@@ -35,15 +35,9 @@ const vendor = computed(() => {
|
||||
});
|
||||
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { ipAddress } = ndmDevice.value;
|
||||
const { ethInfo, ipInfo, stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
||||
|
||||
let operStatus = '-';
|
||||
if (!!ethInfo?.operStatus) {
|
||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: '设备型号信息',
|
||||
@@ -56,17 +50,6 @@ const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '设备网卡信息',
|
||||
items: [
|
||||
{ label: 'IP地址', value: ipAddress || '-' },
|
||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
||||
{ label: '运行状态', value: operStatus },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { type NdmServerDiagInfo, type NdmServerResultVO, type Station } from '@/apis';
|
||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerHighAvailable, ServerStreamPush, type DeviceCommonCardProps } from '@/components';
|
||||
import { DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerHighAvailable, ServerStreamPush } from '@/components';
|
||||
import destr from 'destr';
|
||||
import { NFlex } from 'naive-ui';
|
||||
import { computed, toRefs } from 'vue';
|
||||
@@ -19,30 +19,6 @@ const lastDiagInfo = computed(() => {
|
||||
return result as NdmServerDiagInfo;
|
||||
});
|
||||
|
||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
||||
const { ipAddress } = ndmDevice.value;
|
||||
const { ethInfo, ipInfo } = lastDiagInfo.value ?? {};
|
||||
|
||||
let operStatus = '-';
|
||||
if (!!ethInfo?.operStatus) {
|
||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: '设备网卡信息',
|
||||
items: [
|
||||
{ label: 'IP地址', value: ipAddress || '-' },
|
||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
||||
{ label: '运行状态', value: operStatus },
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const cpuUsage = computed(() => lastDiagInfo.value?.commInfo?.CPU使用率);
|
||||
const memUsage = computed(() => lastDiagInfo.value?.commInfo?.内存使用率);
|
||||
const diskUsage = computed(() => lastDiagInfo.value?.commInfo?.磁盘使用率);
|
||||
@@ -53,7 +29,6 @@ const runningTime = computed(() => lastDiagInfo.value?.commInfo?.系统运行时
|
||||
<NFlex vertical>
|
||||
<ServerHighAvailable :ndm-device="ndmDevice" :station="station" />
|
||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||
<DeviceCommonCard :common-info="commonInfo" />
|
||||
<DeviceHardwareCard running-time-label="服务器运行时间" :cpu-usage="cpuUsage" :mem-usage="memUsage" :disk-usage="diskUsage" :running-time="runningTime" />
|
||||
<ServerAlive :ndm-device="ndmDevice" :station="station" />
|
||||
<ServerStreamPush :ndm-device="ndmDevice" :station="station" />
|
||||
|
||||
@@ -8,7 +8,7 @@ const createDeviceNodeKey = (stationCode?: Station['code'], device?: NdmDeviceRe
|
||||
import { initStationDevices, type NdmDeviceResultVO, type NdmNvrResultVO, type Station } from '@/apis';
|
||||
import { useDeviceTree, usePermission, type UseDeviceTreeReturn } from '@/composables';
|
||||
import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType, PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { createNvrClusterRelationship, isNvrCluster, nvrInCluster } from '@/helpers';
|
||||
import { isNvrCluster } from '@/helpers';
|
||||
import { useDeviceStore, usePermissionStore } from '@/stores';
|
||||
import { watchDebounced, watchImmediate } from '@vueuse/core';
|
||||
import destr from 'destr';
|
||||
@@ -297,18 +297,8 @@ const renderDeviceNodePrefix = (device: NdmDeviceResultVO, stationCode: Station[
|
||||
return h(NFlex, { size: 'small' }, { default: () => [renderViewDeviceButton(device, stationCode), renderDeviceStatusTag(device)] });
|
||||
};
|
||||
// 全线设备树
|
||||
const lineDeviceTreeData = computed<Record<DeviceType, TreeOption[]>>(() => {
|
||||
const treeData: Record<DeviceType, TreeOption[]> = {
|
||||
[DEVICE_TYPE_LITERALS.ndmCamera]: [],
|
||||
[DEVICE_TYPE_LITERALS.ndmNvr]: [],
|
||||
[DEVICE_TYPE_LITERALS.ndmSwitch]: [],
|
||||
[DEVICE_TYPE_LITERALS.ndmDecoder]: [],
|
||||
[DEVICE_TYPE_LITERALS.ndmSecurityBox]: [],
|
||||
[DEVICE_TYPE_LITERALS.ndmMediaServer]: [],
|
||||
[DEVICE_TYPE_LITERALS.ndmVideoServer]: [],
|
||||
[DEVICE_TYPE_LITERALS.ndmKeyboard]: [],
|
||||
[DEVICE_TYPE_LITERALS.ndmAlarmHost]: [],
|
||||
};
|
||||
const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() => {
|
||||
const treeData: Record<string, TreeOption[]> = {};
|
||||
deviceTabPanes.forEach(({ name: paneName /* , tab: paneTab */ }) => {
|
||||
treeData[paneName] = stations.value.map<TreeOption>((station) => {
|
||||
const { name: stationName, code: stationCode } = station;
|
||||
@@ -317,55 +307,47 @@ const lineDeviceTreeData = computed<Record<DeviceType, TreeOption[]>>(() => {
|
||||
const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
|
||||
// 对于录像机,需要根据clusterList字段以分号分隔设备IP,进一步形成子树结构
|
||||
if (paneName === DEVICE_TYPE_LITERALS.ndmNvr) {
|
||||
const nvrDevices = devices as NdmNvrResultVO[];
|
||||
|
||||
const { nvrClusters, nvrTreeMap, nvrStandalones } = createNvrClusterRelationship(nvrDevices);
|
||||
|
||||
const nvrs = devices as NdmNvrResultVO[];
|
||||
const nvrClusters: NdmNvrResultVO[] = [];
|
||||
const nvrSingletons: NdmNvrResultVO[] = [];
|
||||
for (const device of nvrs) {
|
||||
if (isNvrCluster(device)) {
|
||||
nvrClusters.push(device);
|
||||
} else {
|
||||
nvrSingletons.push(device);
|
||||
}
|
||||
}
|
||||
return {
|
||||
label: stationName,
|
||||
key: stationCode,
|
||||
prefix: () => renderStationNodePrefix(station),
|
||||
suffix: () => renderIcmpStatistics(onlineDevices?.length ?? 0, offlineDevices?.length ?? 0, devices?.length ?? 0),
|
||||
children: [
|
||||
...nvrClusters.map((cluster) => {
|
||||
return {
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: (nvrTreeMap.get(cluster.ipAddress ?? '') ?? []).map((clusterNode) => {
|
||||
return {
|
||||
label: `${clusterNode.name}`,
|
||||
key: createDeviceNodeKey(stationCode, clusterNode),
|
||||
prefix: () => renderDeviceNodePrefix(clusterNode, stationCode),
|
||||
suffix: () => `${clusterNode.ipAddress}`,
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: clusterNode,
|
||||
};
|
||||
}),
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
...nvrStandalones.map((device) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: device,
|
||||
};
|
||||
}),
|
||||
],
|
||||
children: nvrClusters.map<TreeOption>((cluster) => {
|
||||
return {
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: nvrSingletons.map<TreeOption>((device) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: device,
|
||||
};
|
||||
}),
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
deviceType: activeTab.value,
|
||||
};
|
||||
}
|
||||
// 非录像机设备
|
||||
return {
|
||||
label: stationName,
|
||||
key: stationCode,
|
||||
@@ -400,51 +382,37 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
const onlineCount = stationDevices[deviceType].filter((device) => device.deviceStatus === '10').length;
|
||||
const offlineCount = stationDevices[deviceType].filter((device) => device.deviceStatus === '20').length;
|
||||
if (deviceType === DEVICE_TYPE_LITERALS.ndmNvr) {
|
||||
const nvrDevices = stationDevices[deviceType] as NdmNvrResultVO[];
|
||||
|
||||
const { nvrClusters, nvrTreeMap, nvrStandalones } = createNvrClusterRelationship(nvrDevices);
|
||||
|
||||
const nvrs = stationDevices[deviceType] as NdmNvrResultVO[];
|
||||
const clusters = nvrs.filter((nvr) => isNvrCluster(nvr));
|
||||
const singletons = nvrs.filter((nvr) => !isNvrCluster(nvr));
|
||||
return {
|
||||
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
||||
key: deviceType,
|
||||
suffix: () => renderIcmpStatistics(onlineCount, offlineCount, nvrDevices.length),
|
||||
children: [
|
||||
...nvrClusters.map((cluster) => {
|
||||
return {
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: (nvrTreeMap.get(cluster.ipAddress ?? '') ?? []).map((clusterNode) => {
|
||||
return {
|
||||
label: `${clusterNode.name}`,
|
||||
key: createDeviceNodeKey(stationCode, clusterNode),
|
||||
prefix: () => renderDeviceNodePrefix(clusterNode, stationCode),
|
||||
suffix: () => `${clusterNode.ipAddress}`,
|
||||
stationCode,
|
||||
device: clusterNode,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
...nvrStandalones.map((device) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
stationCode,
|
||||
device,
|
||||
};
|
||||
}),
|
||||
],
|
||||
suffix: () => renderIcmpStatistics(onlineCount, offlineCount, nvrs.length),
|
||||
children: clusters.map<TreeOption>((cluster) => {
|
||||
return {
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: singletons.map<TreeOption>((device) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
stationCode,
|
||||
device,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
deviceType,
|
||||
};
|
||||
}
|
||||
// 非录像机设备
|
||||
return {
|
||||
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
||||
key: deviceType,
|
||||
@@ -520,13 +488,9 @@ const onLocateDeviceTree = async () => {
|
||||
if (stationDevices) {
|
||||
const selectedNvr = selectedDevice.value as NdmNvrResultVO;
|
||||
if (!isNvrCluster(selectedNvr)) {
|
||||
const nvrDevices = stationDevices[DEVICE_TYPE_LITERALS.ndmNvr];
|
||||
const clusters = nvrDevices.filter((device) => {
|
||||
if (!isNvrCluster(device)) return false;
|
||||
const cluster = device;
|
||||
return nvrInCluster(selectedNvr, cluster);
|
||||
});
|
||||
expandedKeys.value.push(...clusters.map((cluster) => createDeviceNodeKey(stationCode, cluster)));
|
||||
const nvrs = stationDevices[DEVICE_TYPE_LITERALS.ndmNvr];
|
||||
const clusters = nvrs.filter((nvr) => isNvrCluster(nvr) && nvr.clusterList?.includes(selectedNvr.clusterList ?? ''));
|
||||
expandedKeys.value.push(...clusters.map((nvr) => createDeviceNodeKey(stationCode, nvr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,8 @@
|
||||
import type { NdmNvrResultVO } from '@/apis';
|
||||
|
||||
// 解析 clusterList 字段
|
||||
export const parseIpListFromClusterList = (nvr: NdmNvrResultVO) => {
|
||||
const ipList = (nvr.clusterList ?? '').split(';');
|
||||
return ipList.map((ip) => ip.trim()).filter((ip) => !!ip);
|
||||
};
|
||||
|
||||
export const isNvrCluster = (maybeNvrCluster: NdmNvrResultVO) => {
|
||||
const { ipAddress } = maybeNvrCluster;
|
||||
const ipList = parseIpListFromClusterList(maybeNvrCluster);
|
||||
if (ipList.length === 0) return false;
|
||||
if (ipList.length === 1 && ipList.at(0) === ipAddress) return false;
|
||||
const { ipAddress, clusterList } = maybeNvrCluster;
|
||||
if (!clusterList?.trim()) return false;
|
||||
if (clusterList === ipAddress) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
export const nvrInCluster = (nvr: NdmNvrResultVO, cluster: NdmNvrResultVO) => {
|
||||
const { ipAddress } = nvr;
|
||||
if (!ipAddress) return false;
|
||||
const ipList = parseIpListFromClusterList(cluster);
|
||||
return ipList.includes(ipAddress);
|
||||
};
|
||||
|
||||
export const createNvrClusterRelationship = (nvrDevices: NdmNvrResultVO[]) => {
|
||||
const nvrClusters = nvrDevices.filter((nvr) => isNvrCluster(nvr));
|
||||
const nvrNotClusters = nvrDevices.filter((nvr) => !isNvrCluster(nvr));
|
||||
const nodedNvrIpAddressSet = new Set<string | null>();
|
||||
const nvrStandalones: NdmNvrResultVO[] = [];
|
||||
const nvrTreeMap = new Map<string, NdmNvrResultVO[]>();
|
||||
// 遍历所有非集群录像机,将它们分配到对应的录像机集群中
|
||||
for (const nvr of nvrNotClusters) {
|
||||
for (const cluster of nvrClusters) {
|
||||
if (nvrInCluster(nvr, cluster)) {
|
||||
if (!!cluster.ipAddress) {
|
||||
// 写入录像机与集群的关系
|
||||
nvrTreeMap.set(cluster.ipAddress, [...(nvrTreeMap.get(cluster.ipAddress) ?? []), nvr]);
|
||||
// 记录已分配的录像机IP地址
|
||||
nodedNvrIpAddressSet.add(nvr.ipAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 分配完成后,过滤出未分配的录像机,形成录像机单机列表
|
||||
nvrNotClusters.forEach((device) => {
|
||||
if (!nodedNvrIpAddressSet.has(device.ipAddress)) {
|
||||
nvrStandalones.push(device);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
nvrClusters,
|
||||
// nvrNotClusters,
|
||||
nvrTreeMap,
|
||||
nvrStandalones,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -175,12 +175,6 @@ const line10ApiProxyList: ProxyItem[] = [
|
||||
{ key: '/1032/api', target: 'http://10.18.244.10:18760', rewrite: ['/1032/api', '/api'] },
|
||||
];
|
||||
|
||||
const line21YangpuTestProxyList: ProxyItem[] = [
|
||||
{ key: '/2175/api', target: 'http://10.24.0.10:18760', rewrite: ['/2175/api', '/api'] },
|
||||
{ key: '/2109/api', target: 'http://10.24.17.10:18760', rewrite: ['/2109/api', '/api'] },
|
||||
{ key: '/2180/api', target: 'http://10.24.116.10:18760', rewrite: ['/2180/api', '/api'] },
|
||||
];
|
||||
|
||||
const apiProxyList: ProxyItem[] = [
|
||||
// { key: '/minio', target: 'http://10.14.0.10:9000', rewrite: ['/minio', ''] },
|
||||
// { key: '/api', target: 'http://10.14.0.10:18760' },
|
||||
@@ -201,12 +195,6 @@ const apiProxyList: ProxyItem[] = [
|
||||
{ key: '/api', target: 'http://10.18.128.10:18760' },
|
||||
{ key: '/ws', target: 'ws://10.18.128.10:18103', ws: true },
|
||||
...line10ApiProxyList,
|
||||
|
||||
// 杨浦厂验环境
|
||||
// { key: '/minio', target: 'http://10.24.0.10:9000', rewrite: ['/minio', ''] },
|
||||
// { key: '/api', target: 'http://10.24.0.10:18760' },
|
||||
// { key: '/ws', target: 'ws://10.24.0.10:18103', ws: true },
|
||||
...line21YangpuTestProxyList,
|
||||
];
|
||||
|
||||
// https://vite.dev/config/
|
||||
|
||||
Reference in New Issue
Block a user