feat: migrate id and improve interaction

- use DB id of device to mark tree node key
- dblclick offlien device node to jump to DevicePage
- dblclick device node to view device detail
This commit is contained in:
yangsy
2025-08-18 11:05:26 +08:00
parent 104400500e
commit 4e14eb3590
2 changed files with 87 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { NInput, NModal, NTree } from 'naive-ui';
import { NButton, NInput, NModal, NTree } from 'naive-ui';
import { computed, ref, toRefs, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import type { TreeOption, TreeOverrideNodeClickBehavior } from 'naive-ui';
@@ -17,6 +17,7 @@ import type {
import { useQueryControlStore } from '@/stores/query-control';
import { DeviceType, DeviceTypeName } from '@/enums/device-type';
import type { Station } from '@/apis/domains';
import { h } from 'vue';
interface Props {
station: Station;
@@ -51,8 +52,8 @@ watch(show, (newValue) => {
const searchPattern = ref('');
const searchFilter: (pattern: string, node: TreeOption) => boolean = (pattern, node) => {
const device = node['device'] as NdmDeviceVO;
const { name, ipAddress } = device;
const device = node['device'] as NdmDeviceVO | undefined;
const { name, ipAddress } = device ?? {};
return (name ?? '').includes(pattern) || (ipAddress ?? '').includes(pattern);
};
@@ -75,12 +76,38 @@ const treeData = computed<TreeOption[]>(() => {
label: `${device.name}`,
key: device.id,
suffix: () => `${device.ipAddress ?? '未知IP地址'}`,
prefix: () => {
return h(
NButton,
{
text: true,
size: 'tiny',
type: 'info',
onClick: () => {
const queryControlStore = useQueryControlStore();
queryControlStore.enablePolling();
const dev = device as NdmDeviceVO;
router.push({
path: '/device',
query: {
stationCode: station.value.code,
deviceType: dev.deviceType,
deviceDBId: dev.id,
from: route.path,
},
});
},
},
{ default: () => h('span', '查看') },
);
},
device,
})),
};
});
});
// 双击设备节点,跳转到`实时设备状`态页面
const nodeProps = ({ option }: { option: TreeOption }) => {
return {
ondblclick: () => {
@@ -93,7 +120,7 @@ const nodeProps = ({ option }: { option: TreeOption }) => {
query: {
stationCode: station.value.code,
deviceType: device.deviceType,
deviceId: device.deviceId,
deviceDBId: device.id,
from: route.path,
},
});
@@ -103,7 +130,7 @@ const nodeProps = ({ option }: { option: TreeOption }) => {
};
const override: TreeOverrideNodeClickBehavior = ({ option }) => {
if (option.children) {
if (!option['device']) {
return 'toggleExpand';
}
return 'default';

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { NdmDeviceResultVO, NdmDeviceVO, NdmNvrResultVO } from '@/apis/models/device';
import type { NdmDeviceResultVO, NdmNvrResultVO } from '@/apis/models/device';
import { useLineDevicesQuery } from '@/composables/query/use-line-devices-query';
import { DeviceType, DeviceTypeName, type DeviceTypeCode, type DeviceTypeKey } from '@/enums/device-type';
import { useStationStore } from '@/stores/station';
@@ -21,6 +21,7 @@ import {
NTabs,
NTag,
NTree,
type TagProps,
type TreeInst,
type TreeOption,
} from 'naive-ui';
@@ -70,16 +71,10 @@ const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
.map<TreeOption>((nvrCluster) => {
return {
label: `${nvrCluster.name}`,
key: nvrCluster.deviceId,
key: nvrCluster.id,
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' ? '离线' : '未知'),
},
);
return renderDeviceNodePrefix(nvrCluster, stationCode);
},
children: nvrs
.filter((nvr) => {
@@ -88,16 +83,10 @@ const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
.map<TreeOption>((nvr) => {
return {
label: `${nvr.name}`,
key: nvr.deviceId,
key: nvr.id,
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' ? '离线' : '未知'),
},
);
return renderDeviceNodePrefix(nvrCluster, stationCode);
},
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
device: nvr,
@@ -117,16 +106,10 @@ const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
suffix: () => `(${onlineDevices?.length ?? 0}/${offlineDevices?.length ?? 0}/${devices?.length ?? 0})`,
children: lineDevices.value?.[stationCode][paneName].map<TreeOption>((device) => ({
label: `${device.name}`,
key: device.deviceId,
key: device.id,
suffix: () => `${device.ipAddress}`,
prefix: () => {
return h(
NTag,
{ type: device.deviceStatus === '10' ? 'success' : device.deviceStatus === '20' ? 'error' : 'warning', size: 'tiny' },
{
default: () => (device.deviceStatus === '10' ? '在线' : device.deviceStatus === '20' ? '离线' : '未知'),
},
);
return renderDeviceNodePrefix(device, stationCode);
},
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
device,
@@ -137,25 +120,56 @@ const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
});
return treeData;
});
const renderDeviceNodePrefix = (device: NdmDeviceResultVO, stationCode: string) => {
const renderDeviceStatusTag = (device: NdmDeviceResultVO) => {
const { deviceStatus } = device;
const tagType: TagProps['type'] = deviceStatus === '10' ? 'success' : deviceStatus === '20' ? 'error' : 'warning';
const tagText = device.deviceStatus === '10' ? '在线' : device.deviceStatus === '20' ? '离线' : '未知';
return h(NTag, { type: tagType, size: 'tiny' }, () => tagText);
};
const renderViewDeviceButton = (device: NdmDeviceResultVO, stationCode: string) => {
return h(
NButton,
{
text: true,
size: 'tiny',
type: 'info',
onClick: () => {
selectedDevice.value = device; // 更新选中的设备
router.replace({
query: {
...route.query,
stationCode: stationCode,
deviceType: selectedTab.value,
deviceDBId: device.id,
},
});
},
},
() => '查看',
);
};
return h('div', [renderViewDeviceButton(device, stationCode), renderDeviceStatusTag(device)]);
};
const selectedTab = ref<DeviceTypeCode>(DeviceType.Camera);
const selectedKeys = ref<string[]>([]);
const selectedKeys = ref<string[]>();
const selectedDevice = ref<NdmDeviceResultVO>();
// 从路由参数中设置选中的设备
const setFromRouteQuery = (query: LocationQuery) => {
const { stationCode, deviceType, deviceId } = query;
const { stationCode, deviceType, deviceDBId } = query;
let stnCode = '';
let devType: DeviceTypeCode = DeviceType.Camera;
let devId = '';
let devDBId = '';
if (stationCode) stnCode = stationCode as string;
if (deviceType) devType = deviceType as DeviceTypeCode;
if (deviceId) devId = deviceId as string;
if (deviceDBId) devDBId = deviceDBId as string;
selectedTab.value = devType;
selectedKeys.value = [devId];
selectedKeys.value = [devDBId];
const stnDevices = lineDevices.value?.[stnCode];
const devices = stnDevices?.[devType];
// 如果没找到,那还是赋值为原来选择的设备
selectedDevice.value = devices?.find((device) => device.deviceId === deviceId) ?? selectedDevice.value;
selectedDevice.value = devices?.find((device) => device.id === devDBId) ?? selectedDevice.value;
};
// 页面加载时,需要设置选中的设备
onMounted(() => setFromRouteQuery(route.query));
@@ -163,19 +177,20 @@ onMounted(() => setFromRouteQuery(route.query));
watch(lineDevices, () => setFromRouteQuery(route.query), { immediate: true });
// 更改选择的设备类型时,更新路由查询参数
watch(selectedTab, (newTab) => router.replace({ query: { ...route.query, deviceType: newTab } }));
// 击设备时更新路由查询参数
// 击设备时更新路由查询参数
const nodeProps = ({ option }: { option: TreeOption }) => {
return {
onclick: () => {
ondblclick: () => {
if (option['device']) {
const device = option['device'] as NdmDeviceResultVO;
selectedDevice.value = device;
selectedKeys.value = device.id ? [device.id] : undefined; // 更新选中的树节点
selectedDevice.value = device; // 更新选中的设备
router.replace({
query: {
...route.query,
stationCode: option['stationCode'] as string,
deviceType: selectedTab.value,
deviceId: device.deviceId,
deviceDBId: device.id,
},
});
}
@@ -196,7 +211,7 @@ const searchPattern = computed(() => {
});
const searchFilter = (pattern: string, node: TreeOption): boolean => {
const { search, status } = destr<{ search: string; status: string }>(pattern);
const device = node['device'] as NdmDeviceVO | undefined;
const device = node['device'] as NdmDeviceResultVO | undefined;
const { name, ipAddress, deviceStatus } = device ?? {};
const searchMatched = (name ?? '').includes(search) || (ipAddress ?? '').includes(search);
const statusMatched = status === '' || status === deviceStatus;
@@ -210,14 +225,14 @@ const onClickLocateDeviceTree = () => {
// 等待Tab切换完成后再执行滚动
nextTick(() => {
const inst = deviceTreeInsts.value?.at(0);
inst?.scrollTo({ key: selectedDevice.value?.deviceId, behavior: 'smooth' });
inst?.scrollTo({ key: selectedDevice.value?.id, behavior: 'smooth' });
});
};
</script>
<template>
<NLayout has-sider style="height: 100%">
<NLayoutSider bordered :width="540" :collapsed-width="0" show-trigger="bar">
<NLayoutSider bordered :width="600" :collapsed-width="0" show-trigger="bar">
<div style="height: 100%; display: flex; flex-direction: column">
<div style="flex-shrink: 0; padding: 12px">
<NInput v-model:value="searchInput" placeholder="搜索设备名称或IP地址" clearable />
@@ -256,7 +271,7 @@ const onClickLocateDeviceTree = () => {
</div>
</div>
</NLayoutSider>
<NLayoutContent :content-style="{ 'padding-left': '24px' }">
<NLayoutContent :content-style="{ 'padding-left': '24px', 'padding-top': '6px' }">
<NPageHeader v-if="route.query['from']" title="" @back="onClickBack">
<template #back>
<NIcon>
@@ -266,7 +281,7 @@ const onClickLocateDeviceTree = () => {
</template>
</NPageHeader>
<div>selectedKeys: {{ selectedKeys }}</div>
<div>selectedDevice:</div>
<div>selectedDevice: {{ selectedDevice?.deviceId }}</div>
<NScrollbar style="width: 500px; height: 400px; border: solid 1px salmon">
<pre>{{ selectedDevice }}</pre>
</NScrollbar>