right.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. <template>
  2. <div>
  3. <CusModule title="节能分析">
  4. <div class="grid-container">
  5. <div class="left-panel">
  6. <div class="spinner"></div>
  7. <div class="value-display">
  8. <span class="value">{{ useElec.value }}</span>
  9. <span class="unit">{{ useElec.unit }}</span>
  10. </div>
  11. <div class="description">
  12. <span class="text">本月用电量</span>
  13. <div class="rate" :style="`${useElec.trend === 'up'? 'red': 'green'}`">
  14. <span>环比</span>
  15. <img src="@/assets/images/home/down-arrow.svg" alt="">
  16. <span>{{ useElec.rate }}%</span>
  17. </div>
  18. </div>
  19. </div>
  20. <div class="right-panel ">
  21. <div class="panel-content">
  22. <div class="image-container">
  23. <img src="@/assets/images/home/water.svg" alt="">
  24. </div>
  25. <div class="whitespace-pre">
  26. <span class="value">{{ userWater.value }}</span>
  27. <span class="unit">{{ userWater.unit }}</span>
  28. <div class="label">本月用水量</div>
  29. <div class="rate">
  30. <span>环比</span>
  31. <img src="@/assets/images/home/down-arrow.svg" alt="">
  32. <span>{{ userWater.rate }}%</span>
  33. </div>
  34. </div>
  35. </div>
  36. <div class="panel-content">
  37. <div class="image-container">
  38. <img src="@/assets/images/home/cost.svg" alt="">
  39. </div>
  40. <div class="whitespace-pre">
  41. <span class="value">{{ userEnergy.value }}</span>
  42. <span class="unit">{{ userEnergy.unit }}</span>
  43. <div class="label">本月用能费用</div>
  44. <div class="rate">
  45. <span>环比</span>
  46. <img src="@/assets/images/home/down-arrow.svg" alt="">
  47. <span>{{ userEnergy.rate }}%</span>
  48. </div>
  49. </div>
  50. </div>
  51. </div>
  52. </div>
  53. </CusModule>
  54. <CusModule class="module-top" title="设备设施">
  55. <div class="equip">
  56. <div v-for="item in equipment" :style="{ 'color': item.color }" class="equip-item" :key="item.name">
  57. <div><span>{{ item.value }}</span> <span class="unit">{{ item.unit }}</span></div>
  58. <div>{{ item.name }}</div>
  59. </div>
  60. </div>
  61. <div class="pie-chart">
  62. <BaseChart height="100%" width="100%" :option="pieOptions"/>
  63. </div>
  64. </CusModule>
  65. <CusModule class="module-top" title="告警信息">
  66. <div class="seamless-header">
  67. <div>告警内容</div>
  68. <div>告警设备</div>
  69. <div>告警时间</div>
  70. </div>
  71. <vue-seamless-scroll :data="listData" class="seamless-warp" :class-option="classOption">
  72. <div class="seamless-item" v-for="(item, index) in listData" :key="index">
  73. <div>{{ item.name }}</div>
  74. <div>{{ item.type }}</div>
  75. <div>{{ item.createTime }}</div>
  76. </div>
  77. </vue-seamless-scroll>
  78. </CusModule>
  79. </div>
  80. </template>
  81. <script>
  82. import CusModule from '../components/CusModule.vue';
  83. import BaseChart from '@/components/BaseChart/index.vue'
  84. import vueSeamlessScroll from 'vue-seamless-scroll'
  85. import {mapState} from 'vuex';
  86. import {qryElecMeterByDate, qryWaterMeterByDate} from "@/api/device/elecMeterH";
  87. import {DateTool} from "@/utils/DateTool";
  88. import {numToStr} from "@/utils";
  89. import {listDeviceStatus, listDeviceType} from "@/api/device/device";
  90. export default {
  91. name: 'HomeRight',
  92. data() {
  93. return {
  94. useElec: {
  95. unit: '度',
  96. },
  97. userWater: {
  98. unit: '吨',
  99. },
  100. userEnergy: {
  101. unit: '元',
  102. },
  103. equipment: [
  104. {name: '设备总数', unit: '个', color: '#01A7F0'},
  105. {name: '在线设备', unit: '个', color: '#B3E3E8'},
  106. {name: '离线设备', unit: '个', color: '#fff'}
  107. ],
  108. equipmentPieData: [],
  109. classOption: {
  110. step: 0.3, // 数值越大速度滚动越快
  111. limitMoveNum: 4, // 开始无缝滚动的数据量 this.dataList.length
  112. hoverStop: true, // 是否开启鼠标悬停stop
  113. direction: 1, // 0向下 1向上 2向左 3向右
  114. openWatch: true, // 开启数据实时监控刷新dom
  115. singleHeight: 0, // 单步运动停止的高度(默认值0是无缝不停止的滚动) direction => 0/1
  116. singleWidth: 0, // 单步运动停止的宽度(默认值0是无缝不停止的滚动) direction => 2/3
  117. waitTime: 1000 // 单步运动停止
  118. },
  119. listData: [
  120. {
  121. name: '温度超阈值上限',
  122. type: '温湿度01',
  123. createTime: '2024-10-07 12:15:46'
  124. },
  125. {
  126. name: '制冷压力超阈值上限',
  127. type: '精密空调',
  128. createTime: '2024-10-08 11:11:41'
  129. },
  130. {
  131. name: '检测到有水',
  132. type: '水浸',
  133. createTime: '2024-10-09 15:15:46'
  134. },
  135. {
  136. name: '开门时间异常',
  137. type: '东门门禁',
  138. createTime: '2024-10-10 09:15:46'
  139. },
  140. {
  141. name: '设备离线',
  142. type: '摄像机1',
  143. createTime: '2024-10-10 12:33:40'
  144. },
  145. {
  146. name: '检测到有人',
  147. type: '红外1',
  148. createTime: '2024-10-10 12:35:40'
  149. }
  150. ]
  151. }
  152. },
  153. components: {
  154. CusModule,
  155. BaseChart,
  156. vueSeamlessScroll
  157. },
  158. computed: {
  159. ...mapState('userState', ['areaType']),
  160. pieOptions() {
  161. return {
  162. legend: {
  163. show: false,
  164. },
  165. tooltip: {
  166. trigger: 'item',
  167. formatter: (params) => {
  168. const {name, value, percent,} = params
  169. return `${name} : ${value}个 (${percent}%)`
  170. }
  171. },
  172. color: ["#3FA7FD", "#8AC540", "#F9AF39", "#82D8F9"],
  173. series: [
  174. {
  175. name: "类别统计",
  176. type: "pie",
  177. radius: ["40%", "55%"],
  178. center: ["50%", "39%"],
  179. emphasis: {
  180. label: {
  181. show: true,
  182. },
  183. },
  184. data: this.equipmentPieData,
  185. label: {
  186. color: "#fff",
  187. alignTo: "edge",
  188. fontSize: 14,
  189. minMargin: 5,
  190. edgeDistance: 20,
  191. lineHeight: 20,
  192. formatter: "{name|{b}}\n{value|{c}个}",
  193. rich: {
  194. name: {
  195. color: "#B3E3E8",
  196. },
  197. value: {
  198. color: "#07E3F9",
  199. },
  200. },
  201. },
  202. labelLine: {
  203. length: 5,
  204. length2: 3,
  205. smooth: true,
  206. },
  207. },
  208. ],
  209. }
  210. }
  211. },
  212. watch: {
  213. areaType() {
  214. this.getPageDatas()
  215. }
  216. },
  217. mounted() {
  218. this.getPageDatas()
  219. },
  220. methods: {
  221. getPageDatas() {
  222. this.getElecWaterCost()
  223. this.getDeviceStatus()
  224. this.cntListDeviceType()
  225. },
  226. async getElecWaterCost() {
  227. const {data: thisMonthElecMeter} = await qryElecMeterByDate(DateTool.thisMonth(), this.areaType)
  228. const {data: thisMonthWaterMeter} = await qryWaterMeterByDate(DateTool.thisMonth(), this.areaType)
  229. const {data: lastMonthElecMeter} = await qryElecMeterByDate(DateTool.lastMonth(), this.areaType)
  230. const {data: lastMonthWaterMeter} = await qryWaterMeterByDate(DateTool.lastMonth(), this.areaType)
  231. const elec = {
  232. unit: '度',
  233. }
  234. elec.value = numToStr(thisMonthElecMeter.quantity)
  235. if (thisMonthElecMeter.quantity > lastMonthElecMeter.quantity) {
  236. elec.rate = ((thisMonthElecMeter.quantity - lastMonthElecMeter.quantity) / lastMonthElecMeter.quantity * 100).toFixed(2)
  237. elec.trend = 'up'
  238. } else {
  239. elec.rate = ((lastMonthElecMeter.quantity - thisMonthElecMeter.quantity) / lastMonthElecMeter.quantity * 100).toFixed(2)
  240. elec.trend = 'down'
  241. }
  242. this.useElec = elec
  243. const water = {
  244. unit: '吨',
  245. }
  246. water.value = numToStr(thisMonthWaterMeter.quantity)
  247. if (thisMonthWaterMeter.quantity > lastMonthWaterMeter.quantity) {
  248. water.rate = ((thisMonthWaterMeter.quantity - lastMonthWaterMeter.quantity) / lastMonthWaterMeter.quantity * 100).toFixed(2)
  249. water.trend = 'up'
  250. } else {
  251. water.rate = ((lastMonthWaterMeter.quantity - thisMonthWaterMeter.quantity) / lastMonthWaterMeter.quantity * 100).toFixed(2)
  252. water.trend = 'down'
  253. }
  254. this.userWater = water
  255. const energy = {
  256. unit: '元',
  257. }
  258. energy.value = numToStr(thisMonthElecMeter.useCost + thisMonthWaterMeter.useCost)
  259. if (thisMonthElecMeter.useCost + thisMonthWaterMeter.useCost > lastMonthElecMeter.useCost + lastMonthWaterMeter.useCost) {
  260. energy.rate = ((thisMonthElecMeter.useCost + thisMonthWaterMeter.useCost - lastMonthElecMeter.useCost - lastMonthWaterMeter.useCost) / lastMonthElecMeter.useCost * 100).toFixed(2)
  261. energy.trend = 'up'
  262. } else {
  263. energy.rate = ((lastMonthElecMeter.useCost + lastMonthWaterMeter.useCost - thisMonthElecMeter.useCost - thisMonthWaterMeter.useCost) / lastMonthElecMeter.useCost * 100).toFixed(2)
  264. energy.trend = 'down'
  265. }
  266. this.userEnergy = energy
  267. },
  268. async getDeviceStatus() {
  269. const {data} = await listDeviceStatus({
  270. areaCode: this.areaType
  271. })
  272. if (!data) {
  273. return
  274. }
  275. const [total, online, offline] = this.equipment
  276. total.value = data.total
  277. online.value = data.onlineCount
  278. offline.value = data.offlineCount
  279. this.equipment = [
  280. total, online, offline
  281. ]
  282. },
  283. async cntListDeviceType() {
  284. const {data} = await listDeviceType({
  285. areaCode: this.areaType
  286. })
  287. this.equipmentPieData = data.map(item => ({
  288. name: item.typeName || '其他',
  289. value: item.total
  290. }))
  291. },
  292. }
  293. }
  294. </script>
  295. <style lang='scss' scoped>
  296. @import url("../index.scss");
  297. .grid-container {
  298. display: flex;
  299. margin-top: 20px;
  300. .left-panel {
  301. position: relative;
  302. text-align: center;
  303. margin-left: 20px;
  304. width: 111px;
  305. background: url('~@/assets/images/home/r1-left_bg.png') no-repeat;
  306. background-size: 100% 100%;
  307. .spinner {
  308. position: absolute;
  309. animation: spin 10s linear infinite;
  310. top: 40px;
  311. left: 25px;
  312. width: 63px;
  313. height: 63px;
  314. background-image: url('~@/assets/images/home/r1-top_center.png');
  315. }
  316. .value-display {
  317. margin-top: 10px;
  318. span.value {
  319. font-size: 18px;
  320. font-weight: bold;
  321. }
  322. }
  323. .description {
  324. position: absolute;
  325. bottom: 8px;
  326. width: 100%;
  327. text-align: center;
  328. span.text {
  329. color: #B3E3E8;
  330. font-size: 14px;
  331. }
  332. .rate {
  333. justify-content: center;
  334. }
  335. }
  336. }
  337. .rate {
  338. color: #00C852;
  339. font-size: 12px;
  340. display: flex;
  341. align-items: center;
  342. img {
  343. margin-left: 3px;
  344. }
  345. }
  346. .right-panel {
  347. padding-left: 20px;
  348. display: flex;
  349. flex-direction: column;
  350. justify-content: space-between;
  351. .panel-content {
  352. width: 200px;
  353. height: 75px;
  354. color: #B3E3E8;
  355. background: url('~@/assets/images/home/l2_item_bg.png') no-repeat;
  356. background-size: 100% 100%;
  357. display: flex;
  358. .whitespace-pre {
  359. margin-top: 5px;
  360. margin-left: 20px;
  361. span.value {
  362. font-size: 18px;
  363. font-weight: bold;
  364. }
  365. }
  366. .label {
  367. font-size: 14px;
  368. }
  369. .image-container {
  370. width: 70px;
  371. display: flex;
  372. align-items: center;
  373. justify-content: center;
  374. img {
  375. height: 24px;
  376. margin-bottom: 5px;
  377. }
  378. }
  379. }
  380. }
  381. }
  382. .equip {
  383. display: flex;
  384. justify-content: space-around;
  385. margin-top: 20px;
  386. .equip-item {
  387. display: flex;
  388. flex-direction: column;
  389. align-items: center;
  390. background: #1B4A64;
  391. color: #B3E3E8;
  392. border-radius: 4px;
  393. padding: 5px 13px;
  394. font-size: 14px;
  395. > div:first-of-type {
  396. span:first-of-type {
  397. font-size: 18px;
  398. font-weight: bold;
  399. }
  400. }
  401. }
  402. }
  403. .pie-chart {
  404. margin: 30px;
  405. height: 201px;
  406. background: url("~@/assets/images/device/l1-pie_bg.png") no-repeat;
  407. background-position: center;
  408. }
  409. .seamless-header {
  410. margin-top: 20px;
  411. display: flex;
  412. align-items: center;
  413. justify-content: space-between;
  414. padding: 5px 10px;
  415. color: #7DBAFF;
  416. background: #1B4A64;
  417. font-size: 16px;
  418. > div:first-of-type,
  419. > div:last-of-type {
  420. flex-basis: 35%;
  421. }
  422. > div {
  423. text-align: center;
  424. }
  425. }
  426. .seamless-warp {
  427. overflow: hidden;
  428. height: 200px;
  429. .seamless-item {
  430. display: flex;
  431. align-items: center;
  432. justify-content: space-between;
  433. padding: 5px 0;
  434. &:nth-child(odd) {
  435. background: #000;
  436. }
  437. > div:first-of-type,
  438. > div:last-of-type {
  439. flex-basis: 38%;
  440. }
  441. > div {
  442. text-align: center;
  443. font-size: 13px;
  444. }
  445. }
  446. }
  447. </style>