黄陂项目
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.

1555 lines
48 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <template>
  2. <div class="env-container">
  3. <div class="env-top-title">
  4. <img src="@/assets/images/logo-2.png" alt="">
  5. <p>档案库房智能管理系统</p>
  6. </div>
  7. <div class="header-date">
  8. <div class="time">{{ nowDate.split(' ')[1] }}</div>
  9. <div class="time-other">
  10. <span>{{ currentWeek }}</span>
  11. <span>{{ nowDate.split(' ')[0] }}</span>
  12. </div>
  13. </div>
  14. <div class="env-main">
  15. <div class="env-main-left">
  16. <div class="env-item container-wrap" style="height: 130px !important; ">
  17. <span class="right-top-line" />
  18. <span class="left-bottom-line" />
  19. <h3>
  20. <svg-icon icon-class="danganjieyue" style="margin-right:10px" />档案借阅
  21. </h3>
  22. <div class="chart-wrapper">
  23. <lend-across :lend-data="lendData" :refreshtime="refreshtime" />
  24. </div>
  25. </div>
  26. <div class="env-item container-wrap left-wrap">
  27. <span class="right-top-line" />
  28. <span class="left-bottom-line" />
  29. <h3>
  30. <i class="iconfont icon-kongqizhiliangshuju" />环控数据
  31. </h3>
  32. <!-- <ul
  33. class="leakage-list"
  34. :style="{
  35. height: showScroll ? 'calc(100% - 40px)' : 'auto',
  36. overflow: showScroll ? 'auto' : 'hidden'
  37. }"
  38. >
  39. <li
  40. v-for="item in validDisplayConfigData"
  41. :key="item.id"
  42. :class="{ 'leakage-warn': item.NetStatus === 0 }"
  43. :style="{
  44. width: liWidth,
  45. height: liHeight,
  46. marginRight: '12px',
  47. marginBottom: '12px'
  48. }"
  49. >
  50. <p><i class="iconfont icon-shebei" />{{ item.Name }}</p>
  51. <span class="leakage-state-tip" />
  52. </li>
  53. </ul> -->
  54. <ul v-if="newAlarm && newAlarm.length !== 0" class="screen-env-list">
  55. <!-- <li :class="alarmStatus.infrared === '告警' ? 'li-warn alarm-status' : 'alarm-status'">
  56. <div class="msg-txt">
  57. <p class="msg-list-unit">红外 </p>
  58. <span class="msg-list-num">{{ alarmStatus.infrared }}</span>
  59. </div>
  60. </li>
  61. <li :class="alarmStatus.fire === '告警' ? 'li-warn alarm-status' : 'alarm-status'">
  62. <div class="msg-txt">
  63. <p class="msg-list-unit">消防 </p>
  64. <span class="msg-list-num">{{ alarmStatus.fire }}</span>
  65. </div>
  66. </li>
  67. <li :class="alarmStatus.waterLeak === '告警' ? 'li-warn alarm-status' : 'alarm-status'">
  68. <div class="msg-txt">
  69. <p class="msg-list-unit">漏水 </p>
  70. <span class="msg-list-num">{{ alarmStatus.waterLeak }}</span>
  71. </div>
  72. </li>-->
  73. <li>
  74. <svg-icon icon-class="pm25" class-name="msg-list-svg" />
  75. <div class="msg-txt">
  76. <span class="msg-list-num">{{ avgData.pm25 }}</span>
  77. <p class="msg-list-unit">PM2.5浓度 <br>{{ avgData.pm25Unit }}</p>
  78. </div>
  79. </li>
  80. <li>
  81. <svg-icon icon-class="pm10" class-name="msg-list-svg" />
  82. <div class="msg-txt">
  83. <span class="msg-list-num">{{ avgData.pm10 }}</span>
  84. <p class="msg-list-unit">PM10浓度 <br>{{ avgData.pm10Unit }}</p>
  85. </div>
  86. </li>
  87. <li>
  88. <svg-icon icon-class="voc" class-name="msg-list-svg" />
  89. <div class="msg-txt">
  90. <span class="msg-list-num">{{ avgData.tvoc }}</span>
  91. <p class="msg-list-unit">TVOC {{ avgData.tvocUnit }}</p>
  92. </div>
  93. </li>
  94. <li>
  95. <svg-icon icon-class="co2" class-name="msg-list-svg" />
  96. <div class="msg-txt">
  97. <span class="msg-list-num">{{ avgData.co2 }}</span>
  98. <p class="msg-list-unit">二氧化碳 {{ avgData.co2Unit }}</p>
  99. </div>
  100. </li>
  101. <li>
  102. <svg-icon icon-class="jiaquan" class-name="msg-list-svg" style="font-size: 68px;" />
  103. <div class="msg-txt">
  104. <span class="msg-list-num">{{ avgData.formaldehyde }}</span>
  105. <p class="msg-list-unit">甲醛 {{ avgData.formaldehydeUnit }}</p>
  106. </div>
  107. </li>
  108. </ul>
  109. </div>
  110. <div class="env-item container-wrap left-wrap">
  111. <span class="right-top-line" />
  112. <span class="left-bottom-line" />
  113. <h3>
  114. <i class="iconfont icon-kongqizhiliangshuju" />设备联调状态
  115. </h3>
  116. <div style="display: flex; justify-content: space-between; height: calc(100% - 40px);">
  117. <ul v-if="hasValidData" class="leakage-list">
  118. <li :class="alarmStatus.infrared === '告警' ? 'leakage-warn' : ''">
  119. <p><i class="iconfont icon-shebei" />红外</p>
  120. <span class="leakage-state-tip" />
  121. </li>
  122. <li :class="alarmStatus.fire === '告警' ? 'leakage-warn' : ''">
  123. <p><i class="iconfont icon-shebei" />消防</p>
  124. <span class="leakage-state-tip" />
  125. </li>
  126. <li :class="alarmStatus.waterLeak === '告警' ? 'leakage-warn' : ''">
  127. <p><i class="iconfont icon-shebei" />漏水</p>
  128. <span class="leakage-state-tip" />
  129. </li>
  130. </ul>
  131. <ul class="leakage-list">
  132. <li
  133. v-for="item in validDisplayConfigData.slice().sort((a, b) => {
  134. // 空值处理
  135. if (!a.Name) return 1;
  136. if (!b.Name) return -1;
  137. // 按长度升序,长度相同则按名称排序
  138. if (a.Name.length === b.Name.length) {
  139. return a.Name.localeCompare(b.Name, 'zh-CN', { numeric: true });
  140. }
  141. return a.Name.length - b.Name.length; // 升序;降序则 b.Name.length - a.Name.length
  142. })"
  143. :key="item.id"
  144. :class="{ 'leakage-warn': item.NetStatus === 0 }"
  145. >
  146. <p><i class="iconfont icon-shebei" />{{ item.Name }}</p>
  147. <span class="leakage-state-tip" />
  148. </li>
  149. </ul>
  150. </div>
  151. </div>
  152. </div>
  153. <div class="env-main-middle">
  154. <div class="env-3d">
  155. <iframe id="myIframe" ref="myIframe" name="iframeMap" class="iframe_box" src="/web3D/index.html" frameborder="0" scrolling="no" style=" margin: 0 auto; display: block;" />
  156. </div>
  157. <div class="env-alarm-container">
  158. <ul v-if="hasValidData" class="env-alarm-list env-alarm-list-first">
  159. <li>
  160. <svg-icon icon-class="temperature" class-name="msg-list-svg" />
  161. <div>
  162. <span>{{ avgData.temperature }} </span>
  163. <p>温度 {{ avgData.temperatureUnit }}</p>
  164. </div>
  165. </li>
  166. <li>
  167. <svg-icon icon-class="shidu" class-name="msg-list-svg" />
  168. <div>
  169. <span>{{ avgData.humidity }}</span>
  170. <p>湿度 {{ avgData.humidityUnit }}</p>
  171. </div>
  172. </li>
  173. </ul>
  174. <div
  175. v-if="hasValidData"
  176. class="air-quality"
  177. :class="[
  178. aqiStatus === '优' ? 'air-excellent' : '',
  179. aqiStatus === '良' ? 'air-good' : '',
  180. aqiStatus === '轻度污染' ? 'air-lightPollution' : '',
  181. aqiStatus === '中度污染' ? 'air-mediumPollution' : '',
  182. aqiStatus === '重度污染' ? 'air-heavyPollution' : '',
  183. aqiStatus === '严重污染' ? 'air-severePollution' : ''
  184. ]"
  185. >
  186. <h3>空气质量指数</h3>
  187. <div class="air-params">
  188. <div class="air-left">
  189. <span class="air-title">实时AQI</span>
  190. <div class="air-result">{{ aqiValue }}</div>
  191. </div>
  192. <div class="air-right">
  193. <span>空气质量</span>
  194. <!-- <p>{{ aqiStatus }}</p> -->
  195. <p class="air-status-text" v-html="formatAqiStatus" />
  196. </div>
  197. </div>
  198. </div>
  199. <!-- <el-row :gutter="10" class="panel-group" type="flex" justify="space-between">
  200. <el-col class="card-panel-col">
  201. <div class="card-panel zaixianshebei">
  202. <div class="card-panel-icon-wrapper icon-shopping">
  203. <svg-icon icon-class="zaixianshebei" class-name="card-panel-icon" />
  204. </div>
  205. <div class="card-panel-description">
  206. <div class="card-panel-text">
  207. <count-to v-if="getDeviceFlag" :start-val="0" :end-val="onlineDeviceNum" :duration="3200" class="card-panel-num" />
  208. <div v-if="!getDeviceFlag" class="card-panel-text"><span class="card-panel-num">获取中...</span></div>
  209. </div>
  210. 在线设备
  211. </div>
  212. </div>
  213. </el-col>
  214. <el-col class="card-panel-col">
  215. <div class="card-panel lixianshebei">
  216. <div class="card-panel-icon-wrapper icon-shopping">
  217. <svg-icon icon-class="lixianshebei" class-name="card-panel-icon" />
  218. </div>
  219. <div class="card-panel-description">
  220. <div class="card-panel-text">
  221. <count-to v-if="getDeviceFlag" :start-val="0" :end-val="offlineDeviceNum" :duration="3200" class="card-panel-num" />
  222. <div v-if="!getDeviceFlag" class="card-panel-text"><span class="card-panel-num">获取中...</span></div>
  223. </div>
  224. 离线设备
  225. </div>
  226. </div>
  227. </el-col>
  228. </el-row> -->
  229. <ul class="env-alarm-list env-alarm-list-first">
  230. <li>
  231. <svg-icon icon-class="zaixian" class-name="msg-list-svg" />
  232. <div>
  233. <!-- <span>{{ avgData.temperature }} </span> -->
  234. <count-to v-if="getDeviceFlag" :start-val="0" :end-val="onlineDeviceNum" :duration="3200" class="card-panel-num" />
  235. <div v-if="!getDeviceFlag" class="card-panel-text"><span class="card-panel-num">获取中...</span></div>
  236. <p>在线设备</p>
  237. </div>
  238. </li>
  239. <li>
  240. <svg-icon icon-class="lixian" class-name="msg-list-svg" />
  241. <div>
  242. <!-- <span>{{ avgData.humidity }}</span> -->
  243. <count-to v-if="getDeviceFlag" :start-val="0" :end-val="offlineDeviceNum" :duration="3200" class="card-panel-num" />
  244. <div v-if="!getDeviceFlag" class="card-panel-text"><span class="card-panel-num">获取中...</span></div>
  245. <p>离线设备</p>
  246. </div>
  247. </li>
  248. </ul>
  249. </div>
  250. </div>
  251. <div class="env-main-right">
  252. <!-- 环控实时报警 -->
  253. <warehouse-warning :height="'calc(100% - 38px)'" />
  254. <!-- 门禁出入记录 -->
  255. <AccessDoor :height="'calc(100% - 40px)'" size="4" />
  256. <!-- 当日告警统计 -->
  257. <div class="env-item container-wrap">
  258. <span class="right-top-line" />
  259. <span class="left-bottom-line" />
  260. <h3>
  261. <svg-icon icon-class="alerm" class-name="warehouse-svg" />当日告警统计
  262. </h3>
  263. <div class="chart-wrapper" style="height: calc(100% - 40px);">
  264. <catePie :cate-data="alarmChartData" :refreshtime="refreshtime" />
  265. </div>
  266. </div>
  267. </div>
  268. </div>
  269. </div>
  270. </template>
  271. <script>
  272. import CountTo from 'vue-count-to'
  273. import { getCurrentTime } from '@/utils/index'
  274. import lendAcross from '@/views/components/echarts/lendAcross.vue'
  275. import catePie from './module/catePie.vue'
  276. import WarehouseWarning from '@/views/components/WarehouseWarning'
  277. import AccessDoor from '@/views/components/AccessDoor'
  278. import { statisticsCrud } from '@/views/system/archiveStatistics/mixins/statistics'
  279. import displayConfigApi from '@/api/storeManage/displayConfig'
  280. import { getDeviceOnoff, getTodayHikAlarmLog } from '@/api/home/device'
  281. import alarmApi from '@/api/home/alarm'
  282. // import { allDeviceData, mockIpData } from './index.js'
  283. // const mockFetchDataForIP = (params) => {
  284. // return new Promise((resolve) => {
  285. // setTimeout(() => {
  286. // const ip = params.ip
  287. // const result = mockIpData[ip] || { code: 200, message: '操作成功', data: [], timestamp: Date.now() }
  288. // resolve(result.data)
  289. // }, 500)
  290. // })
  291. // }
  292. export default {
  293. name: 'EnvironmentalScreen',
  294. components: {
  295. CountTo,
  296. WarehouseWarning,
  297. AccessDoor,
  298. lendAcross,
  299. catePie
  300. },
  301. mixins: [statisticsCrud],
  302. data() {
  303. return {
  304. bannerRoomName: '3F 全景图',
  305. nowDate: '',
  306. currentWeek: this.getCurrentWeek(),
  307. timer: null,
  308. echartsTimer: null,
  309. roomId: 'D6490DA3D4261E8C26D0E3',
  310. allDisplayConfigData: [],
  311. displayConfigData: [],
  312. url: '',
  313. allDeviceIds: [], // 所有设备IP(无排除)
  314. refreshtime: 60000,
  315. lendData: [],
  316. typeData: [],
  317. newAlarm: [], // 所有设备的原始数据
  318. aqiValue: 45,
  319. aqiStatus: '健康',
  320. // 需展示的指标列表(包含所有要计算的指标)
  321. keepIndicators: [
  322. '二氧化碳',
  323. 'CO2浓度',
  324. '甲醛',
  325. '综合气体',
  326. 'PM2.5浓度',
  327. 'PM10浓度',
  328. '温度',
  329. '湿度',
  330. '空气质量',
  331. 'TVOC',
  332. '红外',
  333. '消防',
  334. '漏水'
  335. ],
  336. iframeWin: null,
  337. // 平均值数据(新增温湿度和单位字段)
  338. avgData: {
  339. temperature: '0.00', // 温度
  340. temperatureUnit: '', // 温度单位
  341. humidity: '0.00', // 湿度
  342. humidityUnit: '', // 湿度单位
  343. pm25: '0.00',
  344. pm25Unit: '', // PM2.5单位
  345. tvoc: '0.00',
  346. tvocUnit: '', // TVOC单位
  347. pm10: '0.00',
  348. pm10Unit: '', // PM10单位
  349. co2: '0.00',
  350. co2Unit: '', // 二氧化碳单位
  351. formaldehyde: '0.00',
  352. formaldehydeUnit: '' // 甲醛单位
  353. },
  354. // 告警状态(ON=告警,OFF=正常)
  355. alarmStatus: {
  356. infrared: '正常',
  357. fire: '正常',
  358. waterLeak: '正常'
  359. },
  360. aqiGradeStandard: [
  361. { aqiMin: 0, aqiMax: 50, level: '优', colorKey: 'excellent' },
  362. { aqiMin: 51, aqiMax: 100, level: '良', colorKey: 'good' },
  363. { aqiMin: 101, aqiMax: 150, level: '轻度污染', colorKey: 'lightPollution' },
  364. { aqiMin: 151, aqiMax: 200, level: '中度污染', colorKey: 'mediumPollution' },
  365. { aqiMin: 201, aqiMax: 300, level: '重度污染', colorKey: 'heavyPollution' },
  366. { aqiMin: 301, aqiMax: 500, level: '严重污染', colorKey: 'severePollution' }
  367. ],
  368. pollutantLimits: {
  369. pm25: { excellent: 35, good: 75, light: 115, medium: 150, heavy: 250, severe: 500 }, // μg/m³
  370. pm10: { excellent: 50, good: 150, light: 250, medium: 350, heavy: 420, severe: 600 } // μg/m³
  371. },
  372. hasValidData: false, // 是否有有效数据
  373. hkConfig: {
  374. 'username': 'admin',
  375. 'password': 'ftzn83560792',
  376. 'ip': '192.168.99.125', // 固定为目标IP
  377. 'port': '554'
  378. },
  379. webRtcServer: null,
  380. camera_ip: '127.0.0.1:9527',
  381. cameraList: [], // 摄像头列表
  382. targetDeviceIps: ['192.168.99.101:5003', '192.168.99.101:6003', '192.168.99.101:5004'],
  383. getDeviceFlag: false,
  384. totalDeviceNum: 0,
  385. onlineDeviceNum: 0,
  386. offlineDeviceNum: 0,
  387. alarmStatisticsRaw: [], // 原始告警统计数据
  388. alarmChartData: [], // 格式化后给饼图的数据源
  389. alarmRefreshTimer: null // 告警数据刷新定时器
  390. }
  391. },
  392. computed: {
  393. // 处理空气质量状态换行的计算属性
  394. formatAqiStatus() {
  395. const status = this.aqiStatus
  396. // 仅当文本长度为4时,在第2个字后插入<br/>换行
  397. if (status && status.length === 4) {
  398. return status.slice(0, 2) + '<br/>' + status.slice(2)
  399. }
  400. // 其他长度(2字/3字)直接返回原文本
  401. return status || ''
  402. },
  403. validDisplayConfigData() {
  404. return this.allDisplayConfigData.filter(item => {
  405. return item && item.Name && this.targetDeviceIps.includes(item.IP?.trim())
  406. })
  407. },
  408. itemsPerRow() {
  409. const len = this.validDisplayConfigData.length
  410. if (len === 0) return 0
  411. return len <= 2 ? len : 2
  412. },
  413. liWidth() {
  414. if (this.itemsPerRow === 0) return '0'
  415. return `calc(100% / ${this.itemsPerRow} - 12px)`
  416. },
  417. liHeight() {
  418. const len = this.validDisplayConfigData.length
  419. if (len === 0) return '0'
  420. const rows = Math.ceil(len / this.itemsPerRow)
  421. return `calc(100% / ${rows} - 12px)`
  422. },
  423. showScroll() {
  424. return this.validDisplayConfigData.length > 8
  425. }
  426. },
  427. async created() {
  428. // 时间更新定时器
  429. this.timer = setInterval(() => {
  430. this.nowDate = getCurrentTime()
  431. }, 1000)
  432. this.getDevice()
  433. this.getTodayHikAlarmLog()
  434. // 初始化
  435. window.getIframeLoading = this.getIframeLoading
  436. // this.allDisplayConfigData = allDeviceData
  437. // this.handleDeviceIpList()
  438. await alarmApi.FetchYpGetSite().then((data) => {
  439. if (data && data.length > 0) {
  440. this.allDisplayConfigData = data
  441. this.handleDeviceIpList()
  442. } else {
  443. this.allDisplayConfigData = []
  444. }
  445. })
  446. if (this.allDeviceIds.length > 0) {
  447. await this.getAllDevicesData()
  448. this.calcAllAvgData() // 计算所有指标平均值
  449. this.calcAQIByAvg() // 根据平均值计算AQI
  450. } else {
  451. console.warn('无设备IP数据')
  452. this.hasValidData = false
  453. }
  454. },
  455. mounted() {
  456. this.iframeWin = this.$refs.myIframe?.contentWindow
  457. // Echarts数据刷新定时器
  458. this.echartsTimer = setInterval(() => {
  459. this.lendData = []
  460. this.typeData = []
  461. this.alarmChartData = []
  462. this.getBorrowerNumSta()
  463. this.getTodayHikAlarmLog()
  464. }, this.refreshtime)
  465. // this.getVideoUrl()
  466. },
  467. beforeDestroy() {
  468. // 清理所有定时器
  469. if (this.timer) clearInterval(this.timer)
  470. if (this.echartsTimer) clearInterval(this.echartsTimer)
  471. // 销毁视频流
  472. if (this.webRtcServer) {
  473. this.webRtcServer.disconnect()
  474. this.webRtcServer = null
  475. }
  476. // if (this.alarmRefreshTimer) clearInterval(this.alarmRefreshTimer)
  477. },
  478. methods: {
  479. getTodayHikAlarmLog() {
  480. getTodayHikAlarmLog().then(data => {
  481. console.log('今日海康告警日志', data)
  482. this.alarmStatisticsRaw = this.transformAlarmData(data || {})
  483. this.formatAlarmChartData()
  484. }).catch(error => {
  485. console.error('获取告警统计数据失败:', error)
  486. this.alarmStatisticsRaw = []
  487. this.alarmChartData = []
  488. })
  489. },
  490. transformAlarmData(alarmObj) {
  491. const fieldMap = [
  492. { key: 'accessAlarm', name: '门禁告警' },
  493. { key: 'firefightingAlarm', name: '消防告警' },
  494. { key: 'equipmentAlarm', name: '设备告警' },
  495. { key: 'environmentAlarm', name: '环境告警' }
  496. ]
  497. return fieldMap.map(item => ({
  498. alarmValue: item.name,
  499. num: alarmObj[item.key] || 0
  500. }))
  501. },
  502. formatAlarmChartData() {
  503. // const allZero = this.alarmStatisticsRaw.every(item => item.num === 0)
  504. // if (allZero) {
  505. // this.alarmChartData = [{ name: '无告警', value: 1, itemStyle: { color: '#666666' }}]
  506. // return
  507. // }
  508. const alarmColorMap = {
  509. '门禁告警': '#F65164',
  510. '消防告警': '#FFB800',
  511. '设备告警': '#339CFF',
  512. '环境告警': '#1CADAB'
  513. }
  514. // 备用颜色列表(防止新增告警类型没有配置颜色)
  515. const colorList = ['#F65164', '#339CFF', '#FFB800', '#1CADAB', '#9B6BCC', '#FF7D00']
  516. this.alarmChartData = this.alarmStatisticsRaw.map((item, index) => {
  517. const alarmName = item.alarmValue
  518. // 优先使用配置的颜色,没有则使用备用颜色
  519. const color = alarmColorMap[alarmName] || colorList[index % colorList.length] || '#666666'
  520. return {
  521. name: alarmName,
  522. value: item.num || 0, // 保留0值
  523. itemStyle: {
  524. color: color
  525. },
  526. ...(item.num === 0 ? { itemStyle: { color: color, opacity: 0.5 }} : {})
  527. }
  528. })
  529. },
  530. getDevice() {
  531. getDeviceOnoff().then(data => {
  532. this.getDeviceFlag = true
  533. this.totalDeviceNum = data.deviceall.length
  534. this.onlineDeviceNum = data.online.length
  535. this.offlineDeviceNum = data.offline.length
  536. // this.onlineDevice = data.online
  537. // this.offDevice = data.offline
  538. })
  539. },
  540. getVideoUrl() {
  541. displayConfigApi.list({ storeroomId: '01A1DC2123C2B75E1A579D', isQueryAll: 1 }).then((res) => {
  542. console.log('摄像头列表', res)
  543. if (res && res.length > 0) {
  544. // 只筛选IP为192.168.99.125的摄像头
  545. this.cameraList = res.filter(item =>
  546. item.divPosition && item.divPosition.includes('cam') &&
  547. item.deviceInfo && item.deviceInfo.deviceIp === '192.168.99.125'
  548. )
  549. if (this.cameraList.length > 0) {
  550. const targetCamera = this.cameraList[0].deviceInfo
  551. this.hkConfig = {
  552. username: targetCamera.deviceAccount || 'admin',
  553. password: targetCamera.devicePassword || 'ftzn83560792',
  554. ip: targetCamera.deviceIp || '192.168.99.125',
  555. port: targetCamera.devicePort || '554'
  556. }
  557. this.$nextTick(() => {
  558. this.initVideo()
  559. })
  560. } else {
  561. this.$message({
  562. message: '未找到IP为192.168.99.125的摄像头配置',
  563. type: 'warning'
  564. })
  565. }
  566. } else {
  567. this.$message({
  568. message: '请先配置摄像头',
  569. type: 'error'
  570. })
  571. }
  572. }).catch(error => {
  573. console.error('获取摄像头列表失败', error)
  574. this.$message({
  575. message: '获取摄像头配置失败',
  576. type: 'error'
  577. })
  578. })
  579. },
  580. initVideo() {
  581. const linkSrc = process.env.NODE_ENV === 'production' ? window.g.ApiWebRtcServerUrl : process.env.VUE_APP_WEBRTCSTREAMER_API
  582. this.camera_ip = linkSrc
  583. console.log('hkConfig', this.hkConfig)
  584. // 先销毁已存在的视频流
  585. if (this.webRtcServer) {
  586. this.webRtcServer.disconnect()
  587. }
  588. // 初始化新的视频流
  589. // eslint-disable-next-line no-undef
  590. this.webRtcServer = new WebRtcStreamer('video', location.protocol + '//' + this.camera_ip)
  591. // 拼接RTSP地址
  592. const rtspUrl = `rtsp://${this.hkConfig.username}:${this.hkConfig.password}@${this.hkConfig.ip}:${this.hkConfig.port}/h264/1/1`
  593. console.log('RTSP地址:', rtspUrl)
  594. this.webRtcServer.connect(rtspUrl)
  595. },
  596. /**
  597. * 获取当前星期
  598. */
  599. getCurrentWeek() {
  600. const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
  601. const date = new Date()
  602. return days[date.getDay()]
  603. },
  604. /**
  605. * 处理设备IP列表仅去重无排除
  606. */
  607. handleDeviceIpList() {
  608. const ipSet = new Set()
  609. this.allDisplayConfigData.forEach(element => {
  610. const ip = (element.IP || '').trim()
  611. if (ip) { // 仅过滤空IP,无其他排除逻辑
  612. ipSet.add(ip)
  613. }
  614. })
  615. this.allDeviceIds = Array.from(ipSet)
  616. },
  617. /**
  618. * 获取所有设备的实时数据
  619. */
  620. async getAllDevicesData() {
  621. const allData = []
  622. for (const ip of this.allDeviceIds) {
  623. try {
  624. // const data = await mockFetchDataForIP({ ip })
  625. // 真实请求
  626. const data = await alarmApi.FetchDataForIP({ ip })
  627. // 过滤需要的指标并添加到总数据
  628. const filtered = data.filter(item => this.keepIndicators.includes(item.subName)).map(item => {
  629. if (item.subName === 'CO2浓度') {
  630. return { ...item, subName: '二氧化碳' }
  631. }
  632. return item
  633. })
  634. if (filtered.length > 0) {
  635. allData.push(...filtered)
  636. }
  637. } catch (error) {
  638. console.error(`获取IP【${ip}】数据失败:`, error)
  639. }
  640. }
  641. this.newAlarm = allData
  642. this.hasValidData = allData.length > 0
  643. },
  644. /**
  645. * 计算所有设备指标的平均值适配ON/OFF告警状态保留单位
  646. */
  647. calcAllAvgData() {
  648. if (!this.hasValidData) return
  649. // 1. 初始化各指标的总和、计数和单位
  650. const sumMap = {
  651. temperature: { sum: 0, count: 0, unit: '' }, // 温度
  652. humidity: { sum: 0, count: 0, unit: '' }, // 湿度
  653. pm25: { sum: 0, count: 0, unit: '' },
  654. tvoc: { sum: 0, count: 0, unit: '' },
  655. pm10: { sum: 0, count: 0, unit: '' },
  656. co2: { sum: 0, count: 0, unit: '' },
  657. formaldehyde: { sum: 0, count: 0, unit: '' },
  658. infrared: [], // 红外状态(ON/OFF)
  659. fire: [], // 消防状态(ON/OFF)
  660. waterLeak: [] // 漏水状态(ON/OFF)
  661. }
  662. // 2. 遍历所有数据累加,同时记录单位
  663. this.newAlarm.forEach(item => {
  664. const value = parseFloat(item.value) || 0
  665. // 提取单位(优先使用数据中的dw字段,无则用默认值)
  666. const unit = item.dw || this.getDefaultUnit(item.subName)
  667. switch (item.subName) {
  668. case '温度':
  669. sumMap.temperature.sum += value
  670. sumMap.temperature.count++
  671. if (!sumMap.temperature.unit && unit) sumMap.temperature.unit = unit
  672. break
  673. case '湿度':
  674. sumMap.humidity.sum += value
  675. sumMap.humidity.count++
  676. if (!sumMap.humidity.unit && unit) sumMap.humidity.unit = unit
  677. break
  678. case '二氧化碳':
  679. case 'CO2浓度':
  680. sumMap.co2.sum += value
  681. sumMap.co2.count++
  682. if (!sumMap.co2.unit && unit) sumMap.co2.unit = unit
  683. break
  684. case 'PM2.5浓度':
  685. sumMap.pm25.sum += value
  686. sumMap.pm25.count++
  687. if (!sumMap.pm25.unit && unit) sumMap.pm25.unit = unit
  688. break
  689. case 'TVOC':
  690. sumMap.tvoc.sum += value
  691. sumMap.tvoc.count++
  692. if (!sumMap.tvoc.unit && unit) sumMap.tvoc.unit = unit
  693. break
  694. case 'PM10浓度':
  695. sumMap.pm10.sum += value
  696. sumMap.pm10.count++
  697. if (!sumMap.pm10.unit && unit) sumMap.pm10.unit = unit
  698. break
  699. case '甲醛':
  700. sumMap.formaldehyde.sum += value
  701. sumMap.formaldehyde.count++
  702. if (!sumMap.formaldehyde.unit && unit) sumMap.formaldehyde.unit = unit
  703. break
  704. // 告警类指标直接存储状态
  705. case '红外':
  706. sumMap.infrared.push(item.value)
  707. break
  708. case '消防':
  709. sumMap.fire.push(item.value)
  710. break
  711. case '漏水':
  712. sumMap.waterLeak.push(item.value)
  713. break
  714. }
  715. })
  716. console.log('累加结果:', sumMap)
  717. // 3. 计算平均值(保留两位小数),并赋值单位
  718. this.avgData = {
  719. temperature: sumMap.temperature.count ? (sumMap.temperature.sum / sumMap.temperature.count).toFixed(2) : '0.00',
  720. temperatureUnit: sumMap.temperature.unit || '℃',
  721. humidity: sumMap.humidity.count ? (sumMap.humidity.sum / sumMap.humidity.count).toFixed(2) : '0.00',
  722. humidityUnit: sumMap.humidity.unit || '%',
  723. pm25: sumMap.pm25.count ? (sumMap.pm25.sum / sumMap.pm25.count).toFixed(2) : '0.00',
  724. pm25Unit: sumMap.pm25.unit || 'ug/立方米',
  725. tvoc: sumMap.tvoc.count ? (sumMap.tvoc.sum / sumMap.tvoc.count).toFixed(2) : '0.00',
  726. tvocUnit: sumMap.tvoc.unit || 'LuK',
  727. pm10: sumMap.pm10.count ? (sumMap.pm10.sum / sumMap.pm10.count).toFixed(2) : '0.00',
  728. pm10Unit: sumMap.pm10.unit || 'ug/立方米',
  729. co2: sumMap.co2.count ? (sumMap.co2.sum / sumMap.co2.count).toFixed(2) : '0.00',
  730. co2Unit: sumMap.co2.unit || 'ppm',
  731. formaldehyde: sumMap.formaldehyde.count ? (sumMap.formaldehyde.sum / sumMap.formaldehyde.count).toFixed(2) : '0.00',
  732. formaldehydeUnit: sumMap.formaldehyde.unit || 'ppm'
  733. }
  734. console.log('平均值:', this.avgData)
  735. // 4. 处理告警状态(ON=告警,OFF=正常;只要有一个ON就显示告警)
  736. this.alarmStatus = {
  737. infrared: sumMap.infrared.some(s => s === 'ON') ? '告警' : '正常',
  738. fire: sumMap.fire.some(s => s === 'ON') ? '告警' : '正常',
  739. waterLeak: sumMap.waterLeak.some(s => s === 'ON') ? '告警' : '正常'
  740. }
  741. // 兼容无数据的情况
  742. if (sumMap.infrared.length === 0) this.alarmStatus.infrared = '正常'
  743. if (sumMap.fire.length === 0) this.alarmStatus.fire = '正常'
  744. if (sumMap.waterLeak.length === 0) this.alarmStatus.waterLeak = '正常'
  745. },
  746. /**
  747. * 获取指标默认单位兜底用
  748. */
  749. getDefaultUnit(subName) {
  750. const unitMap = {
  751. '温度': '℃',
  752. '湿度': '%',
  753. 'PM2.5浓度': 'ug/立方米',
  754. 'TVOC': 'LuK',
  755. 'PM10浓度': 'ug/立方米',
  756. '二氧化碳': 'ppm',
  757. 'CO2浓度': 'ppm',
  758. '甲醛': 'ppm',
  759. '综合气体': '无量纲'
  760. }
  761. return unitMap[subName] || ''
  762. },
  763. /**
  764. * 根据所有设备的平均值计算AQI
  765. */
  766. calcAQIByAvg() {
  767. if (!this.hasValidData) {
  768. this.aqiValue = 45
  769. this.aqiStatus = '优'
  770. return
  771. }
  772. // 1. 提取PM2.5、PM10数值(转数字,兜底0),单位:μg/m³(国标24小时平均)
  773. const pm25 = parseFloat(this.avgData.pm25) || 0
  774. const pm10 = parseFloat(this.avgData.pm10) || 0
  775. console.log('PM2.5平均值:', pm25, 'μg/m³ | PM10平均值:', pm10, 'μg/m³')
  776. // 2. 国标AQI浓度限值区间(24小时平均,μg/m³)
  777. const aqiBpTable = {
  778. pm25: [
  779. { iaqiLo: 0, iaqiHi: 50, bpLo: 0, bpHi: 35 }, // 优
  780. { iaqiLo: 50, iaqiHi: 100, bpLo: 35, bpHi: 75 }, // 良
  781. { iaqiLo: 100, iaqiHi: 150, bpLo: 75, bpHi: 115 }, // 轻度污染
  782. { iaqiLo: 150, iaqiHi: 200, bpLo: 115, bpHi: 150 }, // 中度污染
  783. { iaqiLo: 200, iaqiHi: 300, bpLo: 150, bpHi: 250 }, // 重度污染
  784. { iaqiLo: 300, iaqiHi: 500, bpLo: 250, bpHi: 500 } // 严重污染
  785. ],
  786. pm10: [
  787. { iaqiLo: 0, iaqiHi: 50, bpLo: 0, bpHi: 50 }, // 优
  788. { iaqiLo: 50, iaqiHi: 100, bpLo: 50, bpHi: 150 }, // 良
  789. { iaqiLo: 100, iaqiHi: 150, bpLo: 150, bpHi: 250 }, // 轻度污染
  790. { iaqiLo: 150, iaqiHi: 200, bpLo: 250, bpHi: 350 }, // 中度污染
  791. { iaqiLo: 200, iaqiHi: 300, bpLo: 350, bpHi: 420 }, // 重度污染
  792. { iaqiLo: 300, iaqiHi: 500, bpLo: 420, bpHi: 500 } // 严重污染
  793. ]
  794. }
  795. // 3. 计算单污染物分指数(IAQI)的核心方法
  796. const calculateIAQI = (conc, pollutantType) => {
  797. const bpTable = aqiBpTable[pollutantType]
  798. // 浓度超过上限,直接返回500
  799. if (conc > bpTable[bpTable.length - 1].bpHi) {
  800. return 500
  801. }
  802. // 匹配浓度所在区间
  803. const matchedBp = bpTable.find(item => conc >= item.bpLo && conc <= item.bpHi) || bpTable[0]
  804. // 国标IAQI计算公式
  805. const iaqi = ((matchedBp.iaqiHi - matchedBp.iaqiLo) / (matchedBp.bpHi - matchedBp.bpLo)) * (conc - matchedBp.bpLo) + matchedBp.iaqiLo
  806. // 四舍五入,且不超过500
  807. return Math.min(Math.round(iaqi), 500)
  808. }
  809. // 4. 计算PM2.5、PM10的分指数
  810. const iaqiPm25 = calculateIAQI(pm25, 'pm25')
  811. const iaqiPm10 = calculateIAQI(pm10, 'pm10')
  812. // 5. AQI取分指数最大值
  813. const finalAQI = Math.max(iaqiPm25, iaqiPm10)
  814. this.aqiValue = finalAQI
  815. // 6. 匹配AQI对应的空气质量等级
  816. const matchedGrade = this.aqiGradeStandard.find(grade =>
  817. finalAQI >= grade.aqiMin && finalAQI <= grade.aqiMax
  818. )
  819. this.aqiStatus = matchedGrade ? matchedGrade.level : '严重污染'
  820. // this.aqiStatus = '严重污染'
  821. console.log('IAQI-PM2.5:', iaqiPm25, 'IAQI-PM10:', iaqiPm10, '最终AQI:', finalAQI, '| 等级:', this.aqiStatus)
  822. },
  823. /**
  824. * iframe加载回调保留原有逻辑
  825. */
  826. getIframeLoading(value) {}
  827. }
  828. }
  829. </script>
  830. <style rel="stylesheet/scss" lang="scss" scoped>
  831. @import "~@/assets/styles/lend-manage.scss";
  832. .env-container {
  833. width: 100%;
  834. height: calc(100vh);
  835. background-color: #031435;
  836. .env-top-title {
  837. display: flex;
  838. justify-content: center;
  839. align-items: flex-start;
  840. font-size: 44px;
  841. letter-spacing: 0.1em;
  842. text-align: center;
  843. color: #fff;
  844. width: calc(100vw);
  845. height: 130px;
  846. background: url("~@/assets/images/largeScreen/top.png") no-repeat 0 -14px;
  847. background-size: contain;
  848. font-family: 'LianMengQiYiLuShuaiZhengRuiHei';
  849. img{
  850. display: block;
  851. height: 50px;
  852. margin-right: 10px;
  853. margin-top: 26px;
  854. }
  855. p{
  856. margin-top: 26px;
  857. }
  858. }
  859. .header-date {
  860. position: fixed;
  861. top: 10px;
  862. right: 80px;
  863. display: flex;
  864. justify-content: flex-start;
  865. align-items: center;
  866. color: #fff;
  867. .time {
  868. font-size: 32px;
  869. font-weight: bold;
  870. line-height: 30px;
  871. padding-right: 20px;
  872. border-right: 1px solid rgba(255, 255, 255, 0.5);
  873. }
  874. .time-other {
  875. font-size: 18px;
  876. line-height: 22px;
  877. padding-left: 20px;
  878. span {
  879. display: block;
  880. }
  881. }
  882. }
  883. .env-main {
  884. display: flex;
  885. justify-content: space-between;
  886. padding: 0 25px;
  887. margin-top: -12px;
  888. .env-main-left,
  889. .env-main-right {
  890. max-width: 22%;
  891. flex: 1;
  892. height: calc(100vh - 138px);
  893. overflow: hidden;
  894. z-index: 9999;
  895. ::v-deep .el-table .el-table__body-wrapper td.el-table__cell,
  896. ::v-deep .el-table .el-table__fixed-right td.el-table__cell{
  897. font-size: 15px !important;
  898. }
  899. ::v-deep .el-table .el-table__header .el-table__cell .cell,
  900. ::v-deep .el-table.warehose-el-table .el-table__header .el-table__cell .cell{
  901. font-size: 16px !important;
  902. }
  903. }
  904. .env-main-middle {
  905. position: relative;
  906. flex: 1;
  907. margin: 0 20px;
  908. height: calc(100vh - 138px);
  909. overflow: hidden;
  910. background: url("~@/assets/images/largeScreen/bg.png") no-repeat center center;
  911. }
  912. .env-main-left .container-wrap {
  913. min-height: auto;
  914. }
  915. .env-main-left .container-wrap.left-wrap {
  916. height: calc(100% / 2 - 85px);
  917. }
  918. .env-main-right .container-wrap {
  919. height: calc(100% / 3 - 14px);
  920. min-height: auto;
  921. margin-bottom: 20px;
  922. }
  923. .env-item {
  924. margin-bottom: 20px;
  925. text-align: center;
  926. h3 {
  927. position: relative;
  928. display: inline-block;
  929. padding: 10px 70px;
  930. font-size: 20px;
  931. color: #fff;
  932. .iconfont {
  933. margin-right: 10px;
  934. font-size: 14px;
  935. color: #f65163;
  936. }
  937. &::before {
  938. content: "";
  939. position: absolute;
  940. left: 0;
  941. top: 50%;
  942. width: 36px;
  943. height: 12px;
  944. margin-top: -6px;
  945. background: url("~@/assets/images/largeScreen/item-left.png") no-repeat;
  946. background-size: cover;
  947. }
  948. &::after {
  949. content: "";
  950. position: absolute;
  951. top: 50%;
  952. right: 0;
  953. width: 36px;
  954. height: 12px;
  955. margin-top: -6px;
  956. background: url("~@/assets/images/largeScreen/item-right.png") no-repeat;
  957. background-size: cover;
  958. }
  959. }
  960. }
  961. .env-3d {
  962. position: fixed;
  963. left: 0;
  964. top: 10px;
  965. width: 100%;
  966. // height: calc(100%);
  967. height: 100%;
  968. // background: url("~@/assets/images/largeScreen/bg.png") no-repeat center 0;
  969. overflow: hidden;
  970. .iframe_box {
  971. width: 100%;
  972. height: 100%;
  973. }
  974. }
  975. }
  976. }
  977. .iframe_box {
  978. /* 移除原有width/height,改用内联样式或下面的样式 */
  979. width: 100% !important;
  980. height: 100% !important;
  981. border: none;
  982. }
  983. .banner-top-name{
  984. position: absolute;
  985. left: 0;
  986. top: 80px;
  987. padding: 0 15px;
  988. height: 34px;
  989. line-height: 32px;
  990. font-size: 18px;
  991. color: #fff;
  992. background-color: #113d72;
  993. border: 1px solid #339cff;
  994. border-radius: 4px;
  995. }
  996. .air-quality{
  997. // position: absolute;
  998. // bottom: 10px;
  999. // right: 20px;
  1000. // width: calc(100% / 3 - 100px);
  1001. width: calc(20% + 100px);
  1002. color: #fff;
  1003. margin: 0 20px 0 0;
  1004. padding: 10px;
  1005. height: 90px;
  1006. background-image: linear-gradient(to bottom, rgba(24, 176, 143, .5), rgba(24, 176, 143, 0));
  1007. border-radius: 5px;
  1008. z-index: 9999;
  1009. h3{
  1010. font-size: 18px;
  1011. padding: 0 0 6px 0;
  1012. }
  1013. .air-params{
  1014. display: flex;
  1015. justify-content: space-between;
  1016. align-items: center;
  1017. .air-left{
  1018. display: flex;
  1019. justify-content: flex-start;
  1020. align-items: center;
  1021. .air-title{
  1022. position: relative;
  1023. padding-left: 12px;
  1024. font-size: 12px;
  1025. &::before{
  1026. content: "";
  1027. position: absolute;
  1028. left: 0;
  1029. top: 50%;
  1030. width: 6px;
  1031. height: 6px;
  1032. background-color: #18B08F;
  1033. border-radius: 50%;
  1034. margin-top: -3px;
  1035. }
  1036. }
  1037. .air-result{
  1038. font-size: 30px;
  1039. font-weight: 600;
  1040. padding: 0 0 0 10px;
  1041. // p{
  1042. // font-size: 22px;
  1043. // font-weight: 600;
  1044. // padding: 0 6px 0 10px;
  1045. // }
  1046. // span{
  1047. // display: block;
  1048. // font-size: 11px;
  1049. // opacity: .6;
  1050. // }
  1051. }
  1052. }
  1053. .air-right{
  1054. display: flex;
  1055. justify-content: flex-start;
  1056. align-items: center;
  1057. span{
  1058. display: block;
  1059. font-size: 12px;
  1060. // padding: 8px 0;
  1061. margin-right: 6px;
  1062. }
  1063. p{
  1064. font-size: 24px;
  1065. font-weight: 600;
  1066. padding: 8px 15px;
  1067. background-color: rgba(24, 176, 143, .2);
  1068. border-radius: 5px;
  1069. margin-top: -4px;
  1070. }
  1071. }
  1072. }
  1073. // 原有基础样式保留
  1074. &.air-excellent { // 优 - 绿色
  1075. background-image: linear-gradient(to bottom, rgba(40, 180, 40, .5), rgba(40, 180, 40, 0));
  1076. .air-params .air-right p {
  1077. background-color: rgba(40, 180, 40, .2);
  1078. }
  1079. }
  1080. &.air-good { // 良 - 黄色
  1081. background-image: linear-gradient(to bottom, rgba(255, 200, 0, .5), rgba(255, 200, 0, 0));
  1082. .air-params .air-right p {
  1083. background-color: rgba(255, 200, 0, .2);
  1084. }
  1085. }
  1086. &.air-lightPollution { // 轻度污染 - 橙色
  1087. h3{
  1088. padding: 0;
  1089. }
  1090. background-image: linear-gradient(to bottom, rgba(255, 140, 0, .5), rgba(255, 140, 0, 0));
  1091. .air-params {
  1092. margin-top: -6px;
  1093. .air-right p {
  1094. background-color: rgba(255, 140, 0, .2);
  1095. }
  1096. }
  1097. }
  1098. &.air-mediumPollution { // 中度污染 - 红色
  1099. h3{
  1100. padding: 0;
  1101. }
  1102. background-image: linear-gradient(to bottom, rgba(255, 0, 0, .5), rgba(255, 0, 0, 0));
  1103. .air-params {
  1104. margin-top: -6px;
  1105. .air-right p {
  1106. background-color: rgba(255, 0, 0, .2);
  1107. }
  1108. }
  1109. }
  1110. &.air-heavyPollution { // 重度污染 - 紫色
  1111. h3{
  1112. padding: 0;
  1113. }
  1114. background-image: linear-gradient(to bottom, rgba(150, 0, 200, .5), rgba(150, 0, 200, 0));
  1115. .air-params {
  1116. margin-top: -6px;
  1117. .air-right p {
  1118. background-color: rgba(150, 0, 200, .2);
  1119. }
  1120. }
  1121. }
  1122. &.air-severePollution { // 严重污染 - 褐红色
  1123. h3{
  1124. padding: 0;
  1125. }
  1126. background-image: linear-gradient(to bottom, rgba(120, 0, 0, .5), rgba(120, 0, 0, 0));
  1127. .air-params {
  1128. margin-top: -6px;
  1129. .air-right p {
  1130. background-color: rgba(120, 0, 0, .2);
  1131. }
  1132. }
  1133. }
  1134. }
  1135. .air-warn{
  1136. background-image: linear-gradient(to bottom, rgba(246, 81, 99, .5), rgba(24, 176, 143, 0));
  1137. .air-params{
  1138. .air-right{
  1139. p{
  1140. background-color: rgba(246, 81, 99, .2);
  1141. }
  1142. }
  1143. }
  1144. }
  1145. .leakage-list {
  1146. display: flex;
  1147. flex-direction: column;
  1148. justify-content: space-between;
  1149. flex-wrap: wrap;
  1150. text-align: left;
  1151. width: calc(100% / 2 - 14px);
  1152. height: calc(100%);
  1153. margin: 0 7px;
  1154. padding: 20px 0;
  1155. li {
  1156. position: relative;
  1157. display: flex;
  1158. justify-content: space-between;
  1159. align-items: center;
  1160. padding: 0 15px;
  1161. border: 1px solid #3581cc;
  1162. background-color: #02255f;
  1163. border-radius: 2px;
  1164. width: calc(100%);
  1165. height: calc(100% / 4 - 10px);
  1166. margin: 10px 0;
  1167. &::before {
  1168. content: "";
  1169. position: absolute;
  1170. top: 4px;
  1171. left: 4px;
  1172. width: 0;
  1173. height: 0;
  1174. border-color: transparent #339cff;
  1175. border-width: 0 0 6px 6px;
  1176. border-style: solid;
  1177. }
  1178. p {
  1179. font-size: 16px;
  1180. color: #fff;
  1181. i {
  1182. margin-right: 4px;
  1183. }
  1184. }
  1185. span.leakage-state-tip {
  1186. position: relative;
  1187. display: block;
  1188. width: 6px;
  1189. height: 6px;
  1190. border-radius: 50%;
  1191. background-color: #18b08f;
  1192. &::before {
  1193. content: "";
  1194. position: absolute;
  1195. left: 50%;
  1196. top: 50%;
  1197. width: 14px;
  1198. height: 14px;
  1199. border-radius: 50%;
  1200. box-shadow: inset 0px 0px 10px 1px #18b08f;
  1201. transform: translate(-50%, -50%);
  1202. }
  1203. }
  1204. &.leakage-warn {
  1205. border-color: #f65164;
  1206. box-shadow: inset 0px 0px 15px 1px #f65164;
  1207. color: #f65164;
  1208. &::before {
  1209. border-color: transparent #f65164;
  1210. }
  1211. span.leakage-state-tip {
  1212. background-color: #f65164;
  1213. &::before {
  1214. box-shadow: inset 0px 0px 10px 1px #f65164;
  1215. }
  1216. }
  1217. }
  1218. }
  1219. }
  1220. .env-alarm-list{
  1221. display: flex;
  1222. justify-content: flex-start;
  1223. // width: 100%;
  1224. width: calc(40%);
  1225. li{
  1226. display: flex;
  1227. flex-direction: column;
  1228. align-items: center;
  1229. align-content: center;
  1230. justify-content: center;
  1231. height: 66px;
  1232. margin-right: 24px;
  1233. background: url('~@/assets/images/data_border_default.png') no-repeat;
  1234. background-size: 100% 100%;
  1235. position: relative;
  1236. color: #fff;
  1237. font-size: 14px;
  1238. &.li-warn{
  1239. background: url('~@/assets/images/data_border_warn.png') no-repeat;
  1240. background-size: 100% 100%;
  1241. }
  1242. p{
  1243. color: #339CFF;
  1244. font-weight: bold;
  1245. }
  1246. span{
  1247. display: block;
  1248. margin-top: 12px;
  1249. font-weight: bold;
  1250. }
  1251. }
  1252. }
  1253. // 告警列表容器
  1254. .env-alarm-container {
  1255. position: absolute;
  1256. top: 10px;
  1257. left: 10px;
  1258. width: calc(100%);
  1259. display: flex;
  1260. justify-content: flex-start;
  1261. // 第一行:温湿度
  1262. .env-alarm-list-first {
  1263. margin-bottom: 20px;
  1264. li {
  1265. width: calc(100% / 2 - 10px);
  1266. height: 90px;
  1267. font-size: 24px;
  1268. flex-direction: row;
  1269. & .msg-list-svg{
  1270. font-size: 40px;
  1271. margin-right: 14px;
  1272. }
  1273. & div{
  1274. span{
  1275. margin-top: 0 !important;
  1276. }
  1277. p{
  1278. font-size: 18px;
  1279. margin-top: 6px;
  1280. }
  1281. }
  1282. }
  1283. }
  1284. }
  1285. .env-alarm-list-second {
  1286. position: absolute;
  1287. bottom: 10px;
  1288. left: 10px;
  1289. display: flex;
  1290. justify-content: flex-start;
  1291. flex-wrap: wrap;
  1292. color: #fff;
  1293. width: 600px;
  1294. height: 130px;
  1295. font-size: 14px;
  1296. li {
  1297. display: flex;
  1298. align-items: center;
  1299. justify-content: space-between;
  1300. width: calc(100% / 3 - 10px);
  1301. height: calc(100% / 3 - 14px);
  1302. background: linear-gradient(
  1303. 360deg,
  1304. rgba(51, 156, 255, 0.24) 0%,
  1305. rgba(56, 158, 225, 0) 70%,
  1306. rgba(56, 158, 225, 0) 100%
  1307. );
  1308. margin-left: 10px;
  1309. margin-bottom: 14px;
  1310. padding: 0 10px;
  1311. p{
  1312. font-size: 12px;
  1313. color: #339CFF;
  1314. }
  1315. span{
  1316. display: inline-block;
  1317. font-weight: bold;
  1318. }
  1319. &.li-warn{
  1320. background: linear-gradient(
  1321. 360deg,
  1322. rgba(246, 81, 100, 0.5) 0%,
  1323. rgba(56, 158, 225, 0) 70%,
  1324. rgba(56, 158, 225, 0) 100%
  1325. );
  1326. span{
  1327. color: #F65164;
  1328. }
  1329. }
  1330. }
  1331. }
  1332. .device-container{
  1333. position: absolute;
  1334. bottom: 0;
  1335. left: 10px;
  1336. }
  1337. .new-leakage-list{
  1338. width: 660px;
  1339. height: 120px;
  1340. padding: 0;
  1341. li {
  1342. display: flex;
  1343. align-items: center;
  1344. justify-content: space-between;
  1345. width: calc(100% / 3 - 10px);
  1346. height: calc(100% / 2 - 14px);
  1347. margin-right: 10px;
  1348. margin-bottom: 14px;
  1349. p{
  1350. font-size: 14px;
  1351. }
  1352. }
  1353. }
  1354. .device-info {
  1355. width: 600px;
  1356. height: 30px;
  1357. display: flex;
  1358. flex-direction: row;
  1359. justify-content: space-between;
  1360. margin-bottom: 14px;
  1361. li {
  1362. width: calc(100% / 2);
  1363. height: calc(100%);
  1364. background: linear-gradient(
  1365. 360deg,
  1366. rgba(51, 156, 255, 0.24) 0%,
  1367. rgba(56, 158, 225, 0) 70%,
  1368. rgba(56, 158, 225, 0) 100%
  1369. );
  1370. display: flex;
  1371. align-items: center;
  1372. justify-content: space-between;
  1373. margin-left: 10px;
  1374. .row-item {
  1375. display: flex;
  1376. align-items: center;
  1377. .svg-box {
  1378. margin-right: 10px;
  1379. .card-panel-icon {
  1380. font-size: 24px;
  1381. }
  1382. }
  1383. }
  1384. .row-num {
  1385. font-size: 18px;
  1386. color: #fff;
  1387. margin-right: 10px;
  1388. }
  1389. }
  1390. }
  1391. .screen-env-list {
  1392. flex-wrap: wrap;
  1393. height: calc(100% - 54px);
  1394. padding: 0 10px;
  1395. li {
  1396. position: relative;
  1397. flex: none;
  1398. width: calc(100% / 2 - 22px);
  1399. margin: 10px 10px 0 10px;
  1400. height: calc(100% / 3 - 10px) !important;
  1401. .msg-txt{
  1402. display: flex;
  1403. flex-direction: column;
  1404. align-items: center;
  1405. justify-content: center;
  1406. padding-left: 40px;
  1407. height: calc(100%);
  1408. .msg-list-num{
  1409. font-size: 18px;
  1410. position: static;
  1411. }
  1412. .msg-list-unit{
  1413. font-size: 12px;
  1414. position: static;
  1415. margin-top: 8px;
  1416. }
  1417. }
  1418. .msg-list-svg {
  1419. position: absolute;
  1420. left: 20px;
  1421. top: 0;
  1422. font-size: 40px;
  1423. margin-left: 0 !important;
  1424. }
  1425. &.msg-pm {
  1426. .msg-list-svg {
  1427. font-size: 46px;
  1428. }
  1429. }
  1430. &.alarm-status{
  1431. .msg-txt{
  1432. flex-direction: row;
  1433. }
  1434. &.li-warn{
  1435. span{
  1436. color: #F65164;
  1437. }
  1438. }
  1439. }
  1440. }
  1441. }
  1442. .panel-group {
  1443. // width: calc(100% / 3 + 60px);
  1444. width: calc(40% - 100px);
  1445. .card-panel-col {
  1446. margin-bottom: 20px;
  1447. }
  1448. .card-panel {
  1449. cursor: pointer;
  1450. height: 100px;
  1451. font-size: 15px;
  1452. position: relative;
  1453. overflow: hidden;
  1454. opacity: 0.86;
  1455. &.zaixianshebei {
  1456. color: #c4c859;
  1457. background: linear-gradient(
  1458. 180deg,
  1459. rgba(196, 200, 89, 0.5) 0%,
  1460. rgba(196, 200, 89, 0) 100%
  1461. );
  1462. border-top: 2px #c4c859 solid;
  1463. & span.card-panel-num {
  1464. background: linear-gradient(180deg, #ffffff 0%, #bfc458 100%);
  1465. }
  1466. }
  1467. &.lixianshebei {
  1468. color: #f65164;
  1469. background: linear-gradient(
  1470. 180deg,
  1471. rgba(246, 81, 100, 0.5) 0%,
  1472. rgba(247, 80, 100, 0) 100%
  1473. );
  1474. border-top: 2px #f65164 solid;
  1475. & span.card-panel-num {
  1476. background: linear-gradient(180deg, #ffffff 0%, #f55164 100%);
  1477. }
  1478. }
  1479. .card-panel-icon-wrapper {
  1480. float: left;
  1481. margin: 0 8px;
  1482. padding: 20px 0;
  1483. transition: all 0.38s ease-out;
  1484. border-radius: 6px;
  1485. }
  1486. .card-panel-icon {
  1487. float: left;
  1488. font-size: 40px;
  1489. }
  1490. .card-panel-description {
  1491. margin: 20px 4px;
  1492. margin-left: 0px;
  1493. .card-panel-text {
  1494. line-height: 30px;
  1495. color: rgba(0, 0, 0, 0.45);
  1496. font-size: 20px;
  1497. margin-bottom: 11px;
  1498. & span {
  1499. -webkit-background-clip: text;
  1500. color: transparent;
  1501. font-weight: bold;
  1502. }
  1503. }
  1504. }
  1505. }
  1506. }
  1507. ::v-deep .table-title{
  1508. font-size: 20px;
  1509. }
  1510. </style>