feat(drawer): 重构抽屉菜单并新增数据统计面板

- 重新设计抽屉菜单布局,采用栅格系统优化结构
- 新增报警、预警统计展示模块
- 添加项目设备统计数据展示
- 集成项目列表及设备类型数量表格
- 实现最新告警列表展示功能
- 添加activated钩子处理项目列表路由参数变化
This commit is contained in:
fhysy 2025-10-13 15:42:26 +08:00
parent 7716599c8b
commit 8d6a326d5f
4 changed files with 666 additions and 56 deletions

View File

@ -1,44 +1,225 @@
<template>
<el-drawer
:visible.sync="visible"
direction="btt"
size="95%"
custom-class="drawer-menu"
:with-header="false"
@close="$emit('update:visible', false)"
>
<el-drawer :visible.sync="visible" direction="btt" size="95%" custom-class="drawer-menu" :with-header="false"
@close="$emit('update:visible', false)">
<div class="drawer-menu-content">
<div class="drawer-menu-title">快捷菜单</div>
<div v-for="group in filteredMenuGroups" :key="group.path" class="drawer-menu-group">
<div class="drawer-menu-group-title">{{ group.meta && group.meta.title }}</div>
<div class="drawer-menu-grid">
<div
v-for="item in group.children"
:key="item.path"
class="drawer-menu-item"
@click="handleMenuClick(item, group.path)"
>
<div class="drawer-menu-icon">
<svg-icon :icon-class="item.meta && item.meta.icon"/>
<!-- <i v-if="item.meta && item.meta.icon" :class="['iconfont', item.meta.icon]" /> -->
<!-- <div class="drawer-menu-title">快捷菜单</div> -->
<el-row :gutter="12">
<el-col :span="15">
<div v-for="group in filteredMenuGroups" :key="group.path" class="drawer-menu-group">
<div class="drawer-menu-group-title menu-card">{{ group.meta && group.meta.title }}</div>
<div class="drawer-menu-grid">
<div v-for="item in group.children" :key="item.path" class="drawer-menu-item menu-card"
@click="handleMenuClick(item, group.path)">
<div class="drawer-menu-icon">
<svg-icon :icon-class="item.meta && item.meta.icon" />
<svg-icon :icon-class="item.meta && item.meta.icon" class="icon-shadow" />
<!-- <i v-if="item.meta && item.meta.icon" :class="['iconfont', item.meta.icon]" /> -->
</div>
<div class="drawer-menu-label">{{ item.meta && item.meta.title }}</div>
</div>
</div>
<div class="drawer-menu-label">{{ item.meta && item.meta.title }}</div>
</div>
</div>
</div>
</el-col>
<el-col :span="9">
<!-- 报警预警统计 -->
<div class="statistics-box">
<div class="statistics-alarm-item card-item-box">
<div class="statistics-item-header">
今日累计
</div>
<div class="statistics-item-content">
<div class="content-item">
<div class="content-item-value color-red">{{ alarmCountObj.todayAlarmCount }}</div>
<div class="content-item-label">报警</div>
</div>
<div class="content-item">
<div class="content-item-value color-orange">{{ alarmCountObj.todayWarningCount }}</div>
<div class="content-item-label">预警</div>
</div>
</div>
</div>
<div class="statistics-alarm-item card-item-box">
<div class="statistics-item-header">
昨日累计
</div>
<div class="statistics-item-content">
<div class="content-item">
<div class="content-item-value color-red">{{ alarmCountObj.yesterdayAlarmCount }}</div>
<div class="content-item-label">报警</div>
</div>
<div class="content-item">
<div class="content-item-value color-orange">{{ alarmCountObj.yesterdayWarningCount }}</div>
<div class="content-item-label">预警</div>
</div>
</div>
</div>
</div>
<!-- 项目设备统计 -->
<div class="device-box">
<div class="device-item card-item-box">
<div class="content-item">
<div class="content-item-value color-green">{{ tempObject.modelCount }}</div>
<div class="content-item-label">项目数量</div>
</div>
<div class="content-item">
<div class="content-item-value color-green">{{ tempObject.deviceCount }}</div>
<div class="content-item-label">设备数量</div>
</div>
</div>
<div class="device-item card-item-box">
<div class="content-item">
<div class="content-item-value color-green">{{ tempObject.onlineCount }}</div>
<div class="content-item-label">在线</div>
</div>
<div class="content-item">
<div class="content-item-value color-red">{{ tempObject.offlineCount }}</div>
<div class="content-item-label">离线</div>
</div>
</div>
</div>
<!-- 项目列表 -->
<div class="project-box card-item-box">
<div>
<div class="project-item" v-for="item in projectList" :key="item.projectCode">
<div class="project-item-left">
<div class="iconfont iconAdd-jisuanqi"></div>
<div class="project-item-name">{{ item.projectName }}</div>
</div>
<div class="project-item-right">
<el-button @click="goProject(item)" type="text" size="small">查看</el-button>
</div>
</div>
</div>
<!-- <div class="project-item">
<div class="project-item-left">
<div class="iconfont iconAdd-jisuanqi"></div>
<div class="project-item-name">谷云网络</div>
</div>
<div class="project-item-right">
<el-button @click="goProject('aaaqaa')" type="text" size="small">查看</el-button>
</div>
</div> -->
</div>
<!-- 设备类型数量列表 -->
<div class="device-list card-item-box">
<div class="card-header">
<div class="iconfont iconAdd-jisuanqi"></div>
<div>设备类型数量</div>
</div>
<div class="card-content">
<el-table :data="deviceData" stripe size="small">
<el-table-column prop="typeName" label="设备类型">
</el-table-column>
<el-table-column prop="num" label="设备数量" align="center">
</el-table-column>
<el-table-column prop="onlineCount" label="在线" align="center">
</el-table-column>
<el-table-column prop="offlineCount" label="离线" align="center">
</el-table-column>
<el-table-column prop="faultCount" label="故障" align="center">
</el-table-column>
<el-table-column prop="alarmCount" label="告警" align="center">
</el-table-column>
</el-table>
</div>
</div>
<!-- 告警列表 -->
<div class="alarm-list card-item-box">
<div class="card-header">
<div class="iconfont iconAdd-jisuanqi"></div>
<div>最新告警列表</div>
</div>
<div class="card-content">
<div class="alarm-item" v-for="item in alarmList" :key="item.recordId">
<div class="alarm-item-left">
<div class="alarm-item-status" :class="item.processStatus === '0' ? 'bg-red' : 'bg-green'"></div>
<div class="alarm-item-main">
<div class="alarm-item-txt">
{{ item.deviceName + item.alarmContent }} - {{ item.alarmDivide === 'ALARM' ? '告警' : '预警' }}
</div>
<div class="alarm-item-time">
{{ item.alarmTime }}
</div>
</div>
</div>
<div class="alarm-item-right">
<el-button @click="goAlarmHtml(item)" type="text" size="small">查看</el-button>
</div>
</div>
<el-empty v-if="alarmList.length === 0" description="告警列表为空"></el-empty>
</div>
</div>
</el-col>
</el-row>
</div>
</el-drawer>
</template>
<script>
import { mapGetters } from 'vuex';
import { listRecord } from "@/api/alarm/record";
import { homeCount } from "@/api/system/home";
import moment from 'moment';
import { listProject } from "@/api/tenant/project";
function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path)
return /^(https?:|http?:|mailto:|tel:)/.test(path)
}
export default {
name: 'DrawerMenu',
data() {
return {
deviceData: [
{
typeId: '10001',
typeName: '物联网网关',
onlineCount: 233, // 95%线
offlineCount: 10, // 4%线
faultCount: 2, // 线20%线
alarmCount: 3, // +1
num: 245, //
},
{
typeId: '10002',
typeName: '微型断路器',
onlineCount: 189, // 95%
offlineCount: 8, // 4%线
faultCount: 2, // 线25%
alarmCount: 2, //
num: 199, //
},
{
typeId: '10005',
typeName: '可燃气体传感器',
onlineCount: 159, // 91%广
offlineCount: 14, // 8%线
faultCount: 2, // 线14%
alarmCount: 5, // 2+3
num: 175, //
},
],
alarmCountObj: {
todayAlarmCount: 0,
todayWarningCount: 0,
yesterdayAlarmCount: 0,
yesterdayWarningCount: 0,
},
projectList: [],
alarmList: [],
tempObject: {
onlineCount: "0",
activeCount: "0",
deviceCount: "0",
modelCount: "0",
offlineCount: "0",
userId: "",
userName: ""
},
userType: '',
}
},
props: {
visible: {
type: Boolean,
@ -62,7 +243,149 @@ export default {
return filterMenu(this.sidebarRouters).filter(group => group.children && group.children.length > 0);
}
},
watch: {
visible(newVal) {
if (newVal) {
this.init();
}
}
},
created() {
this.init();
},
methods: {
init() {
this.userType = this.$store.getters.userType;
this.getCount();
this.getAlarmList();
this.getAlarmTime('today');
this.getAlarmTime('yesterday');
this.getInProjectList();
},
getInProjectList() {
listProject({
pageNum: 1,
pageSize: 99,
orderByColumn: 'createTime',
isAsc: 'desc'
}).then(response => {
this.projectList = response.rows || [];
});
},
getAlarmTime(type) {
let beginTime = null;
let endTime = null;
if (type === 'today') {
beginTime = moment().startOf('day').format('YYYY-MM-DD HH:mm:ss');
endTime = moment().endOf('day').format('YYYY-MM-DD HH:mm:ss');
} else {
beginTime = moment().subtract(1, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss');
endTime = moment().subtract(1, 'days').endOf('day').format('YYYY-MM-DD HH:mm:ss');
}
let alarmCount = 0;
let warningCount = 0;
listRecord({
pageNum: 1,
pageSize: 9999,
beginTime,
endTime,
// alarmDivide: "ALARM",
isAsc: 'desc',
orderByColumn: 'alarmTime'
}).then((response) => {
response.rows.forEach(item => {
if (item.alarmDivide == 'WARNING') {
warningCount++;
} else {
alarmCount++;
}
});
if (type === 'today') {
this.alarmCountObj.todayAlarmCount = alarmCount;
this.alarmCountObj.todayWarningCount = warningCount;
} else {
this.alarmCountObj.yesterdayAlarmCount = alarmCount;
this.alarmCountObj.yesterdayWarningCount = warningCount;
}
});
},
getAlarmList() {
listRecord({
pageNum: 1,
pageSize: 5,
typeName: null,
typeCode: null,
inProject: null,
beginTime: null,
endTime: null,
// alarmDivide: "ALARM",
isAsc: 'desc',
orderByColumn: 'alarmTime'
}).then((response) => {
this.alarmList = response.rows;
});
},
getCount() {
homeCount().then(response => {
this.tempObject = {
onlineCount: response.data.onlineTotal,
activeCount: response.data.activeCount,
deviceCount: response.data.deviceTotal,
modelCount: response.data.projectTotal,
offlineCount: (response.data.deviceTotal - response.data.onlineTotal) || 0
}
this.alarmCount = {
processCount: response.data.processed,
unProcessCount: response.data.unProcessed,
alarmCount: response.data.alarmTotal
};
}).catch(err => {
console.log(err)
})
},
goProject(row) {
if (this.userType === 'SYSTEM') {
this.$router.push({
path: '/project/project',
query: { projectId: row.projectId }
});
} else {
this.$router.push({
path: '/project_tenant/project_tenant',
query: { projectId: row.projectId }
});
}
this.$emit('update:visible', false)
},
goAlarmHtml(row) {
if (this.userType === 'SYSTEM') {
if (row.alarmDivide === 'ALARM') {
this.$router.push({
path: '/alarm/record',
query: { recordId: row.recordId }
});
} else {
this.$router.push({
path: '/alarm/waringrecord',
query: { recordId: row.recordId }
});
}
} else {
if (row.alarmDivide === 'ALARM') {
this.$router.push({
path: 'alarm_tenant/alarm_tenant',
query: { recordId: row.recordId }
});
} else {
this.$router.push({
path: '/alarm_tenant/warning_tenant',
query: { recordId: row.recordId }
});
}
}
this.$emit('update:visible', false)
},
handleMenuClick(item, parentPath = '') {
// path
let fullPath = '';
@ -89,15 +412,19 @@ export default {
}
</script>
<style scoped>
<style scoped lang="scss">
.drawer-menu {
border-radius: 16px 16px 0 0;
background: #19294a;
min-height: 300px;
}
.drawer-menu-content {
padding: 24px 16px 32px 16px;
font-family: Source Han Sans CN;
line-height: 1em;
}
.drawer-menu-title {
color: #000;
font-size: 22px;
@ -105,39 +432,300 @@ export default {
font-weight: bold;
margin-bottom: 10px;
}
.drawer-menu-group-title {
color: #000;
font-size: 18px;
line-height: 26px;
font-weight: bold;
margin-bottom: 10px;
}
.drawer-menu-grid {
.drawer-menu-group {
display: flex;
flex-wrap: wrap;
gap: 10px 10px;
.drawer-menu-group-title {
line-height: 26px;
margin-right: 12px;
margin-bottom: 12px;
font-weight: 400;
font-size: 18px;
color: #444444;
width: 86px;
padding: 0 20px;
box-sizing: border-box;
text-align: center;
}
.drawer-menu-grid {
flex: 1;
display: flex;
flex-wrap: wrap;
gap: 12px 12px;
margin-bottom: 12px;
.drawer-menu-item {
width: calc(20% - 12px);
text-align: center;
cursor: pointer;
.drawer-menu-icon {
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
color: #00A9AB;
position: relative;
.icon-shadow {
position: absolute;
top: 0px;
left: 4px;
color: rgba($color: #00A9AB, $alpha: 0.1);
display: inline-block;
-webkit-background-clip: text;
font-size: 32px !important;
}
}
.drawer-menu-label {
margin-top: 8px;
font-weight: 400;
font-size: 17px;
line-height: 17px;
color: #444444;
}
}
}
}
.drawer-menu-item {
width: 100px;
text-align: center;
cursor: pointer;
margin-bottom: 10px;
.menu-card {
background: linear-gradient(0deg, #FAFAFA 0%, #EBECF0 100%);
box-shadow: 0px 6px 12px 0px rgba(0, 0, 0, 0.1);
border-radius: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 120px;
}
.drawer-menu-icon {
width: 56px;
height: 56px;
margin: 0 auto 5px auto;
border-radius: 10px;
background: #00A9AB;
.statistics-box {
display: flex;
gap: 12px;
.statistics-alarm-item {
flex: 1;
text-align: center;
.statistics-item-header {
font-weight: 400;
font-size: 16px;
color: #444444;
line-height: 1em;
}
.statistics-item-content {
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 10px;
}
}
}
.device-box {
display: flex;
gap: 12px;
.device-item {
flex: 1;
text-align: center;
display: flex;
}
}
.project-box {
height: 86px;
overflow-y: auto !important;
.project-item {
display: flex;
align-items: center;
justify-content: space-between;
line-height: 30px;
.project-item-left {
display: flex;
align-items: center;
gap: 10px;
.iconfont {
color: #00A9AB;
}
.project-item-name {
font-weight: 400;
font-size: 16px;
line-height: 1em;
color: #444444;
}
}
}
}
::v-deep .project-item-right .el-button--text span {
color: #00A9AB;
}
.device-list {
flex: 1;
padding: 0 !important;
}
::v-deep .el-table .el-table__header-wrapper th,
::v-deep .el-table .el-table__fixed-header-wrapper th {
background: #EEEFF2 !important;
.cell {
color: #00A9AB !important;
}
}
//
.alarm-list {
flex: 1;
padding: 0 !important;
.alarm-item {
display: flex;
align-items: center;
padding: 10px 0;
background: #fff;
.alarm-item-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
margin-right: 10px;
.alarm-item-status {
margin: 0 10px;
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.alarm-item-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
.alarm-item-txt {
white-space: nowrap;
/* 文本不换行 */
overflow: hidden;
/* 超出部分隐藏 */
text-overflow: ellipsis;
/* 超出部分显示省略号 */
font-weight: 400;
font-size: 14px;
color: #333333;
line-height: 22px;
}
.alarm-item-time {
font-weight: 400;
font-size: 14px;
color: #999999;
}
}
}
.alarm-item-right {
padding-right: 10px;
}
}
}
.card-header {
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
color: #fff;
}
.drawer-menu-label {
color: #000;
height: 45px;
font-weight: 400;
font-size: 14px;
line-height: 18px;
padding-left: 20px;
.iconfont {
margin-right: 10px;
color: #00A9AB;
}
}
</style>
.card-content {
padding: 0 10px;
}
.content-item {
flex: 1;
.content-item-value {
margin-top: 6px;
font-weight: 500;
font-size: 28px;
line-height: 1em;
}
.content-item-label {
margin-top: 6px;
font-weight: 400;
font-size: 14px;
color: #444444;
line-height: 1em;
}
}
.card-item-box {
margin-bottom: 12px;
border-top: 2px solid #0A9A9C;
background: linear-gradient(0deg, #FFFFFF 0%, #ECEDF0 100%);
box-shadow: 0px 6px 12px 0px rgba(0, 0, 0, 0.1);
border-radius: 0px 0px 12px 12px;
padding: 10px;
overflow: hidden;
}
.color-red {
color: #FF4A56;
}
.color-green {
color: #00A9AB;
}
.color-dark-blue {
color: #615DAA;
}
.color-orange {
color: #FB762B;
}
.color-blue {
color: #3CA0EC;
}
.color-yellow {
color: #FDB535;
}
.color-slate-blue {
color: #7598B3;
}
.bg-red {
background: #FF4A56;
}
.bg-green {
background: #75DC68;
}
</style>

View File

@ -107,7 +107,10 @@ export const constantRoutes = [
title: '概览页', icon: 'dashboard', noCache: false, affix: false
},
},
]
],
meta: {
title: '数据概览'
}
},
{

View File

@ -693,6 +693,11 @@ export default {
this.getList();
this.getTreeselect();
},
activated() {
if (this.$route.query["projectId"]) {
this.handleDetails(this.$route.query);
}
},
methods: {
listContract,
listDevice,

View File

@ -756,10 +756,24 @@ export default {
}
@media (min-width: 992px) and (max-width: 1200px) {
.statistics-box > [class*="el-col"] {
padding-left: 10px !important;
padding-right: 10px !important;
.statistics-item{
margin-bottom: 20px;
}
}
.overvie-right-header {
min-height: 498px;
}
}
@media (max-width: 1200px) {
.statistics-box > [class*="el-col"] {
.statistics-item{
margin-bottom: 20px;
}
}
}
}
.page-cloud::-webkit-scrollbar {