<script setup name="Dashboard">
|
import { defineComponent } from 'vue';
|
import HdwCard from '@/components/HdwCard/index.vue';
|
import LedNum from '@/components/LedNum/index.vue';
|
import mapChart from "@/components/echarts/mapChart.vue";
|
import pie from "@/components/echarts/pie.vue";
|
import bar from "@/components/echarts/bar.vue";
|
import groupBar from "@/components/echarts/groupBar.vue";
|
|
import { ref, watchEffect } from 'vue';
|
import useWebsocket from '@/hooks/useWebsocket';
|
|
const { message: listMessage } = useWebsocket('home');
|
|
const stationNum = ref(0);
|
const chartData = ref([]);
|
const map = ref();
|
const typePie = ref();
|
const prodPie = ref();
|
const testBar = ref();
|
const useageRateChart = ref();
|
|
let lockDatas = ref([]);
|
const rateList = ref([]);
|
const rateType = ref('month');
|
|
// "offLineNum": 6,
|
// "unLoadNum": 5,
|
// "sumLinf": 7,
|
// "openNum": 0,
|
// "onlineNum": 1,
|
// "closeNum": 2
|
|
const offLine_num = ref(0);
|
const unLoad_num = ref(0);
|
const sumLinf_num = ref(0);
|
const open_num = ref(0);
|
const online_num = ref(0);
|
const close_num = ref(0);
|
const rtData = ref([]);
|
const alarmData = ref([]);
|
|
watchEffect(() => {
|
if (listMessage.value) {
|
const { resScreenBox, resErrorCtlLog, resAllCtlLog, resLockState, resReport, resAllAinf } = JSON.parse(listMessage.value)?.data;
|
console.log('resScreenBox, resErrorCtlLog, resAllCtlLog, resLockState, resReport', resScreenBox, resErrorCtlLog, resAllCtlLog, resLockState, resReport, '=============');
|
|
let locks = [];
|
let _offLine = 0,
|
_unLoad = 0,
|
_sumLinf = 0,
|
_open = 0,
|
_online = 0,
|
_close = 0;
|
|
if (resLockState.code && resLockState.data) {
|
let _list = resLockState.data2;
|
locks = _list.allLinfs.filter(v => v.longitude || v.latitude);
|
_offLine = _list.offLineNum;
|
_unLoad = _list.unLoadNum;
|
_sumLinf = _list.sumLinf;
|
_open = _list.openNum;
|
_online = _list.onlineNum;
|
_close = _list.closeNum;
|
}
|
|
offLine_num.value = _offLine;
|
unLoad_num.value = _unLoad;
|
sumLinf_num.value = _sumLinf;
|
open_num.value = _open;
|
online_num.value = _online;
|
close_num.value = _close;
|
|
lockDatas.value = locks;
|
|
if (resAllCtlLog.code && resAllCtlLog.data) {
|
let _list = resAllCtlLog.data2.map(v => ({
|
...v,
|
state: v.ctlResult ? '开锁成功' : '开锁失败'
|
}));
|
rtData.value = _list;
|
}
|
if (resErrorCtlLog.code && resErrorCtlLog.data) {
|
let _list = resErrorCtlLog.data2.map(v => ({
|
...v,
|
state: v.ctlResult ? '开锁成功' : '开锁失败'
|
}));
|
alarmData.value = _list;
|
}
|
|
let _rateList = [];
|
if (resReport.code && resReport.data) {
|
_rateList = resReport.data2;
|
}
|
rateList.value = _rateList;
|
|
|
let area_num = 0;
|
if (resAllAinf.code && resAllAinf.data) {
|
area_num = resAllAinf.data2;
|
}
|
|
stationNum.value = area_num;
|
|
if (resScreenBox.code && resScreenBox.data) {
|
let { product, type } = resScreenBox.data2;
|
let prodData = formatPieData(product);
|
let typeData = formatPieData(type);
|
|
typePie.value.updateChart(typeData.sData);
|
prodPie.value.updateChart(prodData.sData);
|
}
|
|
updateMap();
|
updateStateChart();
|
updateUseageRateChart();
|
}
|
});
|
|
function updateStateChart() {
|
let labels = ['锁具', '未安装', '开锁', '关锁', '在线', '离线'];
|
let datas = [sumLinf_num.value, unLoad_num.value, open_num.value, close_num.value, online_num.value, offLine_num.value];
|
testBar.value.updateChart(labels, datas);
|
}
|
|
/**
|
* 格式化数据 取前4 剩下的为其他
|
*/
|
function formatPieData(data) {
|
let res = { sData: [] };
|
let arr = Object.keys(data)
|
.map((v) => ({ name: v, value: data[v] }))
|
.sort((a, b) => {
|
return b.value - a.value;
|
});
|
let total = 0;
|
arr.map(v => {
|
total += v.value * 1;
|
});
|
|
if (arr.length <= 5) {
|
res.sData = arr;
|
} else {
|
let name = "其他";
|
let value = 0;
|
let otherList = [];
|
arr.splice(4).forEach((v) => {
|
value += v.value * 1;
|
let percent = (total ? v.value / total : 0).toFixed(4);
|
otherList.push({
|
name: v.name,
|
value: v.value,
|
percent: (percent * 100).toFixed(2) + "%"
|
});
|
});
|
|
|
res.sData = arr;
|
res.sData.push({ name, value });
|
}
|
return res;
|
}
|
|
function updateMap() {
|
const getColor = (onLine) => ["#aaa", "#0f0"][onLine];
|
let data = lockDatas.value;
|
map.value.updateChart(
|
data.map((v) => {
|
return {
|
...v,
|
label: v.lockName,
|
color: getColor(v.lockOnline),
|
points: [v.longitude, v.latitude],
|
// 无实际意义
|
value: 100,
|
};
|
})
|
);
|
}
|
|
function updateUseageRateChart() {
|
// 获取当前的日期对象
|
let currentDate = new Date();
|
// 获取月份数(0 表示一月,11 表示十二月)
|
let month = currentDate.getMonth() + 1;
|
// 获取季度数
|
let quarter = Math.ceil(month / 3);
|
let type = rateType.value;
|
let _list = rateList.value;
|
let list = [];
|
switch (type) {
|
case 'month':
|
list = _list.map(v => ({
|
label: v.lockName, value: v['month' + month]
|
})).sort((a, b) => b.value - a.value);
|
break;
|
case 'quarter':
|
list = _list.map(v => ({
|
label: v.lockName, value: v['quarter' + quarter]
|
})).sort((a, b) => b.value - a.value);
|
break;
|
case 'year':
|
list = _list.map(v => ({
|
label: v.lockName, value: v.yearCount
|
})).sort((a, b) => b.value - a.value);
|
break;
|
}
|
|
|
let labels = list.slice(0, 5).map(v => v.label);
|
let datas = list.slice(0, 5).map(v => v.value);
|
|
useageRateChart.value.updateChart(labels, datas);
|
}
|
|
</script>
|
|
<template>
|
<div class="dashboard-wrapper">
|
<div class="dashboard-left">
|
<div class="left-content-list">
|
<div class="left-content-item">
|
<hdw-card is-full title="屏柜类型">
|
<pie ref="typePie"></pie>
|
</hdw-card>
|
</div>
|
<div class="left-content-item">
|
<hdw-card is-full title="设备工作状态">
|
<bar ref="testBar" unit="套"></bar>
|
</hdw-card>
|
</div>
|
<div class="left-content-item last">
|
<hdw-card is-full title="环境状态">
|
<div class="env">
|
<!-- 烟感 -->
|
<div class="item">
|
<div class="label">烟感</div>
|
<div class="value">0</div>
|
</div>
|
<!-- 温度 -->
|
<div class="item">
|
<div class="label">温度</div>
|
<div class="value">0</div>
|
</div>
|
<!-- 湿度 -->
|
<div class="item">
|
<div class="label">湿度</div>
|
<div class="value">0</div>
|
</div>
|
</div>
|
</hdw-card>
|
</div>
|
</div>
|
</div>
|
<div class="dashboard-middle">
|
<div class="middle-content-wrapper">
|
<hdw-card>
|
<div class="number-list-wrapper">
|
<div class="number-item">
|
<div class="number-label">区域</div>
|
<div class="number-value">
|
<led-num color="#f00" :num="stationNum"></led-num>
|
</div>
|
</div>
|
<div class="number-item">
|
<div class="number-label">锁具</div>
|
<div class="number-value">
|
<led-num color="#f00" :num="sumLinf_num"></led-num>
|
</div>
|
</div>
|
<!-- <div class="number-item">
|
<div class="number-label">电子卡</div>
|
<div class="number-value">
|
<led-num color="#f00" :num="cardNum"></led-num>
|
</div>
|
</div> -->
|
</div>
|
</hdw-card>
|
<div class="map-wrapper">
|
<hdw-card is-full>
|
<map-chart ref="map" @mapMounted="updateMap">
|
<template #tools>
|
<div class="map-mark">
|
<div class="mark online">
|
<el-icon class="ico"></el-icon>在线
|
<div class="count">{{ online_num }}</div>
|
</div>
|
<div class="mark offline">
|
<el-icon class="ico"></el-icon>离线
|
<div class="count">{{ offLine_num }}</div>
|
</div>
|
</div>
|
</template>
|
</map-chart>
|
</hdw-card>
|
</div>
|
<div class="dev-real-info">
|
<hdw-card title="实时开锁信息" is-full>
|
<el-table :data="rtData" style="width: 100%; height: 100%">
|
<el-table-column prop="ctlTime" label="时间" width="180" />
|
<el-table-column prop="lockId" label="锁具ID" width="180" />
|
<el-table-column prop="lockName" label="锁具名称" />
|
<el-table-column prop="state" label="操作状态" />
|
</el-table>
|
</hdw-card>
|
</div>
|
</div>
|
</div>
|
<div class="dashboard-right">
|
<div class="left-content-list">
|
<div class="left-content-item">
|
<hdw-card is-full title="屏柜品牌">
|
<pie ref="prodPie"></pie>
|
</hdw-card>
|
|
</div>
|
<div class="left-content-item">
|
<hdw-card is-full title="使用频次">
|
<template #tools>
|
<el-radio-group v-model="rateType" size="small">
|
<el-radio-button value="month" label="本月"></el-radio-button>
|
<el-radio-button value="quarter" label="本季度"></el-radio-button>
|
<el-radio-button value="year" label="本年"></el-radio-button>
|
</el-radio-group>
|
</template>
|
<bar ref="useageRateChart" :rotate="30" unit="次"></bar>
|
</hdw-card>
|
</div>
|
<div class="left-content-item last">
|
<hdw-card is-full title="告警统计">
|
<el-table :data="alarmData" style="width: 100%; height: 100%">
|
<el-table-column prop="ctlTime" label="时间" width="160" />
|
<el-table-column prop="lockId" label="锁具ID" width="90" />
|
<el-table-column prop="lockName" label="锁具名称" />
|
<el-table-column prop="state" label="操作状态" />
|
</el-table>
|
</hdw-card>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped lang="less">
|
.dashboard-wrapper {
|
display: flex;
|
flex-direction: row;
|
height: 100%;
|
|
.dashboard-left {
|
width: 500px;
|
height: 100%;
|
}
|
|
.dashboard-middle {
|
flex: 1;
|
height: 100%;
|
}
|
|
.dashboard-right {
|
width: 500px;
|
height: 100%;
|
}
|
}
|
|
.left-content-list {
|
padding: 8px;
|
height: 100%;
|
|
.left-content-item {
|
height: 33%;
|
padding-bottom: 8px;
|
|
&.last {
|
height: 34%;
|
padding-bottom: 0;
|
}
|
}
|
}
|
|
.middle-content-wrapper {
|
display: flex;
|
flex-direction: column;
|
padding: 8px 0;
|
height: 100%;
|
|
.number-list-wrapper {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 0 10px;
|
margin-top: 8px;
|
|
.number-item {
|
cursor: pointer;
|
display: flex;
|
// border-radius: 6px;
|
align-items: center;
|
|
.number-label {
|
white-space: nowrap;
|
background: #0ff;
|
border-radius: 6px;
|
color: #333;
|
font-size: 16px;
|
font-weight: bold;
|
padding: 0 10px;
|
}
|
|
.number-value {
|
width: 4.5em;
|
height: 1.5em;
|
padding: 0 10px;
|
}
|
}
|
}
|
|
.map-wrapper {
|
flex: 1;
|
padding: 8px 0;
|
}
|
|
.wrap-chart {
|
flex: 1;
|
// position: relative;
|
}
|
|
.map-mark {
|
position: absolute;
|
right: 10px;
|
bottom: 10px;
|
|
.mark {
|
display: flex;
|
align-items: center;
|
|
.ico {
|
font-size: 14px;
|
margin-right: 0.4em;
|
}
|
|
.count {
|
margin-left: 1em;
|
}
|
|
&.online {
|
color: #0f0;
|
}
|
|
&.offline {
|
color: #aaa;
|
}
|
}
|
}
|
|
.dev-real-info {
|
height: 34%;
|
}
|
|
}
|
|
.env {
|
height: 100%;
|
padding: 10px;
|
display: flex;
|
flex-direction: row;
|
align-items: center;
|
.item {
|
flex: 1;
|
border-radius: 8px;
|
padding: 4px 8px;
|
background: #0ff;
|
& + .item {
|
margin-left: 20px;
|
}
|
.label {
|
color: #333;
|
font-weight: bold;
|
margin-bottom: 0.4em;
|
text-align: center;
|
}
|
.value {
|
background: #000;
|
border-radius: 6px;
|
padding: 4px 8px;
|
color: #fff;
|
font-weight: bold;
|
text-align: center;
|
}
|
}
|
}
|
</style>
|