diff --git a/src/components/device/device-tree/device-tree.vue b/src/components/device/device-tree/device-tree.vue index 5b43616..d48d753 100644 --- a/src/components/device/device-tree/device-tree.vue +++ b/src/components/device/device-tree/device-tree.vue @@ -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>(() => { - const treeData: Record = {}; +const lineDeviceTreeData = computed>(() => { + const treeData: Record = { + [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((station) => { const { name: stationName, code: stationCode } = station; @@ -307,47 +317,55 @@ const lineDeviceTreeData = computed>(() => 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((cluster) => { - return { - label: `${cluster.name}`, - key: createDeviceNodeKey(stationCode, cluster), - prefix: () => renderDeviceNodePrefix(cluster, stationCode), - suffix: () => `${cluster.ipAddress}`, - children: nvrSingletons.map((device) => { - return { - label: `${device.name}`, - key: createDeviceNodeKey(stationCode, device), - prefix: () => renderDeviceNodePrefix(device, stationCode), - suffix: () => `${device.ipAddress}`, - // 当选择设备时,能获取到设备的所有信息,以及设备所属的车站 - stationCode, - device: device, - }; - }), - // 当选择设备时,能获取到设备的所有信息,以及设备所属的车站 - stationCode, - device: cluster, - }; - }), + 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, + }; + }), + ], stationCode, deviceType: activeTab.value, }; } + // 非录像机设备 return { label: stationName, key: stationCode, @@ -382,37 +400,51 @@ const stationDeviceTreeData = computed(() => { 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((cluster) => { - return { - label: `${cluster.name}`, - key: createDeviceNodeKey(stationCode, cluster), - prefix: () => renderDeviceNodePrefix(cluster, stationCode), - suffix: () => `${cluster.ipAddress}`, - children: singletons.map((device) => { - return { - label: `${device.name}`, - key: createDeviceNodeKey(stationCode, device), - prefix: () => renderDeviceNodePrefix(device, stationCode), - suffix: () => `${device.ipAddress}`, - stationCode, - device, - }; - }), - stationCode, - device: 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: (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, + }; + }), + ], 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))); } } } diff --git a/src/helpers/device/nvr/util.ts b/src/helpers/device/nvr/util.ts index fe8f8c6..f99a3cc 100644 --- a/src/helpers/device/nvr/util.ts +++ b/src/helpers/device/nvr/util.ts @@ -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(); + const nvrStandalones: NdmNvrResultVO[] = []; + const nvrTreeMap = new Map(); + // 遍历所有非集群录像机,将它们分配到对应的录像机集群中 + 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, + }; +};