Compare commits
3 Commits
0b39c9c602
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e2ad96e56 | |||
| 6c57af9e55 | |||
| ca7d6baa2e |
@@ -0,0 +1,271 @@
|
||||
# 网络设备管理平台
|
||||
|
||||
## 项目概述
|
||||
|
||||
这是网络设备管理平台的前端项目,用于在地铁线路运管中心检测和查看各车站网络设备的详细数据、运行情况、异常告警,并提供分析、统计、日志和权限管理能力。
|
||||
|
||||
主要设备类型:
|
||||
|
||||
- 摄像机
|
||||
- 网络录像机
|
||||
- 交换机
|
||||
- 解码器
|
||||
- 智能安防箱
|
||||
- 媒体服务器
|
||||
- 视频服务器
|
||||
- 网络键盘
|
||||
- 报警主机
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 包管理: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. 不要在最终说明中声称执行了未实际执行的命令。
|
||||
@@ -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 { isNvrCluster } from '@/helpers';
|
||||
import { createNvrClusterRelationship, isNvrCluster, nvrInCluster } from '@/helpers';
|
||||
import { useDeviceStore, usePermissionStore } from '@/stores';
|
||||
import { watchDebounced, watchImmediate } from '@vueuse/core';
|
||||
import destr from 'destr';
|
||||
@@ -297,8 +297,18 @@ const renderDeviceNodePrefix = (device: NdmDeviceResultVO, stationCode: Station[
|
||||
return h(NFlex, { size: 'small' }, { default: () => [renderViewDeviceButton(device, stationCode), renderDeviceStatusTag(device)] });
|
||||
};
|
||||
// 全线设备树
|
||||
const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() => {
|
||||
const treeData: Record<string, TreeOption[]> = {};
|
||||
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]: [],
|
||||
};
|
||||
deviceTabPanes.forEach(({ name: paneName /* , tab: paneTab */ }) => {
|
||||
treeData[paneName] = stations.value.map<TreeOption>((station) => {
|
||||
const { name: stationName, code: stationCode } = station;
|
||||
@@ -307,28 +317,39 @@ const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() =>
|
||||
const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
|
||||
// 对于录像机,需要根据clusterList字段以分号分隔设备IP,进一步形成子树结构
|
||||
if (paneName === DEVICE_TYPE_LITERALS.ndmNvr) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
const nvrDevices = devices as NdmNvrResultVO[];
|
||||
|
||||
const { nvrClusters, nvrTreeMap, nvrStandalones } = createNvrClusterRelationship(nvrDevices);
|
||||
|
||||
return {
|
||||
label: stationName,
|
||||
key: stationCode,
|
||||
prefix: () => renderStationNodePrefix(station),
|
||||
suffix: () => renderIcmpStatistics(onlineDevices?.length ?? 0, offlineDevices?.length ?? 0, devices?.length ?? 0),
|
||||
children: nvrClusters.map<TreeOption>((cluster) => {
|
||||
children: [
|
||||
...nvrClusters.map((cluster) => {
|
||||
return {
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: nvrSingletons.map<TreeOption>((device) => {
|
||||
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),
|
||||
@@ -339,15 +360,12 @@ const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() =>
|
||||
device: device,
|
||||
};
|
||||
}),
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
],
|
||||
stationCode,
|
||||
deviceType: activeTab.value,
|
||||
};
|
||||
}
|
||||
// 非录像机设备
|
||||
return {
|
||||
label: stationName,
|
||||
key: stationCode,
|
||||
@@ -382,20 +400,36 @@ 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 nvrs = stationDevices[deviceType] as NdmNvrResultVO[];
|
||||
const clusters = nvrs.filter((nvr) => isNvrCluster(nvr));
|
||||
const singletons = nvrs.filter((nvr) => !isNvrCluster(nvr));
|
||||
const nvrDevices = stationDevices[deviceType] as NdmNvrResultVO[];
|
||||
|
||||
const { nvrClusters, nvrTreeMap, nvrStandalones } = createNvrClusterRelationship(nvrDevices);
|
||||
|
||||
return {
|
||||
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
||||
key: deviceType,
|
||||
suffix: () => renderIcmpStatistics(onlineCount, offlineCount, nvrs.length),
|
||||
children: clusters.map<TreeOption>((cluster) => {
|
||||
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: singletons.map<TreeOption>((device) => {
|
||||
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),
|
||||
@@ -405,14 +439,12 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
device,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
],
|
||||
stationCode,
|
||||
deviceType,
|
||||
};
|
||||
}
|
||||
// 非录像机设备
|
||||
return {
|
||||
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
||||
key: deviceType,
|
||||
@@ -488,9 +520,13 @@ const onLocateDeviceTree = async () => {
|
||||
if (stationDevices) {
|
||||
const selectedNvr = selectedDevice.value as NdmNvrResultVO;
|
||||
if (!isNvrCluster(selectedNvr)) {
|
||||
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)));
|
||||
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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,56 @@
|
||||
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, clusterList } = maybeNvrCluster;
|
||||
if (!clusterList?.trim()) return false;
|
||||
if (clusterList === ipAddress) return false;
|
||||
const { ipAddress } = maybeNvrCluster;
|
||||
const ipList = parseIpListFromClusterList(maybeNvrCluster);
|
||||
if (ipList.length === 0) return false;
|
||||
if (ipList.length === 1 && ipList.at(0) === 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,6 +175,12 @@ 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' },
|
||||
@@ -195,6 +201,12 @@ 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