Files
fengjie-datascreen/src/views/scenic/components/box-2.vue
duanliang 09dcabadda 417
2025-04-17 20:18:38 +08:00

794 lines
23 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.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="parseFloat(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.occupiedSpaces"
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>
<div v-if="dataLists.length">
<PieRow
label="停车总数"
:dataList="dataLists"
:total="carTotal"
:width="360"
:height="300"
/>
</div>
<div class="null-box" v-else> 暂无数据 </div>
</div>
</div>
</div>
<div class="box-2">
<Title1 title="异常信息 " />
<div @click="handleToWorkOrder" class="count-box flex">
<count-item
v-for="item in 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">
<div class="look-box">
<Title1 title="交通信息" />
<div @click="hanldeLookMap" class="look-btn">查看详情</div>
</div>
<div class="count-box flex">
<count-item
v-for="(item, index) in scenicStore.trafficData.infoList"
:key="index"
:label="item.name"
:count="item?.value || 0"
/>
</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.timeRate"
/>
</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-7.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"
/>
<traMap :longitude="longitude" :latitude="latitude" v-model="traMapShow"></traMap>
</template>
<script setup>
import countup from 'vue-countup-v3'
import PubSub from 'pubsub-js'
import carIcon from '@/assets/images/car.png'
import carStopIcon from '@/assets/images/car-stop.png'
import carOfflineIcon from '@/assets/images/car-offline.png'
import shipIcon from '@/assets/images/ship.png'
import shipStopIcon from '@/assets/images/ship-stop.png'
import shipOfflineIcon from '@/assets/images/ship-offline.png'
import age from './age'
import jam from './jam'
import BigMap from './big-map'
import traMap from './tra-map'
import TrafficFlow from './traffic-flow'
import { useRouter } from 'vue-router'
import { useMap } from '@/hooks/map'
import { useScenicStore } from '@/stores/scenic'
const router = useRouter()
const scenicStore = useScenicStore()
const { initMap, addMarker, marker } = useMap()
let show = ref(false)
let scenicChange = null
let carOverlays = ref([])
let shipOverlays = ref([])
let traMapShow = ref(false)
//查看交通信息
let latitude = ref('')
let longitude = ref('')
const hanldeLookMap = () => {
router.push('/traffic')
// traMapShow.value = true
}
const handleToWorkOrder = () => {
router.push('/workOrder')
}
const garageList = computed(() => {
return scenicStore.stopCarData.headList
})
const alarmTotal = computed(() => {
return scenicStore.secureData.dataList.reduce((pre, cur) => {
return pre + cur.count
}, 0)
})
const stopCarDataLists = computed(() => {
return scenicStore.stopCarData.dataLists
})
const carTotal = computed(() => {
return dataLists.value.reduce((pre, cur) => {
return pre + parseInt(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 dataLists = ref([])
watch(
() => scenicStore.stopCarData.dataLists,
(val) => {
console.log(val.length, 'watch 监听')
if (val.length) {
dataLists.value = val
} else {
dataLists.value = []
}
},
{ immediate: true }
)
let headList = ref([])
watch(
() => scenicStore.secureData.headList,
(val) => {
if (val.length) {
headList.value = scenicStore.secureData.headList
} else {
headList.value = []
}
},
{ immediate: true }
)
watch(
() => scenicStore.carShipData,
(val) => {
return false
setTimeout(() => {
if (carOverlays.value.length > 0) {
for (let i = 0; i < carOverlays.value.length; i++) {
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
})
continue
}
}
}
} else {
val.car.list.map((item, i) => {
if (item.status == '行驶') {
addMarker(carIcon, [item.lng, item.lat], [36, 50])
}
if (item.status == '离线') {
addMarker(carOfflineIcon, [item.lng, item.lat], [36, 50])
}
if (item.status == '静止') {
addMarker(carStopIcon, [item.lng, item.lat], [36, 50])
}
carOverlays.value[i] = marker.value
})
}
if (shipOverlays.value.length > 0) {
for (let i = 0; i < shipOverlays.value.length; i++) {
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
})
continue
}
}
}
} else {
val.ship.list.map((item, i) => {
if (item.status == '行驶') {
addMarker(shipIcon, [item.lng, item.lat], [36, 50])
}
if (item.status == '离线') {
addMarker(shipOfflineIcon, [item.lng, item.lat], [36, 50])
}
if (item.status == '静止') {
addMarker(shipStopIcon, [item.lng, item.lat], [36, 50])
}
shipOverlays.value[i] = marker.value
})
}
}, 1000)
},
{ immediate: true }
)
onMounted(() => {
scenicChange = PubSub.subscribe('scenicChange', (msg, data) => {
latitude.value = data.lat
longitude.value = data.lng
carOverlays.value = []
shipOverlays.value = []
initMap('car-ship', data.lng, data.lat, 15)
})
})
onUnmounted(() => {
PubSub.unsubscribe(scenicChange)
})
</script>
<style scoped lang="scss">
.null-box {
width: vw(360);
height: vh(300);
display: flex;
align-items: center;
color: #fff;
text-align: center;
font-size: vw(20);
justify-content: center;
}
.look-box {
position: relative;
.look-btn {
position: absolute;
right: vw(20);
top: 50%;
transform: translateY(-50%);
padding: vw(12);
display: flex;
align-items: center;
background: #0a4190;
border-radius: vw(4);
font-size: vw(14);
color: #fff;
}
}
: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>