feat(overview): 添加概览页面并更新相关路由和样式

- 新增概览页面 overview.vue,包含项目数量、设备状态、告警信息等统计展示
- 更新路由配置,将原 index 路由指向新的 overview 页面
- 引入自定义图标字体文件 iconfont.css,支持页面图标显示
- 修改 Navbar 组件导入路径,确保 DrawerMenu 正确加载
- 调整饼图组件样式,注释掉绝对定位 top 值以适应新布局
- 在 main.js 中引入图标字体样式文件
- 更新面包屑组件与搜索组件之间的间距处理
This commit is contained in:
fhysy 2025-10-11 11:33:06 +08:00
parent be51e130e9
commit 7716599c8b
13 changed files with 850 additions and 6 deletions

View File

@ -0,0 +1,55 @@
@font-face {
font-family: "iconfont"; /* Project id 5037028 */
src: url('iconfont.woff2?t=1760066516599') format('woff2'),
url('iconfont.woff?t=1760066516599') format('woff'),
url('iconfont.ttf?t=1760066516599') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconAdd-xiangmu:before {
content: "\e69f";
}
.iconAdd-jisuanqi:before {
content: "\e6a4";
}
.iconAdd-shebeilixian:before {
content: "\e751";
}
.iconAdd-shebeizaixian:before {
content: "\e752";
}
.iconAdd-icon_gaojingjilu:before {
content: "\e75e";
}
.iconAdd-icon_gengxin:before {
content: "\e769";
}
.iconAdd-shebeidianjiantixing:before {
content: "\e779";
}
.iconAdd-product:before {
content: "\e63a";
}
.iconAdd-shebeixinxi1:before {
content: "\e78f";
}
.iconAdd-zanwuxinxi:before {
content: "\e796";
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -62,7 +62,7 @@ import SizeSelect from '@/components/SizeSelect'
import Search from '@/components/HeaderSearch'
import RuoYiGit from '@/components/SmartPower/Git'
import RuoYiDoc from '@/components/SmartPower/Doc'
import DrawerMenu from '@/components/DrawerMenu'
import DrawerMenu from '@/components/DrawerMenu/index'
import { getIotFileUrl } from "@/utils/hciot"
export default {

View File

@ -7,6 +7,9 @@ import './assets/styles/element-variables.scss'
import '@/assets/styles/ruoyi.scss'
import '@/assets/styles/index.scss'
//补充图标
import '@/assets/font/iconfont.css'
import App from './App'
import store from './store'
import router from './router'

View File

@ -101,14 +101,15 @@ export const constantRoutes = [
// },
{
path: '/index',
component: (resolve) => require(['@/views/index'], resolve),
component: (resolve) => require(['@/views/overview'], resolve),
name: 'BigScreen',
meta: {
title: '监控大屏', icon: 'dashboard', noCache: false, affix: false
title: '概览页', icon: 'dashboard', noCache: false, affix: false
},
},
]
},
{
path: '/project',
component: Layout,

View File

@ -94,6 +94,6 @@ export default {
<style lang="scss" >
.bigscreen-echarts {
position: relative;
top: -62px;
// top: -62px;
}
</style>

785
src/views/overview.vue Normal file
View File

@ -0,0 +1,785 @@
<template>
<div class="page-cloud">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="24" :lg="16" :xl="16">
<el-row :gutter="10" class="statistics-box">
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="statistics-item card-box">
<div class="statistics-item-left">
<div class="iconfont iconAdd-xiangmu color-green"></div>
</div>
<div class="statistics-main">
<p class="statistics-item-name">项目数量</p>
<p class="statistics-item-value color-green">{{ tempObject.modelCount }}</p>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="statistics-item card-box">
<div class="statistics-item-left">
<div class="iconfont iconAdd-shebeizaixian color-dark-blue"></div>
</div>
<div class="statistics-main">
<p class="statistics-item-name">在线设备数</p>
<p class="statistics-item-value color-dark-blue">{{ tempObject.onlineCount }}</p>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="statistics-item card-box">
<div class="statistics-item-left">
<div class="iconfont iconAdd-shebeilixian color-orange"></div>
</div>
<div class="statistics-main">
<p class="statistics-item-name">离线设备数</p>
<p class="statistics-item-value color-orange">{{ tempObject.offlineCount }}</p>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="statistics-item card-box">
<div class="statistics-item-left">
<div class="iconfont iconAdd-shebeixinxi1 color-blue"></div>
</div>
<div class="statistics-main">
<p class="statistics-item-name">设备数量</p>
<p class="statistics-item-value color-blue">{{ tempObject.deviceCount }}</p>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="statistics-item card-box">
<div class="statistics-item-left color-yellow">
<div class="iconfont iconAdd-icon_gaojingjilu color-yellow"></div>
</div>
<div class="statistics-main">
<p class="statistics-item-name">告警数量</p>
<p class="statistics-item-value">{{ alarmCount.alarmCount }}</p>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="statistics-item card-box">
<div class="statistics-item-left">
<div class="iconfont iconAdd-shebeidianjiantixing color-slate-blue"></div>
</div>
<div class="statistics-main">
<p class="statistics-item-name">未处理量</p>
<p class="statistics-item-value color-slate-blue">{{ alarmCount.unProcessCount }}</p>
</div>
</div>
</el-col>
</el-row>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="overvie-right-header card-box">
<div class="overvie-right-header-item">
<div class="echart-txt">
<span style="font-size: 17px; font-family: 'Microsoft YaHei'; font-weight: bold;" class="color-green">{{
deviceRate(
tempObject.deviceCount, tempObject.onlineCount) }}</span>
<span style="font-size: 14px; font-family: 'Microsoft YaHei'; font-weight: bold;">在线率</span>
</div>
<pancake styles=" width: 180px;height: 180px; border-radius: 50%;" id="pancake026" :config="deviceConfig"
:option="{ data: [{ value: tempObject.onlineCount, name: '在线' }, { value: tempObject.offlineCount, name: '离线' }], title: '设备状态' }">
</pancake>
</div>
<div class="overvie-right-header-item">
<div class="echart-txt">
<span style="font-size: 17px; font-family: 'Microsoft YaHei'; font-weight: bold;"
class="color-slate-blue">{{
alarmRate(alarmCount.alarmCount,
alarmCount.processCount) }}</span>
<span style="font-size: 14px; font-family: 'Microsoft YaHei'; font-weight: bold; ">处理率</span>
</div>
<pancake styles="width: 180px; height: 180px;border-radius: 50%;" :config="alarmConfig" id="pancake157"
:option="{ data: [{ value: alarmCount.processCount, name: '已处理' }, { value: alarmCount.unProcessCount, name: '未处理' }], title: '报警处理' }">
</pancake>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="device-list card-box">
<div class="card-header">
<div class="iconfont iconAdd-product"></div>
<div>设备类型数量列表</div>
</div>
<div class="card-content">
<el-table :data="deviceData">
<el-table-column type="index" width="50">
</el-table-column>
<el-table-column prop="typeName" label="设备类型">
</el-table-column>
<el-table-column prop="num" label="设备数量" align="center">
</el-table-column>
<el-table-column fixed="right" label="操作" width="60">
<template slot-scope="scope">
<el-button @click="goDeviceType(scope.row)" type="text" size="small">查看</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="alarm-list card-box">
<div class="card-header">
<div class="iconfont iconAdd-product"></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-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div class="dashboard-box card-box">
<div class="dashboard-item">
<div class="dashboard-item-left">
<img :src="ydxcx" alt="">
<div class="dashboard-item-title">
微信小程序
</div>
</div>
<div class="dashboard-item-right">
企业用户和个人用户通过微信小程序可以查看设备控制设备查看设备实时数据和历史数据查看报警信息处理报警信息查看项目信息等
</div>
</div>
<div class="dashboard-item">
<div class="dashboard-item-left">
<img :src="gzhqr" alt="">
<div class="dashboard-item-title">
微信公众号
</div>
</div>
<div class="dashboard-item-right">
通过关注微信公众号可以实时接收到系统通知触发器消息等
</div>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import indexTips from "./bashboardcom/indexTips";
import pancake from "./bashboardcom/pancake_echarts";
import { homeCount } from "@/api/system/home";
import { newUplog } from '@/api/system/uplog'
import { listNotice } from "@/api/system/notice";
import { listRecord } from "@/api/alarm/record";
import gzhqr from "@/assets/images/drgy/gzhqr.jpg"
import ydxcx from "@/assets/images/drgy/ydxcx.jpg"
export default {
name: "overview",
data() {
return {
deviceData: [{
typeId: '10001',
typeName: '物联网网关',
num: 245,
}, {
typeId: '10002',
typeName: '微型断路器',
num: 199,
}, {
typeId: '10005',
typeName: '可燃气体传感器',
num: 175,
}, {
typeId: '10006',
typeName: '烟雾传感器',
num: 55,
}, {
typeId: '10008',
typeName: '刷卡机柜锁',
num: 23,
}],
alarmList: [],
ydxcx,
gzhqr,
activeNames: ["1", "2", "3", "4"],
DATA: [],
text: "",
actor: "",
count: 0,
isText: false,
currentRole: "adminDashboard",
headerObj: "",
versionObj: {},
newsList: [],
tempObject: {
onlineCount: "0",
activeCount: "0",
deviceCount: "0",
modelCount: "0",
offlineCount: "0",
userId: "",
userName: ""
},
alarmCount: {
processCount: "0",
unProcessCount: "0",
alarmCount: "0"
},
deviceConfig: {
tooltip: {
formatter: "{a} <br/>{b}: {c} ({d}%)"
},
series: {
radius: ["50%", "75%"]
},
itemStyle: {
normal: {
color: function (params) {
var colorList = ["#66CCDA", "rgb(211, 251, 253)"];
return colorList[params.dataIndex];
}
}
}
},
alarmConfig: {
tooltip: {
formatter: "{a} <br/>{b}: {c} ({d}%)"
},
series: {
radius: ["50%", "75%"]
},
itemStyle: {
normal: {
color: function (params) {
var colorList = ["#5E87A6", "#B7C9D7"];
return colorList[params.dataIndex];
}
}
}
},
option1: [
{
img: ydxcx,
lable: "微信小程序",
describe: "企业用户和个人用户通过微信小程序,可以查看设备,控制设备,查看设备实时数据和历史数据;查看报警信息,处理报警信息,查看项目信息等。"
}
],
option2: [
{
img: gzhqr,
lable: "微信公众号",
describe: "通过关注微信公众号,可以实时接收到系统通知,触发器消息等。"
}
],
index_m_tzgg: false,
head_btn_gjjk: false,
head_btn_sbjk: false,
index_m_ydxcx: false,
userType: null
};
},
components: {
indexTips,
pancake
},
created() {
this.gzhqr = require('@/assets/' + process.env.VUE_APP_GZGQR_URL);
this.init();
},
methods: {
getAlarmList(){
listRecord({
pageNum: 1,
pageSize: 7,
typeName: null,
typeCode: null,
inProject: null,
beginTime: null,
endTime: null,
// alarmDivide: "ALARM",
isAsc: 'desc',
orderByColumn: 'alarmTime'
}).then((response) => {
this.alarmList = response.rows;
});
},
goDeviceType(e) {
if(this.userType === 'SYSTEM'){
this.$router.push({
path: '/device/device',
query: {deviceType: e.typeId}
});
}else{
this.$router.push({
path: '/device_tenant/device_tenant',
query: {deviceType: e.typeId}
});
}
},
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 }
});
}
}
},
init() {
this.userType = this.$store.getters.userType;
this.getCount();
this.getVersionList();
this.getNoticeList();
this.getAlarmList();
},
versionSeeMoreClick() {
this.$router.push({
path: "/uplog/list"
});
},
noticeSeeMoreClick() {
this.$router.push({ path: "/news" });
// this.$router.push({
// path: "/admin/notices/list"
// });
},
getNoticeList() {
listNotice({
pageNum: 1,
pageSize: 5
}).then(response => {
this.newsList = response.rows
}).catch(err => { })
},
getVersionList() {
newUplog().then(response => {
this.versionObj = response.data
}).catch(err => { })
},
noticeClick(row) {
this.$router.push({ path: "/news", query: { newId: row.noticeId } });
},
contentToString(val) {
let str = "";
if (val) {
let list = new Array();
list = val.split(";");
list.forEach((v, index) => {
// if (index !== list.length -1) {
str += v + " <br>";
// }
});
}
return str;
},
deviceRate(count, zk) {
let num = ((parseInt(zk) / parseInt(count)) * 100).toFixed(0);
if (num !== "NaN") {
return num + " %";
} else {
return "0 %";
}
},
alarmRate(count, zk) {
let num = ((parseInt(zk) / parseInt(count)) * 100).toFixed(0);
if (num !== "NaN") {
return num + " %";
} else {
return "0 %";
}
},
getAlarmCount() {
},
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)
})
},
toDeviceFu() {
this.$router.push({
path: "/iot/devices"
});
},
toProdFu() {
this.$router.push({
path: "/iot/prod"
});
},
toAlarmFu() {
this.$router.push({
path: "/trigger/trigger/log"
});
},
getData() {
if (this.count < this.DATA.length - 1) {
this.count++;
} else {
this.count = 0;
}
this.isText = true;
this.actor = this.DATA[this.count];
},
setData() {
let num = 0;
let count = 0;
let active = false;
let timeoutstart = 5000;
let timeoutend = 1000;
let timespeed = 10;
setInterval(() => {
if (this.isText) {
if (count == this.actor.length) {
active = true;
} else {
active = false;
}
if (active) {
num--;
this.text = this.actor.substr(0, num);
if (num == 0) {
this.isText = false;
setTimeout(() => {
count = 0;
this.getData();
}, timeoutend);
}
} else {
num++;
this.text = this.actor.substr(0, num);
if (num == this.actor.length) {
this.isText = false;
setTimeout(() => {
this.isText = true;
count = this.actor.length;
}, timeoutstart);
}
}
}
}, timespeed);
}
}
};
</script>
<style lang="scss">
.page-cloud {
height: calc(100vh - 84px);
width: 100%;
padding: 20px;
padding-bottom: 0;
background: #f0f2f5;
overflow: auto;
// display: flex;
font-family: Source Han Sans CN;
.statistics-box {
.statistics-item {
height: 125px;
display: flex;
align-items: center;
padding: 15px;
margin-bottom: 10px;
.statistics-item-left {
width: 100px;
height: 100px;
background: url(~@/assets/images/overview/statistics-bg.png) no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 0 18px;
.iconfont {
font-size: 48px;
margin-top: -6px;
}
}
.statistics-main {
flex: 1;
text-align: center;
.statistics-item-name {
font-weight: 400;
font-size: 16px;
color: #666666;
}
.statistics-item-value {
font-weight: 500;
font-size: 24px;
}
}
}
}
//
.device-list {
flex: 1;
min-height: 498px;
.el-table .el-table__header-wrapper th,
.el-table .el-table__fixed-header-wrapper th {
background: #fff !important;
}
}
//
.alarm-list {
flex: 1;
min-height: 498px;
.alarm-item {
display: flex;
align-items: center;
padding: 10px 0;
.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;
}
.alarm-item-time {
font-weight: 400;
font-size: 14px;
color: #999999;
}
}
}
.alarm-item-right {
padding-right: 10px;
}
}
}
.card-header {
display: flex;
align-items: center;
height: 64px;
font-weight: 400;
font-size: 18px;
color: #444444;
padding-left: 20px;
border-bottom: 1px solid #EAEAEA;
.iconfont {
margin-right: 10px;
}
}
.card-content {
padding: 0 10px;
}
.overvie-right-header {
min-height: 260px;
display: flex;
align-items: center;
justify-content: space-evenly;
.overvie-right-header-item {
display: flex;
align-items: center;
justify-content: center;
width: 180px;
height: 180px;
position: relative;
background-size: 100% 100%;
&:first-child {
background: url(~@/assets/images/overview/echart-bg2.png) no-repeat;
}
&:last-child {
background: url(~@/assets/images/overview/echart-bg1.png) no-repeat;
}
}
.echart-txt {
position: absolute;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
.dashboard-box {
padding: 20px !important;
min-height: 498px;
display: flex;
flex-direction: column;
justify-content: space-around;
.dashboard-item {
display: flex;
align-items: center;
.dashboard-item-left {
text-align: center;
img {
width: 182px;
height: 182px;
margin-right: 10px;
}
}
.dashboard-item-right {
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 30px;
}
}
}
.card-box {
background: #FFFFFF;
box-shadow: 0px 5px 10px 0px rgba(213, 225, 239, 0.5);
border-radius: 10px;
padding: 0;
margin-bottom: 20px;
}
.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;
}
@media (min-width: 992px) and (max-width: 1200px) {
.overvie-right-header {
min-height: 498px;
}
}
}
.page-cloud::-webkit-scrollbar {
/*滚动条整体样式*/
width: 8px;
/*高宽分别对应横竖滚动条的尺寸*/
height: 5px;
}
.page-cloud::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 10px;
// box-shadow: inset 0 0 5px #9e9d9d;
background: #929292a6;
}
.page-cloud::-webkit-scrollbar-track {
/*滚动条里面轨道*/
box-shadow: inset 0 0 5px #f6f6f6;
border-radius: 10px;
background: #ffffff;
}
</style>