|
|
|
|
@ -10,6 +10,8 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="title">RFID中间件监控平台</div>
|
|
|
|
|
<div class="title1">成功率</div>
|
|
|
|
|
<div class="title2">告警统计</div>
|
|
|
|
|
<div class="topTitle" style="left: 13%">设备数量:</div>
|
|
|
|
|
<div class="topTitle" style="left: 37.5%">在线数量:</div>
|
|
|
|
|
<div class="topTitle" style="left: 62%">离线数量:</div>
|
|
|
|
|
@ -57,12 +59,12 @@
|
|
|
|
|
</div>
|
|
|
|
|
</vue3ScrollSeamless>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="center" ref="scrollNodeRef" @mouseenter="hover = true" @mouseleave="mouseleave">
|
|
|
|
|
<div class="center" ref="scrollNodeRef" @mouseenter="mouseenter" @mouseleave="mouseleave">
|
|
|
|
|
<div v-masonry style="width: 100%; height: 100%" ref="masonryRef">
|
|
|
|
|
<TreeItem v-masonry-tile :data="i" v-for="i in centerData" :key="i.id" :wsData="wsData" />
|
|
|
|
|
<TreeItem v-masonry-tile :data="i" v-for="i in centerData" :key="i.id" :wsData="wsData" :copy="false" />
|
|
|
|
|
</div>
|
|
|
|
|
<div v-masonry style="width: 100%; height: 100%">
|
|
|
|
|
<TreeItem v-masonry-tile :data="i" v-for="i in centerData" :key="i.id" :wsData="wsData" />
|
|
|
|
|
<TreeItem v-masonry-tile :data="i" v-for="i in centerData" :key="i.id" :wsData="wsData" :copy="true" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -73,9 +75,12 @@ import thbg from '@/assets/chart/thbg.png';
|
|
|
|
|
import trbg from '@/assets/chart/trbg.png';
|
|
|
|
|
import TreeItem from './treeItem.vue';
|
|
|
|
|
import { vue3ScrollSeamless } from 'vue3-scroll-seamless';
|
|
|
|
|
import { parseTime } from '@/utils/ruoyi.js';
|
|
|
|
|
|
|
|
|
|
import { getRealtimeStats, getLocationTree, getSuccessRateTrends } from '@/api/rfid/dashboard';
|
|
|
|
|
|
|
|
|
|
let timer = null;
|
|
|
|
|
|
|
|
|
|
function findParentsWithLocationType3(arr) {
|
|
|
|
|
const result = [];
|
|
|
|
|
|
|
|
|
|
@ -122,6 +127,13 @@ const timeFun = () => {
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mouseenter = () => {
|
|
|
|
|
hover.value = true;
|
|
|
|
|
if (timer) {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
}
|
|
|
|
|
timer = null;
|
|
|
|
|
};
|
|
|
|
|
const mouseleave = () => {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
hover.value = false;
|
|
|
|
|
@ -172,7 +184,9 @@ const MenuItem = defineComponent({
|
|
|
|
|
})}
|
|
|
|
|
</ElSubMenu>
|
|
|
|
|
) : (
|
|
|
|
|
<el-menu-item index={`${data.id}`}>{data.locationAlias}</el-menu-item>
|
|
|
|
|
<el-menu-item onClick={() => scrollToId(data.id)} index={`${data.id}`}>
|
|
|
|
|
{data.locationAlias}
|
|
|
|
|
</el-menu-item>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
@ -195,6 +209,31 @@ function scrollToBottom(scrollNode, speed = 1) {
|
|
|
|
|
requestAnimationFrame(step);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const scrollToId = (id) => {
|
|
|
|
|
const container = scrollNodeRef.value;
|
|
|
|
|
if (!container) return;
|
|
|
|
|
|
|
|
|
|
const target = container.querySelector(`#tree${id}`);
|
|
|
|
|
if (target) {
|
|
|
|
|
const containerTop = container.getBoundingClientRect().top;
|
|
|
|
|
const targetTop = target.getBoundingClientRect().top;
|
|
|
|
|
|
|
|
|
|
const scrollOffset = targetTop - containerTop + container.scrollTop;
|
|
|
|
|
|
|
|
|
|
container.scrollTo({
|
|
|
|
|
top: scrollOffset,
|
|
|
|
|
behavior: 'smooth' // 平滑滚动
|
|
|
|
|
});
|
|
|
|
|
hover.value = true;
|
|
|
|
|
if (timer) {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
}
|
|
|
|
|
timer = null;
|
|
|
|
|
timer = setTimeout(() => {
|
|
|
|
|
hover.value = false;
|
|
|
|
|
}, 5000);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const getData = () => {
|
|
|
|
|
getSuccessRateTrends().then((res) => {
|
|
|
|
|
successRateRef.value.setData({
|
|
|
|
|
@ -233,7 +272,14 @@ const getData = () => {
|
|
|
|
|
},
|
|
|
|
|
xAxis: {
|
|
|
|
|
type: 'category',
|
|
|
|
|
data: res.data.map((item) => item.timePoint)
|
|
|
|
|
data: res.data.map((item) => item.timePoint),
|
|
|
|
|
axisLabel: {
|
|
|
|
|
show: true,
|
|
|
|
|
color: '#7ae7f8'
|
|
|
|
|
},
|
|
|
|
|
axisLine: {
|
|
|
|
|
show: false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
grid: {
|
|
|
|
|
top: 30,
|
|
|
|
|
@ -242,10 +288,18 @@ const getData = () => {
|
|
|
|
|
bottom: 30
|
|
|
|
|
},
|
|
|
|
|
yAxis: {
|
|
|
|
|
name: '%',
|
|
|
|
|
nameTextStyle: {
|
|
|
|
|
color: '#7ae7f8'
|
|
|
|
|
},
|
|
|
|
|
splitLine: {
|
|
|
|
|
show: false
|
|
|
|
|
},
|
|
|
|
|
type: 'value'
|
|
|
|
|
type: 'value',
|
|
|
|
|
axisLabel: {
|
|
|
|
|
show: true,
|
|
|
|
|
color: '#7ae7f8'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
@ -284,6 +338,7 @@ const getData = () => {
|
|
|
|
|
getLocationTree().then((res) => {
|
|
|
|
|
treeData.value = res.data;
|
|
|
|
|
centerData.value = findParentsWithLocationType3(res.data);
|
|
|
|
|
console.log(centerData.value);
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
scrollToBottom(scrollNodeRef.value, 2);
|
|
|
|
|
});
|
|
|
|
|
@ -333,11 +388,37 @@ const getSocket = () => {
|
|
|
|
|
const socket = new WebSocket('ws://192.168.0.16:7181/ws');
|
|
|
|
|
|
|
|
|
|
socket.addEventListener('open', () => {
|
|
|
|
|
// console.log('✅ WebSocket 连接成功');
|
|
|
|
|
console.log('✅ WebSocket 连接成功');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.addEventListener('message', (event) => {
|
|
|
|
|
// console.log(JSON.parse(event.data));
|
|
|
|
|
let data = JSON.parse(event.data);
|
|
|
|
|
console.log(data);
|
|
|
|
|
if (data?.deviceStatus === '1') {
|
|
|
|
|
wsData.value[data.deviceId] = data;
|
|
|
|
|
}
|
|
|
|
|
if (data?.deviceStatus === '2' && data?.alarmFlag === '1') {
|
|
|
|
|
tableData.value.push({
|
|
|
|
|
alarmTime: parseTime(data.recordTime),
|
|
|
|
|
deviceName: wsData.value[data.deviceId]?.epcStr.trimStart() || '',
|
|
|
|
|
location: centerData.value.flatMap((item) => item.children || []).find((child) => child.deviceId === targetDeviceId)?.locationAlias || '',
|
|
|
|
|
alarmAction: '未获取到标签'
|
|
|
|
|
});
|
|
|
|
|
if (data.dataType === '2') {
|
|
|
|
|
wsData.value[data.deviceId] = data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (data?.deviceStatus === '3' && data?.alarmFlag === '1') {
|
|
|
|
|
tableData.value.push({
|
|
|
|
|
alarmTime: parseTime(data.recordTime),
|
|
|
|
|
deviceName: wsData.value[data.deviceId]?.epcStr.trimStart() || '',
|
|
|
|
|
location: centerData.value.flatMap((item) => item.children || []).find((child) => child.deviceId === targetDeviceId)?.locationAlias || '',
|
|
|
|
|
alarmAction: '链接断开'
|
|
|
|
|
});
|
|
|
|
|
if (data.dataType === '2') {
|
|
|
|
|
wsData.value[data.deviceId] = data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
socket.addEventListener('close', () => {
|
|
|
|
|
@ -353,7 +434,6 @@ const getSocket = () => {
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
getData();
|
|
|
|
|
getSocket();
|
|
|
|
|
wsData.value[data.deviceId] = data;
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
|
@ -434,6 +514,28 @@ onMounted(() => {
|
|
|
|
|
color: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.title1 {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 27.9%;
|
|
|
|
|
left: 76.5%;
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
font-size: 1vw;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
letter-spacing: 0.2vw;
|
|
|
|
|
color: #7ae7f8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.title2 {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 61.5%;
|
|
|
|
|
left: 76.5%;
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
font-size: 1vw;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
letter-spacing: 0.2vw;
|
|
|
|
|
color: #7ae7f8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.topTitle {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 16.7%;
|
|
|
|
|
|