Files
fengjie-datascreen/src/views/scenic/components/box-2.vue
2025-01-17 19:15:14 +08:00

679 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="container">
<div class="flex">
<div class="box mr-8">
<Title1 title="排队信息" />
<div class="count-box flex justify-between">
<count-item
label="今日出票"
:count="scenicStore.scenicQueueData.header.jrcp"
suffix="张"
/>
<count-item
label="今日接待人数"
:count="scenicStore.scenicQueueData.header.jrjdrs"
suffix="人"
/>
<count-item
label="排队人数"
:count="scenicStore.scenicQueueData.header.pdrs"
suffix="人"
/>
<count-item
label="排队持续时间"
:count="scenicStore.scenicQueueData.header.pdcxsj"
suffix="分钟"
/>
</div>
<div class="border">
<div class="pt-10">
<Title3 title="景区排队人数" />
</div>
<Line
:width="490"
:height="300"
:data="scenicQueueData"
:xAxisData="scenicQueueXAxisData"
/>
</div>
</div>
<div class="box mr-8">
<Title1 title="景区承载" />
<div class="flex">
<circle-progress
:width="200"
:height="70"
:value="scenicStore.scenicBearData.header.jqRate"
:title="`${scenicStore.scenicBearData.header.jqRate}%`"
subTitle="景区承载率"
/>
<div class="flex flex-1 justify-between">
<count-item
label="景区当前人数"
:count="scenicStore.scenicBearData.header.jrjdrs"
suffix="人"
/>
<count-item
label="景区最大承载"
:count="scenicStore.scenicBearData.header.jqzdcz"
suffix="人"
/>
</div>
</div>
<div class="border">
<div class="pt-10">
<Title3 title="今日景区承载量" />
</div>
<Line
:width="490"
:height="300"
:data="scenicBearData"
:xAxisData="scenicBearXAxisData"
/>
</div>
</div>
<div class="box-1 mr-8">
<Title1 title="停车信息" />
<div class="flex">
<div class="height70 flex flex-1">
<circle-progress
v-for="(item, index) in garageList"
:key="index"
:width="140"
:height="70"
:value="item.ratio"
:title="item.over"
:subTitle="`${item.name}剩余`"
/>
</div>
<div class="ml-20 flex flex-1 justify-between">
<count-item label="总车位数" :count="scenicStore.stopCarData.info.count" suffix="个" />
<count-item
label="剩余车位数"
:count="scenicStore.stopCarData.info.remain"
suffix="个"
/>
</div>
</div>
<div class="flex">
<div class="border mr-8 flex-1">
<div class="pt-10">
<Title3 title="今日景区承载量" />
</div>
<Line :width="360" :height="300" :data="carBearData" :xAxisData="carBearXAxisData" />
</div>
<div class="border flex-1">
<div class="pt-10">
<Title3 title="车辆归属地占比" />
</div>
<PieRow
label="停车总数"
:dataList="scenicStore.stopCarData.dataLists"
:total="carTotal"
:width="360"
:height="300"
/>
</div>
</div>
</div>
<div class="box-2">
<Title1 title="安全信息 " />
<div class="count-box flex">
<count-item
v-for="item in scenicStore.secureData.headList"
:label="item.name"
:count="item.count"
:type="item.type"
:color="item.type == 0 ? '#0096FF' : '#E21B1B'"
suffix="次"
/>
</div>
<div class="border flex-1">
<div class="pt-10">
<Title3 title="异常告警占比" />
</div>
<PieRow
label="安全告警占比"
:dataList="scenicStore.secureData.dataList"
:total="alarmTotal"
:width="440"
:height="300"
/>
</div>
</div>
</div>
<div class="flex mt-8">
<div class="box-3 mr-8">
<Title1 title="交通信息" />
<div class="count-box flex">
<count-item
v-for="(item, index) in scenicStore.trafficData.infoList"
:key="index"
:label="item.name"
:count="item.value"
suffix="张"
/>
</div>
<div class="flex">
<div class="border mr-8">
<Title3 title="今日交通负载" />
<traffic-flow :list="scenicStore.trafficData.data.congestion" />
</div>
<div class="border mr-8">
<Title3 title="拥堵次数占比" />
<jam
:width="220"
:height="300"
sub-title="拥堵频次总数"
:list="scenicStore.trafficData.data.countRate"
/>
</div>
<div class="border">
<Title3 title="拥堵时长占比" />
<jam
:width="220"
:height="300"
sub-title="拥堵总时长"
:list="scenicStore.trafficData.data.countRate"
/>
</div>
</div>
</div>
<div class="box-4 mr-8">
<Title1 title="用户画像" />
<div class="flex">
<div class="border mr-8 flex-1">
<Title3 title="年龄/性别占比" />
<age :list="scenicStore.userPortraitData.data.ageRate" />
<div class="count">总人数<countup :end-val="ageTotal" /></div>
<div
class="cell pt-20"
v-for="(item, index) in scenicStore.userPortraitData.data.genderRate"
:key="index"
>
<img v-if="item.name == '男'" class="icon" src="@/assets/images/man.png" />
<img v-if="item.name == '女'" class="icon" src="@/assets/images/woman.png" />
<div class="bg">
<span class="text">{{ item.name }}</span>
<div class="progress">
<el-progress
:percentage="parseFloat(item.value)"
:show-text="false"
:color="
item.name == '女'
? 'linear-gradient( to right, #0A4482 0%, #FF7021 100%)'
: 'linear-gradient( to right, #074D90 0%, #55E0FF 100%)'
"
/>
</div>
<span :class="[item.name == '男' ? 'man' : 'woman']">{{ item.value }}%</span>
</div>
</div>
</div>
<div class="border mr-8 flex-1">
<Title3 title="客源地分析TOP5" />
<top />
</div>
<div class="border flex-1">
<Title3 title="购票来源" />
<div class="count">总人数<countup :end-val="channelTotal" /></div>
<ticket />
</div>
</div>
</div>
<div class="box-5">
<Title1 title="车船信息" />
<div class="flex mb-6">
<div class="border mr-8 pt-10 pb-10">
<div class="flex justify-between">
<Title2 title="景区车辆" />
<div class="sum">
<span>总数</span>
<countup
:end-val="
scenicStore.carShipData.car.count?.drivingCount +
scenicStore.carShipData.car.count?.nonDrivingCount
"
/>
</div>
</div>
<div class="car-box mt-10">
<img class="icon" src="@/assets/images/icon-6.png" />
<div class="car-item pr-20">
<div class="label">运营车辆</div>
<div class="value flex">
<countup :end-val="scenicStore.carShipData.car.count?.drivingCount" />
<span class="suffix"> </span>
</div>
</div>
<div class="car-item">
<div class="label">空闲车辆</div>
<div class="value flex">
<countup :end-val="scenicStore.carShipData.car.count?.nonDrivingCount" />
<span class="suffix"> </span>
</div>
</div>
</div>
</div>
<div class="border pt-10 pb-10">
<div class="flex justify-between">
<Title2 title="景区船只" />
<div class="sum">
<span>总数</span>
<countup
:end-val="
scenicStore.carShipData.ship.count?.drivingCount +
scenicStore.carShipData.ship.count?.nonDrivingCount
"
/>
</div>
</div>
<div class="car-box mt-10">
<img class="icon" src="@/assets/images/icon-6.png" />
<div class="car-item pr-20">
<div class="label">运营船只</div>
<div class="value flex">
<countup :end-val="scenicStore.carShipData.ship.count?.drivingCount" />
<span class="suffix"></span>
</div>
</div>
<div class="car-item">
<div class="label">空闲船只</div>
<div class="value flex">
<countup :end-val="scenicStore.carShipData.ship.count?.nonDrivingCount" />
<span class="suffix"></span>
</div>
</div>
</div>
</div>
</div>
<div class="border pt-10 pb-10">
<div id="car-ship" class="car-ship" />
<img class="full" src="@/assets/images/full.png" @click="show = true" />
</div>
</div>
</div>
</div>
<BigCarShipMap v-model="show" />
</template>
<script setup>
import carIcon from '@/assets/images/car.png'
import shipIcon from '@/assets/images/ship.png'
import age from './age'
import top from './top'
import jam from './jam'
import ticket from './ticket'
import TrafficFlow from './traffic-flow'
import BigCarShipMap from './big-car-ship-map'
import countup from 'vue-countup-v3'
import { useMap } from '@/hooks/map'
import { useScenicStore } from '@/stores/scenic'
const scenicStore = useScenicStore()
const { initMap, addMarker } = useMap()
const garageList = computed(() => {
return scenicStore.stopCarData.headList.map((item) => {
return {
...item,
ratio: ((item.count - item.over) / item.count) * 100
}
})
})
const channelTotal = computed(() => {
return scenicStore.userPortraitData.data.channel.reduce((pre, cur) => {
return pre + parseInt(cur.count)
}, 0)
})
const ageTotal = computed(() => {
return scenicStore.userPortraitData.data.genderRate.reduce((pre, cur) => {
return pre + parseInt(cur.count)
}, 0)
})
const alarmTotal = computed(() => {
return scenicStore.secureData.dataList.reduce((pre, cur) => {
return pre + cur.count
}, 0)
})
const carTotal = computed(() => {
return scenicStore.stopCarData.dataLists.reduce((pre, cur) => {
return pre + cur.count
}, 0)
})
const carBearData = computed(() => {
return [{ data: scenicStore.stopCarData.dataList.map((item) => item.value) }]
})
const carBearXAxisData = computed(() => {
return scenicStore.stopCarData.dataList.map((item) => item.name)
})
const scenicBearData = computed(() => {
return [{ data: scenicStore.scenicBearData.dataList.map((item) => item.value) }]
})
const scenicBearXAxisData = computed(() => {
return scenicStore.scenicBearData.dataList.map((item) => item.name)
})
const scenicQueueData = computed(() => {
return [{ data: scenicStore.scenicQueueData.dataList.map((item) => item.value) }]
})
const scenicQueueXAxisData = computed(() => {
return scenicStore.scenicQueueData.dataList.map((item) => item.name)
})
let show = ref(false)
watch(
() => scenicStore.carShipData,
(val) => {
if (val.car.list.length > 0) {
setTimeout(() => {
val.car.list.map((item) => {
addMarker(carIcon, [109.551419, 31.050001], [36, 50])
})
val.ship.list.map((item) => {
addMarker(shipIcon, [109.551671, 31.04847], [36, 50])
})
}, 1000)
}
},
{ immediate: true }
)
onMounted(() => {
initMap('car-ship', 109.552461, 31.049607, 15)
})
</script>
<style scoped lang="scss">
:deep(.BMap_cpyCtrl) {
display: none;
}
:deep(.anchorBL) {
display: none;
}
.height70 {
height: vh(70);
}
.legend {
display: flex;
align-items: center;
justify-content: center;
@mixin icon($column) {
width: vw(50);
height: vh(60);
display: flex;
flex-direction: $column;
align-items: center;
justify-content: center;
}
&__wrapper {
display: flex;
flex-wrap: wrap;
gap: vw(8);
width: vw(170);
}
&-item {
color: #fff;
}
&-item:nth-child(1) {
@include icon(column);
background-image: url('@/assets/images/legend-item-1.png');
background-size: 100% 100%;
}
&-item:nth-child(2) {
@include icon(column);
background-image: url('@/assets/images/legend-item-1.png');
background-size: 100% 100%;
}
&-item:nth-child(3) {
@include icon(column);
background-image: url('@/assets/images/legend-item-1.png');
background-size: 100% 100%;
}
&-item:nth-child(4) {
@include icon(column-reverse);
background-image: url('@/assets/images/legend-item-2.png');
background-size: 100% 100%;
}
&-item:nth-child(5) {
@include icon(column-reverse);
background-image: url('@/assets/images/legend-item-2.png');
background-size: 100% 100%;
}
&-item:nth-child(6) {
@include icon(column-reverse);
background-image: url('@/assets/images/legend-item-2.png');
background-size: 100% 100%;
}
&-item-label {
font-weight: 400;
font-size: vw(12);
line-height: vh(14);
}
&-item-value {
font-weight: bold;
font-size: vw(16);
line-height: vh(18);
}
}
.dialog {
:deep(.el-dialog) {
width: vw(2540);
height: vh(904);
}
}
.container {
margin: vh(120) 0 0 vw(10);
.count-box {
padding: 0 vw(10);
height: vh(70);
}
.bg {
background: linear-gradient(321deg, #0b2f64 0%, #062b57 100%);
}
.border {
position: relative;
padding: 0 vw(10);
box-sizing: border-box;
background-image: url('@/assets/images/bg-3.png');
background-size: 100% 100%;
}
.box {
@extend .bg;
width: vw(510);
height: vh(475);
}
.box-1 {
@extend .bg;
height: vh(475);
}
.box-2 {
@extend .bg;
height: vh(475);
}
.box-3 {
@extend .bg;
height: vh(465);
}
.box-4 {
@extend .bg;
height: vh(465);
}
.box-5 {
@extend .bg;
height: vh(465);
}
.car-box {
width: vw(316);
height: vh(74);
display: flex;
align-items: center;
.icon {
position: absolute;
width: vw(350);
height: vw(74);
}
.car-item {
display: flex;
flex-direction: column;
align-items: center;
&:nth-child(2) {
padding-left: vw(80);
}
}
.label {
font-weight: 400;
font-size: vw(14);
color: #fff;
}
.value {
font-weight: bold;
font-size: vw(24);
color: #02f9fa;
margin-top: vh(6);
align-items: flex-end;
}
.suffix {
font-size: vw(12);
color: #02f9fa;
}
}
.sum {
font-weight: bold;
font-size: vw(18);
color: #02f9fa;
display: flex;
align-items: center;
}
.car-ship {
width: vw(660);
height: vh(250);
}
.full {
cursor: pointer;
position: absolute;
right: vw(20);
bottom: vw(20);
width: vw(50);
z-index: 999;
}
.count {
margin: vw(20) vw(20) 0 vw(20);
height: vh(24);
font-weight: bold;
font-size: vw(14);
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
border-left: vw(4) solid #37d8fc;
border-right: vw(4) solid #37d8fc;
background: rgba(0, 150, 255, 0.19);
}
.cell {
margin-left: vw(10);
display: flex;
align-items: center;
.bg {
display: flex;
align-items: center;
height: vh(20);
padding-right: vw(10);
background: linear-gradient(to right, rgba(0, 150, 255, 0), rgba(0, 150, 255, 0.17) 100%);
}
.icon {
width: vw(26);
height: vw(28);
margin-right: vw(4);
}
.text {
font-weight: 400;
font-size: vw(14);
color: rgba(255, 255, 255, 0.9);
}
.progress {
width: vw(110);
margin-left: vw(4);
}
.man {
font-weight: bold;
font-size: vw(14);
color: #02f9fa;
margin-left: vw(10);
}
.woman {
font-weight: bold;
font-size: vw(14);
color: #f15a25;
margin-left: vw(10);
}
}
.ticket-box {
margin-top: vh(20);
width: 100%;
height: vh(106);
background-color: radial-gradient(to right, #0a4190 0%, rgba(0, 77, 136, 0.6) 100%);
.title {
width: vw(253);
height: vh(28);
display: flex;
align-items: center;
background-image: url('@/assets/images/title-5.png');
background-size: 100% 100%;
& > span {
padding-left: vw(22);
font-weight: bold;
font-size: vw(15);
background-image: linear-gradient(to bottom, #ffffff 0%, #75c1ff 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent; /* 兼容WebKit内核浏览器 */
color: transparent; /* 兼容其他浏览器 */
}
}
}
.ticket-wrap {
display: flex;
align-items: center;
justify-content: space-between;
& > img {
width: vw(74);
height: vh(74);
}
& > div {
flex: 1;
height: vh(58);
display: flex;
align-items: center;
background-image: url('@/assets/images/ticket-item-bg.png');
background-size: 100% 100%;
}
.label {
padding-left: vw(10);
font-weight: 400;
font-size: vw(14);
color: rgba(255, 255, 255, 0.9);
}
.countup-wrap {
color: #02f9fa;
font-size: vw(28);
font-weight: bold;
}
}
}
</style>