Files
ndm-web-platform/src/components/device/device-card/components/current-diag/security-box-circuit-link-modal.vue
yangsy cd0bc86803 feat: 设备关联与解除关联
- 支持配置交换机端口的下游关联设备
- 支持配置安防箱电路的下游关联设备
- 支持解除关联
- 删除设备时校验是否存在上/下游设备
2025-12-26 13:42:11 +08:00

213 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { detailDeviceApi, updateDeviceApi, type NdmCameraLinkDescription, type NdmDeviceResultVO, type NdmSecurityBoxLinkDescription, type NdmSecurityBoxResultVO, type Station } from '@/apis';
import { DeviceTree } from '@/components';
import { tryGetDeviceType } from '@/enums';
import { useDeviceStore, useSettingStore } from '@/stores';
import { parseErrorFeedback } from '@/utils';
import { useMutation } from '@tanstack/vue-query';
import { isCancel } from 'axios';
import destr from 'destr';
import { cloneDeep } from 'es-toolkit';
import { NButton, NFlex, NModal } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { computed, ref, toRefs } from 'vue';
const props = defineProps<{
ndmDevice: NdmSecurityBoxResultVO;
station: Station;
circuitIndex?: number;
}>();
const show = defineModel<boolean>('show', { default: false });
const deviceStore = useDeviceStore();
const settingStore = useSettingStore();
const { offlineDev } = storeToRefs(settingStore);
const { ndmDevice, station, circuitIndex } = toRefs(props);
const upperDeviceLinkDescription = computed(() => {
const result = destr<any>(ndmDevice.value.linkDescription);
if (!result) return null;
if (typeof result !== 'object') return null;
return result as NdmSecurityBoxLinkDescription;
});
const lowerDevice = ref<NdmDeviceResultVO>();
const lowerDeviceLinkDescription = computed<NdmCameraLinkDescription | null>(() => {
if (!lowerDevice.value) return null;
const result = destr<any>(lowerDevice.value.linkDescription);
if (!result) return null;
if (typeof result !== 'object') return null;
return result;
});
const onAfterSelectDevice = (device: NdmDeviceResultVO) => {
lowerDevice.value = device;
};
const abortController = ref<AbortController>(new AbortController());
const { mutate: linkPortToDevice, isPending: linking } = useMutation({
mutationFn: async () => {
abortController.value.abort();
abortController.value = new AbortController();
window.$loadingBar.start();
const upperDeviceType = tryGetDeviceType(ndmDevice.value.deviceType);
if (!upperDeviceType) throw new Error('本设备类型未知');
const upperDeviceDbId = ndmDevice.value.id;
if (!upperDeviceDbId) throw new Error('本设备没有ID');
if (circuitIndex.value === undefined) throw new Error('该电路不存在');
if (!lowerDevice.value) throw new Error('请选择要关联的设备');
const lowerDeviceType = tryGetDeviceType(lowerDevice.value?.deviceType);
if (!lowerDeviceType) throw new Error('该设备类型未知');
const lowerDeviceDbId = lowerDevice.value?.id;
if (!lowerDeviceDbId) throw new Error('该设备没有ID');
// 0. 检查上游设备的linkDescription的downstream字段是否存在某个端口已经关联下游设备
const duplicated = Object.entries(upperDeviceLinkDescription.value?.downstream ?? {}).find(([, deviceStoreIndex]) => {
return deviceStoreIndex.deviceDbId === lowerDeviceDbId;
});
if (duplicated) {
const [portName] = duplicated;
throw new Error(`该设备已关联到端口${portName}`);
}
// 1. 修改上游设备的linkDescription的downstream字段
const modifiedUpperDevice = cloneDeep(ndmDevice.value);
let modifiedUpperDeviceLinkDescription: NdmSecurityBoxLinkDescription;
if (!upperDeviceLinkDescription.value) {
modifiedUpperDeviceLinkDescription = {
downstream: {
[circuitIndex.value]: {
stationCode: station.value.code,
deviceType: lowerDeviceType,
deviceDbId: lowerDeviceDbId,
},
},
};
} else {
modifiedUpperDeviceLinkDescription = {
...upperDeviceLinkDescription.value,
downstream: {
...upperDeviceLinkDescription.value.downstream,
[circuitIndex.value]: {
stationCode: station.value.code,
deviceType: lowerDeviceType,
deviceDbId: lowerDeviceDbId,
},
},
};
}
modifiedUpperDevice.linkDescription = JSON.stringify(modifiedUpperDeviceLinkDescription);
// 2. 修改下游设备的linkDescription的upstream字段
const modifiedLowerDevice = cloneDeep(lowerDevice.value);
let modifiedLowerDeviceLinkDescription: NdmCameraLinkDescription;
if (!lowerDeviceLinkDescription.value) {
modifiedLowerDeviceLinkDescription = {
upstream: [
{
stationCode: station.value.code,
deviceType: upperDeviceType,
deviceDbId: upperDeviceDbId,
},
],
};
} else {
const upstream = cloneDeep(lowerDeviceLinkDescription.value.upstream);
if (!upstream) {
modifiedLowerDeviceLinkDescription = {
...lowerDeviceLinkDescription.value,
upstream: [
{
stationCode: station.value.code,
deviceType: upperDeviceType,
deviceDbId: upperDeviceDbId,
},
],
};
} else {
const deviceStoreIndex = upstream.find((deviceStoreIndex) => deviceStoreIndex.deviceDbId === upperDeviceDbId);
if (!deviceStoreIndex) {
upstream.push({
stationCode: station.value.code,
deviceType: upperDeviceType,
deviceDbId: upperDeviceDbId,
});
}
modifiedLowerDeviceLinkDescription = {
...lowerDeviceLinkDescription.value,
upstream,
};
}
}
modifiedLowerDevice.linkDescription = JSON.stringify(modifiedLowerDeviceLinkDescription);
// TODO: 3. 发起update请求并获取最新的设备详情离线模式下直接修改本地数据
if (offlineDev.value) {
return { upperDevice: modifiedUpperDevice, lowerDevice: modifiedLowerDevice };
}
const stationCode = station.value.code;
const signal = abortController.value.signal;
await updateDeviceApi(modifiedUpperDevice, { stationCode, signal });
await updateDeviceApi(modifiedLowerDevice, { stationCode, signal });
const latestUpperDevice = await detailDeviceApi(modifiedUpperDevice, { stationCode, signal });
const latestLowerDevice = await detailDeviceApi(modifiedLowerDevice, { stationCode, signal });
return { upperDevice: latestUpperDevice, lowerDevice: latestLowerDevice };
},
onSuccess: ({ upperDevice, lowerDevice }) => {
show.value = false;
window.$loadingBar.finish();
window.$message.success('关联成功');
if (!!upperDevice && !!lowerDevice) {
deviceStore.patchDevice(station.value.code, { ...upperDevice });
deviceStore.patchDevice(station.value.code, { ...lowerDevice });
}
},
onError: (error) => {
window.$loadingBar.error();
if (isCancel(error)) return;
console.error(error);
const errorFeedback = parseErrorFeedback(error);
window.$message.error(errorFeedback);
},
});
const onLink = () => {
linkPortToDevice();
};
const onCancel = () => {
abortController.value.abort();
show.value = false;
};
</script>
<template>
<NModal v-model:show="show" preset="card" style="width: 600px; height: 600px" :content-style="{ height: '100%', overflow: 'hidden' }" @close="onCancel" @esc="onCancel">
<template #header>
<span>{{ ndmDevice.name }}</span>
<span> - </span>
<span>电路{{ circuitIndex ? circuitIndex + 1 : '-' }}</span>
<span> - </span>
<span>关联设备</span>
</template>
<template #default>
<DeviceTree :station="station" :events="['select']" :device-prefix-label="'选择'" @after-select-device="onAfterSelectDevice" />
</template>
<template #action>
<NFlex justify="end">
<NButton size="small" quaternary @click="onCancel">取消</NButton>
<NButton size="small" type="primary" :disabled="!lowerDevice" :loading="linking" @click="onLink">关联</NButton>
</NFlex>
</template>
</NModal>
</template>
<style scoped lang="scss"></style>