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.
695 lines
20 KiB
695 lines
20 KiB
<template>
|
|
<div style="width: 100%; height: 100%; display: flex; overflow: hidden">
|
|
<div class="legend-box">
|
|
<template v-for="(item, index) in legend" :key="index">
|
|
<div class="legend-box-item" @click="selectLegend(item, index)">
|
|
<div
|
|
class="legend-box-item-img"
|
|
:style="{
|
|
'background-image': selectIndex === index ? 'url(' + selectImg + ')' : '',
|
|
}">
|
|
<img :src="item.url" />
|
|
</div>
|
|
<div class="legend-box-item-name">
|
|
{{ item.name }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<div class="map-box">
|
|
<!-- 温度 -->
|
|
<div v-if="selectIndex === 0">
|
|
<template v-for="(item, index) in sensorData" :key="index">
|
|
<div
|
|
style="position: absolute"
|
|
:style="{ left: item.styleText.left, bottom: item.styleText.bottom }">
|
|
<singleModel :dataSource="item" />
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<!-- 人数 -->
|
|
<div v-if="selectIndex === 1">
|
|
<template v-for="(item, index) in peopleData" :key="index">
|
|
<div
|
|
style="position: absolute"
|
|
:style="{ left: item.styleText.left, bottom: item.styleText.bottom }">
|
|
<singleModel :dataSource="item" />
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<!-- 新风主机 -->
|
|
<div v-if="selectIndex === 2">
|
|
<template v-for="(item, index) in newTrend" :key="index">
|
|
<div
|
|
style="position: absolute"
|
|
:style="{
|
|
left: 'calc(' + item.styleText.left + ' - 14%)',
|
|
bottom: 'calc(' + item.styleText.bottom + ' + 8%)',
|
|
'z-index': 2,
|
|
}">
|
|
<doubleSingleModel :dataSource="item" />
|
|
</div>
|
|
<img
|
|
style="width: 50px; height: 75px; position: absolute"
|
|
:style="{
|
|
left: 'calc(' + item.styleText.left + ' - 2%)',
|
|
bottom: 'calc(' + item.styleText.bottom + ' + 4%)',
|
|
transform: 'rotateY(180deg)',
|
|
}"
|
|
src="../image/liftState/lift/line.png" />
|
|
<img
|
|
style="width: 42px; height: 42px; position: absolute; z-index: 2"
|
|
:style="{ left: item.styleText.left, bottom: item.styleText.bottom }"
|
|
src="../image/airConditioningSystem/fan.png" />
|
|
</template>
|
|
</div>
|
|
<!-- 新风主机详情 -->
|
|
<a-drawer
|
|
:visible="selectIndex === 2"
|
|
:title="' '"
|
|
:mask="false"
|
|
class="newTrendModel"
|
|
placement="right"
|
|
headerStyle="background-color:rgba(33, 40, 54, 0.95); border-bottom: 2px solid #454545;"
|
|
bodyStyle="background-color: rgba(33, 40, 54, 0.95);"
|
|
@close="handleClose">
|
|
<newTrendModel :dataSource="newTrend" />
|
|
</a-drawer>
|
|
<!-- 空调箱 -->
|
|
<div v-if="selectIndex === 3">
|
|
<template v-for="(item, index) in conditioningData" :key="index">
|
|
<div
|
|
style="position: absolute; z-index: 2"
|
|
:style="{
|
|
left: 'calc(' + item.styleText.left + (item.lineType === 1 ? ' - 12.8%)' : ' - 13%)'),
|
|
bottom:
|
|
'calc(' + item.styleText.bottom + (item.lineType === 1 ? ' + 7%)' : ' - 14%)'),
|
|
}">
|
|
<doubleSingleModel :dataSource="item" />
|
|
</div>
|
|
<img
|
|
style="width: 50px; height: 75px; position: absolute"
|
|
:style="{
|
|
left: 'calc(' + item.styleText.left + (item.lineType === 1 ? ' - 1.5%)' : ' - 1.8%)'),
|
|
bottom:
|
|
'calc(' + item.styleText.bottom + (item.lineType === 1 ? ' + 4.5%)' : ' - 9%)'),
|
|
transform: item.lineType === 1 ? 'rotateY(180deg)' : 'rotate(180deg)',
|
|
}"
|
|
src="../image/liftState/lift/line.png" />
|
|
<img
|
|
style="width: 42px; height: 42px; position: absolute; z-index: 2"
|
|
:style="{ left: item.styleText.left, bottom: item.styleText.bottom }"
|
|
src="../image/airConditioningSystem/conditioningIcon.png" />
|
|
</template>
|
|
</div>
|
|
<!-- 空调箱详情 -->
|
|
<a-drawer
|
|
:visible="selectIndex === 3"
|
|
:title="' '"
|
|
:mask="false"
|
|
class="newTrendModel"
|
|
placement="right"
|
|
headerStyle="background-color:rgba(33, 40, 54, 0.95); border-bottom: 2px solid #454545;"
|
|
bodyStyle="background-color: rgba(33, 40, 54, 0.95);"
|
|
@close="handleClose">
|
|
<conditioningModel
|
|
ref="conditioningModels"
|
|
@selectConditioningData="selectConditioningData" />
|
|
</a-drawer>
|
|
<!-- 空调箱 顶部详情 现阶段没有数据 先隐藏掉 -->
|
|
<transition name="zep">
|
|
<div
|
|
v-if="selectIndex === 3 && selectConditioning && selectConditioning?.title && false"
|
|
class="newTrend-box">
|
|
<conditioningBox :selectConditioning="selectConditioning" />
|
|
</div>
|
|
</transition>
|
|
<!-- 地暖 -->
|
|
<div v-if="selectIndex === 4">
|
|
<template v-for="(item, index) in floorHeatingData" :key="index">
|
|
<div
|
|
style="position: absolute; z-index: 1"
|
|
:style="{
|
|
left:
|
|
'calc(' + item.styleText.left + (item.lineType === 1 ? ' - 12.9%)' : ' - 13.3%)'),
|
|
bottom:
|
|
'calc(' + item.styleText.bottom + (item.lineType === 1 ? ' + 8%)' : ' - 14%)'),
|
|
}">
|
|
<doubleSingleModel :dataSource="item" />
|
|
</div>
|
|
<img
|
|
style="width: 50px; height: 75px; position: absolute"
|
|
:style="{
|
|
left: 'calc(' + item.styleText.left + (item.lineType === 1 ? ' - 1.5%)' : ' - 2%)'),
|
|
bottom: 'calc(' + item.styleText.bottom + (item.lineType === 1 ? ' + 4%)' : ' - 9%)'),
|
|
transform: item.lineType === 1 ? 'rotateY(180deg)' : 'rotate(180deg)',
|
|
}"
|
|
src="../image/liftState/lift/line.png" />
|
|
<img
|
|
style="width: 42px; height: 42px; position: absolute; z-index: 1"
|
|
:style="{ left: item.styleText.left, bottom: item.styleText.bottom }"
|
|
src="../image/airConditioningSystem/floorHeatingIcon.png" />
|
|
<div
|
|
class="shape"
|
|
:style="{
|
|
width: item.styleText.width,
|
|
height: item.styleText.height,
|
|
background: item.styleText.color,
|
|
left: item.styleText.lineLeft,
|
|
bottom: item.styleText.lineBottom,
|
|
transform: item.styleText.transform,
|
|
}">
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<!-- 地暖详情 -->
|
|
<a-drawer
|
|
:visible="selectIndex === 4"
|
|
:title="' '"
|
|
:mask="false"
|
|
class="newTrendModel"
|
|
placement="right"
|
|
headerStyle="background-color:rgba(33, 40, 54, 0.95); border-bottom: 2px solid #454545;"
|
|
bodyStyle="background-color: rgba(33, 40, 54, 0.95);"
|
|
@close="handleClose">
|
|
<floorHeatingModel :dataSource="floorHeatingData" />
|
|
</a-drawer>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
/* 图片 */
|
|
import temperature from '../image/airConditioningSystem/temperature.svg';
|
|
import people from '../image/airConditioningSystem/people.svg';
|
|
import freshAir from '../image/airConditioningSystem/freshAir.svg';
|
|
import airConditioner from '../image/airConditioningSystem/airConditioner.svg';
|
|
import floorHeating from '../image/airConditioningSystem/floorHeating.svg';
|
|
import selectImg from '../image/airConditioningSystem/selectImg.png';
|
|
// 温度颜色
|
|
import sunRed from '../image/airConditioningSystem/sunRed.png';
|
|
import sunYellow from '../image/airConditioningSystem/sunYellow.png';
|
|
import sunGreen from '../image/airConditioningSystem/sunGreen.png';
|
|
|
|
import singleModel from '../components/singleModel.vue';
|
|
import doubleSingleModel from '../components/doubleSingleModel.vue';
|
|
import newTrendModel from './components/newTrendModel.vue';
|
|
import conditioningModel from './components/conditioningModel.vue';
|
|
import conditioningBox from './components/conditioningBox.vue';
|
|
import floorHeatingModel from './components/floorHeatingModel.vue';
|
|
import { http } from '/nerv-lib/util/http';
|
|
import { ventilating } from '/@/api/ventilatingSystem';
|
|
import { airConditioningSystemApi } from '/@/api/airConditioningSystem';
|
|
|
|
// 全局变量
|
|
import { items } from '/@/store/item';
|
|
// 定位数据
|
|
import {
|
|
devicePosition,
|
|
newTrendPosition,
|
|
airConditioningPosition,
|
|
floorHeatingPosition,
|
|
} from './devicePosition';
|
|
// 全局变量
|
|
const state = items();
|
|
onMounted(() => {});
|
|
onUnmounted(() => {});
|
|
//图例
|
|
const legend = ref([
|
|
{ url: temperature, name: '温度' },
|
|
{ url: people, name: '人流' },
|
|
{ url: freshAir, name: '新风主机' },
|
|
{ url: airConditioner, name: '空调箱' },
|
|
{ url: floorHeating, name: '地暖' },
|
|
]);
|
|
const newTrendModels = ref(null);
|
|
|
|
//温度传感器
|
|
const sensorData = ref([]);
|
|
//人流
|
|
const peopleData = ref([]);
|
|
//新风主机
|
|
const newTrend = ref([
|
|
{
|
|
title: 'D区新风主机',
|
|
styleText: { left: '43%', bottom: '44%' },
|
|
type: '新风主机',
|
|
unit: '℃',
|
|
number: 10,
|
|
url: freshAir,
|
|
},
|
|
{
|
|
title: 'C区新风主机',
|
|
styleText: { left: '45%', bottom: '23%' },
|
|
type: '新风主机',
|
|
number: 20,
|
|
unit: '℃',
|
|
},
|
|
{
|
|
title: 'B区新风主机',
|
|
styleText: { left: '61.5%', bottom: '54%' },
|
|
type: '新风主机',
|
|
unit: '℃',
|
|
number: 20,
|
|
url: freshAir,
|
|
},
|
|
{
|
|
title: 'A区新风主机',
|
|
styleText: { left: '63%', bottom: '36%' },
|
|
type: '新风主机',
|
|
number: 30,
|
|
unit: '℃',
|
|
},
|
|
]);
|
|
//空调箱
|
|
const conditioningData = ref([
|
|
{
|
|
title: '走廊区',
|
|
styleText: { left: '13%', bottom: '23%' },
|
|
type: '空调箱',
|
|
lineType: 1,
|
|
enableRules: 1,
|
|
unit: '℃',
|
|
number: 20,
|
|
url: freshAir,
|
|
},
|
|
{
|
|
title: '西区',
|
|
styleText: { left: '28%', bottom: '28%' },
|
|
type: '空调箱',
|
|
lineType: 1,
|
|
unit: '℃',
|
|
number: 34,
|
|
url: freshAir,
|
|
},
|
|
{
|
|
title: '西北区',
|
|
styleText: { left: '38%', bottom: '45.5%' },
|
|
type: '空调箱',
|
|
lineType: 1,
|
|
unit: '℃',
|
|
number: 34,
|
|
url: freshAir,
|
|
},
|
|
{
|
|
title: '东北区',
|
|
styleText: { left: '57%', bottom: '53%' },
|
|
lineType: 1,
|
|
type: '空调箱',
|
|
unit: '℃',
|
|
number: 34,
|
|
url: freshAir,
|
|
},
|
|
{
|
|
title: '东区',
|
|
styleText: { left: '73%', bottom: '53%' },
|
|
lineType: 1,
|
|
type: '空调箱',
|
|
unit: '℃',
|
|
number: 34,
|
|
url: freshAir,
|
|
},
|
|
{
|
|
title: '东南区',
|
|
styleText: { left: '68%', bottom: '38%' },
|
|
lineType: 2,
|
|
type: '空调箱',
|
|
unit: '℃',
|
|
number: 15,
|
|
url: freshAir,
|
|
},
|
|
{
|
|
title: '西南区',
|
|
styleText: { left: '43.5%', bottom: '22%' },
|
|
lineType: 2,
|
|
type: '空调箱',
|
|
unit: '℃',
|
|
number: 15,
|
|
url: freshAir,
|
|
},
|
|
]);
|
|
const conditioningModels = ref(null);
|
|
//选择的空调箱
|
|
const selectConditioning = ref({});
|
|
//子组件 选择空调箱
|
|
const selectConditioningData = (data: any) => {
|
|
selectConditioning.value = {};
|
|
setTimeout(() => {
|
|
selectConditioning.value = { ...data };
|
|
}, 100);
|
|
};
|
|
// 地暖
|
|
const floorHeatingData = ref([
|
|
{
|
|
title: '走廊西地暖',
|
|
styleText: { left: '13.5%', bottom: '34%' },
|
|
type: '地暖',
|
|
unit: '℃',
|
|
number: 10,
|
|
setUpNumber: 12,
|
|
},
|
|
{
|
|
title: '站厅西地暖',
|
|
styleText: { left: '19.5%', bottom: '38.5%' },
|
|
type: '地暖',
|
|
lineType: 1,
|
|
unit: '℃',
|
|
number: 10,
|
|
setUpNumber: 14,
|
|
},
|
|
{
|
|
title: '站厅西地暖',
|
|
styleText: { left: '33%', bottom: '43%' },
|
|
type: '地暖',
|
|
lineType: 1,
|
|
unit: '℃',
|
|
number: 24,
|
|
setUpNumber: 16,
|
|
},
|
|
{
|
|
title: '安检区地暖',
|
|
styleText: { left: '46%', bottom: '49.5%' },
|
|
type: '地暖',
|
|
lineType: 1,
|
|
unit: '℃',
|
|
number: 40,
|
|
setUpNumber: 18,
|
|
},
|
|
{
|
|
title: '站厅东地暖',
|
|
styleText: { left: '57.5%', bottom: '56%' },
|
|
type: '地暖',
|
|
lineType: 1,
|
|
unit: '℃',
|
|
number: 10,
|
|
setUpNumber: 20,
|
|
},
|
|
{
|
|
title: '办公东地暖',
|
|
styleText: { left: '69.5%', bottom: '62%' },
|
|
type: '地暖',
|
|
lineType: 1,
|
|
unit: '℃',
|
|
number: 10,
|
|
setUpNumber: 22,
|
|
},
|
|
]);
|
|
// 选择的图例
|
|
const selectIndex = ref(0);
|
|
const selectLegend = (item: any, index: any) => {
|
|
if (selectIndex.value !== index) {
|
|
selectIndex.value = index;
|
|
if (selectIndex.value === 2) {
|
|
setTimeout(() => {
|
|
newTrendModels.value.toggle(newTrend);
|
|
}, 100);
|
|
} else if (selectIndex.value === 3) {
|
|
if (!selectConditioning.value?.title) {
|
|
selectConditioning.value = { ...conditioningData.value[0] };
|
|
}
|
|
setTimeout(() => {
|
|
conditioningModels.value.toggle(conditioningData.value, selectConditioning.value);
|
|
}, 100);
|
|
}
|
|
}
|
|
};
|
|
const handleClose = () => {
|
|
selectIndex.value = 0;
|
|
};
|
|
//根据温度获取图片
|
|
const getSunUrl = (number: any) => {
|
|
if (number > 0 && number < 20) {
|
|
return sunGreen;
|
|
} else if (number >= 20 && number < 30) {
|
|
return sunYellow;
|
|
} else {
|
|
return sunRed;
|
|
}
|
|
};
|
|
//获取温度颜色
|
|
const getColor = (number: any) => {
|
|
if (number > 0 && number < 20) {
|
|
return '#0dffa4';
|
|
} else if (number >= 20 && number < 30) {
|
|
return '#f59a23';
|
|
} else {
|
|
return '#f36163';
|
|
}
|
|
};
|
|
// 获取传感器数据
|
|
const getSensorData = () => {
|
|
sensorData.value = [];
|
|
peopleData.value = [];
|
|
http
|
|
.get(ventilating.getSensorData, { projectId: state.projectId, siteId: state.siteId })
|
|
.then((res) => {
|
|
if (res.msg === 'success') {
|
|
res.data.forEach((item: any, index: number) => {
|
|
sensorData.value.push({
|
|
title: item.deviceInfoName,
|
|
styleText: devicePosition[index],
|
|
type: '温度',
|
|
unit: item.record.tempUnit,
|
|
number: item.record.temp,
|
|
url: getSunUrl(item.record.temp),
|
|
fontColor: getColor(item.record.temp),
|
|
});
|
|
peopleData.value.push({
|
|
title: item.deviceInfoName,
|
|
styleText: devicePosition[index],
|
|
type: '人流',
|
|
unit: item.record.peopleNumUnit,
|
|
number: item.record.peopleNum,
|
|
url: people,
|
|
});
|
|
});
|
|
}
|
|
});
|
|
};
|
|
const getnewTrendData = () => {
|
|
newTrend.value = [];
|
|
http
|
|
.get(airConditioningSystemApi.getVentHostCtrlList, {
|
|
projectId: state.projectId,
|
|
siteId: state.siteId,
|
|
})
|
|
.then((res) => {
|
|
if (res.msg === 'success') {
|
|
res.data.forEach((item: any, index: number) => {
|
|
newTrend.value.push({
|
|
title: item.deviceInfoName,
|
|
styleText: newTrendPosition[index],
|
|
type: '新风主机',
|
|
number: item.record.converterTemp,
|
|
numberUnit: item.record.converterTempUnit,
|
|
url: freshAir,
|
|
...item.record,
|
|
});
|
|
});
|
|
}
|
|
});
|
|
};
|
|
const getAcBoxCtrlList = () => {
|
|
conditioningData.value = [];
|
|
http
|
|
.get(airConditioningSystemApi.getAcBoxCtrlList, {
|
|
projectId: state.projectId,
|
|
siteId: state.siteId,
|
|
})
|
|
.then((res) => {
|
|
if (res.msg === 'success') {
|
|
res.data.forEach((item: any, index: number) => {
|
|
conditioningData.value.push({
|
|
title: item.deviceInfoName,
|
|
styleText: airConditioningPosition[index].styleText,
|
|
type: '空调箱',
|
|
numberUnit: item.record.tempUnit,
|
|
lineType: airConditioningPosition[index].lineType,
|
|
number: item.record.temp,
|
|
url: freshAir,
|
|
state: item.record.switchStatus.value,
|
|
...item.record,
|
|
});
|
|
});
|
|
}
|
|
});
|
|
};
|
|
const getTempSysCtrlList = () => {
|
|
floorHeatingData.value = [];
|
|
http
|
|
.get(airConditioningSystemApi.getTempSysCtrlList, {
|
|
projectId: state.projectId,
|
|
siteId: state.siteId,
|
|
})
|
|
.then((res) => {
|
|
if (res.msg === 'success') {
|
|
res.data.forEach((item: any, index: number) => {
|
|
floorHeatingData.value.push({
|
|
title: item.deviceInfoName,
|
|
styleText: floorHeatingPosition[index].styleText,
|
|
type: '地暖',
|
|
number: item.record.currentTemp,
|
|
numberUnit: item.record.currentTempUnit,
|
|
lineType: floorHeatingPosition[index].lineType,
|
|
...item.record,
|
|
});
|
|
});
|
|
}
|
|
});
|
|
};
|
|
const fetchData = () => {
|
|
//传感器
|
|
getSensorData();
|
|
//新风主机
|
|
getnewTrendData();
|
|
//获取空调箱
|
|
getAcBoxCtrlList();
|
|
//获取地暖
|
|
getTempSysCtrlList();
|
|
};
|
|
//实时获取数据 暂定一分钟
|
|
const intervalId = setInterval(fetchData, 60000);
|
|
onMounted(() => {
|
|
fetchData();
|
|
});
|
|
onUnmounted(() => {
|
|
clearInterval(intervalId);
|
|
});
|
|
</script>
|
|
<style lang="less">
|
|
.legend-box {
|
|
width: 80px;
|
|
height: 100%;
|
|
background-color: rgba(33, 40, 54, 0.95);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
padding: 15px 5px;
|
|
.legend-box-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
width: 70px;
|
|
height: 70px;
|
|
.legend-box-item-img {
|
|
width: 50px;
|
|
height: 60px;
|
|
padding-top: 5px;
|
|
flex: 1;
|
|
cursor: pointer;
|
|
background-size: 100% 100%;
|
|
text-align: center;
|
|
img {
|
|
transition: all ease 0.2s;
|
|
}
|
|
img:hover {
|
|
transform: scale(1.1);
|
|
}
|
|
}
|
|
.legend-box-item-name {
|
|
transform: translateY(-5px);
|
|
text-align: center;
|
|
color: white;
|
|
font-size: 13px;
|
|
}
|
|
}
|
|
// .legend-box-item {
|
|
// border: 1px solid red;
|
|
// width: 100%;
|
|
// height: 60px;
|
|
// padding: 0px 0;
|
|
// cursor: pointer;
|
|
// .legend-box-item-img {
|
|
// border: 2px solid orange;
|
|
// width: 100%;
|
|
// height: 50px;
|
|
// display: flex;
|
|
// justify-content: center; /* 水平居中 */
|
|
// align-items: center; /* 垂直居中 */
|
|
// position: relative;
|
|
// background-size: 150% 150%;
|
|
// background-repeat: no-repeat;
|
|
// img {
|
|
// position: absolute;
|
|
// transform: scale(1.2);
|
|
// }
|
|
// }
|
|
// .legend-box-item-name {
|
|
// width: 100%;
|
|
// display: flex;
|
|
// justify-content: center; /* 水平居中 */
|
|
// align-items: center; /* 垂直居中 */
|
|
// color: white;
|
|
// font-size: 12px;
|
|
// }
|
|
// }
|
|
}
|
|
.map-box {
|
|
width: 95%;
|
|
height: 100%;
|
|
position: relative;
|
|
background-image: url(../image/floor-1.png);
|
|
background-size: 100% 100%;
|
|
background-repeat: no-repeat;
|
|
overflow: hidden;
|
|
border-radius: 0px 4px 4px 0px;
|
|
}
|
|
.newTrendModel {
|
|
overflow-y: auto;
|
|
.anticon {
|
|
color: white !important;
|
|
margin-left: -10px;
|
|
}
|
|
}
|
|
.newTrend-box {
|
|
width: 990px;
|
|
height: 146px;
|
|
background-color: #282d33;
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 380px;
|
|
border-radius: 12px;
|
|
padding: 12px;
|
|
color: white;
|
|
}
|
|
.zep-enter-active,
|
|
.zep-leave-active {
|
|
animation-duration: 0.6s; /* 增加动画持续时间 */
|
|
animation-timing-function: ease-in-out; /* 使用更平滑的动画曲线 */
|
|
}
|
|
|
|
.zep-enter-active {
|
|
animation-name: bounce-enter;
|
|
}
|
|
|
|
.zep-leave-active {
|
|
animation-name: bounce-leave;
|
|
}
|
|
|
|
/* 进入动画 */
|
|
@keyframes bounce-enter {
|
|
0% {
|
|
opacity: 0; /* 初始时完全透明 */
|
|
}
|
|
100% {
|
|
opacity: 1; /* 结束时完全不透明 */
|
|
}
|
|
}
|
|
|
|
/* 离开动画 */
|
|
@keyframes bounce-leave {
|
|
0% {
|
|
opacity: 1; /* 初始时完全不透明 */
|
|
}
|
|
100% {
|
|
opacity: 0; /* 结束时完全透明 */
|
|
}
|
|
}
|
|
.shape {
|
|
background-color: #36d792;
|
|
transform: rotateX(23deg) rotateZ(-13deg) skew(17deg) rotateY(-7deg);
|
|
position: absolute;
|
|
perspective: 900px;
|
|
perspective-origin: 900px -120px;
|
|
}
|
|
</style>
|
|
|