|
|
<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>
|