213 lines
7.8 KiB
Vue
213 lines
7.8 KiB
Vue
<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>
|