feat: 新增多厂商安防箱支持并重构相关逻辑

- 新增安防箱适配器模块,统一封装不同厂商的设备操作逻辑
- 拆分原有安防箱API为beidian、ningtech两个厂商的专属实现
- 扩展安防箱诊断信息接口,补充设备ID、版本等元数据字段
- 更新安防箱设备卡片组件,支持自动识别厂商并适配空开控制逻辑
This commit is contained in:
yangsy
2026-05-19 15:01:27 +08:00
parent a43a8b24e3
commit c3f3844cd5
7 changed files with 139 additions and 14 deletions
@@ -12,8 +12,14 @@ export interface NdmSecurityBoxDiagInfo {
]; ];
stCommonInfo?: { stCommonInfo?: {
[key: string]: any; [key: string]: any;
内存使用率?: string; 设备ID?: string; // 'NTBoxMetro'
CPU使用率?: string; 软件版本?: string; // 'V0101'
设备厂商?: string; // 'NingTech'
设备别名?: string; // 'SUN-IBOX'
设备型号?: string; // 'SUN-IBOX'
硬件版本?: string; // 'V0101'
内存使用率?: string; // '18'
CPU使用率?: string; // '1'
}; };
} }
+28 -4
View File
@@ -93,22 +93,46 @@ export const probeSecurityBoxApi = async (ids: string[], options?: { stationCode
unwrapVoidResponse(resp); unwrapVoidResponse(resp);
}; };
export const turnCitcuitStatusApi = async (ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => { // beidian安防箱切换空开状态
export const turnCircuitStatusBeidianApi = async (community: string, ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
const { stationCode, signal } = options ?? {}; const { stationCode, signal } = options ?? {};
const client = stationCode ? ndmClient : userClient; const client = stationCode ? ndmClient : userClient;
const prefix = stationCode ? `/${stationCode}` : ''; const prefix = stationCode ? `/${stationCode}` : '';
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/turnStatus`; const endpoint = `${prefix}/api/ndm/ndmSecurityBox/turnStatus`;
const resp = await client.post<boolean>(endpoint, { community: 'public', ipAddress, circuit: `${circuitIndex}`, status }, { signal }); const resp = await client.post<boolean>(endpoint, { community, ipAddress, circuit: `${circuitIndex}`, status }, { signal });
const data = unwrapResponse(resp); const data = unwrapResponse(resp);
return data; return data;
}; };
export const rebootSecurityBoxApi = async (ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => { // beidian安防箱重启
export const rebootSecurityBoxBeidianApi = async (community: string, ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
const { stationCode, signal } = options ?? {}; const { stationCode, signal } = options ?? {};
const client = stationCode ? ndmClient : userClient; const client = stationCode ? ndmClient : userClient;
const prefix = stationCode ? `/${stationCode}` : ''; const prefix = stationCode ? `/${stationCode}` : '';
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/reboot`; const endpoint = `${prefix}/api/ndm/ndmSecurityBox/reboot`;
const resp = await client.post<boolean>(endpoint, { community: 'public', ipAddress }, { signal }); const resp = await client.post<boolean>(endpoint, { community, ipAddress }, { signal });
const data = unwrapResponse(resp);
return data;
};
// ningtech安防箱切换空开状态
export const turnCircuitStatusNingTechApi = async (community: string, ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
const { stationCode, signal } = options ?? {};
const client = stationCode ? ndmClient : userClient;
const prefix = stationCode ? `/${stationCode}` : '';
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/turnStatusNingTech`;
const resp = await client.post<boolean>(endpoint, { community, ipAddress, circuit: `${circuitIndex}`, status }, { signal });
const data = unwrapResponse(resp);
return data;
};
// ningtech安防箱重启
export const rebootSecurityBoxNingTechApi = async (community: string, ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
const { stationCode, signal } = options ?? {};
const client = stationCode ? ndmClient : userClient;
const prefix = stationCode ? `/${stationCode}` : '';
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/rebootNingTech`;
const resp = await client.post<boolean>(endpoint, { community, ipAddress }, { signal });
const data = unwrapResponse(resp); const data = unwrapResponse(resp);
return data; return data;
}; };
@@ -2,8 +2,6 @@
import { import {
detailDeviceApi, detailDeviceApi,
probeDeviceApi, probeDeviceApi,
rebootSecurityBoxApi,
turnCitcuitStatusApi,
updateDeviceApi, updateDeviceApi,
type LinkDescription, type LinkDescription,
type NdmDeviceResultVO, type NdmDeviceResultVO,
@@ -16,6 +14,7 @@ import { SecurityBoxCircuitLinkModal } from '@/components';
import { usePermission } from '@/composables'; import { usePermission } from '@/composables';
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants'; import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
import { PERMISSION_TYPE_LITERALS } from '@/enums'; import { PERMISSION_TYPE_LITERALS } from '@/enums';
import { normalizeSecurityBoxWriteCommunity, dispatchRebootSecurityBoxApi, dispatchTurnCircuitStatusApi, normalizeSecurityBoxCircuitIndex } from '@/helpers';
import { useDeviceStore, useSettingStore } from '@/stores'; import { useDeviceStore, useSettingStore } from '@/stores';
import { parseErrorFeedback } from '@/utils'; import { parseErrorFeedback } from '@/utils';
import { useMutation } from '@tanstack/vue-query'; import { useMutation } from '@tanstack/vue-query';
@@ -31,6 +30,7 @@ import { computed, inject, ref, toRefs } from 'vue';
const props = defineProps<{ const props = defineProps<{
ndmDevice: NdmSecurityBoxResultVO; ndmDevice: NdmSecurityBoxResultVO;
station: Station; station: Station;
vendor?: string;
circuits?: NdmSecurityBoxCircuit[]; circuits?: NdmSecurityBoxCircuit[];
}>(); }>();
@@ -44,7 +44,7 @@ const { useLocalDB } = storeToRefs(settingStore);
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const { ndmDevice, station, circuits } = toRefs(props); const { ndmDevice, station, vendor, circuits } = toRefs(props);
const showCard = computed(() => !!circuits.value && circuits.value.length > 0); const showCard = computed(() => !!circuits.value && circuits.value.length > 0);
@@ -52,6 +52,10 @@ const localCircuits = ref<NdmSecurityBoxCircuit[]>([]);
watchImmediate(circuits, (newCircuits) => { watchImmediate(circuits, (newCircuits) => {
localCircuits.value = newCircuits?.map((circuit) => ({ ...circuit })) ?? []; localCircuits.value = newCircuits?.map((circuit) => ({ ...circuit })) ?? [];
const circuitCount = ndmDevice.value.circuitCount;
if (!!circuitCount) {
localCircuits.value = localCircuits.value.slice(0, circuitCount);
}
}); });
const getCircuitStatusTagType = (circuit: NdmSecurityBoxCircuit): TagProps['type'] => { const getCircuitStatusTagType = (circuit: NdmSecurityBoxCircuit): TagProps['type'] => {
@@ -79,13 +83,19 @@ const { mutate: turnStatus, isPending: turning } = useMutation({
window.$loadingBar.start(); window.$loadingBar.start();
const { circuitIndex, newStatus } = params; const { circuitIndex, newStatus } = params;
if (!ndmDevice.value.ipAddress) { const community = normalizeSecurityBoxWriteCommunity(ndmDevice.value, vendor.value);
const ipAddress = ndmDevice.value.ipAddress;
if (!ipAddress) {
throw new Error('设备IP地址不存在'); throw new Error('设备IP地址不存在');
} }
const status = newStatus ? 1 : 0; const status = newStatus ? 1 : 0;
const stationCode = station.value.code; const stationCode = station.value.code;
const signal = abortController.value.signal; const signal = abortController.value.signal;
await turnCitcuitStatusApi(ndmDevice.value.ipAddress, circuitIndex, status, { stationCode, signal });
const turnCircuitStatusApi = dispatchTurnCircuitStatusApi(vendor.value);
const normalizedCircuitIndex = normalizeSecurityBoxCircuitIndex(circuitIndex, vendor.value);
await turnCircuitStatusApi(community, ipAddress, normalizedCircuitIndex, status, { stationCode, signal });
await probeDeviceApi(ndmDevice.value, { stationCode, signal }); await probeDeviceApi(ndmDevice.value, { stationCode, signal });
return status; return status;
}, },
@@ -110,13 +120,17 @@ const { mutate: reboot, isPending: rebooting } = useMutation({
window.$loadingBar.start(); window.$loadingBar.start();
if (!ndmDevice.value.ipAddress) { const community = normalizeSecurityBoxWriteCommunity(ndmDevice.value, vendor.value);
const ipAddress = ndmDevice.value.ipAddress;
if (!ipAddress) {
throw new Error('设备IP地址不存在'); throw new Error('设备IP地址不存在');
} }
const stationCode = station.value.code; const stationCode = station.value.code;
const signal = abortController.value.signal; const signal = abortController.value.signal;
await rebootSecurityBoxApi(ndmDevice.value.ipAddress, { stationCode, signal });
const rebootSecurityBoxApi = dispatchRebootSecurityBoxApi(vendor.value);
await rebootSecurityBoxApi(community, ipAddress, { stationCode, signal });
}, },
onSuccess: () => { onSuccess: () => {
window.$loadingBar.finish(); window.$loadingBar.finish();
@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { NdmSecurityBoxDiagInfo, NdmSecurityBoxResultVO, Station } from '@/apis'; import type { NdmSecurityBoxDiagInfo, NdmSecurityBoxResultVO, Station } from '@/apis';
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, SecurityBoxCircuitCard, SecurityBoxEnvCard, type DeviceCommonCardProps } from '@/components'; import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, SecurityBoxCircuitCard, SecurityBoxEnvCard, type DeviceCommonCardProps } from '@/components';
import { SECURITY_BOX_VENDOR_LITERALS } from '@/helpers';
import destr from 'destr'; import destr from 'destr';
import { NFlex } from 'naive-ui'; import { NFlex } from 'naive-ui';
import { computed, toRefs } from 'vue'; import { computed, toRefs } from 'vue';
@@ -19,6 +20,20 @@ const lastDiagInfo = computed(() => {
return result as NdmSecurityBoxDiagInfo; return result as NdmSecurityBoxDiagInfo;
}); });
// 解析安防箱厂商
const vendor = computed(() => {
// 先读取诊断信息中的 stCommonInfo 中的设备厂商
if (!!lastDiagInfo.value?.stCommonInfo?.设备厂商) {
return lastDiagInfo.value.stCommonInfo.设备厂商.trim().toLocaleLowerCase();
}
// 如果 stCommonInfo 中没有设备厂商,再读取 ndmDevice 中的 manufacturer
if (!!ndmDevice.value.manufacturer) {
return ndmDevice.value.manufacturer.trim().toLocaleLowerCase();
}
// 如果 ndmDevice 中也没有 manufacturer,返回 beidian 作为兜底
return SECURITY_BOX_VENDOR_LITERALS.beidian;
});
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => { const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
const { stCommonInfo } = lastDiagInfo.value ?? {}; const { stCommonInfo } = lastDiagInfo.value ?? {};
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {}; const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
@@ -55,7 +70,7 @@ const circuits = computed(() => lastDiagInfo.value?.info?.at(0)?.circuits);
<DeviceCommonCard :common-info="commonInfo" /> <DeviceCommonCard :common-info="commonInfo" />
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" /> <DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
<SecurityBoxEnvCard :fan-speeds="fanSpeeds" :temperature="temperature" :humidity="humidity" :switches="switches" /> <SecurityBoxEnvCard :fan-speeds="fanSpeeds" :temperature="temperature" :humidity="humidity" :switches="switches" />
<SecurityBoxCircuitCard :ndm-device="ndmDevice" :station="station" :circuits="circuits" /> <SecurityBoxCircuitCard :ndm-device="ndmDevice" :station="station" :vendor="vendor" :circuits="circuits" />
</NFlex> </NFlex>
</template> </template>
+1
View File
@@ -1,2 +1,3 @@
export * from './nvr'; export * from './nvr';
export * from './security-box';
export * from './switch'; export * from './switch';
@@ -0,0 +1,64 @@
import { rebootSecurityBoxBeidianApi, rebootSecurityBoxNingTechApi, turnCircuitStatusBeidianApi, turnCircuitStatusNingTechApi, type NdmSecurityBoxResultVO } from '@/apis';
import { objectEntries } from '@vueuse/core';
const UNSUPPORTED_SECURITY_BOX_VENDOR = '不支持的安防箱厂商';
export const SECURITY_BOX_VENDOR_LITERALS = {
beidian: 'beidian',
ningtech: 'ningtech',
} as const;
export type SecurityBoxVendor = keyof typeof SECURITY_BOX_VENDOR_LITERALS;
export const resolveSecurityBoxVendor = (vendor?: string) => {
const entry = objectEntries(SECURITY_BOX_VENDOR_LITERALS).find(([, value]) => value === vendor);
return entry?.at(0);
};
export const normalizeSecurityBoxWriteCommunity = (ndmDevice: NdmSecurityBoxResultVO, vendor?: string) => {
const resolved = resolveSecurityBoxVendor(vendor);
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
const community = ndmDevice.community;
if (!community) throw new Error('团体字符串不存在');
return community;
}
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
const community = ndmDevice.writeCommunity;
if (!community) throw new Error('团体字符串(写)不存在');
return community;
}
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
};
export const normalizeSecurityBoxCircuitIndex = (index: number, vendor?: string) => {
const resolved = resolveSecurityBoxVendor(vendor);
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
return index;
}
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
return index + 1;
}
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
};
export const dispatchTurnCircuitStatusApi = (vendor?: string) => {
const resolved = resolveSecurityBoxVendor(vendor);
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
return turnCircuitStatusBeidianApi;
}
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
return turnCircuitStatusNingTechApi;
}
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
};
export const dispatchRebootSecurityBoxApi = (vendor?: string) => {
const resolved = resolveSecurityBoxVendor(vendor);
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
return rebootSecurityBoxBeidianApi;
}
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
return rebootSecurityBoxNingTechApi;
}
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
};
+1
View File
@@ -0,0 +1 @@
export * from './adapter';