Compare commits
5 Commits
f9f761b4e9
...
de241334a9
| Author | SHA1 | Date | |
|---|---|---|---|
| de241334a9 | |||
| c75338cb70 | |||
| 86e3e1726d | |||
| 943aa27de1 | |||
| b4442fe6c4 |
@@ -1,4 +1,12 @@
|
||||
[
|
||||
{
|
||||
"version": "0.40.0",
|
||||
"date": "2026-04-10",
|
||||
"changes": {
|
||||
"fixes": [{ "content": "修复设备树搜索时节点错乱的问题" }, { "content": "将安防箱面板的“电路”统一更正为“空开”" }, { "content": "修复设备查询缓存键冲突问题" }],
|
||||
"feats": [{ "content": "添加视频服务器双机热备状态" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"version": "0.39.0",
|
||||
"date": "2026-03-02",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "0.39.0",
|
||||
"buildTime": "2026-03-11 14:35:45"
|
||||
"version": "0.40.0",
|
||||
"buildTime": "2026-04-10 15:42:03"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface HighAvailable {
|
||||
pyip: string;
|
||||
vip: string;
|
||||
changeDate: string;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './diag';
|
||||
export * from './high-available';
|
||||
export * from './link-description';
|
||||
export * from './station';
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
import { ndmClient, userClient, type MediaServerStatus, type SendRtpInfo, type Station } from '@/apis';
|
||||
import { unwrapResponse } from '@/utils';
|
||||
import { ndmClient, userClient, type HighAvailable, type MediaServerStatus, type SendRtpInfo, type Station } from '@/apis';
|
||||
import { unwrapNullableResponse, unwrapResponse } from '@/utils';
|
||||
import destr from 'destr';
|
||||
|
||||
// {
|
||||
// "code": 0,
|
||||
// "data": "{pyip:\"10.18.128.14\",vip:\"10.18.128.6\",changeDate:\"2026-03-23 15:55:00\"}",
|
||||
// "msg": "ok",
|
||||
// "path": null,
|
||||
// "extra": null,
|
||||
// "timestamp": "1774421387908",
|
||||
// "errorMsg": "",
|
||||
// "isSuccess": true
|
||||
// }
|
||||
export const getHighAvailableApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmServiceAvailable/highAvailable/get`;
|
||||
const resp = await client.get<string | null>(endpoint, { signal });
|
||||
const data = unwrapNullableResponse(resp);
|
||||
return destr<HighAvailable | null>(data);
|
||||
};
|
||||
|
||||
export const getAllPushApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
|
||||
@@ -92,6 +92,8 @@ const abortController = ref<AbortController>(new AbortController());
|
||||
|
||||
const NVR_RECORD_CHECK_KEY = 'nvr-record-check-query';
|
||||
|
||||
const deviceUniqueKey = computed(() => [station.value.code, ndmDevice.value.id]);
|
||||
|
||||
const DAY_OFFSET = 90;
|
||||
|
||||
const {
|
||||
@@ -99,7 +101,7 @@ const {
|
||||
isFetching: loading,
|
||||
refetch: refetchRecordChecks,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => [NVR_RECORD_CHECK_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
queryKey: computed(() => [NVR_RECORD_CHECK_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
@@ -408,13 +410,13 @@ const ndmRecordChecksPaged = computed(() => {
|
||||
return ndmRecordChecksFiltered.value.slice(startIndex, endIndex);
|
||||
});
|
||||
|
||||
// 当设备ID、最后诊断时间或筛选类型变化时,重置分页为第一页
|
||||
watch([() => ndmDevice.value.id, () => ndmDevice.value.lastDiagTime, filterType, searchInputDebounced], () => {
|
||||
// 当车站号、设备IP、最后诊断时间或筛选类型变化时,重置分页为第一页
|
||||
watch([() => station.value.code, () => ndmDevice.value.ipAddress, () => ndmDevice.value.lastDiagTime, filterType, searchInputDebounced], () => {
|
||||
page.value = 1;
|
||||
});
|
||||
|
||||
// 当设备ID变化时,重置搜索内容,并将筛选类型重置为「全部」
|
||||
watch([() => ndmDevice.value.id], () => {
|
||||
// 当车站号、设备IP变化时,重置搜索内容,并将筛选类型重置为「全部」
|
||||
watch([() => station.value.code, () => ndmDevice.value.ipAddress], () => {
|
||||
searchInput.value = '';
|
||||
filterType.value = 'all';
|
||||
});
|
||||
|
||||
+4
-4
@@ -202,7 +202,7 @@ const contextmenuOptions = computed<DropdownOption[]>(() => [
|
||||
if (!lowerDevice) return;
|
||||
window.$dialog.warning({
|
||||
title: '确认解除关联吗?',
|
||||
content: `将解除【电路${circuitIndex + 1}】与【${lowerDevice.name}】的关联关系。`,
|
||||
content: `将解除【空开${circuitIndex + 1}】与【${lowerDevice.name}】的关联关系。`,
|
||||
style: { width: '600px' },
|
||||
contentStyle: { height: '60px' },
|
||||
negativeText: '取消',
|
||||
@@ -299,7 +299,7 @@ const { mutate: unlinkDevice } = useMutation({
|
||||
<NCard v-if="showCard" hoverable size="small">
|
||||
<template #header>
|
||||
<NFlex align="center">
|
||||
<span>电路状态</span>
|
||||
<span>空开状态</span>
|
||||
<NPopconfirm :positive-text="'确认'" :negative-text="'取消'" @positive-click="() => reboot()">
|
||||
<template #trigger>
|
||||
<NButton secondary size="small" :loading="rebooting">重合闸</NButton>
|
||||
@@ -324,7 +324,7 @@ const { mutate: unlinkDevice } = useMutation({
|
||||
<span>{{ getCircuitStatusText(circuit) }}</span>
|
||||
</template>
|
||||
</NTag>
|
||||
<span>电路{{ circuitIndex + 1 }}</span>
|
||||
<span>空开{{ circuitIndex + 1 }}</span>
|
||||
</NFlex>
|
||||
<NFlex justify="end" align="center">
|
||||
<NPopconfirm :positive-text="'确认'" :negative-text="'取消'" @positive-click="() => turnStatus({ circuitIndex: circuitIndex, newStatus: circuit.status !== 1 })">
|
||||
@@ -332,7 +332,7 @@ const { mutate: unlinkDevice } = useMutation({
|
||||
<NSwitch size="small" :value="circuit.status === 1" :loading="turning" />
|
||||
</template>
|
||||
<template #default>
|
||||
<span>确定要{{ circuit.status === 1 ? '关闭' : '开启' }}电路{{ circuitIndex + 1 }}吗?</span>
|
||||
<span>确定要{{ circuit.status === 1 ? '关闭' : '开启' }}空开{{ circuitIndex + 1 }}吗?</span>
|
||||
</template>
|
||||
</NPopconfirm>
|
||||
</NFlex>
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@ const { mutate: linkPortToDevice, isPending: linking } = useMutation({
|
||||
const upperDeviceDbId = ndmDevice.value.id;
|
||||
if (!upperDeviceDbId) throw new Error('本设备没有ID');
|
||||
|
||||
if (circuitIndex.value === undefined) throw new Error('该电路不存在');
|
||||
if (circuitIndex.value === undefined) throw new Error('该空开不存在');
|
||||
|
||||
if (!lowerDevice.value) throw new Error('请选择要关联的设备');
|
||||
const lowerDeviceType = tryGetDeviceType(lowerDevice.value?.deviceType);
|
||||
@@ -195,7 +195,7 @@ const onCancel = () => {
|
||||
<template #header>
|
||||
<span>{{ ndmDevice.name }}</span>
|
||||
<span> - </span>
|
||||
<span>电路{{ circuitIndex ? circuitIndex + 1 : '-' }}</span>
|
||||
<span>空开{{ circuitIndex ? circuitIndex + 1 : '-' }}</span>
|
||||
<span> - </span>
|
||||
<span>关联设备</span>
|
||||
</template>
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ const { ndmDevice, station } = toRefs(props);
|
||||
const showDetailModal = ref(false);
|
||||
|
||||
const detailTableColumns: DataTableColumns<SecurityBoxCircuitRowData> = [
|
||||
{ title: '电路序号', key: 'number' },
|
||||
{ title: '空开序号', key: 'number' },
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
@@ -98,7 +98,7 @@ const tableColumns: DataTableColumns<SecurityBoxRuntimeRowData> = [
|
||||
},
|
||||
// { title: '开关状态', key: 'switches' },
|
||||
{
|
||||
title: '电路状态',
|
||||
title: '空开状态',
|
||||
key: 'circuits',
|
||||
render(rowData) {
|
||||
const { info } = rowData.diagInfo;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import ServerAlive from './server-alive.vue';
|
||||
import ServerCard from './server-card.vue';
|
||||
import ServerCurrentDiag from './server-current-diag.vue';
|
||||
import ServerHighAvailable from './server-high-available.vue';
|
||||
import ServerHistoryDiag from './server-history-diag.vue';
|
||||
import ServerStreamPush from './server-stream-push.vue';
|
||||
import ServerUpdate from './server-update.vue';
|
||||
|
||||
export { ServerAlive, ServerCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate, ServerStreamPush };
|
||||
export { ServerAlive, ServerCard, ServerCurrentDiag, ServerHighAvailable, ServerHistoryDiag, ServerUpdate, ServerStreamPush };
|
||||
|
||||
@@ -23,8 +23,11 @@ const deviceType = computed(() => tryGetDeviceType(ndmDevice.value.deviceType));
|
||||
|
||||
const MEDIA_SERVER_ALIVE_QUERY_KEY = 'media-server-alive-query';
|
||||
const VIDEO_SERVER_ALIVE_QUERY_KEY = 'video-server-alive-query';
|
||||
|
||||
const deviceUniqueKey = computed(() => [station.value.code, ndmDevice.value.id]);
|
||||
|
||||
const { data: isMediaServerAlive } = useQuery({
|
||||
queryKey: computed(() => [MEDIA_SERVER_ALIVE_QUERY_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
queryKey: computed(() => [MEDIA_SERVER_ALIVE_QUERY_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value && deviceType.value === DEVICE_TYPE_LITERALS.ndmMediaServer),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
@@ -34,7 +37,7 @@ const { data: isMediaServerAlive } = useQuery({
|
||||
},
|
||||
});
|
||||
const { data: isSipServerAlive } = useQuery({
|
||||
queryKey: computed(() => [VIDEO_SERVER_ALIVE_QUERY_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
queryKey: computed(() => [VIDEO_SERVER_ALIVE_QUERY_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value && deviceType.value === DEVICE_TYPE_LITERALS.ndmVideoServer),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { type NdmServerDiagInfo, type NdmServerResultVO, type Station } from '@/apis';
|
||||
import { DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerStreamPush } from '@/components';
|
||||
import { DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerHighAvailable, ServerStreamPush } from '@/components';
|
||||
import destr from 'destr';
|
||||
import { NFlex } from 'naive-ui';
|
||||
import { computed, toRefs } from 'vue';
|
||||
@@ -27,6 +27,7 @@ const runningTime = computed(() => lastDiagInfo.value?.commInfo?.系统运行时
|
||||
|
||||
<template>
|
||||
<NFlex vertical>
|
||||
<ServerHighAvailable :ndm-device="ndmDevice" :station="station" />
|
||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||
<DeviceHardwareCard running-time-label="服务器运行时间" :cpu-usage="cpuUsage" :mem-usage="memUsage" :disk-usage="diskUsage" :running-time="runningTime" />
|
||||
<ServerAlive :ndm-device="ndmDevice" :station="station" />
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { getHighAvailableApi, type NdmServerResultVO, type Station } from '@/apis';
|
||||
import { DEVICE_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||
import { NAlert, NCard, NFlex } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, toRefs, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
ndmDevice: NdmServerResultVO;
|
||||
station: Station;
|
||||
}>();
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
const { activeRequests } = storeToRefs(settingStore);
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const deviceType = computed(() => tryGetDeviceType(ndmDevice.value.deviceType));
|
||||
|
||||
const isVideoServer = computed(() => deviceType.value === DEVICE_TYPE_LITERALS.ndmVideoServer);
|
||||
|
||||
const SERVER_HIGH_AVAILABLE_QUERY_KEY = 'server-high-available-query';
|
||||
|
||||
const deviceUniqueKey = computed(() => [station.value.code, ndmDevice.value.id]);
|
||||
|
||||
const { data: highAvailable } = useQuery({
|
||||
queryKey: computed(() => [SERVER_HIGH_AVAILABLE_QUERY_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value && isVideoServer.value),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
queryFn: async ({ signal }) => {
|
||||
const highAvailable = await getHighAvailableApi({ stationCode: station.value.code, signal });
|
||||
return highAvailable;
|
||||
},
|
||||
});
|
||||
watch(activeRequests, (active) => {
|
||||
if (!active) {
|
||||
queryClient.cancelQueries({ queryKey: [SERVER_HIGH_AVAILABLE_QUERY_KEY] });
|
||||
}
|
||||
});
|
||||
|
||||
const showCard = computed(() => {
|
||||
const { pyip: physicalIp } = highAvailable.value ?? {};
|
||||
const ipAddressMatched = physicalIp === ndmDevice.value.ipAddress;
|
||||
return isVideoServer.value && ipAddressMatched;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NAlert v-if="showCard && !!highAvailable" :bordered="false" type="success">
|
||||
<template #header>
|
||||
<NFlex :align="'center'">
|
||||
<div>正在提供服务</div>
|
||||
<NFlex :align="'center'" style="font-size: smaller">
|
||||
<div>虚拟IP: {{ highAvailable.vip }}</div>
|
||||
<div>启用时间: {{ highAvailable.changeDate }}</div>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NAlert>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -25,8 +25,10 @@ const showCard = computed(() => deviceType.value === DEVICE_TYPE_LITERALS.ndmMed
|
||||
|
||||
const SERVER_STREAM_PUSH_KEY = 'server-stream-push-query';
|
||||
|
||||
const deviceUniqueKey = computed(() => [station.value.code, ndmDevice.value.id]);
|
||||
|
||||
const { data: streamPushes } = useQuery({
|
||||
queryKey: computed(() => [SERVER_STREAM_PUSH_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
queryKey: computed(() => [SERVER_STREAM_PUSH_KEY, deviceUniqueKey.value, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value && showCard.value),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
<script lang="ts">
|
||||
const createDeviceNodeKey = (stationCode?: Station['code'], device?: NdmDeviceResultVO) => {
|
||||
return `${stationCode ?? ''}-${device?.id ?? ''}`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { initStationDevices, type NdmDeviceResultVO, type NdmNvrResultVO, type Station } from '@/apis';
|
||||
import { useDeviceTree, usePermission, type UseDeviceTreeReturn } from '@/composables';
|
||||
@@ -110,7 +116,7 @@ watchImmediate(selectedDeviceType, (newDeviceType) => {
|
||||
}
|
||||
});
|
||||
|
||||
const selectedKeys = computed(() => (selectedDevice.value?.id ? [selectedDevice.value.id] : undefined));
|
||||
const selectedKeys = computed(() => (selectedDevice.value?.id ? [createDeviceNodeKey(selectedStationCode.value, selectedDevice.value)] : undefined));
|
||||
watch([selectedKeys, selectedDevice, selectedStationCode], ([, device, code]) => {
|
||||
if (device && code) {
|
||||
onSelectDevice(device, code);
|
||||
@@ -316,26 +322,26 @@ const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() =>
|
||||
key: stationCode,
|
||||
prefix: () => renderStationNodePrefix(station),
|
||||
suffix: () => renderIcmpStatistics(onlineDevices?.length ?? 0, offlineDevices?.length ?? 0, devices?.length ?? 0),
|
||||
children: nvrClusters.map<TreeOption>((nvrCluster) => {
|
||||
children: nvrClusters.map<TreeOption>((cluster) => {
|
||||
return {
|
||||
label: `${nvrCluster.name}`,
|
||||
key: nvrCluster.id ?? `${nvrCluster.name}`,
|
||||
prefix: () => renderDeviceNodePrefix(nvrCluster, stationCode),
|
||||
suffix: () => `${nvrCluster.ipAddress}`,
|
||||
children: nvrSingletons.map<TreeOption>((nvr) => {
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: nvrSingletons.map<TreeOption>((device) => {
|
||||
return {
|
||||
label: `${nvr.name}`,
|
||||
key: nvr.id ?? `${nvr.name}`,
|
||||
prefix: () => renderDeviceNodePrefix(nvr, stationCode),
|
||||
suffix: () => `${nvr.ipAddress}`,
|
||||
label: `${device.name}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: nvr,
|
||||
device: device,
|
||||
};
|
||||
}),
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
stationCode,
|
||||
device: nvrCluster,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
@@ -352,7 +358,7 @@ const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() =>
|
||||
const device = dev as NdmDeviceResultVO;
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: `${device.name}${device.ipAddress}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||
@@ -383,16 +389,16 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
||||
key: deviceType,
|
||||
suffix: () => renderIcmpStatistics(onlineCount, offlineCount, nvrs.length),
|
||||
children: clusters.map<TreeOption>((device) => {
|
||||
children: clusters.map<TreeOption>((cluster) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: `${device.name}${device.ipAddress}`,
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
label: `${cluster.name}`,
|
||||
key: createDeviceNodeKey(stationCode, cluster),
|
||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||
suffix: () => `${cluster.ipAddress}`,
|
||||
children: singletons.map<TreeOption>((device) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: `${device.name}${device.ipAddress}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
stationCode,
|
||||
@@ -400,7 +406,7 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
device,
|
||||
device: cluster,
|
||||
};
|
||||
}),
|
||||
stationCode,
|
||||
@@ -414,7 +420,7 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
||||
children: stationDevices[deviceType].map<TreeOption>((device) => {
|
||||
return {
|
||||
label: `${device.name}`,
|
||||
key: `${device.name}${device.ipAddress}`,
|
||||
key: createDeviceNodeKey(stationCode, device),
|
||||
prefix: () => renderDeviceNodePrefix(device, stationCode),
|
||||
suffix: () => `${device.ipAddress}`,
|
||||
stationCode,
|
||||
@@ -462,6 +468,7 @@ const onFoldDeviceTree = () => {
|
||||
};
|
||||
const onLocateDeviceTree = async () => {
|
||||
if (!selectedStationCode.value) return;
|
||||
const stationCode = selectedStationCode.value;
|
||||
if (!selectedDevice.value) return;
|
||||
const deviceType = tryGetDeviceType(selectedDevice.value.deviceType);
|
||||
if (!deviceType) return;
|
||||
@@ -473,24 +480,24 @@ const onLocateDeviceTree = async () => {
|
||||
activeTab.value = deviceType;
|
||||
|
||||
// 展开选择的车站
|
||||
expandedKeys.value.push(selectedStationCode.value);
|
||||
expandedKeys.value.push(stationCode);
|
||||
|
||||
// 当选择录像机时,如果不是集群,进一步展开该录像机所在的集群节点
|
||||
if (deviceType === DEVICE_TYPE_LITERALS.ndmNvr) {
|
||||
const stationDevices = lineDevices.value[selectedStationCode.value];
|
||||
const stationDevices = lineDevices.value[stationCode];
|
||||
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) => `${nvr.id}`));
|
||||
expandedKeys.value.push(...clusters.map((nvr) => createDeviceNodeKey(stationCode, nvr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待设备树展开完成,滚动到选择的设备
|
||||
await nextTick();
|
||||
deviceTreeInst.value.scrollTo({ key: `${selectedDevice.value.id}`, behavior: 'smooth' });
|
||||
deviceTreeInst.value.scrollTo({ key: createDeviceNodeKey(stationCode, selectedDevice.value), behavior: 'smooth' });
|
||||
|
||||
animated.value = true;
|
||||
};
|
||||
|
||||
@@ -136,6 +136,17 @@ export const unwrapResponse = <T>(resp: HttpResponse<T>) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
export const unwrapNullableResponse = <T>(resp: HttpResponse<T>) => {
|
||||
const [err, data, result] = resp;
|
||||
if (err) throw err;
|
||||
if (result) {
|
||||
const { isSuccess, path, msg, errorMsg } = result;
|
||||
if (!isSuccess) throw new Error(`${path ? `${path}: ` : ''}${msg || errorMsg || '请求失败'}`);
|
||||
}
|
||||
if (data === undefined) throw new Error('响应数据不存在');
|
||||
return data;
|
||||
};
|
||||
|
||||
// 针对没有数据的响应,直接判断是否存在错误
|
||||
export const unwrapVoidResponse = (resp: HttpResponse<void>) => {
|
||||
const [err, , result] = resp;
|
||||
|
||||
Reference in New Issue
Block a user