feat: ui
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user