黄陂项目
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

787 lines
24 KiB

<template>
<div class="env-container">
<div class="env-top-title" />
<div class="current-date">{{ nowDate }}</div>
<div class="env-main">
<div class="env-main-left">
<div class="env-item container-wrap">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3>
<svg-icon icon-class="danganjieyue" style="margin-right:10px" />档案借阅
</h3>
<div class="chart-wrapper" style="height: calc(100% - 40px);">
<lend-across :lend-data="lendData" :refreshtime="refreshtime" />
</div>
</div>
<div class="env-item container-wrap" style="height: calc(100% - 170px) !important;">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3>
<i class="iconfont icon-kongqizhiliangshuju" />档案库空气质量数据
</h3>
<span style="display:block; text-align: center; font-size: 12px; margin-left: 10px; color: #3a99fd;">
{{ currentDeviceName || '' }}
</span>
<ul v-if="newAlarm && newAlarm.length !== 0" class="screen-env-list">
<li v-for="item in newAlarm" :key="item.SUBID">
<svg-icon v-if="item.subName === '温度'" icon-class="temperature" class-name="msg-list-svg" />
<svg-icon v-if="item.subName === '湿度'" icon-class="shidu" class-name="msg-list-svg" />
<svg-icon v-if="item.subName === 'PM2.5浓度'" icon-class="pm25" class-name="msg-list-svg" />
<svg-icon v-if="item.subName === 'PM10浓度'" icon-class="pm10" class-name="msg-list-svg" />
<svg-icon v-if="item.subName === 'TVOC'" icon-class="voc" class-name="msg-list-svg" />
<svg-icon v-if="item.subName === '二氧化碳'" icon-class="co2" class-name="msg-list-svg" />
<svg-icon v-if="item.subName === '甲醛'" icon-class="jiaquan" class-name="msg-list-svg" style="font-size: 64px; margin-left: 10px;" />
<!-- <svg-icon v-if="item.subName === '综合气体'" icon-class="comprehensive-gas" class-name="msg-list-svg" /> -->
<svg-icon v-if="item.subName === '空气质量'" icon-class="kongqi" class-name="msg-list-svg" />
<div class="msg-txt">
<span class="msg-list-num">{{ item.value }}</span>
<p class="msg-list-unit">{{ item.subName }} {{ item.dw }}</p>
</div>
</li>
</ul>
<!-- 空状态提示 -->
<div v-else class="empty-tip" style="display:flex; justify-content: center; height: calc(100% - 80px); color: #909399; align-items: center; font-size: 12px;">
暂无数据
</div>
</div>
</div>
<div class="env-main-middle" :style="{ height: allDisplayConfigData?.length ? 'calc(100vh - 138px)' : 'calc(100vh - 138px)', overflow: 'hidden' }">
<div class="env-3d" :style="{ height: allDisplayConfigData?.length ? 'calc(100% - 80px)' : 'calc(100% + 80px)'}">
<div class="banner-top-name">{{ bannerRoomName }}</div>
<iframe id="myIframe" ref="myIframe" name="iframeMap" class="iframe_box" src="/web3D/index.html" frameborder="0" scrolling="no" />
</div>
<div v-if="newAlarm && newAlarm.length !== 0" class="air-quality" :class="{'air-warn': aqiStatus === '污染'}">
<h3>实时空气质量指数(AQI)</h3>
<div class="air-params">
<div class="air-left">
<span class="air-title">实时AQI</span>
<div class="air-result"><p>{{ aqiValue }}</p><span>(AQI-US)</span></div>
</div>
<div class="air-right">
<span>空气质量为</span>
<p>{{ aqiStatus }}</p>
</div>
</div>
</div>
<!-- <div v-if="allDisplayConfigData && allDisplayConfigData.length !==0 " class="middle-bottom">
<span class="right-top-line" />
<span class="left-bottom-line" />
<ul class="leakage-list">
<li v-for="item in allDisplayConfigData" :key="item.id" :class="{ 'leakage-warn': item.NetStatus === 0 }">
<p><i class="iconfont icon-weihubaojing" />{{ item.Name }}</p>
<span class="leakage-state-tip" />
</li>
</ul>
</div> -->
<div v-if="validDisplayConfigData.length" class="middle-bottom">
<span class="right-top-line" />
<span class="left-bottom-line" />
<ul
class="leakage-list"
:style="{
height: showScroll ? '147px' : 'auto', // 数量>6时固定高度,否则自适应
overflow: showScroll ? 'auto' : 'hidden' // 数量>6时显示滚动
}"
>
<li
v-for="item in validDisplayConfigData"
:key="item.id"
:class="{ 'leakage-warn': item.NetStatus === 0 }"
:style="{
width: liWidth,
height: liHeight,
marginRight: '14px',
marginBottom: '14px'
}"
>
<p><i class="iconfont icon-shebei" />{{ item.Name }}</p>
<span class="leakage-state-tip" />
</li>
</ul>
</div>
</div>
<div class="env-main-right">
<!-- 门禁记录 -->
<!-- <security-door :height="'calc(100% - 38px)'" style="margin-bottom: 15px;" /> -->
<AccessDoor :height="'calc(100% - 40px)'" style="margin-bottom: 15px;" />
<!-- 报警记录 -->
<warehouse-warning :height="'calc(100% - 38px)'" />
</div>
</div>
</div>
</template>
<script>
import { getCurrentTime } from '@/utils/index'
import lendAcross from '@/views/components/echarts/lendAcross.vue'
import WarehouseWarning from '@/views/components/WarehouseWarning'
// import SecurityDoor from '@/views/components/SecurityDoor'
import AccessDoor from '@/views/components/AccessDoor'
import displayConfigApi from '@/api/storeManage/displayConfig'
// import thirdApi from '@/api/thirdApi'
import { statisticsCrud } from '@/views/system/archiveStatistics/mixins/statistics'
import alarmApi from '@/api/home/alarm'
// import { allDeviceData, mockIpData } from './index.js'
// // 同步mock方法
// const mockFetchDataForIP = (params) => {
// return new Promise((resolve) => {
// setTimeout(() => {
// const ip = params.ip
// const result = mockIpData[ip] || { code: 200, message: '操作成功', data: [], timestamp: Date.now() }
// resolve(result.data)
// }, 500)
// })
// }
export default {
name: 'EnvironmentalScreen',
components: {
WarehouseWarning,
// SecurityDoor,
AccessDoor,
lendAcross
},
mixins: [statisticsCrud],
data() {
return {
bannerRoomName: '3F 全景图',
nowDate: '',
timer: null,
echartsTimer: null,
roomId: 'D6490DA3D4261E8C26D0E3',
allDisplayConfigData: [],
displayConfigData: [],
url: '',
allDeviceIds: [],
oaoMessage: [],
topDisplayData: {
DAK_DIV_TOP_001: { show: true, curValue: '23.10', unit: '℃', curstatus: 0 },
DAK_DIV_TOP_002: { show: true, curValue: '48.90', unit: '%', curstatus: 0 },
DAK_DIV_TOP_003: { show: true, curValue: '619', unit: 'ppm', curstatus: 0 },
DAK_DIV_TOP_004: { show: true, curValue: '0.21', unit: 'mg/m³', curstatus: 0 },
DAK_DIV_TOP_005: { show: true, curValue: '26.00', unit: 'ug/m³', curstatus: 0 },
DAK_DIV_TOP_006: { show: true, curValue: '38.00', unit: 'ug/m³', curstatus: 0 }
},
refreshtime: 60000,
lendData: [],
typeData: [],
// 同步FullView的核心数据
newAlarm: [],
aqiValue: 45,
aqiStatus: '健康',
keepIndicators: [
'二氧化碳',
'甲醛',
'综合气体',
'PM2.5浓度',
'PM10浓度',
'温度',
'湿度',
'空气质量'
],
ipToNameMap: {}, // IP到设备名称映射
currentDeviceName: '', // 当前显示设备名称
currentIpIndex: 0, // IP轮询索引
excludeIpList: ['192.168.99.101:6003'], // 排除IP列表
dataTimer: null, // 数据轮询定时器
iframeWin: null // iframe窗口对象
}
},
computed: {
// 过滤有效数据(排除空值/无效项)
validDisplayConfigData() {
return this.allDisplayConfigData.filter(item => item && item.Name)
},
// 每行显示数量(优先3个,数量不足时自动调整)
itemsPerRow() {
const len = this.validDisplayConfigData.length
if (len === 0) return 0
// 规则:总数<=3 → 每行显示总数;3<总数<=6 → 每行3个;总数>6 → 每行3个(最多显示2行,超出滚动)
return len <= 3 ? len : 3
},
// li的宽度(百分比,预留间距)
liWidth() {
if (this.itemsPerRow === 0) return '0'
// 每行n个 → 宽度 = 100%/n - 间距(14px)
return `calc(100% / ${this.itemsPerRow} - 14px)`
},
// li的高度(百分比,根据总行数均分)
liHeight() {
const len = this.validDisplayConfigData.length
if (len === 0) return '0'
// 总行数 = 向上取整(总数/每行数量)
const rows = Math.ceil(len / this.itemsPerRow)
// 高度 = 100%/总行数 - 间距(14px)
return `calc(100% / ${rows} - 14px)`
},
// 是否显示滚动(数量>6时,限制高度并显示滚动)
showScroll() {
return this.validDisplayConfigData.length > 6
}
},
async created() {
// 时间更新
this.timer = setInterval(() => {
this.nowDate = getCurrentTime()
}, 1000)
// 同步FullView的初始化逻辑
window.getIframeLoading = this.getIframeLoading
// 真实请求请替换:
await alarmApi.FetchYpGetSite().then((data) => {
if (data && data.length > 0) {
this.allDisplayConfigData = data
this.handleDeviceIpList()
} else {
this.allDisplayConfigData = []
}
})
// this.allDisplayConfigData = allDeviceData
// this.handleDeviceIpList()
// 初始加载+创建定时器
if (this.allDeviceIds.length > 0) {
this.currentDeviceName = this.ipToNameMap[this.allDeviceIds[0]] || ''
await this.getRealTimeData(this.allDeviceIds[0])
// 移到这里创建定时器
this.dataTimer = setInterval(async() => {
const currentIp = this.getNextIp()
this.currentDeviceName = this.ipToNameMap[currentIp] || ''
await this.getRealTimeData(currentIp)
}, 10000)
// 10000
console.log(`启动IP轮询,共${this.allDeviceIds.length}个有效IP:`, this.allDeviceIds)
} else {
console.warn('无有效设备IP,停止轮询')
this.newAlarm = []
}
},
mounted() {
this.iframeWin = this.$refs.myIframe?.contentWindow
// if (this.allDeviceIds.length > 0) {
// this.dataTimer = setInterval(async() => {
// const currentIp = this.getNextIp()
// this.currentDeviceName = this.ipToNameMap[currentIp] || ''
// await this.getRealTimeData(currentIp)
// }, 10000)
// console.log(`启动IP轮询,共${this.allDeviceIds.length}个有效IP:`, this.allDeviceIds)
// }
// 原有echarts刷新逻辑
this.echartsTimer = setInterval(() => {
this.lendData = []
this.typeData = []
this.getBorrowerNumSta()
}, this.refreshtime)
},
beforeDestroy() {
// 清理所有定时器
if (this.timer) clearInterval(this.timer)
if (this.echartsTimer) clearInterval(this.echartsTimer)
if (this.dataTimer) {
clearInterval(this.dataTimer)
console.log('停止IP轮询')
}
},
methods: {
/**
* 处理设备IP列表(去重+排除+名称映射)
*/
handleDeviceIpList() {
const ipSet = new Set()
this.ipToNameMap = {}
this.allDisplayConfigData.forEach(element => {
const ip = (element.IP || '').trim()
if (ip && !this.excludeIpList.includes(ip)) {
ipSet.add(ip)
this.ipToNameMap[ip] = element.Name || `未知设备(${ip})`
console.log('有效设备IP:', ip, '设备名称:', element.Name)
} else if (this.excludeIpList.includes(ip)) {
console.log('排除指定IP:', ip, '设备名称:', element.Name)
} else if (!ip) {
console.log('过滤空IP:', element.Name)
}
})
this.allDeviceIds = Array.from(ipSet)
},
/**
* 获取下一个轮询IP(循环切换)
*/
getNextIp() {
if (this.allDeviceIds.length === 0) return ''
// 先重置索引(防止数组长度变化导致越界)
this.currentIpIndex = this.currentIpIndex % this.allDeviceIds.length
const ip = this.allDeviceIds[this.currentIpIndex]
this.currentIpIndex = (this.currentIpIndex + 1) % this.allDeviceIds.length
console.log(`轮询切换 - 当前IP:${ip},下一个索引:${this.currentIpIndex}`)
return ip
},
/**
* 请求指定IP的实时数据(过滤+AQI计算)
*/
async getRealTimeData(targetIp) {
if (!targetIp) {
this.newAlarm = []
this.currentDeviceName = ''
return
}
try {
console.log(`开始请求IP【${targetIp}】的实时数据(模拟)`)
// 模拟
// const data = await mockFetchDataForIP({ ip: targetIp })
// 真实请求
const data = await alarmApi.FetchDataForIP({ ip: targetIp })
// 过滤需要的指标
const filteredData = data.filter(item =>
this.keepIndicators.includes(item.subName)
)
if (filteredData.length > 0) {
this.newAlarm = filteredData
console.log(`IP【${targetIp}】(${this.currentDeviceName}) 过滤后的数据:`, filteredData)
this.calcAQI(filteredData)
} else {
this.newAlarm = []
this.aqiValue = 45
this.aqiStatus = '健康'
console.log(`IP【${targetIp}】(${this.currentDeviceName}) 无需要的指标数据`)
}
} catch (error) {
this.newAlarm = []
this.aqiValue = 45
this.aqiStatus = '健康'
this.currentDeviceName = ''
console.error(`IP【${targetIp}】数据请求失败:`, error)
}
},
getIframeLoading(value) {},
// 原有切换房间方法(同步更新数据)
async changeRoomGetDeivce() {
this.allDeviceIds = []
this.allDisplayConfigData = await displayConfigApi.list({ storeroomId: this.roomId })
this.handleDeviceIpList() // 重新处理IP列表
console.log('allDeviceIds2', this.allDeviceIds)
this.displayConfigData = this.allDisplayConfigData.filter((item) => {
return item.isDisplay && item.bindState && item.deviceInfo && (item.divPosition.includes('OAO') || item.divPosition.includes('TOP') || item.divPosition.includes('LS'))
})
console.log('displayConfigData2', this.displayConfigData)
// 重新加载数据
if (this.allDeviceIds.length > 0) {
this.currentDeviceName = this.ipToNameMap[this.allDeviceIds[0]] || ''
await this.getRealTimeData(this.allDeviceIds[0])
} else {
this.newAlarm = []
this.currentDeviceName = ''
}
this.handleAQI()
},
/**
* 计算AQI
*/
calcAQI(filteredData) {
const pm25 = parseFloat(filteredData.find(item => item.subName === 'PM2.5浓度')?.value || 0)
const pm10 = parseFloat(filteredData.find(item => item.subName === 'PM10浓度')?.value || 0)
const formaldehyde = parseFloat(filteredData.find(item => item.subName === '甲醛')?.value || 0)
const co2 = parseFloat(filteredData.find(item => item.subName === '二氧化碳')?.value || 0)
let aqi = 0
if (pm25 > 50 || pm10 > 100 || formaldehyde > 30 || co2 > 1000) {
aqi = Math.floor(Math.random() * 50) + 100
this.aqiStatus = '污染'
} else if (pm25 > 25 || pm10 > 50 || formaldehyde > 10 || co2 > 800) {
aqi = Math.floor(Math.random() * 50) + 50
this.aqiStatus = '一般'
} else {
aqi = Math.floor(Math.random() * 50) + 1
this.aqiStatus = '健康'
}
this.aqiValue = aqi
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
@import "~@/assets/styles/lend-manage.scss";
.env-container {
width: 100%;
height: calc(100vh);
background-color: #031435;
.env-top-title {
width: calc(100vw);
height: 130px;
background: url("~@/assets/images/largeScreen/top-title.png") no-repeat 0 -14px;
background-size: contain;
}
.current-date {
position: fixed;
top: 25px;
right: 150px;
font-size: 16px;
color: #3a99fd;
}
.env-main {
display: flex;
justify-content: space-between;
padding: 0 25px;
// margin-top: -12px;
.env-main-left,
.env-main-right {
max-width: 24%;
flex: 1;
height: calc(100vh - 138px);
overflow: hidden;
}
.env-main-middle {
position: relative;
flex: 1;
margin: 0 20px;
height: calc(100vh - 138px);
overflow: hidden;
}
.env-main-left .container-wrap {
min-height: auto;
}
.env-main-right .container-wrap {
height: calc(100% / 2 - 14px);
min-height: auto;
}
.env-item {
margin-bottom: 20px;
text-align: center;
h3 {
position: relative;
display: inline-block;
padding: 10px 70px;
font-size: 16px;
color: #fff;
.iconfont {
margin-right: 10px;
font-size: 14px;
color: #f65163;
}
&::before {
content: "";
position: absolute;
left: 0;
top: 50%;
width: 36px;
height: 12px;
margin-top: -6px;
background: url("~@/assets/images/largeScreen/item-left.png") no-repeat;
background-size: cover;
}
&::after {
content: "";
position: absolute;
top: 50%;
right: 0;
width: 36px;
height: 12px;
margin-top: -6px;
background: url("~@/assets/images/largeScreen/item-right.png") no-repeat;
background-size: cover;
}
}
.msg-list {
flex-wrap: wrap !important;
padding: 0 20px;
li {
margin-bottom: 20px;
display: flex;
align-items: center;
.msg-list-svg {
font-size: 40px;
margin-left: 20px;
}
.msg-txt {
margin-left: 15px;
text-align: left;
.msg-list-num {
font-size: 24px;
color: #fff;
font-weight: 600;
}
.msg-list-unit {
font-size: 14px;
color: #ccc;
margin: 5px 0 0 0;
}
}
}
}
.empty-tip {
font-size: 14px;
color: #999;
padding: 40px 0;
}
}
.screen-env-list {
flex-wrap: wrap;
// justify-content: space-between;
justify-content: flex-start;
height: calc(100% - 54px);
padding: 0 10px;
li {
flex: none;
width: calc(100% / 2 - 22px);
margin: 20px 10px;
height: calc(100% / 4 - 40px);
.msg-list-svg {
font-size: 40px;
margin-left: 20px;
}
&.msg-pm {
.msg-list-svg {
font-size: 46px;
}
}
}
}
.env-3d {
position: relative;
width: 100%;
// height: calc(100% + 80px);
height: calc(100% - 80px);
background: url("~@/assets/images/largeScreen/bg.png") no-repeat center -130px;
overflow: hidden;
margin-top: -80px;
.iframe_box {
width: 100%;
height: 100%;
}
.screen-env-list {
position: absolute;
right: 0;
bottom: 0;
flex-wrap: wrap;
justify-content: space-between;
padding: 0;
width: 165px;
height: 200px;
z-index: 99999;
li {
width: 100%;
margin: 20px 0 0 0;
height: calc(100% / 2 - 20px);
}
}
}
}
}
.banner-top-name{
position: absolute;
left: 0;
top: 80px;
padding: 0 15px;
height: 34px;
line-height: 32px;
font-size: 18px;
color: #fff;
background-color: #113d72;
border: 1px solid #339cff;
border-radius: 4px;
}
.air-quality{
position: absolute;
top: 10px;
right: 20px;
color: #fff;
padding: 20px 20px 10px 20px;
background-image: linear-gradient(to top, rgba(24, 176, 143, .5), rgba(24, 176, 143, 0));
border-radius: 5px;
z-index: 9999;
h3{
padding: 30px 0;
}
.air-params{
display: flex;
justify-content: space-between;
align-items: last baseline;
.air-left{
.air-title{
position: relative;
padding-left: 12px;
font-size: 14px;
&::before{
content: "";
position: absolute;
left: 0;
top: 50%;
width: 6px;
height: 6px;
background-color: #18B08F;
border-radius: 50%;
}
}
.air-result{
display: flex;
justify-content: space-between;
align-items: last baseline;
padding-top: 10px;
p{
font-size: 30px;
font-weight: 600;
padding: 0 6px 0 10px;
}
span{
display: block;
font-size: 12px;
opacity: .6;
}
}
}
.air-right{
text-align: center;
span{
display: block;
font-size: 12px;
}
p{
font-size: 18px;
font-weight: 600;
padding: 10px 30px;
margin-top: 10px;
background-color: rgba(24, 176, 143, .2);
border-radius: 5px;
}
}
}
}
.air-warn{
background-image: linear-gradient(to top, rgba(246, 81, 99, .5), rgba(24, 176, 143, 0));
.air-params{
.air-right{
p{
background-color: rgba(246, 81, 99, .2);
}
}
}
}
.middle-bottom {
width: 100%;
position: relative;
padding: 0 !important;
background-color: #021941;
border: 1px solid #113d72;
color: #339cff;
font-size: 14px;
&::before,
&::after{
content: "";
position: absolute;
width: 17px;
height: 17px;
z-index: 99;
}
&::before{
top: -1px;
left: -1px;
border-top: 1px solid #339CFF;
border-left: 1px solid #339CFF;
}
&::after{
right: -1px;
bottom: -1px;
border-right: 1px solid #339CFF;
border-bottom: 1px solid #339CFF;
}
}
.leakage-list {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
padding: 14px 0 0 14px;
height: auto;
text-align: left;
&::-webkit-scrollbar {
width: 4px;
height: 4px;
}
&::-webkit-scrollbar-thumb {
background-color: #339cff;
border-radius: 2px;
}
&::-webkit-scrollbar-track {
background-color: #021941;
}
li {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
// width: calc( 100% / 3 - 14px);
// height: calc(100% / 3 - 14px);
width: auto;
height: auto;
// margin: 0 14px 14px 0;
padding: 0 30px;
border: 1px solid #3581cc;
background-color: #02255f;
border-radius: 2px;
&::before {
content: "";
position: absolute;
top: 4px;
left: 4px;
width: 0;
height: 0;
border-color: transparent #339cff;
border-width: 0 0 6px 6px;
border-style: solid;
}
p {
i {
margin-right: 8px;
font-size: 20px;
}
}
span.leakage-state-tip {
position: relative;
display: block;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #18b08f;
&::before {
content: "";
position: absolute;
left: 50%;
top: 50%;
width: 14px;
height: 14px;
border-radius: 50%;
box-shadow: inset 0px 0px 10px 1px #18b08f;
transform: translate(-50%, -50%);
}
}
&.leakage-warn {
border-color: #f65164;
box-shadow: inset 0px 0px 15px 1px #f65164;
color: #f65164;
&::before {
border-color: transparent #f65164;
}
span.leakage-state-tip {
background-color: #f65164;
&::before {
box-shadow: inset 0px 0px 10px 1px #f65164;
}
}
}
}
}
</style>