This commit is contained in:
yangsy
2025-08-17 01:22:08 +08:00
parent 0577042338
commit 8c8a791409
13 changed files with 663 additions and 62 deletions

View File

@@ -1,7 +1,12 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
</script>
<template>
<div>alarm</div>
<pre>{{ route }}</pre>
</template>
<style scoped></style>

View File

@@ -2,28 +2,49 @@
import { ref } from 'vue';
import StationCard from '@/components/station-card.vue';
import { NGrid, NGi } from 'naive-ui';
import { useQuery } from '@tanstack/vue-query';
import { useStationStore } from '@/stores/station';
import { storeToRefs } from 'pinia';
import { DeviceType } from '@/enums/device-type';
import { useLineDevicesQuery } from '@/composables/query/use-line-devices-query';
const stationStore = useStationStore();
const { stationList } = storeToRefs(stationStore);
// 模拟数据
const stations = ref(
Array.from({ length: 32 }).map((_, i) => ({
name: `车站 ${i + 1}`,
code: `${i}`,
name: `浦东1号2号航站楼${i + 1}`,
online: Math.random() > 0.5,
offlineDeviceCount: Math.floor(Math.random() * 20),
alarmCount: Math.floor(Math.random() * 10),
})),
);
// const query = useQuery({
// queryKey: ['line-devices'],
// queryFn: async () => {},
// });
// 获取所有在线车站的设备数据
const { data: lineDevices } = useLineDevicesQuery();
</script>
<template>
<NGrid :cols="8" :x-gap="12" :y-gap="12">
<NGi v-for="station in stations" :key="station.name">
<StationCard :name="station.name" :online="station.online" :offline-device-count="station.offlineDeviceCount" :alarm-count="station.alarmCount" />
<NGrid :cols="8" :x-gap="6" :y-gap="6" style="padding: 8px">
<NGi v-for="(station, i) in stationList" :key="station.name">
<StationCard
:station="station"
:alarm-count="stations[i].alarmCount"
:ndm-device-list="
lineDevices?.[station.code] ?? {
[DeviceType.Camera]: [],
[DeviceType.Decoder]: [],
[DeviceType.Keyboard]: [],
[DeviceType.MediaServer]: [],
[DeviceType.Nvr]: [],
[DeviceType.SecurityBox]: [],
[DeviceType.Switch]: [],
[DeviceType.VideoServer]: [],
}
"
:ndm-device-alarm-log-list="[]"
/>
</NGi>
</NGrid>
</template>

View File

@@ -1,7 +1,145 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import type { NdmDeviceVO } from '@/apis/models/device';
import { useLineDevicesQuery } from '@/composables/query/use-line-devices-query';
import { DeviceType, DeviceTypeName, type DeviceTypeCode } from '@/enums/device-type';
import { useStationStore } from '@/stores/station';
import { ChevronBack } from '@vicons/ionicons5';
import { NIcon, NInput, NLayout, NLayoutContent, NLayoutSider, NPageHeader, NRadio, NRadioGroup, NTabPane, NTabs, NTag, NTree, type TreeOption, type TreeOverrideNodeClickBehavior } from 'naive-ui';
import { storeToRefs } from 'pinia';
import { h } from 'vue';
import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { destr } from 'destr';
const route = useRoute();
const router = useRouter();
const onClickBack = () => router.push({ path: `${route.query['from']}` });
const deviceTabPanes = Object.keys(DeviceType).map((key) => {
const name = DeviceType[key as DeviceTypeCode];
return {
name,
tab: DeviceTypeName[name],
};
});
const override: TreeOverrideNodeClickBehavior = ({ option }) => {
if (option.children) {
return 'toggleExpand';
}
return 'default';
};
// 获取车站和设备数据
const stationStore = useStationStore();
const { stationList } = storeToRefs(stationStore);
const { data: lineDevices } = useLineDevicesQuery();
const lineDeviceTreeData = computed<Record<string, TreeOption[]>>(() => {
const treeData: Record<string, TreeOption[]> = {};
deviceTabPanes.forEach(({ name: paneName /* , tab: paneTab */ }) => {
treeData[paneName] = stationList.value.map<TreeOption>(({ name: stationName, code: stationCode }) => {
const devices = lineDevices.value?.[stationCode][paneName];
const onlineDevices = devices?.filter((device) => device.deviceStatus === '10');
const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
return {
label: stationName,
key: stationCode,
suffix: () => `(${onlineDevices?.length ?? 0}/${offlineDevices?.length ?? 0}/${devices?.length ?? 0})`,
children: lineDevices.value?.[stationCode][paneName].map<TreeOption>((device) => ({
label: `${device.name}`,
key: device.deviceId,
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' ? '离线' : '未知'),
},
);
},
isLeaf: true,
// TODO: 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
device,
stationCode,
})),
};
});
});
return treeData;
});
const searchInput = ref('');
const statusInput = ref('');
// 设备树将搜索框和单选框的值都交给NTree的pattern属性
// 但是如果一个车站下没有匹配的设备,那么这个车站节点也不会显示
const searchPattern = computed(() => {
const search = searchInput.value;
const status = statusInput.value;
if (!search && !status) return ''; // 如果pattern非空会导致NTree组件认为筛选完成UI上发生全量匹配
return JSON.stringify({ search: searchInput.value, status: statusInput.value, timestamp: stationStore.updatedTime });
});
const searchFilter = (pattern: string, node: TreeOption): boolean => {
const { search, status } = destr<{ search: string; status: string }>(pattern);
const device = node['device'] as NdmDeviceVO | undefined;
const { name, ipAddress, deviceStatus } = device ?? {};
const searchMatched = (name ?? '').includes(search) || (ipAddress ?? '').includes(search);
const statusMatched = status === '' || status === deviceStatus;
return searchMatched && statusMatched;
};
</script>
<template>
<div>device</div>
<NLayout has-sider style="height: 100%">
<NLayoutSider bordered :width="540" :collapsed-width="0" show-trigger="arrow-circle">
<div style="height: 100%; display: flex; flex-direction: column">
<div style="flex-shrink: 0; padding: 12px">
<NInput v-model:value="searchInput" placeholder="搜索设备名称或IP地址" />
<NRadioGroup v-model:value="statusInput">
<NRadio value="">全部</NRadio>
<NRadio value="10">在线</NRadio>
<NRadio value="20">离线</NRadio>
</NRadioGroup>
</div>
<div style="flex: 1; min-height: 0">
<NTabs animated type="line" placement="left" style="height: 100%">
<NTabPane v-for="pane in deviceTabPanes" :key="pane.name" :name="pane.name" :tab="pane.tab">
<NTree
:data="lineDeviceTreeData[pane.name]"
:override-default-node-click-behavior="override"
:show-irrelevant-nodes="false"
:pattern="searchPattern"
:filter="searchFilter"
block-line
block-node
show-line
default-expand-all
virtual-scroll
style="height: 100%"
/>
</NTabPane>
</NTabs>
</div>
</div>
</NLayoutSider>
<NLayoutContent>
<NPageHeader v-if="route.query['from']" title="" @back="onClickBack">
<template #back>
<NIcon>
<ChevronBack />
</NIcon>
<div style="font-size: 15px">返回</div>
</template>
</NPageHeader>
<!-- <pre>{{ route.query }}</pre> -->
<pre style="width: 500px; height: 600px; overflow: scroll">{{ lineDeviceTreeData }}</pre>
<!-- <pre style="width: 500px; height: 100%; overflow: scroll">{{ lineDevices }}</pre> -->
</NLayoutContent>
</NLayout>
</template>
<style scoped></style>
<style scoped lang="scss"></style>

View File

@@ -1,7 +1,12 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
</script>
<template>
<div>log</div>
<div>alarm</div>
<pre>{{ route }}</pre>
</template>
<style scoped></style>

View File

@@ -1,7 +1,12 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
</script>
<template>
<div>statistics</div>
<pre>{{ route }}</pre>
</template>
<style scoped></style>