fix: 修复由动画属性导致设备树在特定场景下无法自行滚动及展开节点失效的问题

- 当选中的设备所属车站在设备树的视口外时,定位操作无效,且节点无法点击展开,解决方案是在定位逻辑中切换Tree组件的animated状态
This commit is contained in:
yangsy
2025-12-18 20:47:00 +08:00
parent 5b47734c3b
commit d565fd6a5f

View File

@@ -4,7 +4,6 @@ import { useDeviceTree } from '@/composables';
import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType } from '@/enums'; import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType } from '@/enums';
import { isNvrCluster } from '@/helpers'; import { isNvrCluster } from '@/helpers';
import { useDeviceStore, useStationStore } from '@/stores'; import { useDeviceStore, useStationStore } from '@/stores';
import { sleep } from '@/utils';
import { watchImmediate } from '@vueuse/core'; import { watchImmediate } from '@vueuse/core';
import destr from 'destr'; import destr from 'destr';
import { isFunction } from 'es-toolkit'; import { isFunction } from 'es-toolkit';
@@ -28,7 +27,7 @@ import {
type TreeProps, type TreeProps,
} from 'naive-ui'; } from 'naive-ui';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { computed, h, onBeforeUnmount, ref, toRefs, useTemplateRef, watch, type CSSProperties } from 'vue'; import { computed, h, nextTick, onBeforeUnmount, ref, toRefs, useTemplateRef, watch, type CSSProperties } from 'vue';
const props = defineProps<{ const props = defineProps<{
station?: Station; // 支持渲染指定车站的设备树 station?: Station; // 支持渲染指定车站的设备树
@@ -325,7 +324,7 @@ const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() =>
const device = dev as NdmDeviceResultVO; const device = dev as NdmDeviceResultVO;
return { return {
label: `${device.name}`, label: `${device.name}`,
key: device.id ?? `${device.name}`, key: `${device.id}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站 // 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
@@ -359,13 +358,13 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
children: clusters.map<TreeOption>((device) => { children: clusters.map<TreeOption>((device) => {
return { return {
label: `${device.name}`, label: `${device.name}`,
key: device.id ?? `${device.name}`, key: `${device.id}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
children: singletons.map<TreeOption>((device) => { children: singletons.map<TreeOption>((device) => {
return { return {
label: `${device.name}`, label: `${device.name}`,
key: device.id ?? `${device.name}`, key: `${device.id}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
stationCode, stationCode,
@@ -387,7 +386,7 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
children: stationDevices[deviceType].map<TreeOption>((device) => { children: stationDevices[deviceType].map<TreeOption>((device) => {
return { return {
label: `${device.name}`, label: `${device.name}`,
key: device.id ?? `${device.name}`, key: `${device.id}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
stationCode, stationCode,
@@ -421,39 +420,46 @@ const searchFilter = (pattern: string, node: TreeOption): boolean => {
}; };
// ========== 设备树交互 ========== // ========== 设备树交互 ==========
const expandedKeys = ref<string[]>(); const animated = ref(true);
const expandedKeys = ref<string[]>([]);
const deviceTreeInst = useTemplateRef<TreeInst>('deviceTreeInst'); const deviceTreeInst = useTemplateRef<TreeInst>('deviceTreeInst');
const onFoldDeviceTree = () => { const onFoldDeviceTree = () => {
expandedKeys.value = []; expandedKeys.value = [];
}; };
const onLocateDeviceTree = () => { const onLocateDeviceTree = async () => {
const stationCode = selectedStationCode.value; if (!selectedStationCode.value) return;
const device = selectedDevice.value; if (!selectedDevice.value) return;
if (!stationCode || !device?.id) return; const deviceType = tryGetDeviceType(selectedDevice.value.deviceType);
const deviceTypeVal = tryGetDeviceType(device.deviceType); if (!deviceType) return;
if (!!deviceTypeVal) { if (!deviceTreeInst.value) return;
activeTab.value = deviceTypeVal;
}
const expanded = [stationCode]; animated.value = false;
if (activeTab.value === DEVICE_TYPE_LITERALS.ndmNvr) {
const nvrs = lineDevices.value[stationCode]?.[DEVICE_TYPE_LITERALS.ndmNvr]; // 定位设备类型
if (nvrs) { activeTab.value = deviceType;
const clusterKeys = nvrs.filter((nvr) => !!nvr.clusterList?.trim() && nvr.clusterList !== nvr.ipAddress).map((nvr) => String(nvr.id));
expanded.push(...clusterKeys); // 展开选择的车站
expandedKeys.value = [selectedStationCode.value];
// 当选择录像机时,如果不是集群,进一步展开该录像机所在的集群节点
if (deviceType === DEVICE_TYPE_LITERALS.ndmNvr) {
const stationDevices = lineDevices.value[selectedStationCode.value];
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 = expanded;
// 由于数据量大所以开启虚拟滚动, // 等待设备树展开完成,滚动到选择的设备
// 但是无法知晓NTree内部的虚拟列表容器何时创建完成所以使用setTimeout延迟固定时间后执行滚动 await nextTick();
scrollDeviceTreeToSelectedDevice(); deviceTreeInst.value.scrollTo({ key: `${selectedDevice.value.id}`, behavior: 'smooth' });
animated.value = true;
}; };
async function scrollDeviceTreeToSelectedDevice() {
await sleep(350);
const inst = deviceTreeInst.value;
inst?.scrollTo({ key: selectedDevice?.value?.id ?? `${selectedDevice.value?.name}`, behavior: 'smooth' });
}
</script> </script>
<template> <template>
@@ -483,7 +489,7 @@ async function scrollDeviceTreeToSelectedDevice() {
> >
<template v-if="!station"> <template v-if="!station">
<div style="height: 100%; flex: 0 0 auto"> <div style="height: 100%; flex: 0 0 auto">
<NTabs v-model:value="activeTab" animated type="line" placement="left" style="height: 100%"> <NTabs v-model:value="activeTab" type="line" placement="left" style="height: 100%">
<NTab v-for="pane in deviceTabPanes" :key="pane.name" :name="pane.name" :tab="pane.tab"></NTab> <NTab v-for="pane in deviceTabPanes" :key="pane.name" :name="pane.name" :tab="pane.tab"></NTab>
</NTabs> </NTabs>
</div> </div>
@@ -496,6 +502,7 @@ async function scrollDeviceTreeToSelectedDevice() {
show-line show-line
virtual-scroll virtual-scroll
:ref="'deviceTreeInst'" :ref="'deviceTreeInst'"
:animated="animated"
:selected-keys="selectedKeys" :selected-keys="selectedKeys"
:data="lineDeviceTreeData[activeTab]" :data="lineDeviceTreeData[activeTab]"
:show-irrelevant-nodes="false" :show-irrelevant-nodes="false"
@@ -515,6 +522,7 @@ async function scrollDeviceTreeToSelectedDevice() {
show-line show-line
virtual-scroll virtual-scroll
:data="stationDeviceTreeData" :data="stationDeviceTreeData"
:animated="animated"
:show-irrelevant-nodes="false" :show-irrelevant-nodes="false"
:pattern="searchPattern" :pattern="searchPattern"
:filter="searchFilter" :filter="searchFilter"