Files
fengjie-datascreen/src/views/scenic/components/box-2.vue
2025-02-23 12:52:09 +08:00

711 lines
20 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.rate"
:title="item.occupied"
: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"
/>
</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="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" />
<RegionTop
:list="scenicStore.userPortraitData.data.provinceRate"
:width="250"
:height="366"
/>
</div>
<div class="border flex-1">
<Title3 title="购票来源" />
<TicketSource
:list="scenicStore.userPortraitData.data.channel"
:width="240"
:height="330"
/>
</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>
<big-map
v-model="show"
:carList="scenicStore.carShipData.car.list"
:shipList="scenicStore.carShipData.ship.list"
/>
</template>
<script setup>
import countup from 'vue-countup-v3'
import PubSub from 'pubsub-js'
import carIcon from '@/assets/images/car.png'
import shipIcon from '@/assets/images/ship.png'
import age from './age'
import jam from './jam'
import BigMap from './big-map'
import TrafficFlow from './traffic-flow'
import { useMap } from '@/hooks/map'
import { useScenicStore } from '@/stores/scenic'
const scenicStore = useScenicStore()
const { initMap, addMarker, marker } = useMap()
let show = ref(false)
let scenicChange = null
let carOverlays = ref([])
let shipOverlays = ref([])
const garageList = computed(() => {
return scenicStore.stopCarData.headList
})
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)
})
watch(
() => scenicStore.carShipData,
(val) => {
setTimeout(() => {
if (carOverlays.value.length > 0) {
let flag = false
for (let i = 0; i < carOverlays.value.length; i++) {
if (flag) break
for (let j = 0; j < val.car.list.length; j++) {
if (carOverlays.value[i].sim == val.car.list[j].sim) {
carOverlays.value[i].setPosition({
lng: val.car.list[j].lng,
lat: val.car.list[j].lat
})
flag = true
break
}
}
}
} else {
val.car.list.map((item, i) => {
addMarker(carIcon, [item.lng, item.lat], [36, 50])
carOverlays.value[i] = marker.value
})
}
if (shipOverlays.value.length > 0) {
let flag = false
for (let i = 0; i < shipOverlays.value.length; i++) {
if (flag) break
for (let j = 0; j < val.ship.list.length; j++) {
if (carOverlays.value[i].sim == val.ship.list[j].sim) {
carOverlays.value[i].setPosition({
lng: val.ship.list[j].lng,
lat: val.ship.list[j].lat
})
flag = true
break
}
}
}
} else {
val.ship.list.map((item, i) => {
addMarker(shipIcon, [item.lng, item.lat], [36, 50])
shipOverlays.value[i] = marker.value
})
}
}, 1000)
},
{ immediate: true }
)
onMounted(() => {
scenicChange = PubSub.subscribe('scenicChange', (msg, data) => {
carOverlays.value = []
shipOverlays.value = []
initMap('car-ship', data.lng, data.lat, 15)
})
})
onUnmounted(() => {
PubSub.unsubscribe(scenicChange)
})
</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);
}
}
.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 {
flex: 1;
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;
margin-bottom: vw(4);
}
}
.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>