Browse Source

新版大屏

master
xuhuajiao 1 day ago
parent
commit
245a4e8d30
  1. 31
      public/web3D/index.js
  2. 8
      src/assets/icons/svg/jiankong.svg
  3. 4
      src/views/components/AccessDoor.vue
  4. 787
      src/views/environmentalScreen/index copy.vue
  5. 27
      src/views/environmentalScreen/index.js
  6. 782
      src/views/environmentalScreen/index.vue
  7. 749
      src/views/home111.vue

31
public/web3D/index.js

@ -28,6 +28,8 @@ var targetAngles = {
var camera
BABYLON.DefaultLoadingScreen.prototype.displayLoadingUI = function() {
if (document.getElementById('customLoadingScreenDiv')) {
document.getElementById('customLoadingScreenDiv').style.display = 'initial'
@ -243,6 +245,32 @@ BABYLON.DracoCompression.Configuration.decoder.wasmUrl = './js/draco_wasm_wrappe
BABYLON.DracoCompression.Configuration.decoder.wasmBinaryUrl = './js/draco_decoder_gltf.wasm'
BABYLON.DracoCompression.Configuration.decoder.fallbackUrl = './js/draco_decoder_gltf.js'
// 3D模型页面(web3D/index.html)的createScene函数后添加适配逻辑
var canvas = document.getElementById('renderCanvas')
var engine = new BABYLON.Engine(canvas, true, { stencil: true })
// ========== 新增:画布自适应iframe尺寸 ==========
function resizeCanvas() {
// 获取iframe的容器尺寸(如果iframe有父容器,也可以取父容器尺寸)
const iframeWidth = window.innerWidth || document.documentElement.clientWidth
const iframeHeight = window.innerHeight || document.documentElement.clientHeight
// 设置canvas尺寸为iframe的100%(保证填满iframe,且跟随iframe缩放)
canvas.width = iframeWidth
canvas.height = iframeHeight
// 通知引擎更新尺寸
engine.resize()
}
// 初始化时执行一次
resizeCanvas()
// 监听窗口大小变化(iframe缩放时触发)
window.addEventListener('resize', resizeCanvas)
// createScene function that creates and return the scene
var createScene = function() {
engine.displayLoadingUI()
@ -256,13 +284,14 @@ var createScene = function() {
camera.setTarget(new BABYLON.Vector3(0, 0, 0))
camera.attachControl(canvas, true)
// 相机参数(保持原优化)
camera.lowerRadiusLimit = 0.1
camera.upperRadiusLimit = 500
camera.inertia = 0.1
camera.minZ = 0.01
camera.maxZ = 1000
camera.fov = 0.8
camera.fov = 1.0
camera.useAutoRotationBehavior = isRotating // 初始启用自动旋转

8
src/assets/icons/svg/jiankong.svg

@ -0,0 +1,8 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1765795623113" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9168" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256">
<style type="text/css">
.st0{fill:#F65163;} /* 和上一个图标一致的红色 */
</style>
<path d="M637.44 816.64C716.8 846.08 766.72 898.56 766.72 960H247.04c0-61.44 53.76-115.2 135.68-143.36C232.96 762.88 125.44 619.52 125.44 450.56 125.44 235.52 296.96 64 512 64s386.56 171.52 386.56 386.56c0 168.96-107.52 312.32-261.12 366.08zM519.68 332.8c-111.36 0-204.8 93.44-204.8 204.8s93.44 204.8 204.8 204.8c115.2 0 204.8-93.44 204.8-204.8s-90.88-204.8-204.8-204.8z m0 314.88c-53.76 0-99.84-43.52-99.84-99.84 0-53.76 46.08-99.84 99.84-99.84s99.84 43.52 99.84 99.84c0 53.76-43.52 99.84-99.84 99.84z" class="st0" p-id="9169"></path>
</svg>

4
src/views/components/AccessDoor.vue

@ -13,7 +13,7 @@
<div>{{ scope.row.time | parseTime }}</div>
</template>
</el-table-column>
<el-table-column label="姓名" :show-overflow-tooltip="true" min-width="60">
<el-table-column label="姓名" :show-overflow-tooltip="true" min-width="40">
<template slot-scope="scope">
<div>{{ scope.row.name }}</div>
</template>
@ -126,7 +126,7 @@ export default {
}
},
getAccessdoor() {
FetchInitHikDoorLog({ page: 0, size: 30 }).then((data) => {
FetchInitHikDoorLog({ page: 0, size: 5 }).then((data) => {
if (data.content && data.content.length > 0) {
this.tableData.splice(0, data.content.length, ...data.content)
}

787
src/views/environmentalScreen/index copy.vue

@ -0,0 +1,787 @@
<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 32
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>

27
src/views/environmentalScreen/index.js

@ -23,6 +23,33 @@ export const mockIpData = {
'dw': '℃',
'SUBID': 2,
'TIME': '2025-12-11 13:22:31'
},
{
'IP': '192.168.99.101:6003',
'subName': '红外',
'WYID': '192.168.99.101:6003_9',
'value': 'ON',
'dw': '',
'SUBID': 9,
'TIME': '2025-12-11 13:25:55'
},
{
'IP': '192.168.99.101:6003',
'subName': '消防',
'WYID': '192.168.99.101:6003_10',
'value': 'OFF',
'dw': '',
'SUBID': 10,
'TIME': '2025-12-11 13:25:55'
},
{
'IP': '192.168.99.101:6003',
'subName': '漏水',
'WYID': '192.168.99.101:6003_11',
'value': 'OFF',
'dw': '',
'SUBID': 11,
'TIME': '2025-12-11 13:25:55'
}
// {
// 'IP': '192.168.99.101:5003',

782
src/views/environmentalScreen/index.vue
File diff suppressed because it is too large
View File

749
src/views/home111.vue

@ -0,0 +1,749 @@
<template>
<div class="dashboard-container">
<div class="dashboard-editor-container">
<panel-group />
<el-row :gutter="20" style="margin-bottom:20px;height: 152px;">
<el-col :xs="24" :sm="24" :lg="8">
<!-- search-area -->
<div class="container-left" style="height: 100%;margin: 0; position: relative;">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3 class=" table-title" style="margin-bottom: 26px;">
<p class="title-arrow">
档案检索
</p>
</h3>
<SearchAcrives :is-home-search="isHomeSearch" />
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="container-left" style="height: 100%;margin: 0; position: relative;">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3 class=" table-title">
<p class="title-arrow">
<i class="iconfont icon-kongqizhiliangshuju" />环境数据
</p>
</h3>
<div class="home-floor-tab">
<p :class="{ 'active-floor': floorEnvIndex == 0 }" @click="changeFloorEnvTab(0)">3楼</p>
</div>
<el-carousel ref="carouselEnvRef" trigger="click" :interval="10000" indicator-position="none" height="110px" arrow="never" @change="handleEnvChange">
<el-carousel-item>
<div class="warehouse-tab" style="display: flex; justify-content: center; align-items: center; height: calc(100%); ">
<div class="five-bottom">
<!-- <p class="env-title">档案库</p> -->
<!-- <ul class="leakage-list">
<li :class="{ 'leakage-warn': topDisplayData.DAK_DIV_TOP_001.curstatus > 0 }">
<p>温度</p>
<span>{{ topDisplayData.DAK_DIV_TOP_001.curValue }}</span>
</li>
<li :class="{ 'leakage-warn': topDisplayData.DAK_DIV_TOP_002.curstatus > 0 }">
<p>湿度</p>
<span>{{ topDisplayData.DAK_DIV_TOP_002.curValue }}</span>
</li>
<li :class="{ 'leakage-warn': topDisplayData.DAK_DIV_TOP_003.curstatus > 0 }">
<p>CO2</p>
<span>{{ topDisplayData.DAK_DIV_TOP_003.curValue }}</span>
</li>
<li :class="{ 'leakage-warn': topDisplayData.DAK_DIV_TOP_005.curstatus > 0 }">
<p>PM2.5</p>
<span>{{ topDisplayData.DAK_DIV_TOP_005.curValue }}</span>
</li>
<li :class="{ 'leakage-warn': topDisplayData.DAK_DIV_TOP_006.curstatus > 0 }">
<p>PM10</p>
<span>{{ topDisplayData.DAK_DIV_TOP_006.curValue }}</span>
</li>
<li :class="{ 'leakage-warn': topDisplayData.DAK_DIV_TOP_004.curstatus > 0 }">
<p>TVOC</p>
<span>{{ topDisplayData.DAK_DIV_TOP_004.curValue }}</span>
</li>
</ul> -->
<ul v-if="newAlarm && newAlarm.length !== 0" class="leakage-list">
<li v-for="item in newAlarm" :key="item.SUBID">
<div class="msg-txt">
<p>{{ item.subName }}</p>
<span>{{ item.value }}</span>
</div>
</li>
</ul>
</div>
</div>
</el-carousel-item>
</el-carousel>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<div class="container-left" style="height: 100%;margin: 0; position: relative;">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3 class=" table-title">
<p class="title-arrow">
<svg-icon icon-class="a-3Dkufang" class-name="warehouse-svg" />3D库房
</p>
</h3>
<div class="home-floor-tab">
<p :class="{ 'active-floor': floorIndex == 0 }" @click="changeFloorTab(0)">3楼</p>
<!-- <p :class="{ 'active-floor': floorIndex == 1 }" @click="changeFloorTab(1)">7楼</p> -->
</div>
<el-carousel ref="carouselRef" trigger="click" :interval="4000" indicator-position="none" height="110px" arrow="never" @change="handleChange">
<el-carousel-item>
<div class="warehouse-tab">
<ul class="warehouse-nav">
<li @click="changeActiveTab(1,0)">
<span />
<p>全景图</p>
</li>
</ul>
</div>
</el-carousel-item>
</el-carousel>
</div>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-bottom:20px;height: calc(50vh - 251px);">
<el-col :xs="24" :sm="24" :lg="8">
<!-- 待办事项 -->
<div class="container-wrap">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3 class="table-title">
<p class="title-arrow">
<svg-icon icon-class="tongzhigonggao" class-name="warehouse-svg" />待办事项
</p>
</h3>
<div style="height: calc(100% - 40px);overflow-y: auto;overflow-x: hidden;">
<ul v-if="waitBorrowerData.length !== 0" class="todo-list">
<li v-for="(item,index) in waitBorrowerData" :key="index" :class="item.title.includes('警告') || item.title.includes('逾期') ? 'warn-info' :''" @click="handleToWaiting(item)">
<el-tooltip class="item" effect="dark" :content="item.title" :enterable="false" placement="top">
<p>{{ item.title }}</p>
</el-tooltip>
<span>{{ item.update_time | parseTime }}</span>
</li>
</ul>
<div v-else class="empty-main" style="height: 100%;">
<svg-icon icon-class="empty" class-name="empty-img" />
<p>暂无数据</p>
</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<!-- 门禁记录 -->
<!-- <security-door :height="'calc(100% - 40px)'" /> -->
<AccessDoor :height="'calc(100% - 40px)'" />
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<!-- 报警记录 -->
<warehouse-warning :height="'calc(100% - 40px)'" />
</el-col>
</el-row>
<el-row :gutter="20" style="height: calc(50vh - 251px);">
<el-col :xs="24" :sm="24" :lg="8">
<!-- 档案借阅 -->
<div class="container-wrap">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3 class="table-title">
<p class="title-arrow">档案借阅</p>
</h3>
<div class="chart-wrapper">
<lend-across :lend-data="lendData" />
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<!-- 档案类别 -->
<div class="container-wrap">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3 class="table-title">
<p class="title-arrow">档案类别</p>
</h3>
<div class="chart-wrapper">
<cate-pie :cate-data="cateData" />
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :lg="8">
<!-- 档案类型 -->
<div class="container-wrap">
<span class="right-top-line" />
<span class="left-bottom-line" />
<h3 class="table-title">
<p class="title-arrow">档案类型</p>
</h3>
<div v-if="typeData.length !== 0" class="chart-wrapper">
<type-pie :type-data="typeData" />
</div>
<div v-else class="empty-main">
<svg-icon icon-class="empty" class-name="empty-img" />
<p>暂无数据</p>
</div>
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import PanelGroup from './dashboard/PanelGroup'
import lendAcross from '@/views/components/echarts/lendAcross.vue'
import catePie from '@/views/components/echarts/catePie.vue'
import typePie from '@/views/components/echarts/typePie.vue'
import WarehouseWarning from '@/views/components/WarehouseWarning'
// import SecurityDoor from '@/views/components/SecurityDoor'
import AccessDoor from '@/views/components/AccessDoor'
import SearchAcrives from '@/views/archivesManage/archivesSearch/index'
import { statisticsCrud } from '@/views/system/archiveStatistics/mixins/statistics'
import { FetchWaitBorrower } from '@/api/archivesManage/lendManage'
// import displayConfigApi from '@/api/storeManage/displayConfig'
// import thirdApi from '@/api/thirdApi'
import alarmApi from '@/api/home/alarm'
import { allDeviceData, mockIpData } from '@/views/environmentalScreen/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)
// })
// }
import { mapGetters } from 'vuex'
export default {
name: 'Dashboard',
components: {
WarehouseWarning,
// SecurityDoor,
AccessDoor,
PanelGroup,
lendAcross,
catePie,
typePie,
SearchAcrives
},
mixins: [statisticsCrud],
data() {
return {
waitBorrowerData: [],
floorIndex: 0,
floorEnvIndex: 0,
isHomeSearch: false,
roomId: null,
allDisplayConfigData: [],
displayConfigData: [],
url: '',
allDeviceIds: [],
oaoMessage: {
ZLS_MO_OAO_001: {
show: false,
wendu: '',
sidu: '',
alarmState: 0
},
YLS_MO_OAO_001: {
show: false,
wendu: '',
sidu: '',
alarmState: 0
}
},
topDisplayData: {
DAK_DIV_TOP_001: {
show: false,
curValue: '',
unit: '',
curstatus: 0
},
DAK_DIV_TOP_002: {
show: false,
curValue: '',
unit: '',
curstatus: 0
},
DAK_DIV_TOP_003: {
show: false,
curValue: '',
unit: '',
curstatus: 0
},
DAK_DIV_TOP_004: {
show: false,
curValue: '',
unit: '',
curstatus: 0
},
DAK_DIV_TOP_005: {
show: false,
curValue: '',
unit: '',
curstatus: 0
},
DAK_DIV_TOP_006: {
show: false,
curValue: '',
unit: '',
curstatus: 0
}
},
// FullView
newAlarm: [],
aqiValue: 45,
aqiStatus: '健康',
keepIndicators: [
'二氧化碳',
'甲醛',
'综合气体',
'PM2.5浓度',
'PM10浓度',
'温度',
'湿度',
'空气质量'
],
ipToNameMap: {}, // IP
currentDeviceName: '', //
currentIpIndex: 0, // IP
excludeIpList: ['192.168.99.101:6003'] // IP
}
},
computed: {
...mapGetters([
'roles'
])
},
async created() {
this.getWaitBorrower()
// console.log('allDeviceData:', allDeviceData)
// this.allDisplayConfigData = allDeviceData
// this.handleDeviceIpList()
//
await alarmApi.FetchYpGetSite().then((data) => {
// if (data && data.length > 0) {
// this.allDisplayConfigData = data
// // IP
// 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])
console.log('初始加载IP数据:', this.allDeviceIds[0])
} else {
console.warn('无有效设备IP,停止轮询')
this.newAlarm = []
}
},
mounted() {
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)
}
},
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 ''
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)
} 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)
}
},
handleChange(index) {
this.floorIndex = index
},
handleEnvChange(index) {
this.floorEnvIndex = index
},
changeFloorTab(index) {
this.floorIndex = index
const carousel = this.$refs.carouselRef
carousel.setActiveItem(index)
},
changeFloorEnvTab(index) {
this.floorEnvIndex = index
const carousel = this.$refs.carouselEnvRef
carousel.setActiveItem(index)
},
// handleSetLineChartData(type) {
// this.lineChartData = lineChartData[type]
// },
getWaitBorrower() {
FetchWaitBorrower().then(data => {
if (data) {
this.waitBorrowerData = data
}
})
},
changeActiveTab(floorId, roomId) {
if (this.roles.includes('admin') || this.roles.includes('warehouse3D')) {
this.$router.push({
name: 'warehouse3D',
params: {
floorId: floorId,
roomId: roomId
}
})
} else {
this.$message({
message: '当前账号没有权限',
type: 'warning'
})
}
},
handleToWaiting(item) {
if (this.roles.includes('admin') || this.roles.includes('lendManage:list')) {
// 0
// 1
// 2
// 3
let locationIndex = 0
if (item.title.includes('待借档案')) {
locationIndex = 0
} else if (item.title.includes('借出确认')) {
locationIndex = 1
} else if (item.title.includes('逾期警告')) {
locationIndex = 2
} else if (item.title.includes('即将到期')) {
locationIndex = 2
}
this.$router.push({
name: 'lendManage',
params: {
locationIndex: locationIndex
}
})
} else {
this.$message({
message: '当前账号没有权限',
type: 'warning'
})
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
@import "~@/assets/styles/lend-manage.scss";
.dashboard-editor-container {
padding: 20px;
background-color: #031435;
position: relative;
.chart-wrapper {
height: calc(100% - 40px);
}
}
@media (max-width: 1024px) {
.chart-wrapper {
padding: 8px;
}
}
.warehouse-tab {
color: #fff;
.warehouse-nav {
display: flex;
justify-content: space-around;
position: absolute;
bottom: 15px;
z-index: 11;
width: 100%;
padding: 0;
li {
display: flex;
flex-direction: column;
align-items: center;
flex-wrap: nowrap;
align-content: center;
justify-content: center;
height: 90px;
text-align: right;
font-size: 14px;
position: relative;
&:hover {
cursor: pointer;
}
span {
width: 72px;
height: 52px;
margin-bottom: 7px;
}
p {
text-align: left;
}
&:first-child span {
background: url("~@/assets/images/tab_fullview_logo.png") no-repeat;
}
&:nth-child(2) span {
background: url("~@/assets/images/tab_archives_logo.png") no-repeat;
}
&:nth-child(3) span {
background: url("~@/assets/images/tab_collate_logo.png") no-repeat;
}
&:nth-child(4) span {
background: url("~@/assets/images/tab_read_logo.png") no-repeat;
}
}
}
}
.el-col {
height: 100%;
}
.container-left,
.container-right,
.container-wrap,
.el-card,
.header-container-wrap {
min-height: 100%;
}
.warehose-el-table ::v-deep .el-table__header-wrapper {
box-shadow: inset 0px 0px 6px 1px #339cff;
background: none !important;
}
.container-wrap {
min-height: auto;
height: 100%;
// overflow: hidden;
}
.todo-list {
padding: 0 20px;
& li {
height: 42px;
line-height: 42px;
margin-bottom: 10px;
font-size: 13px;
color: #ffffff;
background: #02255f;
box-shadow: inset 0px 0px 6px 1px #339cff;
border-radius: 26px;
opacity: 1;
cursor: pointer;
&.warn-info{
color: #F65163;
box-shadow: inset 0px 0px 15px 1px #f65164;
}
& p {
display: inline-block;
width: calc(100% - 140px);
padding-left: 10px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
word-break: break-all;
}
& span {
float: right;
padding-right: 10px;
}
}
}
::v-deep
.el-table--striped
.el-table__body
tr.el-table__row--striped
td.el-table__cell {
background: #02255f;
}
.search-area {
width: 100%;
height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
}
::v-deep .search-main{
padding: 0 0 0 20px;
.head-container{
padding: 0;
width: 100% !important;
.search-input {
width: 100% !important;
}
.input-with-select{
width: 100% !important;
}
}
}
.home-floor-tab{
position: absolute;
right: 14px;
top: 10px;
color: #fff;
display: flex;
justify-self: flex-start;
p{
font-size: 14px;
padding:2px 6px;
margin-right: 6px;
color: #339cff;
border: 1px solid #339cff;
border-radius: 4px;
cursor: pointer;
&.active-floor,
&:hover{
color: #fff;
background-color: #113d72;
}
}
}
.icon-kongqizhiliangshuju{
font-size: 14px;
color: #F65163;
margin-right: 6px
}
.env-title{
width: 50px;
height: 40px;
line-height: 20px;
text-align: center;
font-size: 12px;
margin: 10px 10px 0 0;
}
.leakage-list {
display: flex;
justify-content:flex-start;
flex-wrap: wrap;
flex: 1;
text-align: left;
font-size: 10px;
padding-left: 10px;
li {
position: relative;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
align-items: center;
width: 72px;
height: 40px;
border: 1px solid #3581cc;
background-color: #02255f;
border-radius: 2px;
padding: 4px;
margin: 6px 10px 0 0;
font-size: 12px;
&::before {
content: "";
position: absolute;
top: 2px;
right: 2px;
width: 0;
height: 0;
border-color: transparent #339cff;
border-width: 0 4px 4px 0;
border-style: solid;
}
p {
width: 100%;
}
span{
width: 100%;
color: #18B08F;
text-align: right;
}
&.leakage-warn {
border-color: #f65164;
box-shadow: inset 0px 0px 15px 1px #f65164;
color: #f65164;
&::before {
border-color: transparent #f65164;
}
span {
color: #f65164;
}
}
}
}
.five-bottom{
display: flex;
justify-content: flex-start;
align-items: center;
.env-title{
line-height: 40px;
}
}
</style>
Loading…
Cancel
Save