<template> <div> <!-- 主体内容 --> <div class="main-content"> <!-- 左侧 --> <div class="sidebar sidebar-left"> <div class="sidebar-item H917"> <div class="sub-title por"><span>环境监测实时数据 </span></div> <div class="table-box"> <vue-seamless-scroll ref="sssjControl" :data="tableData1" :class-option="classOption" @mousewheel.native="handleScroll" class="warp" > <ul class="item"> <div :class="upWindowData.upWindowActive === i ? 'active' : ''" style="cursor: pointer" v-for="(item, i) in tableData1" :key="i" @click="lookRealData(item, i)" > <li class="table-item"> <el-row :gutter="16"> <el-col :span="6" class="jz"> <img class="img-class" :src=" require(`@/assets/images/screen/hjjc/${item.typeId}.png`) " /> </el-col> <el-col :span="10"> <p class="device-name">{{ item.tpName }}</p> <p class="device-time">{{ item.time }}</p> </el-col> <el-col :span="8"> <p class="device-value"> {{ item.value | capitalize }}{{ item.unit }} </p> </el-col> </el-row> </li> </div> </ul> </vue-seamless-scroll> </div> </div> </div> <!-- 中间 --> <div class="main-area"> <!-- 设备点位 --> <div class="dot" v-for="(item, i) in equipmentList" :key="i" :class="item.isWarning == 1 ? 'active' : ''" :style="{ left: item.x + 'px', top: item.y + 'px' }" > <div class="dot1" @click=""> <div class="tip-box"> <p v-for="(iitem, l) in item.monitorPositionList" :key="l" :class="iitem.isWarning == 1 ? 'active' : ''" > {{ iitem.equipmentName }} <span></span> </p> </div> </div> </div> <!-- 现实设备切换按钮 --> <div class="toggle-button"> <el-checkbox-group v-model="checkList"> <el-checkbox label="1">监控</el-checkbox> <el-checkbox label="2">监测设备</el-checkbox> </el-checkbox-group> </div> </div> <!-- 右侧 --> <div class="sidebar sidebar-right"> <!-- 右上历史数据图组件 --> <div class="sidebar-item H286 alert-analysis"> <div class="sub-title por"> <span>历史数据图</span> <span class="scroll-title">{{ sssjName }}</span> </div> <div class="sub-con"> <div class="sub-con"> <span v-for="(item, i) in sssjData" :key="i" :class="i == sssjMark ? 'active' : ''" >{{ item.name }} <p class="subscript"></p> </span> </div> <div class="left-chart" ref="sssj" id="sssj"></div> </div> </div> <!-- 右中历史数据图组件 --> <div class="sidebar-item H286 alert-analysis"> <div class="sub-title por"> <span>历史数据图</span> <span class="scroll-title">1</span> </div> <div class="sub-con"> <div class="sub-con"> <span v-for="(item, i) in sssjData" :key="i" :class="i == sssjMark ? 'active' : ''" >{{ item.name }} <p class="subscript"></p> </span> </div> <div class="left-chart" ref="right" id="right"></div> </div> </div> <!-- 近7日设备监测报警信息 --> <div class="sidebar-item H286 alert-analysis"> <div class="sub-title por"><span>近7日设备监测报警信息</span></div> <div class="sub-con sub-con-scroll-table"> <div class="custom-header custom-header2"> <span class="custom-header-item custom-header-item2">名称</span> <span class="custom-header-item custom-header-item2" >报警时间</span > <span class="custom-header-item custom-header-item2">检测值</span> </div> <vue-seamless-scroll class="warp" :data="tableData2" :class-option="classOption2" @mousewheel.native="handleScrollR" ref="RightScroll" > <ul class="item"> <li class="table-item custom-item custom-item2" v-for="(item, i) in tableData2" :key="i" > <span class="custom-item-content custom-item-content2">{{ item.name }}</span> <span class="custom-item-content custom-item-content2">{{ item.alertTime }}</span> <span class="custom-item-content custom-item-content2">{{ item.alertLocation }}</span> </li> </ul> </vue-seamless-scroll> </div> </div> </div> <!-- 弹窗内容 --> <transition name="el-fade-in"> <div v-show="upWindowData.upWindowVisible" class="up-window"> <p><span></span>{{ upWindowData.title }}</p> <i class="el-icon-close close-button" @click="closeUpWindow"></i> <p class="date-title">{{ upWindowData.date }}</p> <div class="chart-box" id="chartBox"></div> </div> </transition> </div> </div> </template> <script> import * as echarts from "echarts"; import { getRealData, getRtData, getDeviceStatus, getDeviceData, } from "@/api/tyler/hjjc"; import ScrollTable from "@/components/Tyler/ScrollTable.vue"; import screenfull from "screenfull"; import vueSeamlessScroll from "vue-seamless-scroll"; export default { name: "cockpit", components: { ScrollTable, vueSeamlessScroll, }, data() { return { // 设备实施数据弹窗 upWindowData: { upWindowActive: "", upWindowVisible: false, title: "", date: "2025-03-19", }, // 左边列表数据 tableData1: [], // 左侧滚动 参数 classOption: { singleHeight: 110, hoverStop: true, autoPlay: true, }, // 设备定位分类开关 checkList: ["1", "2"], // 右下滚动 参数 classOption2: { singleHeight: 47, hoverStop: true, }, // 左侧实施数据列表 sssjData: [ { name: "", value: [ { data: { xData: { data: [], }, yData: { data: [], }, }, }, ], }, ], // 右上设备轮播下标 sssjMark: 0, // 右上设备轮播名称 sssjName: "", // 右下 列表数据 tableData2: [ { name: "陈玉强", department: "生产部", alertTime: "2025-01-10 14:21:31", alertLocation: "四中车场", }, { name: "陈玉强", department: "生产部", alertTime: "2025-01-10 14:21:31", alertLocation: "四中车场", }, { name: "陈玉强", department: "生产部", alertTime: "2025-01-10 14:21:31", alertLocation: "四中车场", }, { name: "陈玉强", department: "生产部", alertTime: "2025-01-10 14:21:31", alertLocation: "四中车场", }, { name: "陈玉强", department: "生产部", alertTime: "2025-01-10 14:21:31", alertLocation: "四中车场", }, { name: "陈玉强", department: "生产部", alertTime: "2025-01-10 14:21:31", alertLocation: "四中车场", }, { name: "陈玉强", department: "生产部", alertTime: "2025-01-10 14:21:31", alertLocation: "四中车场", }, ], // 中间图片数据列表 equipmentList: [ { isWarning: 0, //是否报警 x: 85, y: 233, name: "一中避险硐室", // 监测点 monitorPositionList: [ { equipmentName: "一中避险硐室氧气", //设备名称 equipmentTy: "", //设备类型 isWarning: 0, //是否报警 }, { equipmentName: "一中避险硐室一氧化碳", //设备名称 equipmentTy: "", //设备类型 isWarning: 0, //是否报警 }, ], }, ], }; }, mounted() { if (screenfull && screenfull.enabled && !screenfull.isFullscreen) { screenfull.request(); } // this.initEchartBox("right", this.salvProName, this.salvProValue, 28); // this.initEchartBox("sssj", this.salvProName, this.salvProValue, 28); this.initRealData(); getRtData().then((res) => { this.sssjData = res.data; this.playFun(this.sssjData); }); getDeviceStatus().then((res) => { this.equipmentList = res.data; }); }, created() {}, methods: { // 辅助方法:获取字段值 getItemField(item, fieldKey) { return item[this.fieldMap[fieldKey]] || ""; }, //初始化获取左侧环境监测实施数据 initRealData() { getRealData().then((res) => { this.tableData1 = res.data; }); }, // 初始化eChartDOM initEchartBox(id, xData = [], yData = [], mkData = 28, step = 4, grid) { let that = this; let myChart = echarts.init(document.getElementById(id)); let option = { grid: grid, xAxis: { show: true, type: "category", boundaryGap: false, data: xData, axisLabel: { show: true, interval: step, textStyle: { color: "#FFFFFF", //更改坐标轴文字颜色 fontSize: 16, //更改坐标轴文字大小 }, }, axisTick: { alignWithLabel: true, }, splitLine: { //网格线 lineStyle: { type: "dashed", //设置网格线类型 dotted:虚线 solid:实线 opacity: 0.5, }, show: true, //隐藏或显示 }, }, yAxis: { type: "value", boundaryGap: false, axisTick: { alignWithLabel: true, }, axisLabel: { show: true, textStyle: { color: "#FFFFFF", //更改坐标轴文字颜色 fontSize: 16, //更改坐标轴文字大小 }, }, splitLine: { //网格线 lineStyle: { type: "dashed", //设置网格线类型 dotted:虚线 solid:实线 opacity: 0.5, }, show: true, //隐藏或显示 }, }, series: [ { type: "line", stack: "Total", smooth: 0.2, lineStyle: { width: 2, color: "#35B1F3", }, markLine: { data: [ { name: "警戒线", yAxis: mkData, label: { show: false, }, }, ], symbol: ["none", "none"], lineStyle: { color: "red", width: 2, }, }, showSymbol: false, areaStyle: { opacity: 0.8, color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: "rgb(55, 184, 251,0.5)", }, { offset: 1, color: "rgb(55, 184, 251,0.1)", }, ]), }, emphasis: { focus: "series", }, data: yData, }, ], }; myChart.setOption(option); setTimeout(() => { myChart.resize(); }, 600); }, // 轮播eChart // 更改数据达到轮播效果 playFun(list = []) { // 启动定时器,每隔 3 秒执行一次 let i = 0; let l = 0; let grid = { left: "4%", right: "5%", bottom: "4%", top: "7%", containLabel: true, }; const timer = setInterval(() => { if (i < list.length) { if (Array.isArray(list[i].value) && l < list[i].value.length) { this.sssjMark = i; this.sssjName = list[i].value[l].tpName; // this.initEchartBox( // "sssj", // list[i].value[l].data.xData.data, // list[i].value[l].data.yData.data, // "", // grid // ); l++; } else { i++; } } else { i = 0; l = 0; } }, 15000); }, //自动滚动 autoScroll() { const divData = this.$refs.scroll_List3; // 拿到表格中承载数据的div元素 divData.scrollTop += 1; if ( Math.round(divData.clientHeight + divData.scrollTop) + 1 >= divData.scrollHeight ) { // 重置table距离顶部距离 divData.scrollTop = 0; } this.scrolltimer3 = window.requestAnimationFrame( this.autoScroll.bind(this) ); }, // 查看设备24H实时数据 lookRealData(item, i) { let grid = { left: 0, right: "1.7%", bottom: "4%", top: "7%", containLabel: true, }; let xData = []; let yData = []; getDeviceData({ id: item.id }).then((res) => { if (res.code == 200) { this.upWindowData.upWindowActive = i; this.upWindowData.upWindowVisible = true; this.upWindowData.title = item.tpName; this.upWindowData.date = res.data.day; this.classOption.autoPlay = false; let threshold = 0; res.data.envScreenEditDto.forEach((i) => { xData.push(i.time); yData.push(i.value); }); threshold = res.data.threshold || 0; this.initEchartBox("chartBox", xData, yData, threshold, 0, grid); } else { this.$message.error("数据请求失败!"); } }); }, // 关闭弹窗 closeUpWindow() { this.upWindowData.upWindowActive = ""; this.upWindowData.upWindowVisible = false; this.classOption.autoPlay = true; this.$refs.sssjControl._startMove(); }, goToSys() { var link = this.$router.resolve({ path: "/", }); window.open(link.href); return; }, // 左侧 鼠标滚动代码 handleScroll(e) { // 改变组件内部 yPos 的值,这样html的translate(0, yPos)就会随之改变 // e.deltaY是滚动的距离 this.$refs.sssjControl.yPos = this.$refs.sssjControl.yPos - e.deltaY; // 如果是正数 说明是往上滚 if (this.$refs.sssjControl.yPos > 0) { this.$refs.sssjControl.yPos = 0; return; } // 如果yPos超过内部实际高度的一半则重新到顶部滚动 // 一半的原因是因为组件实际上创建了两个dom,以达到无缝衔接的效果 if ( Math.abs(this.$refs.sssjControl.yPos) > this.$refs.sssjControl.realBoxHeight / 2 ) { this.$refs.sssjControl.yPos = 0; } }, // 右侧 鼠标滚动代码 handleScrollR(e) { // 改变组件内部 yPos 的值,这样html的translate(0, yPos)就会随之改变 // e.deltaY是滚动的距离 this.$refs.RightScroll.yPos = this.$refs.RightScroll.yPos - e.deltaY; // 如果是正数 说明是往上滚 if (this.$refs.RightScroll.yPos > 0) { this.$refs.RightScroll.yPos = 0; return; } // 如果yPos超过内部实际高度的一半则重新到顶部滚动 // 一半的原因是因为组件实际上创建了两个dom,以达到无缝衔接的效果 if ( Math.abs(this.$refs.RightScroll.yPos) > this.$refs.RightScroll.realBoxHeight / 2 ) { this.$refs.RightScroll.yPos = 0; } }, }, filters: { capitalize: function (value) { if (!value) return ""; value = value.slice(0, 2); return value; }, }, watch: { tableData1: { handler() { // 数据更改时也要加 this.$nextTick(() => { this.$refs.vueSeamlessScroll.reset(); }); }, immediate: true, }, tableData2: { handler() { // 数据更改时也要加 this.$nextTick(() => { this.$refs.RightScroll.reset(); }); }, immediate: true, }, }, }; </script> <style lang="scss" scoped> .por { position: relative; } .sidebar-left { transform-origin: left center; transform: scaleX(1) perspective(610px) rotateY(5deg); } .sidebar-right { transform-origin: center right; transform: scaleX(1) perspective(610px) rotateY(-5deg); } /* 主体内容 */ .main-content { display: flex; justify-content: space-between; padding: 0 30px; } /* 左右侧边栏 */ .sidebar { width: 460px; border-radius: 8px; flex: 1; // display: flex; // flex-direction: column; // display: grid; grid-template-rows: repeat(3, 1fr); gap: 2px; margin-top: -10px; } .H917 { height: 917px; } .H286 { height: 286px; } .sidebar-item { width: 100%; // height: 286px; margin-bottom: 30px; background: linear-gradient( 180deg, rgba(1, 33, 58, 0.2) 0%, rgba(8, 132, 233, 0.2) 100% ); position: relative; &::before { content: ""; width: 460px; height: 4px; position: absolute; background: url("~@/assets/images/screen/bottom.png") no-repeat center; left: 0; bottom: 0; right: 0; z-index: 20; } .sub-title { width: 100%; height: 46px; background: url("~@/assets/images/screen/title1.png") no-repeat center; font-weight: bold; span { position: absolute; top: -10px; left: 30px; font-size: 22px; } .scroll-title { position: absolute; top: -7px; left: 157px; font-weight: 800; font-size: 18px; color: #ffc62e; } } .table-box { height: 866px; width: calc(100% - 4px); .table-item { height: 110px; width: calc(100% - 46px); margin: 0 auto; border-bottom: 2px dashed rgba(100, 194, 255, 0.3); padding-top: 9px; .img-class { display: block; width: 85px; height: 85px; } .device-name { width: 100%; font-weight: 400; font-size: 18px; color: #63c2ff; font-family: Source Han Sans SC; height: 15px; } .device-time { font-weight: 400; font-size: 16px; color: #b4d5ea; font-family: Source Han Sans SC; } .device-value { font-family: Source Han Sans SC; font-weight: 500; font-size: 28px; color: #ffffff; background: linear-gradient( 0deg, #3cbffa 0%, #ffffff 56.005859375%, #e2f6ff 100% ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; width: 100%; text-align: center; } } .warp { width: 100%; height: 100%; overflow: hidden; ul { list-style: none; margin: 0 auto; .active { // width: 100%; background: RGBA(2, 87, 149, 1); } } } } .jz { display: flex; align-items: center; justify-content: center; } } /* 中间主区域 */ .main-area { width: calc(100% - 920px); background: url("~@/assets/images/screen/bg3.png") no-repeat center; text-align: center; position: relative; .dot { width: 42px; height: 70px; position: absolute; top: 100px; left: 500px; background: url("~@/assets/images/screen/hjjc/icon8.png") no-repeat center; .dot1 { position: relative; .tip-box { width: 216px; background: linear-gradient(0deg, #062451 0%, #09162d 100%); box-shadow: 0px 15px 11px 2px rgba(0, 20, 39, 0.31); border-radius: 4px; border: 1px solid #11b9ff; position: absolute; bottom: 10px; left: -81px; padding: 9px 14px 14px 14px; z-index: 999; display: none; p { font-family: Source Han Sans SC; font-weight: 400; font-size: 16px; color: #1cd2ff; margin: 5px 0 0 0; text-align: left; span { display: none; } &.active { color: rgba(255, 43, 58, 1); span { display: inline-block; width: 16px; height: 16px; background: url("~@/assets/images/screen/hjjc/icon10.png") no-repeat center; margin-bottom: -3px; } } } } } &:hover { background: url("~@/assets/images/screen/hjjc/icon8_hover.png") no-repeat center; .tip-box { display: block; } } &.active { background: url("~@/assets/images/screen/hjjc/icon9.png") no-repeat center; &:hover { background: url("~@/assets/images/screen/hjjc/icon9_hover.png") no-repeat center; } } } .toggle-button { width: 120px; height: 90px; background: RGBA(8, 27, 58, 1); position: absolute; bottom: 58px; right: 0px; .el-checkbox-group { width: 100%; height: 100%; .el-checkbox { margin-top: 16px; color: #fdfeff; font-size: 16px; font-weight: 500; } } } } /* 实时分布图区域 */ .distribution-map { height: 300px; background: rgba(8, 28, 49, 0.8); border-radius: 8px; } .sub-con { .sub-con { height: 2em; width: 100%; // display: flex; // justify-content: space-around; // align-items: center; span { float: left; margin-left: 25px; min-width: 63px; height: 2em; font-size: 16px; line-height: 16px; font-weight: 500; color: rgba(179, 240, 255, 1); text-align: center; } .active { color: rgba(46, 213, 255, 1); .subscript { height: 19px; width: 100%; margin-top: 3px; background: url("~@/assets/images/screen/hjjc/icon7.png") no-repeat center; } } } } .left-chart { width: 100%; height: 12em; } .sub-con-scroll-table { width: 95%; margin: 0 auto; height: 83%; overflow: hidden; } .custom-header { width: 100%; display: grid; grid-template-columns: 1fr 1fr 1fr 2fr; text-align: center; color: #2ed5ff; } .custom-item { width: 100%; display: grid; grid-template-columns: 1fr 1fr 1fr 2fr; text-align: center; } .custom-header2 { width: 100%; display: grid; grid-template-columns: 1.5fr 2.2fr 1.3fr; text-align: center; color: #2ed5ff; height: 36px; line-height: 36px; color: #2ed5ff; font-size: 16px; font-weight: 500; background: linear-gradient( 0deg, rgba(61, 98, 147, 0.35) 0%, rgba(61, 98, 147, 0.03) 100% ); } .warp { height: calc(100% - 36px); overflow: hidden; .item { padding-left: 0px; margin-bottom: 0px; margin-top: 0px; } .custom-item2 { width: 100%; height: 36px; display: grid; grid-template-columns: 1.5fr 2.2fr 1.3fr; text-align: center; background: linear-gradient( 0deg, rgba(61, 98, 147, 0.35) 0%, rgba(61, 98, 147, 0.03) 100% ); margin-top: 11px; } .custom-item-content2 { font-family: Source Han Sans SC; font-size: 16px; line-height: 36px; color: #bbd7ea; } } .sub-con-r { width: 100%; display: flex; justify-content: space-evenly; align-items: center; height: calc(100% - 70px); .sub-con-r-left { width: 127px; height: 161px; background: url("~@/assets/images/screen/icon3.png") no-repeat center; background-size: 100%; } .sub-con-r-right { width: 250px; display: flex; flex-direction: column; // gap: 10px; p { display: grid; grid-template-columns: 4fr 4fr 1fr; font-size: 18px; line-height: 24px; color: #bbd7ea; background-color: rgba(61, 98, 147, 0.2); padding: 10px 10px; text-align: justify; box-sizing: border-box; span:nth-child(2) { color: rgba(187, 215, 234, 0.2); } span:nth-child(3) { font-weight: bold; font-size: 24px; color: #bbd7ea; background: linear-gradient(0deg, #47c5ff 0%, #fdfeff 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } } } } .up-window { position: absolute; top: 50%; left: 50%; width: 1360px; height: 480px; transform: translate(-50%, -50%); background: url("~@/assets/images/screen/hjjc/popup.png") no-repeat; background-size: 1377px 501px; box-shadow: 0px 15px 11px 2px rgba(0, 20, 39, 0.31); padding: 28px 37px; p { width: 1271px; height: 37px; margin: 0px; background: url("~@/assets/images/screen/hjjc/title-popup.png") no-repeat center; background-size: 100%; font-weight: 500; font-size: 22px; color: #ffffff; height: 37px; background-position-y: 9px; span { width: 27px; display: inline-block; } } .close-button { font-size: 30px; position: absolute; top: 16px; right: 16px; cursor: pointer; &:hover { -webkit-transform: rotate(360deg); transform: rotate(360deg); -webkit-transition: -webkit-transform 1s linear; transition: transform 1s linear; } } .date-title { width: 118px; height: 18px; font-weight: 500; font-size: 22px; color: #15f1ff; background: none; margin-left: 56px; margin-top: 5px; } .chart-box { width: 100%; height: 355px; } } </style>