feat: enhance device tree
This commit is contained in:
@@ -73,9 +73,8 @@ const treeData = computed<TreeOption[]>(() => {
|
|||||||
key: type,
|
key: type,
|
||||||
children: offlineDeviceList.map<TreeOption>((device) => ({
|
children: offlineDeviceList.map<TreeOption>((device) => ({
|
||||||
label: `${device.name}`,
|
label: `${device.name}`,
|
||||||
key: device.deviceId,
|
key: device.id,
|
||||||
suffix: () => `${device.ipAddress ?? '未知IP地址'}`,
|
suffix: () => `${device.ipAddress ?? '未知IP地址'}`,
|
||||||
isLeaf: true,
|
|
||||||
device,
|
device,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
@@ -85,7 +84,7 @@ const treeData = computed<TreeOption[]>(() => {
|
|||||||
const nodeProps = ({ option }: { option: TreeOption }) => {
|
const nodeProps = ({ option }: { option: TreeOption }) => {
|
||||||
return {
|
return {
|
||||||
ondblclick: () => {
|
ondblclick: () => {
|
||||||
if (option.isLeaf) {
|
if (option['device']) {
|
||||||
const queryControlStore = useQueryControlStore();
|
const queryControlStore = useQueryControlStore();
|
||||||
queryControlStore.enablePolling();
|
queryControlStore.enablePolling();
|
||||||
const device = option['device'] as NdmDeviceVO;
|
const device = option['device'] as NdmDeviceVO;
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmDeviceResultVO, NdmDeviceVO } from '@/apis/models/device';
|
import type { NdmDeviceResultVO, NdmDeviceVO, NdmNvrResultVO } from '@/apis/models/device';
|
||||||
import { useLineDevicesQuery } from '@/composables/query/use-line-devices-query';
|
import { useLineDevicesQuery } from '@/composables/query/use-line-devices-query';
|
||||||
import { DeviceType, DeviceTypeName, type DeviceTypeCode, type DeviceTypeKey } from '@/enums/device-type';
|
import { DeviceType, DeviceTypeName, type DeviceTypeCode, type DeviceTypeKey } from '@/enums/device-type';
|
||||||
import { useStationStore } from '@/stores/station';
|
import { useStationStore } from '@/stores/station';
|
||||||
import { ChevronBack } from '@vicons/ionicons5';
|
import { ChevronBack } from '@vicons/ionicons5';
|
||||||
import {
|
import {
|
||||||
|
NButton,
|
||||||
|
NButtonGroup,
|
||||||
|
NFlex,
|
||||||
NIcon,
|
NIcon,
|
||||||
NInput,
|
NInput,
|
||||||
NLayout,
|
NLayout,
|
||||||
@@ -18,11 +21,11 @@ import {
|
|||||||
NTabs,
|
NTabs,
|
||||||
NTag,
|
NTag,
|
||||||
NTree,
|
NTree,
|
||||||
|
type TreeInst,
|
||||||
type TreeOption,
|
type TreeOption,
|
||||||
type TreeOverrideNodeClickBehavior,
|
|
||||||
} from 'naive-ui';
|
} from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { h } from 'vue';
|
import { h, nextTick, onMounted, useTemplateRef } from 'vue';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useRoute, useRouter, type LocationQuery } from 'vue-router';
|
import { useRoute, useRouter, type LocationQuery } from 'vue-router';
|
||||||
import { destr } from 'destr';
|
import { destr } from 'destr';
|
||||||
@@ -40,13 +43,6 @@ const deviceTabPanes = Object.keys(DeviceType).map((key) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const override: TreeOverrideNodeClickBehavior = ({ option }) => {
|
|
||||||
if (option.children) {
|
|
||||||
return 'toggleExpand';
|
|
||||||
}
|
|
||||||
return 'default';
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取车站和设备数据
|
// 获取车站和设备数据
|
||||||
const stationStore = useStationStore();
|
const stationStore = useStationStore();
|
||||||
const { stationList } = storeToRefs(stationStore);
|
const { stationList } = storeToRefs(stationStore);
|
||||||
@@ -59,6 +55,62 @@ const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
|
|||||||
const devices = lineDevices.value?.[stationCode][paneName];
|
const devices = lineDevices.value?.[stationCode][paneName];
|
||||||
const onlineDevices = devices?.filter((device) => device.deviceStatus === '10');
|
const onlineDevices = devices?.filter((device) => device.deviceStatus === '10');
|
||||||
const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
|
const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
|
||||||
|
// 对于录像机,需要根据clusterList字段以分号分隔设备IP,进一步形成子树结构
|
||||||
|
if (paneName === DeviceType.Nvr) {
|
||||||
|
const nvrs = devices as NdmNvrResultVO[] | undefined;
|
||||||
|
return {
|
||||||
|
label: stationName,
|
||||||
|
key: stationCode,
|
||||||
|
suffix: () => `(${onlineDevices?.length ?? 0}/${offlineDevices?.length ?? 0}/${devices?.length ?? 0})`,
|
||||||
|
children: nvrs
|
||||||
|
?.filter((device) => {
|
||||||
|
const nvr = device as NdmNvrResultVO;
|
||||||
|
return !!nvr.clusterList?.trim() && nvr.clusterList !== nvr.ipAddress;
|
||||||
|
})
|
||||||
|
.map<TreeOption>((nvrCluster) => {
|
||||||
|
return {
|
||||||
|
label: `${nvrCluster.name}`,
|
||||||
|
key: nvrCluster.deviceId,
|
||||||
|
suffix: () => `${nvrCluster.ipAddress}`,
|
||||||
|
prefix: () => {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{ type: nvrCluster.deviceStatus === '10' ? 'success' : nvrCluster.deviceStatus === '20' ? 'error' : 'warning', size: 'tiny' },
|
||||||
|
{
|
||||||
|
default: () => (nvrCluster.deviceStatus === '10' ? '在线' : nvrCluster.deviceStatus === '20' ? '离线' : '未知'),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
children: nvrs
|
||||||
|
.filter((nvr) => {
|
||||||
|
return nvrCluster.clusterList?.includes(nvr.ipAddress ?? '');
|
||||||
|
})
|
||||||
|
.map<TreeOption>((nvr) => {
|
||||||
|
return {
|
||||||
|
label: `${nvr.name}`,
|
||||||
|
key: nvr.deviceId,
|
||||||
|
suffix: () => `${nvr.ipAddress}`,
|
||||||
|
prefix: () => {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{ type: nvr.deviceStatus === '10' ? 'success' : nvr.deviceStatus === '20' ? 'error' : 'warning', size: 'tiny' },
|
||||||
|
{
|
||||||
|
default: () => (nvr.deviceStatus === '10' ? '在线' : nvr.deviceStatus === '20' ? '离线' : '未知'),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||||
|
device: nvr,
|
||||||
|
stationCode,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||||
|
device: nvrCluster,
|
||||||
|
stationCode,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
label: stationName,
|
label: stationName,
|
||||||
key: stationCode,
|
key: stationCode,
|
||||||
@@ -76,8 +128,7 @@ const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isLeaf: true,
|
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||||
// TODO: 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
|
||||||
device,
|
device,
|
||||||
stationCode,
|
stationCode,
|
||||||
})),
|
})),
|
||||||
@@ -90,7 +141,8 @@ const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
|
|||||||
const selectedTab = ref<DeviceTypeCode>(DeviceType.Camera);
|
const selectedTab = ref<DeviceTypeCode>(DeviceType.Camera);
|
||||||
const selectedKeys = ref<string[]>([]);
|
const selectedKeys = ref<string[]>([]);
|
||||||
const selectedDevice = ref<NdmDeviceResultVO>();
|
const selectedDevice = ref<NdmDeviceResultVO>();
|
||||||
const initFromRouteQuery = (query: LocationQuery) => {
|
// 从路由参数中设置选中的设备
|
||||||
|
const setFromRouteQuery = (query: LocationQuery) => {
|
||||||
const { stationCode, deviceType, deviceId } = query;
|
const { stationCode, deviceType, deviceId } = query;
|
||||||
let stnCode = '';
|
let stnCode = '';
|
||||||
let devType: DeviceTypeCode = DeviceType.Camera;
|
let devType: DeviceTypeCode = DeviceType.Camera;
|
||||||
@@ -102,18 +154,20 @@ const initFromRouteQuery = (query: LocationQuery) => {
|
|||||||
selectedKeys.value = [devId];
|
selectedKeys.value = [devId];
|
||||||
const stnDevices = lineDevices.value?.[stnCode];
|
const stnDevices = lineDevices.value?.[stnCode];
|
||||||
const devices = stnDevices?.[devType];
|
const devices = stnDevices?.[devType];
|
||||||
|
// 如果没找到,那还是赋值为原来选择的设备
|
||||||
selectedDevice.value = devices?.find((device) => device.deviceId === deviceId) ?? selectedDevice.value;
|
selectedDevice.value = devices?.find((device) => device.deviceId === deviceId) ?? selectedDevice.value;
|
||||||
};
|
};
|
||||||
watch(
|
// 页面加载时,需要设置选中的设备
|
||||||
() => route.query,
|
onMounted(() => setFromRouteQuery(route.query));
|
||||||
(query) => initFromRouteQuery(query),
|
// 页面加载时设备数据可能不存在,因此当设备数据更新时,需要重新设置选中的设备
|
||||||
{ immediate: true },
|
watch(lineDevices, () => setFromRouteQuery(route.query), { immediate: true });
|
||||||
);
|
// 更改选择的设备类型时,更新路由查询参数
|
||||||
watch(lineDevices, () => initFromRouteQuery(route.query), { immediate: true });
|
watch(selectedTab, (newTab) => router.replace({ query: { ...route.query, deviceType: newTab } }));
|
||||||
|
// 点击设备时更新路由查询参数
|
||||||
const nodeProps = ({ option }: { option: TreeOption }) => {
|
const nodeProps = ({ option }: { option: TreeOption }) => {
|
||||||
return {
|
return {
|
||||||
ondblclick: () => {
|
onclick: () => {
|
||||||
if (option.isLeaf) {
|
if (option['device']) {
|
||||||
const device = option['device'] as NdmDeviceResultVO;
|
const device = option['device'] as NdmDeviceResultVO;
|
||||||
selectedDevice.value = device;
|
selectedDevice.value = device;
|
||||||
router.replace({
|
router.replace({
|
||||||
@@ -128,7 +182,6 @@ const nodeProps = ({ option }: { option: TreeOption }) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
watch(selectedTab, (newTab) => router.replace({ query: { ...route.query, deviceType: newTab } }));
|
|
||||||
|
|
||||||
const searchInput = ref('');
|
const searchInput = ref('');
|
||||||
const statusInput = ref('');
|
const statusInput = ref('');
|
||||||
@@ -149,28 +202,44 @@ const searchFilter = (pattern: string, node: TreeOption): boolean => {
|
|||||||
const statusMatched = status === '' || status === deviceStatus;
|
const statusMatched = status === '' || status === deviceStatus;
|
||||||
return searchMatched && statusMatched;
|
return searchMatched && statusMatched;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 定位到选中的设备
|
||||||
|
const deviceTreeInsts = useTemplateRef<TreeInst[]>('deviceTreeInsts');
|
||||||
|
const onClickLocateDeviceTree = () => {
|
||||||
|
selectedTab.value = (selectedDevice.value?.deviceType ?? selectedTab.value) as DeviceTypeCode;
|
||||||
|
// 等待Tab切换完成后再执行滚动
|
||||||
|
nextTick(() => {
|
||||||
|
const inst = deviceTreeInsts.value?.at(0);
|
||||||
|
inst?.scrollTo({ key: selectedDevice.value?.deviceId, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NLayout has-sider style="height: 100%">
|
<NLayout has-sider style="height: 100%">
|
||||||
<NLayoutSider bordered :width="540" :collapsed-width="0" show-trigger="arrow-circle">
|
<NLayoutSider bordered :width="540" :collapsed-width="0" show-trigger="bar">
|
||||||
<div style="height: 100%; display: flex; flex-direction: column">
|
<div style="height: 100%; display: flex; flex-direction: column">
|
||||||
<div style="flex-shrink: 0; padding: 12px">
|
<div style="flex-shrink: 0; padding: 12px">
|
||||||
<NInput v-model:value="searchInput" placeholder="搜索设备名称或IP地址" />
|
<NInput v-model:value="searchInput" placeholder="搜索设备名称或IP地址" clearable />
|
||||||
<NRadioGroup v-model:value="statusInput">
|
<NFlex justify="space-between" align="center">
|
||||||
<NRadio value="">全部</NRadio>
|
<NRadioGroup v-model:value="statusInput">
|
||||||
<NRadio value="10">在线</NRadio>
|
<NRadio value="">全部</NRadio>
|
||||||
<NRadio value="20">离线</NRadio>
|
<NRadio value="10">在线</NRadio>
|
||||||
</NRadioGroup>
|
<NRadio value="20">离线</NRadio>
|
||||||
|
</NRadioGroup>
|
||||||
|
<NButtonGroup>
|
||||||
|
<NButton text size="tiny" type="info" @click="onClickLocateDeviceTree">定位</NButton>
|
||||||
|
</NButtonGroup>
|
||||||
|
</NFlex>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="flex: 1; min-height: 0">
|
<div style="flex: 1; min-height: 0">
|
||||||
<NTabs v-model:value="selectedTab" animated type="line" placement="left" style="height: 100%">
|
<NTabs v-model:value="selectedTab" animated type="line" placement="left" style="height: 100%">
|
||||||
<NTabPane v-for="pane in deviceTabPanes" :key="pane.name" :name="pane.name" :tab="pane.tab">
|
<NTabPane v-for="pane in deviceTabPanes" :key="pane.name" :name="pane.name" :tab="pane.tab">
|
||||||
<NTree
|
<NTree
|
||||||
|
:ref="'deviceTreeInsts'"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
:data="lineDeviceTreeData[pane.name]"
|
:data="lineDeviceTreeData[pane.name]"
|
||||||
:override-default-node-click-behavior="override"
|
|
||||||
:node-props="nodeProps"
|
:node-props="nodeProps"
|
||||||
:show-irrelevant-nodes="false"
|
:show-irrelevant-nodes="false"
|
||||||
:pattern="searchPattern"
|
:pattern="searchPattern"
|
||||||
@@ -187,7 +256,7 @@ const searchFilter = (pattern: string, node: TreeOption): boolean => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NLayoutSider>
|
</NLayoutSider>
|
||||||
<NLayoutContent>
|
<NLayoutContent :content-style="{ 'padding-left': '24px' }">
|
||||||
<NPageHeader v-if="route.query['from']" title="" @back="onClickBack">
|
<NPageHeader v-if="route.query['from']" title="" @back="onClickBack">
|
||||||
<template #back>
|
<template #back>
|
||||||
<NIcon>
|
<NIcon>
|
||||||
@@ -197,10 +266,10 @@ const searchFilter = (pattern: string, node: TreeOption): boolean => {
|
|||||||
</template>
|
</template>
|
||||||
</NPageHeader>
|
</NPageHeader>
|
||||||
<div>selectedKeys: {{ selectedKeys }}</div>
|
<div>selectedKeys: {{ selectedKeys }}</div>
|
||||||
<NScrollbar style="width: 500px; height: 400px">
|
<div>selectedDevice:</div>
|
||||||
|
<NScrollbar style="width: 500px; height: 400px; border: solid 1px salmon">
|
||||||
<pre>{{ selectedDevice }}</pre>
|
<pre>{{ selectedDevice }}</pre>
|
||||||
</NScrollbar>
|
</NScrollbar>
|
||||||
<!-- <pre style="width: 500px; height: 100%; overflow: scroll">{{ lineDevices }}</pre> -->
|
|
||||||
</NLayoutContent>
|
</NLayoutContent>
|
||||||
</NLayout>
|
</NLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user