物管理前端
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.

567 lines
15 KiB

  1. <template>
  2. <div class="borads-index">
  3. <screen-adapter class="adapter">
  4. <div class="boards-container">
  5. <div class="center" title="使用鼠标左键、滚轮实现拖拽、放大缩小。"></div>
  6. <div class="top">
  7. <div class="title" @click="reset" title="点击此标题将鸟瞰图恢复成初始状态。">{{ $store.state.user.tenantName || '平台看板' }}</div>
  8. <div class="time" v-if="timeCount && timeCount.ts > 0">
  9. <div class="time-left">
  10. {{ $m(timeCount.ts).format('HH:mm:ss') }}
  11. </div>
  12. <div class="time-right">
  13. <div>{{ $date.getDay(timeCount.ts) }}</div>
  14. <div>
  15. {{ $m(timeCount.ts).format('YYYY/MM/DD') }}
  16. </div>
  17. </div>
  18. </div>
  19. </div>
  20. <div class="pop">
  21. <pop v-for="(item, index) in pop" :key="index" :item="item" :index="index"></pop>
  22. </div>
  23. <div class="left">
  24. <block v-for="(item, index) in left" :key="index" :item="item" :data-form="dataForm" @change-code="dataForm.code = $event">
  25. <component :is="item.comp" :key="index" :item="item" :option="item.option" tip-loop></component>
  26. </block>
  27. </div>
  28. <div class="right">
  29. <block v-for="(item, index) in right" :key="index" :item="item" :data-form="dataForm" @change-code="dataForm.code = $event">
  30. <component :is="item.comp" :key="index" :item="item" :option="item.option" tip-loop></component>
  31. </block>
  32. </div>
  33. <!--<div class="bottom">
  34. <block size-type="2" :item="bottom">
  35. <component :is="bottom.comp" :item="bottom" :option="bottom.option" tip-loop></component>
  36. </block>
  37. </div>-->
  38. </div>
  39. <tooltip :data="toolTipData" />
  40. </screen-adapter>
  41. </div>
  42. </template>
  43. <script lang="ts">
  44. import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, onBeforeUnmount } from 'vue';
  45. import ScreenAdapter from '@/components/screen-adapter.vue';
  46. import Block from './comp/block.vue';
  47. import Bar from './comp/bar.vue';
  48. import Pop from './comp/pop.vue';
  49. import timeCountHooks from '@/hooks/time-count';
  50. import { useFetch } from '@/hooks/fetch';
  51. import utilService from '@/service/utilService';
  52. import Chart from '@/components/chart.vue';
  53. import { costStatistic, elecPayload, monthEnergyUsageTrend, usageRanking, energyUsageDistribution } from './common/model';
  54. import { IObject } from '@/types/interface';
  55. import moment from 'moment';
  56. import toolHooks from '@/hooks/tool';
  57. import { init, setPointerLineStatusAll } from './common/style';
  58. import Tooltip from './comp/tooltip.vue';
  59. import switchScreenHooks from '@/hooks/switch-screen';
  60. export default defineComponent({
  61. components: {
  62. ScreenAdapter,
  63. Tooltip,
  64. Block,
  65. Chart,
  66. Bar,
  67. Pop,
  68. },
  69. setup() {
  70. const { timeCount } = timeCountHooks();
  71. const { generateSvg2Dom } = toolHooks();
  72. const state = reactive({
  73. opt: { scale: 0.8 },
  74. dragStretch: null,
  75. toolTipData: {},
  76. timer: null,
  77. dataForm: {
  78. code: '电',
  79. codeMap: { : 'kWh', : 't', 蒸汽: 't', 压缩空气: 'Nm³' },
  80. },
  81. timeCount,
  82. pop: [
  83. {
  84. key: 'yearCarbon',
  85. icon: '',
  86. label: '年碳排放量',
  87. value: '--',
  88. unit: 'tCO₂',
  89. toFixed: 2,
  90. },
  91. {
  92. key: 'monthConsumptionValue',
  93. icon: '',
  94. label: '月综合能耗',
  95. value: '--',
  96. unit: 'tce',
  97. },
  98. {
  99. key: 'yearConsumptionValue',
  100. icon: '',
  101. label: '年综合能耗',
  102. value: '--',
  103. unit: 'tce',
  104. },
  105. ],
  106. left: [
  107. {
  108. comp: 'bar',
  109. title: '月用能统计',
  110. data: [
  111. {
  112. key: 'electricDayValue',
  113. icon: 'elec',
  114. label: '电用量',
  115. value: '',
  116. unit: 'kWh',
  117. },
  118. {
  119. key: 'waterDayValue',
  120. icon: 'water',
  121. label: '水用量',
  122. value: '',
  123. unit: 't',
  124. },
  125. {
  126. key: 'steamDayValue',
  127. icon: 'steam',
  128. label: '蒸汽用量',
  129. value: '',
  130. unit: 't',
  131. },
  132. {
  133. key: 'airDayValue',
  134. icon: 'air',
  135. label: '压缩空气用量',
  136. value: '',
  137. unit: 'Nm³',
  138. },
  139. {
  140. key: 'gasValue',
  141. icon: 'gas',
  142. label: '天然气用量',
  143. value: '',
  144. unit: 'Nm³',
  145. },
  146. ],
  147. },
  148. {
  149. title: '年用能统计',
  150. comp: 'bar',
  151. data: [
  152. {
  153. key: 'electricDayValue',
  154. icon: 'elec',
  155. label: '电用量',
  156. value: '',
  157. unit: 'kWh',
  158. },
  159. {
  160. key: 'waterDayValue',
  161. icon: 'water',
  162. label: '水用量',
  163. value: '',
  164. unit: 't',
  165. },
  166. {
  167. key: 'steamDayValue',
  168. icon: 'steam',
  169. label: '蒸汽用量',
  170. value: '',
  171. unit: 't',
  172. },
  173. {
  174. key: 'airDayValue',
  175. icon: 'air',
  176. label: '压缩空气用量',
  177. value: '',
  178. unit: 'Nm³',
  179. },
  180. {
  181. key: 'gasValue',
  182. icon: 'gas',
  183. label: '天然气用量',
  184. value: '',
  185. unit: 'Nm³',
  186. },
  187. ],
  188. },
  189. {
  190. comp: 'chart',
  191. title: '当日电力负荷',
  192. option: elecPayload(),
  193. unit: 'kWh',
  194. },
  195. ],
  196. right: [
  197. {
  198. comp: 'chart',
  199. title: '能耗统计(月)',
  200. option: costStatistic(),
  201. unit: '',
  202. },
  203. {
  204. comp: 'chart',
  205. title: '用能排行(月)',
  206. option: usageRanking(),
  207. unit: 'kWh',
  208. select: true,
  209. },
  210. {
  211. title: '当月用能趋势',
  212. comp: 'chart',
  213. option: monthEnergyUsageTrend(),
  214. unit: 'kWh',
  215. select: true,
  216. },
  217. ],
  218. bottom: {
  219. comp: 'chart',
  220. title: '24小时用能分布情况',
  221. option: energyUsageDistribution(),
  222. },
  223. }) as IObject;
  224. const changeType = (type: string) => {
  225. const c = state.dataForm.code;
  226. const u = state.dataForm.codeMap[c];
  227. state.left[2].unit = u;
  228. state.right[1].unit = u;
  229. //当月用能趋势
  230. useFetch('/board/monthEnergy/analyse', {
  231. data: {
  232. month: moment().format('YYYY-MM'),
  233. type,
  234. },
  235. cb: (res: any) => {
  236. if (utilService.isValidObject(res) && utilService.isValidArray(res.currentMonthData)) {
  237. const d = res.currentMonthData;
  238. const c = state.dataForm.code;
  239. const u = state.dataForm.codeMap[c];
  240. state.right[2].option = monthEnergyUsageTrend(d, u);
  241. }
  242. },
  243. });
  244. //用能排行(月)
  245. useFetch('/board/monthEnergy/ranking', {
  246. data: {
  247. day: moment().format('YYYY-MM'),
  248. type,
  249. },
  250. cb: (res: any) => {
  251. if (utilService.isValidArray(res)) {
  252. const c = state.dataForm.code;
  253. const u = state.dataForm.codeMap[c];
  254. state.right[1].option = usageRanking(res, u);
  255. }
  256. },
  257. });
  258. };
  259. const fn = () => {
  260. //三个小气泡
  261. useFetch('/board/carbonAndConsumption', {
  262. cb: (res: any) => {
  263. if (utilService.isValidObject(res)) {
  264. for (const item of state.pop) {
  265. item.value = res[item.key] ? (item.toFixed ? utilService.number2Fixed(res[item.key]) : res[item.key]) : '--';
  266. }
  267. }
  268. },
  269. });
  270. //月用能统计
  271. useFetch('/board/eachEnergy', {
  272. cb: (res: any) => {
  273. if (utilService.isValidObject(res)) {
  274. for (const item of state.left[0].data) {
  275. item.value = res[item.key] ?? '--';
  276. }
  277. }
  278. },
  279. });
  280. //年用能统计
  281. useFetch('/board/eachEnergyYear', {
  282. cb: (res: any) => {
  283. if (utilService.isValidObject(res)) {
  284. for (const item of state.left[1].data) {
  285. item.value = res[item.key] ?? '--';
  286. }
  287. }
  288. },
  289. });
  290. //能耗费用统计
  291. useFetch('/board/energyCost', {
  292. data: {
  293. day: moment().format('YYYY-MM-DD'),
  294. },
  295. cb: (res: any) => {
  296. if (utilService.isValidArray(res)) {
  297. state.right[0].option = costStatistic(res);
  298. }
  299. },
  300. });
  301. //当日电力负荷
  302. useFetch('/board/qcDayLoad/analyse', {
  303. data: {
  304. day: moment().format('YYYY-MM-DD'),
  305. },
  306. cb: (res: any) => {
  307. if (utilService.isValidObject(res) && utilService.isValidArray(res.currentMonthData)) {
  308. const d = res.currentMonthData;
  309. state.left[2].option = elecPayload(d);
  310. }
  311. },
  312. });
  313. };
  314. //24小时用能分布情况
  315. // useFetch('/board/dayEnergy/trend', {
  316. // data: {
  317. // //day: '2025-04-01',
  318. // day: moment().subtract(1, 'days').format('YYYY-MM-DD'),
  319. // },
  320. // cb: (res: any) => {
  321. // if (utilService.isValidObject(res)) {
  322. // state.bottom.option = energyUsageDistribution(res);
  323. // }
  324. // },
  325. // });
  326. changeType(state.dataForm.code);
  327. fn();
  328. const reset = () => {
  329. setPointerLineStatusAll('none');
  330. state.dragStretch.reset();
  331. };
  332. const methods = { changeType, reset };
  333. watch(
  334. () => state.dataForm.code,
  335. (newCode: string) => {
  336. changeType(newCode);
  337. },
  338. );
  339. const getSvg = () => {
  340. const target = document.querySelector('.boards-container .center') as HTMLElement;
  341. generateSvg2Dom('/cdn/normal_bg.svg', target, () => {
  342. target.style.transform = 'scale(0.7)';
  343. nextTick(() => {
  344. init(state, target);
  345. // document.getElementById("layer-d-animate").innerHTML = '<animate href="#path23264" attributeName="stroke-dashoffset" dur="15s" to="0" fill="freeze"></animate><animate href="#path23264-3" attributeName="stroke-dashoffset" dur="15s" to="0" fill="freeze"></animate>';
  346. });
  347. });
  348. };
  349. onMounted(() => {
  350. getSvg();
  351. state.timer = setInterval(() => {
  352. changeType(state.dataForm.code);
  353. fn();
  354. }, 1 * 60 * 1000);
  355. });
  356. onBeforeUnmount(() => {
  357. clearInterval(state.timer);
  358. state.timer = null;
  359. });
  360. return { ...toRefs(state), ...methods, ...switchScreenHooks({ screen: '1' }) };
  361. },
  362. });
  363. </script>
  364. <!-- <style lang="scss">
  365. .rr-view-ctx-card {
  366. height: 100%;
  367. .el-card__body {
  368. position: relative;
  369. height: 100%;
  370. }
  371. }
  372. </style> -->
  373. <style lang="less" scoped>
  374. .borads-index {
  375. position: relative;
  376. width: 100%;
  377. height: 100%;
  378. display: flex;
  379. flex-direction: column;
  380. background: #000b31;
  381. .adapter {
  382. .boards-container {
  383. position: relative;
  384. height: 100%;
  385. display: flex;
  386. flex-direction: column;
  387. > .top {
  388. position: relative;
  389. z-index: 2;
  390. height: 88px;
  391. > .title {
  392. position: absolute;
  393. left: 0;
  394. top: 0;
  395. width: 100%;
  396. height: 100%;
  397. color: #e2edff;
  398. letter-spacing: 6px;
  399. background: url('@/assets/images/njhl/title.png') no-repeat center;
  400. background-size: 100% 100%;
  401. text-align: center;
  402. font-weight: bold;
  403. font-size: 40px;
  404. cursor: pointer;
  405. }
  406. > .time {
  407. position: absolute;
  408. left: 0;
  409. top: 3px;
  410. display: flex;
  411. align-items: center;
  412. > .time-left {
  413. position: relative;
  414. font-size: 32px;
  415. margin: 0 60px;
  416. background: linear-gradient(to top, #fff 30%, #3e576f 100%);
  417. -webkit-background-clip: text;
  418. -webkit-text-fill-color: transparent;
  419. font-family: 'Alibaba PuHuiTi Heavy'; //阿里巴巴 普惠体
  420. &:after {
  421. position: absolute;
  422. content: '';
  423. background: #3e576f;
  424. right: -30px;
  425. top: 50%;
  426. width: 1px;
  427. height: 40px;
  428. transform: translate(-50%, -50%);
  429. }
  430. }
  431. > .time-right {
  432. color: rgba(#fff, 0.7);
  433. }
  434. }
  435. }
  436. > .center {
  437. position: absolute;
  438. left: 0;
  439. top: 0;
  440. width: 1920px;
  441. height: 1080px;
  442. cursor: move;
  443. //background: url("@/assets/images/njhl/bg.png");
  444. //transform: translate(-50%, -50%);
  445. /* 初始阴影设置(透明) */
  446. &::before {
  447. content: '';
  448. position: absolute;
  449. top: 0;
  450. left: 0;
  451. right: 0;
  452. bottom: 0;
  453. border-radius: inherit;
  454. //opacity: 0;
  455. //transition: opacity 0.4s;
  456. box-shadow: inset 0 0 80px 30px #000b31;
  457. }
  458. &:hover::before {
  459. // opacity: 1;
  460. }
  461. }
  462. .icon {
  463. opacity: 0.4;
  464. position: absolute;
  465. top: 8px;
  466. right: 120px;
  467. z-index: 999;
  468. ::v-deep svg {
  469. transition: all 80ms;
  470. // opacity: 0.8;
  471. //&:hover {
  472. // width: 35px !important;
  473. // height: 35px !important;
  474. // opacity: 1;
  475. //}
  476. }
  477. }
  478. .pop {
  479. position: absolute;
  480. display: flex;
  481. align-items: center;
  482. justify-content: space-between;
  483. height: 86px;
  484. left: 50%;
  485. top: 88px;
  486. transform: translate(-50%);
  487. > * {
  488. margin: 0 40px;
  489. background: rgba(#000b31, 0.7);
  490. }
  491. }
  492. .left,
  493. .right {
  494. position: absolute;
  495. top: 88px;
  496. height: calc(100% - 108px);
  497. display: flex;
  498. flex-direction: column;
  499. justify-content: space-between;
  500. background: rgba(#000b31, 0.7);
  501. z-index: 2;
  502. }
  503. .bottom {
  504. > * {
  505. background-color: rgba(#000b31, 0.7) !important;
  506. }
  507. }
  508. .left {
  509. //left: 0;
  510. left: 16px;
  511. }
  512. .right {
  513. //right: 0;
  514. right: 16px;
  515. }
  516. .bottom {
  517. position: absolute;
  518. bottom: 20px;
  519. width: 100%;
  520. height: 318px;
  521. left: 0;
  522. > * {
  523. margin: 0 auto;
  524. }
  525. }
  526. }
  527. }
  528. }
  529. </style>