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
567 lines
15 KiB
<template>
|
|
<div class="borads-index">
|
|
<screen-adapter class="adapter">
|
|
<div class="boards-container">
|
|
<div class="center" title="使用鼠标左键、滚轮实现拖拽、放大缩小。"></div>
|
|
<div class="top">
|
|
<div class="title" @click="reset" title="点击此标题将鸟瞰图恢复成初始状态。">{{ $store.state.user.tenantName || '平台看板' }}</div>
|
|
<div class="time" v-if="timeCount && timeCount.ts > 0">
|
|
<div class="time-left">
|
|
{{ $m(timeCount.ts).format('HH:mm:ss') }}
|
|
</div>
|
|
<div class="time-right">
|
|
<div>{{ $date.getDay(timeCount.ts) }}</div>
|
|
<div>
|
|
{{ $m(timeCount.ts).format('YYYY/MM/DD') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="pop">
|
|
<pop v-for="(item, index) in pop" :key="index" :item="item" :index="index"></pop>
|
|
</div>
|
|
<div class="left">
|
|
<block v-for="(item, index) in left" :key="index" :item="item" :data-form="dataForm" @change-code="dataForm.code = $event">
|
|
<component :is="item.comp" :key="index" :item="item" :option="item.option" tip-loop></component>
|
|
</block>
|
|
</div>
|
|
<div class="right">
|
|
<block v-for="(item, index) in right" :key="index" :item="item" :data-form="dataForm" @change-code="dataForm.code = $event">
|
|
<component :is="item.comp" :key="index" :item="item" :option="item.option" tip-loop></component>
|
|
</block>
|
|
</div>
|
|
<!--<div class="bottom">
|
|
<block size-type="2" :item="bottom">
|
|
<component :is="bottom.comp" :item="bottom" :option="bottom.option" tip-loop></component>
|
|
</block>
|
|
</div>-->
|
|
</div>
|
|
<tooltip :data="toolTipData" />
|
|
</screen-adapter>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, onBeforeUnmount } from 'vue';
|
|
import ScreenAdapter from '@/components/screen-adapter.vue';
|
|
import Block from './comp/block.vue';
|
|
import Bar from './comp/bar.vue';
|
|
import Pop from './comp/pop.vue';
|
|
import timeCountHooks from '@/hooks/time-count';
|
|
import { useFetch } from '@/hooks/fetch';
|
|
import utilService from '@/service/utilService';
|
|
import Chart from '@/components/chart.vue';
|
|
import { costStatistic, elecPayload, monthEnergyUsageTrend, usageRanking, energyUsageDistribution } from './common/model';
|
|
import { IObject } from '@/types/interface';
|
|
import moment from 'moment';
|
|
import toolHooks from '@/hooks/tool';
|
|
import { init, setPointerLineStatusAll } from './common/style';
|
|
import Tooltip from './comp/tooltip.vue';
|
|
import switchScreenHooks from '@/hooks/switch-screen';
|
|
export default defineComponent({
|
|
components: {
|
|
ScreenAdapter,
|
|
Tooltip,
|
|
Block,
|
|
Chart,
|
|
|
|
Bar,
|
|
Pop,
|
|
},
|
|
setup() {
|
|
const { timeCount } = timeCountHooks();
|
|
const { generateSvg2Dom } = toolHooks();
|
|
|
|
const state = reactive({
|
|
opt: { scale: 0.8 },
|
|
dragStretch: null,
|
|
toolTipData: {},
|
|
timer: null,
|
|
dataForm: {
|
|
code: '电',
|
|
codeMap: { 电: 'kWh', 水: 't', 蒸汽: 't', 压缩空气: 'Nm³' },
|
|
},
|
|
timeCount,
|
|
pop: [
|
|
{
|
|
key: 'yearCarbon',
|
|
icon: '',
|
|
label: '年碳排放量',
|
|
value: '--',
|
|
unit: 'tCO₂',
|
|
toFixed: 2,
|
|
},
|
|
{
|
|
key: 'monthConsumptionValue',
|
|
icon: '',
|
|
label: '月综合能耗',
|
|
value: '--',
|
|
unit: 'tce',
|
|
},
|
|
{
|
|
key: 'yearConsumptionValue',
|
|
icon: '',
|
|
label: '年综合能耗',
|
|
value: '--',
|
|
unit: 'tce',
|
|
},
|
|
],
|
|
left: [
|
|
{
|
|
comp: 'bar',
|
|
title: '月用能统计',
|
|
data: [
|
|
{
|
|
key: 'electricDayValue',
|
|
icon: 'elec',
|
|
label: '电用量',
|
|
value: '',
|
|
unit: 'kWh',
|
|
},
|
|
{
|
|
key: 'waterDayValue',
|
|
icon: 'water',
|
|
label: '水用量',
|
|
value: '',
|
|
unit: 't',
|
|
},
|
|
{
|
|
key: 'steamDayValue',
|
|
icon: 'steam',
|
|
label: '蒸汽用量',
|
|
value: '',
|
|
unit: 't',
|
|
},
|
|
{
|
|
key: 'airDayValue',
|
|
icon: 'air',
|
|
label: '压缩空气用量',
|
|
value: '',
|
|
unit: 'Nm³',
|
|
},
|
|
{
|
|
key: 'gasValue',
|
|
icon: 'gas',
|
|
label: '天然气用量',
|
|
value: '',
|
|
unit: 'Nm³',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: '年用能统计',
|
|
comp: 'bar',
|
|
data: [
|
|
{
|
|
key: 'electricDayValue',
|
|
icon: 'elec',
|
|
label: '电用量',
|
|
value: '',
|
|
unit: 'kWh',
|
|
},
|
|
{
|
|
key: 'waterDayValue',
|
|
icon: 'water',
|
|
label: '水用量',
|
|
value: '',
|
|
unit: 't',
|
|
},
|
|
{
|
|
key: 'steamDayValue',
|
|
icon: 'steam',
|
|
label: '蒸汽用量',
|
|
value: '',
|
|
unit: 't',
|
|
},
|
|
{
|
|
key: 'airDayValue',
|
|
icon: 'air',
|
|
label: '压缩空气用量',
|
|
value: '',
|
|
unit: 'Nm³',
|
|
},
|
|
{
|
|
key: 'gasValue',
|
|
icon: 'gas',
|
|
label: '天然气用量',
|
|
value: '',
|
|
unit: 'Nm³',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
comp: 'chart',
|
|
title: '当日电力负荷',
|
|
option: elecPayload(),
|
|
unit: 'kWh',
|
|
},
|
|
],
|
|
right: [
|
|
{
|
|
comp: 'chart',
|
|
title: '能耗统计(月)',
|
|
option: costStatistic(),
|
|
unit: '',
|
|
},
|
|
{
|
|
comp: 'chart',
|
|
title: '用能排行(月)',
|
|
option: usageRanking(),
|
|
unit: 'kWh',
|
|
select: true,
|
|
},
|
|
{
|
|
title: '当月用能趋势',
|
|
comp: 'chart',
|
|
option: monthEnergyUsageTrend(),
|
|
unit: 'kWh',
|
|
select: true,
|
|
},
|
|
],
|
|
bottom: {
|
|
comp: 'chart',
|
|
title: '24小时用能分布情况',
|
|
option: energyUsageDistribution(),
|
|
},
|
|
}) as IObject;
|
|
|
|
const changeType = (type: string) => {
|
|
const c = state.dataForm.code;
|
|
const u = state.dataForm.codeMap[c];
|
|
state.left[2].unit = u;
|
|
state.right[1].unit = u;
|
|
|
|
//当月用能趋势
|
|
useFetch('/board/monthEnergy/analyse', {
|
|
data: {
|
|
month: moment().format('YYYY-MM'),
|
|
type,
|
|
},
|
|
cb: (res: any) => {
|
|
if (utilService.isValidObject(res) && utilService.isValidArray(res.currentMonthData)) {
|
|
const d = res.currentMonthData;
|
|
const c = state.dataForm.code;
|
|
const u = state.dataForm.codeMap[c];
|
|
state.right[2].option = monthEnergyUsageTrend(d, u);
|
|
}
|
|
},
|
|
});
|
|
|
|
//用能排行(月)
|
|
useFetch('/board/monthEnergy/ranking', {
|
|
data: {
|
|
day: moment().format('YYYY-MM'),
|
|
type,
|
|
},
|
|
cb: (res: any) => {
|
|
if (utilService.isValidArray(res)) {
|
|
const c = state.dataForm.code;
|
|
const u = state.dataForm.codeMap[c];
|
|
state.right[1].option = usageRanking(res, u);
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const fn = () => {
|
|
//三个小气泡
|
|
useFetch('/board/carbonAndConsumption', {
|
|
cb: (res: any) => {
|
|
if (utilService.isValidObject(res)) {
|
|
for (const item of state.pop) {
|
|
item.value = res[item.key] ? (item.toFixed ? utilService.number2Fixed(res[item.key]) : res[item.key]) : '--';
|
|
}
|
|
}
|
|
},
|
|
});
|
|
//月用能统计
|
|
useFetch('/board/eachEnergy', {
|
|
cb: (res: any) => {
|
|
if (utilService.isValidObject(res)) {
|
|
for (const item of state.left[0].data) {
|
|
item.value = res[item.key] ?? '--';
|
|
}
|
|
}
|
|
},
|
|
});
|
|
//年用能统计
|
|
useFetch('/board/eachEnergyYear', {
|
|
cb: (res: any) => {
|
|
if (utilService.isValidObject(res)) {
|
|
for (const item of state.left[1].data) {
|
|
item.value = res[item.key] ?? '--';
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
//能耗费用统计
|
|
useFetch('/board/energyCost', {
|
|
data: {
|
|
day: moment().format('YYYY-MM-DD'),
|
|
},
|
|
cb: (res: any) => {
|
|
if (utilService.isValidArray(res)) {
|
|
state.right[0].option = costStatistic(res);
|
|
}
|
|
},
|
|
});
|
|
|
|
//当日电力负荷
|
|
useFetch('/board/qcDayLoad/analyse', {
|
|
data: {
|
|
day: moment().format('YYYY-MM-DD'),
|
|
},
|
|
cb: (res: any) => {
|
|
if (utilService.isValidObject(res) && utilService.isValidArray(res.currentMonthData)) {
|
|
const d = res.currentMonthData;
|
|
state.left[2].option = elecPayload(d);
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
//24小时用能分布情况
|
|
// useFetch('/board/dayEnergy/trend', {
|
|
// data: {
|
|
// //day: '2025-04-01',
|
|
// day: moment().subtract(1, 'days').format('YYYY-MM-DD'),
|
|
// },
|
|
// cb: (res: any) => {
|
|
// if (utilService.isValidObject(res)) {
|
|
// state.bottom.option = energyUsageDistribution(res);
|
|
// }
|
|
// },
|
|
// });
|
|
|
|
changeType(state.dataForm.code);
|
|
fn();
|
|
|
|
const reset = () => {
|
|
setPointerLineStatusAll('none');
|
|
|
|
state.dragStretch.reset();
|
|
};
|
|
|
|
const methods = { changeType, reset };
|
|
|
|
watch(
|
|
() => state.dataForm.code,
|
|
(newCode: string) => {
|
|
changeType(newCode);
|
|
},
|
|
);
|
|
|
|
const getSvg = () => {
|
|
const target = document.querySelector('.boards-container .center') as HTMLElement;
|
|
generateSvg2Dom('/cdn/normal_bg.svg', target, () => {
|
|
target.style.transform = 'scale(0.7)';
|
|
|
|
nextTick(() => {
|
|
init(state, target);
|
|
// 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>';
|
|
});
|
|
});
|
|
};
|
|
|
|
onMounted(() => {
|
|
getSvg();
|
|
state.timer = setInterval(() => {
|
|
changeType(state.dataForm.code);
|
|
fn();
|
|
}, 1 * 60 * 1000);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
clearInterval(state.timer);
|
|
state.timer = null;
|
|
});
|
|
|
|
return { ...toRefs(state), ...methods, ...switchScreenHooks({ screen: '1' }) };
|
|
},
|
|
});
|
|
</script>
|
|
<!-- <style lang="scss">
|
|
.rr-view-ctx-card {
|
|
height: 100%;
|
|
|
|
.el-card__body {
|
|
position: relative;
|
|
height: 100%;
|
|
}
|
|
}
|
|
</style> -->
|
|
|
|
<style lang="less" scoped>
|
|
.borads-index {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: #000b31;
|
|
|
|
.adapter {
|
|
.boards-container {
|
|
position: relative;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
> .top {
|
|
position: relative;
|
|
z-index: 2;
|
|
height: 88px;
|
|
|
|
> .title {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
color: #e2edff;
|
|
letter-spacing: 6px;
|
|
background: url('@/assets/images/njhl/title.png') no-repeat center;
|
|
background-size: 100% 100%;
|
|
text-align: center;
|
|
font-weight: bold;
|
|
font-size: 40px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
> .time {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 3px;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
> .time-left {
|
|
position: relative;
|
|
font-size: 32px;
|
|
margin: 0 60px;
|
|
background: linear-gradient(to top, #fff 30%, #3e576f 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
font-family: 'Alibaba PuHuiTi Heavy'; //阿里巴巴 普惠体
|
|
|
|
&:after {
|
|
position: absolute;
|
|
content: '';
|
|
background: #3e576f;
|
|
right: -30px;
|
|
top: 50%;
|
|
width: 1px;
|
|
height: 40px;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
}
|
|
|
|
> .time-right {
|
|
color: rgba(#fff, 0.7);
|
|
}
|
|
}
|
|
}
|
|
|
|
> .center {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
width: 1920px;
|
|
height: 1080px;
|
|
cursor: move;
|
|
//background: url("@/assets/images/njhl/bg.png");
|
|
//transform: translate(-50%, -50%);
|
|
/* 初始阴影设置(透明) */
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
border-radius: inherit;
|
|
//opacity: 0;
|
|
//transition: opacity 0.4s;
|
|
box-shadow: inset 0 0 80px 30px #000b31;
|
|
}
|
|
|
|
&:hover::before {
|
|
// opacity: 1;
|
|
}
|
|
}
|
|
|
|
.icon {
|
|
opacity: 0.4;
|
|
position: absolute;
|
|
top: 8px;
|
|
right: 120px;
|
|
z-index: 999;
|
|
::v-deep svg {
|
|
transition: all 80ms;
|
|
// opacity: 0.8;
|
|
//&:hover {
|
|
// width: 35px !important;
|
|
// height: 35px !important;
|
|
// opacity: 1;
|
|
//}
|
|
}
|
|
}
|
|
|
|
.pop {
|
|
position: absolute;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
height: 86px;
|
|
left: 50%;
|
|
top: 88px;
|
|
transform: translate(-50%);
|
|
|
|
> * {
|
|
margin: 0 40px;
|
|
background: rgba(#000b31, 0.7);
|
|
}
|
|
}
|
|
|
|
.left,
|
|
.right {
|
|
position: absolute;
|
|
top: 88px;
|
|
height: calc(100% - 108px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
background: rgba(#000b31, 0.7);
|
|
z-index: 2;
|
|
}
|
|
|
|
.bottom {
|
|
> * {
|
|
background-color: rgba(#000b31, 0.7) !important;
|
|
}
|
|
}
|
|
.left {
|
|
//left: 0;
|
|
left: 16px;
|
|
}
|
|
|
|
.right {
|
|
//right: 0;
|
|
right: 16px;
|
|
}
|
|
|
|
.bottom {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
width: 100%;
|
|
height: 318px;
|
|
left: 0;
|
|
> * {
|
|
margin: 0 auto;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|