Commit 9adbca23 authored by wangdonghao's avatar wangdonghao

新增功能

parent fb2fac6c
...@@ -41,3 +41,18 @@ export const getVideoUrl = (params) => { ...@@ -41,3 +41,18 @@ export const getVideoUrl = (params) => {
export const getFlowSections = (data) => { export const getFlowSections = (data) => {
return request.post({ url: '/qxsd/tunnel/sectionCongestion', data }) return request.post({ url: '/qxsd/tunnel/sectionCongestion', data })
} }
// 获取事件告警详情
export const getEventDetail = (params) => {
return request.get({ url: '/qxsd/event-detail/alarm', params })
}
// 获取隧道统计
export const getTunnelReport = (data) => {
return request.post({ url: '/qxsd/report/tunnelStatisticalTodayByHole', data })
}
// 获取事件统计
export const getEventReport = (data) => {
return request.post({ url: '/qxsd/report/tunnelStatisticalByEventType', data })
}
\ No newline at end of file
...@@ -276,6 +276,7 @@ export const IconJson = { ...@@ -276,6 +276,7 @@ export const IconJson = {
'wallet-filled', 'wallet-filled',
'warning', 'warning',
'warning-filled', 'warning-filled',
'warn-triangle-filled',
'watch', 'watch',
'watermelon', 'watermelon',
'wind-power', 'wind-power',
......
...@@ -13,13 +13,24 @@ export const EVENT_TYPES = [ ...@@ -13,13 +13,24 @@ export const EVENT_TYPES = [
{ name: '逆行', key: '4' }, { name: '逆行', key: '4' },
{ name: '拥堵', key: '5' }, { name: '拥堵', key: '5' },
{ name: '可疑事件', key: '6' }, { name: '可疑事件', key: '6' },
{ name: '追尾', key: '7' }, { name: '事故', key: '7' },
{ name: '停车', key: '8' }, { name: '违章停车', key: '8' },
{ name: '超速', key: '16' }, { name: '超速', key: '16' },
{ name: '其他', key: '100' }, { name: '其他', key: '100' },
{ name: '', key: '99' }, { name: '', key: '99' },
{ name: '机动车闯禁行', key: '22' }, { name: '机动车闯禁行', key: '22' },
{ name: '大型客车', key: '21' }, { name: '大型客车', key: '21' },
{ name: '缓慢行驶', key: '9' },
{ name: '超慢行驶', key: '10' },
{ name: '停驶', key: '11' },
{ name: '行人', key: '12' },
{ name: '抛洒物', key: '13' },
{ name: '火灾', key: '14' },
{ name: '异常天气', key: '15' },
{ name: '闯入', key: '17' },
{ name: '不按车道行驶', key: '18' },
{ name: '非机动车', key: '19' },
{ name: '危化品车辆', key: '20' },
] ]
export const MODEL_CLASS = [ export const MODEL_CLASS = [
......
import { store } from '@/store' import { store } from '@/store'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
interface EventNotify {
id?: string // 事件ID
eventType?: string // 事件类型(对应EVENT_TYPES)
eventStartTime?: string // 事件开始时间
eventEndTime?: string // 事件结束时间
eventCarPlate?: string // 车牌号
eventSituation?: string // 事件描述
eventStatus?: number // 事件状态(对应EVENT_STATUS)
}
interface TrafficType { interface TrafficType {
waves: string[], // 标注车辆,事件相关等 waves: string[] // 标注车辆,事件相关等
nowEvents: any[], // 当前事件标注 nowEvents: any[] // 当前事件标注
oldEvents: any[] // 回溯事件标注 oldEvents: any[] // 回溯事件标注
eventCount: number // 事件数量
eventNotify: EventNotify | null // 事件通知
} }
export const useTrafficStore = defineStore('traffic', { export const useTrafficStore = defineStore('traffic', {
state: (): TrafficType => ({ state: (): TrafficType => ({
waves: [], waves: [],
nowEvents: [], nowEvents: [],
oldEvents: [] oldEvents: [],
eventCount: 0,
eventNotify: null
}), }),
getters: { getters: {
getWaves() {
getWaves () {
return this.waves return this.waves
}, },
getNowEvents () { getNowEvents() {
return this.nowEvents return this.nowEvents
}, },
getOldEvents () { getOldEvents() {
return this.oldEvents return this.oldEvents
} },
getEventCount() {
return this.eventCount
}, },
actions: { getEventNotify() {
return this.eventNotify
}
},
setWaves (e) { actions: {
setWaves(e) {
this.waves = e || [] this.waves = e || []
}, },
setNowEvents (e) { setNowEvents(e) {
this.nowEvents = e || [] this.nowEvents = e || []
}, },
setOldEvents (e) { setOldEvents(e) {
this.oldEvents = e || [] this.oldEvents = e || []
} },
} setEventCount(e) {
this.eventCount = e || 0
},
setEventNotify(e) {
this.eventNotify = e || null
}
}
}) })
export const useTrafficStoreOut = () => { export const useTrafficStoreOut = () => {
......
...@@ -251,14 +251,16 @@ export const useTunnelStore = defineStore('tunnel', { ...@@ -251,14 +251,16 @@ export const useTunnelStore = defineStore('tunnel', {
this.faclities = tunnelPassages.map(item => { this.faclities = tunnelPassages.map(item => {
const { leftHole, type } = item const { leftHole, rightHole, type, width } = item
const classMap = { const classMap = {
x0: 'man-cross', x0: 'man-cross',
x1: 'car-cross' x1: 'car-cross'
} }
return { return {
start: +leftHole, leftStart: +leftHole,
rightStart: +rightHole,
width: +width,
type: classMap[type] type: classMap[type]
} }
......
...@@ -160,7 +160,7 @@ onMounted(async () => { ...@@ -160,7 +160,7 @@ onMounted(async () => {
params.carImages = (res2.bayonetImage || []).map(item => { params.carImages = (res2.bayonetImage || []).map(item => {
return { return {
name: item.carPlate, name: item.carPlate,
imgs: item.imagePath images: item.imagePath
} }
}) })
params.evtImages = res2.eventImagesPath || [] params.evtImages = res2.eventImagesPath || []
......
...@@ -272,7 +272,8 @@ ...@@ -272,7 +272,8 @@
retainedTime: null, // 留存时间 retainedTime: null, // 留存时间
eventStartTimeSearchStart: '', // 开始时间 eventStartTimeSearchStart: '', // 开始时间
eventStartTimeSearchEnd: '', // 结束时间 eventStartTimeSearchEnd: '', // 结束时间
eventType: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '100'], // 事件类型 // eventType: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '100'], // 事件类型
eventType: [], // 事件类型
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
total: 0 total: 0
...@@ -334,7 +335,8 @@ ...@@ -334,7 +335,8 @@
params.eventStartTimeSearchStart = '' params.eventStartTimeSearchStart = ''
params.eventStartTimeSearchEnd = '' params.eventStartTimeSearchEnd = ''
// params.eventType = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '100'] // params.eventType = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '100']
params.eventType = EVENT_TYPES.map(item=> {return item.key}) // params.eventType = EVENT_TYPES.map(item=> {return item.key})
params.eventType = []
} }
goGetList() goGetList()
...@@ -400,7 +402,8 @@ ...@@ -400,7 +402,8 @@
} }
onBeforeMount(() => { onBeforeMount(() => {
params.eventType = EVENT_TYPES.map(item=> {return item.key}) // params.eventType = EVENT_TYPES.map(item=> {return item.key})
params.eventType = []
goGetList() goGetList()
}) })
......
...@@ -36,7 +36,8 @@ import { useTunnelStoreWithOut } from '@/store/modules/tunnel' ...@@ -36,7 +36,8 @@ import { useTunnelStoreWithOut } from '@/store/modules/tunnel'
import { getFocus, getRealCameras, setFocus } from '@/flash/Home' import { getFocus, getRealCameras, setFocus } from '@/flash/Home'
const props = defineProps<{ const props = defineProps<{
direction direction: number
holeType: string
}>() }>()
const tunnelStore = useTunnelStoreWithOut() const tunnelStore = useTunnelStoreWithOut()
...@@ -48,6 +49,10 @@ const focus = computed(() => { ...@@ -48,6 +49,10 @@ const focus = computed(() => {
}) })
const cameras = computed(() => { const cameras = computed(() => {
if( props.holeType === 'new' ) {
// (新洞/老洞)目前还没有实现,需后续补功能
return getRealCameras(props.direction)
}
return getRealCameras(props.direction) return getRealCameras(props.direction)
}) })
......
<template>
<div class="CarCard">
<div class="content">
<div class="images">
<el-image
:style="{ height: '100%', width: '100%', transform: 'rotateY(180deg)' }"
:src="IMAGE_BASE + carInfo.carImageUrl"
fit="contain"
>
<template #error>
<div class="image-error">
<Icon icon="ep:picture" />
</div>
</template>
</el-image>
</div>
<div class="lines">
<div class="line-ttl">
<div class="ttl">{{ carName }}</div>
<div
@click="collectChange"
:class="['icon', { collected: carInfo.isFocusMark || carInfo.isDangerous }]"
></div>
</div>
<div class="line-text">
<div class="name">隧道方向:</div>
<div class="text">{{ directionMap[carInfo.direction] }}</div>
</div>
<div class="line-text">
<div class="name">进入时间:</div>
<div class="text">{{ carInfo.inTunnelTime }}</div>
</div>
</div>
</div>
<div class="footer" v-if="carInfo.retainedMinutesTime">
<div class="cols">
<div class="col-x">
<div class="icon"></div>
<div class="text"> 留存 {{ carInfo.retainedMinutesTime }}分钟 </div>
</div>
<div class="col-x">
<div class="icon"></div>
<div class="text"> {{ carInfo.speed }}km/h </div>
</div>
<div class="col-x">
<div class="icon"></div>
<div class="text"> {{ numToStone(carInfo.carMilestone) }} </div>
</div>
</div>
<div v-if="!carInfo.mook" @click="goTracker" class="def-btn">轨迹追踪</div>
</div>
<div class="footer" v-else>车辆已驶出隧道</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { numToStone } from '@/utils/tunnel'
import { carMark, cancelMark } from '@/api/home'
import { ROAD_DIRECTION } from '@/config/enum'
import { ASYNC_DIRECTIONS } from '@/config/dict'
import { CAR_TYPES } from '@/config/dict'
const IMAGE_BASE = import.meta.env.VITE_IMAGE_BASE
interface CarInfo {
carId: string
type: number
carImageUrl?: string
carPlate?: string
isFocusMark?: boolean
isDangerous?: boolean
direction: string
inTunnelTime?: string
retainedMinutesTime?: number
speed?: number
carMilestone: string
mook?: boolean
}
const props = defineProps<{
carInfo: CarInfo
}>()
const carName = computed(() => {
return `${CAR_TYPES[props.carInfo.type].name}(${props.carInfo.carPlate})`
})
const router = useRouter()
// 收藏与取消
const collectChange = async () => {
const { carId, isFocusMark, isDangerous } = props.carInfo
if (isDangerous) {
return ElMessage.warning('危化品车不可取消关注!')
}
if (isFocusMark) {
await cancelMark({ carId })
} else {
await carMark({ carId })
}
}
// 方向
const directionMap = computed(() => {
return Object.fromEntries(
ASYNC_DIRECTIONS().map((item) => {
return [item.key, item.name]
})
)
})
// 轨迹追踪
const goTracker = () => {
const { carId, direction } = props.carInfo
router.push({
path: 'track',
query: {
id: carId,
direction: ROAD_DIRECTION[direction],
from: 'home'
}
})
}
</script>
<style lang="scss" scoped>
$base_url: '@/assets/images/home/';
.CarCard {
width: 445px;
height: 158px;
border-radius: 4px;
background: #0d3561;
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
margin-right: 8px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: #fff;
flex-shrink: 0;
&:last-child {
margin-right: 0;
}
.content {
display: flex;
align-items: center;
padding: 16px;
.images {
width: 74px;
height: 74px;
background: #eaeaea;
position: relative;
:deep(.image-error) {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #666666;
}
}
.lines {
flex-grow: 1;
margin-left: 12px;
.line-ttl {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
.ttl {
font-size: 18px;
line-height: 28px;
}
.icon {
width: 21px;
height: 20px;
background: url($base_url + 'collect_1.png') no-repeat;
background-size: cover;
cursor: pointer;
&.collected {
background: url($base_url + 'collect_2.png') no-repeat;
background-size: cover;
}
}
}
.line-text {
display: flex;
margin-bottom: 2px;
.name {
font-weight: normal;
color: rgba(240, 245, 255, 0.95);
}
}
}
}
.footer {
height: 47px;
background: rgba(69, 116, 182, 0.36);
display: flex;
align-items: center;
box-sizing: border-box;
padding: 10px 16px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: rgba(240, 245, 255, 0.95);
.cols {
display: flex;
flex-grow: 1;
justify-content: space-between;
margin-right: 57px;
.col-x {
display: flex;
align-items: center;
// font-size: 12px;
justify-content: space-between;
.icon {
width: 20px;
height: 20px;
margin-right: 4px;
// background: salmon;
}
}
.col-x:nth-child(1) {
.icon {
background: url($base_url + 'remain.png') no-repeat;
background-size: cover;
}
}
.col-x:nth-child(2) {
.icon {
background: url($base_url + 'speed.png') no-repeat;
background-size: cover;
}
}
.col-x:nth-child(3) {
.icon {
background: url($base_url + 'distance.png') no-repeat;
background-size: cover;
}
}
}
.def-btn {
width: 72px;
height: 26px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
background: #1270ff;
box-sizing: border-box;
border: 1px solid rgba(155, 193, 255, 0.74);
}
}
}
</style>
This diff is collapsed.
<template> <template>
<div :class="['ctrls', ctrlClass]"> <div :class="['ctrls', ctrlClass, holeCtrls]">
<div class="title"> <div class="title">
<div class="text"> <div class="text">
...@@ -38,7 +38,8 @@ import { useRouter } from 'vue-router' ...@@ -38,7 +38,8 @@ import { useRouter } from 'vue-router'
import { ASYNC_DIRECTIONS } from '@/config/dict' import { ASYNC_DIRECTIONS } from '@/config/dict'
const props = defineProps<{ const props = defineProps<{
infos: any infos: any,
holeTypeName: string
}>() }>()
const emits = defineEmits(['car-filter']) const emits = defineEmits(['car-filter'])
...@@ -46,6 +47,8 @@ const router = useRouter() ...@@ -46,6 +47,8 @@ const router = useRouter()
const ctrlClass = computed(() => props.infos.direction ? 'lower' : 'up') const ctrlClass = computed(() => props.infos.direction ? 'lower' : 'up')
const holeCtrls = computed(() => props.holeTypeName === '新洞' ? 'newTop' : 'oldButtom')
const act = ref('') const act = ref('')
const filters = ref([ const filters = ref([
{ text: '危化品车', key: 'dangerousCount', type: '0'}, { text: '危化品车', key: 'dangerousCount', type: '0'},
...@@ -58,7 +61,7 @@ const filters = ref([ ...@@ -58,7 +61,7 @@ const filters = ref([
const directionName = computed(() => { const directionName = computed(() => {
const arrs = ASYNC_DIRECTIONS() const arrs = ASYNC_DIRECTIONS()
return arrs[props.infos.direction].name return arrs[props.infos.direction].name + props.holeTypeName
}) })
...@@ -102,10 +105,10 @@ $car-url: '@/assets/images/cars/'; ...@@ -102,10 +105,10 @@ $car-url: '@/assets/images/cars/';
.ctrls { .ctrls {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
height: 42px; height: 40px;
.title { .title {
color: white; color: white;
width: 316px; width: 350px;
height: 38px; height: 38px;
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -129,7 +132,6 @@ $car-url: '@/assets/images/cars/'; ...@@ -129,7 +132,6 @@ $car-url: '@/assets/images/cars/';
} }
&.up { &.up {
margin: 0 auto 15px;
.title { .title {
background-image: url($base-url + 'arrow_lft.png'); background-image: url($base-url + 'arrow_lft.png');
background-repeat: no-repeat; background-repeat: no-repeat;
...@@ -145,7 +147,6 @@ $car-url: '@/assets/images/cars/'; ...@@ -145,7 +147,6 @@ $car-url: '@/assets/images/cars/';
} }
&.lower { &.lower {
margin: 15px auto 0;
.title { .title {
background-image: url($base-url + 'arrow_rgt.png'); background-image: url($base-url + 'arrow_rgt.png');
background-repeat: no-repeat; background-repeat: no-repeat;
...@@ -156,6 +157,14 @@ $car-url: '@/assets/images/cars/'; ...@@ -156,6 +157,14 @@ $car-url: '@/assets/images/cars/';
margin-right: 32px; margin-right: 32px;
} }
} }
&.newTop {
margin: 0 auto 10px;
}
&.oldButtom {
margin: 0 auto;
}
} }
.filters { .filters {
......
<template>
<div class="Overview">
<div v-for="info in tunnelHoles" :key="info.key">
<div class="title">{{ info.directionName }}今日流量</div>
<div class="flex">
<div class="flex-block m-width">
新洞<span class="number">{{ formatNumber(info.newCount) }}</span>
</div>
<div class="flex-block m-width">
老洞<span class="number">{{ formatNumber(info.oldCount) }}</span>
</div>
</div>
</div>
<div>
<div class="title">
事件统计
<div class="dateSelect-block">
<span
:class="['date-block', { active: date.key === dateSelected }]"
v-for="date in dateList"
:key="date.key"
@click="dateChange(date.key)"
>
{{ date.name }}
</span>
</div>
</div>
<div class="flex">
<div class="flex-block" v-for="accident in accidents" :key="accident.key">
<div :class="['icon', accident.icon]"></div>
<span>{{ accident.name }}</span>
<span class="number">{{ formatNumber(accident.count) }}</span>
</div>
<div class="btn-block" @click="packUp">
收起 <Icon color="rgba(240, 245, 255, 0.95)" :size="14" icon="ep:arrow-up" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ASYNC_DIRECTIONS } from '@/config/dict'
import { getTunnelReport, getEventReport } from '@/api/home'
const emit = defineEmits<{ (e: 'pack-up'): void }>()
const tunnelHoles = ref<any[]>([
{ key: 0, directionName: '杭向', newCount: 0, oldCount: 0 },
{ key: 1, directionName: '衢向', newCount: 0, oldCount: 0 }
])
const accidents = ref<any[]>([
{ key: '1', name: '交通事故', count: 0, icon: 'icon1' },
{ key: '2', name: '车辆故障', count: 0, icon: 'icon2' },
{ key: '3', name: '道路施工', count: 0, icon: 'icon3' },
{ key: '4', name: '路面状况', count: 0, icon: 'icon4' },
{ key: '5', name: '其他', count: 0, icon: 'icon5' }
])
// 日期选择列表
const dateList = ref<any[]>([
{ key: '1', name: '今天' },
{ key: '2', name: '本月' },
{ key: '3', name: '本年' }
])
const dateSelected = ref('1')
// 延迟时间 30分钟
const delay = 30*60*1000
const formatNumber = (num: number) => {
return num.toLocaleString()
}
// 收起
const packUp = () => {
emit('pack-up')
}
// 日期选择
const dateChange = (key: string) => {
dateSelected.value = key
getAccidents()
}
const tunnelHoleCount_timer = ref<NodeJS.Timeout | null>(null)
const accidents_timer = ref<NodeJS.Timeout | null>(null)
// 获取隧道统计
const getTunnelHoleCount = async () => {
if (tunnelHoleCount_timer.value) {
clearTimeout(tunnelHoleCount_timer.value)
tunnelHoleCount_timer.value = null
}
const res = await getTunnelReport({})
if (res) {
tunnelHoles.value[0].newCount = res.tunnelUpNew
tunnelHoles.value[0].oldCount = res.tunnelUpOld
tunnelHoles.value[1].newCount = res.tunnelDownNew
tunnelHoles.value[1].oldCount = res.tunnelDownOld
}
tunnelHoleCount_timer.value = setTimeout(() => {
getTunnelHoleCount()
}, delay)
}
// 获取事件统计
const getAccidents = async () => {
if (accidents_timer.value) {
clearTimeout(accidents_timer.value)
accidents_timer.value = null
}
const res = await getEventReport({ type: dateSelected.value })
if (res) {
accidents.value[0].count = res['交通事故'].count // 交通事故
accidents.value[1].count = res['车辆故障'].count // 车辆故障
accidents.value[2].count = res['道路施工'].count // 道路施工
accidents.value[3].count = res['路面状况'].count // 路面状况
accidents.value[4].count = res['其他'].count // 其他
}
accidents_timer.value = setTimeout(() => {
getAccidents()
}, delay)
}
watch(
() => ASYNC_DIRECTIONS(),
(directions) => {
directions.forEach((dir, index) => {
if (tunnelHoles.value[index]) {
tunnelHoles.value[index].directionName = dir.name
}
})
// 获取数据
getTunnelHoleCount()
},
{ deep: true, immediate: true }
)
onUnmounted(() => {
if (tunnelHoleCount_timer.value) {
clearTimeout(tunnelHoleCount_timer.value)
tunnelHoleCount_timer.value = null
}
if (accidents_timer.value) {
clearTimeout(accidents_timer.value)
accidents_timer.value = null
}
})
onMounted(() => {
getAccidents()
})
</script>
<style lang="scss" scoped>
$base-url: '@/assets/images/home/';
.Overview {
display: flex;
justify-content: space-between;
align-items: center;
font-family: PingFang SC;
font-size: 18px;
font-weight: 600;
line-height: 28px;
letter-spacing: 0em;
color: #ffffff;
.title {
display: flex;
align-items: center;
margin-bottom: 4px;
&::before {
content: '';
display: inline-block;
width: 4px;
height: 20px;
background-color: #00ddff;
border-radius: 2px;
margin-right: 8px;
}
.dateSelect-block {
display: flex;
margin-left: 16px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
border-radius: 8px;
background: rgba(15, 95, 255, 0.2);
overflow: hidden;
cursor: pointer;
.date-block {
// width: 44px;
height: 26px;
display: flex;
justify-content: center;
align-items: center;
padding: 0px 8px;
&.active {
background: #0f60db;
}
}
}
}
.flex-block {
display: flex;
align-items: center;
min-width: 121px;
height: 40px;
border-radius: 4px;
background: #103f79;
font-size: 16px;
font-weight: normal;
line-height: 28px;
padding: 4px 14px;
box-sizing: border-box;
margin-left: 12px;
&.m-width {
min-width: 144px;
}
&:first-child {
margin-left: 0;
}
.number {
margin-left: 12px;
font-size: 24px;
font-weight: 600;
line-height: 32px;
}
.icon {
width: 24px;
height: 24px;
margin-right: 8px;
&.icon1 {
background: url($base-url + 'event_icon1.png') no-repeat;
background-size: 100% 100%;
}
&.icon2 {
background: url($base-url + 'event_icon2.png') no-repeat;
background-size: 100% 100%;
}
&.icon3 {
background: url($base-url + 'event_icon3.png') no-repeat;
background-size: 100% 100%;
}
&.icon4 {
background: url($base-url + 'event_icon4.png') no-repeat;
background-size: 100% 100%;
}
&.icon5 {
background: url($base-url + 'event_icon5.png') no-repeat;
background-size: 100% 100%;
}
}
}
.btn-block {
cursor: pointer;
display: flex;
align-items: center;
font-size: 14px;
font-weight: normal;
line-height: 22px;
font-variation-settings: 'opsz' auto;
color: rgba(240, 245, 255, 0.95);
margin-left: 24px;
}
}
</style>
This diff is collapsed.
This diff is collapsed.
<template> <template>
<div class="home">
<div class="home">
<div class="flow-switch"> <div class="flow-switch">
<div class="inner">
<div class="text-pre">隧道统计:</div>
<el-switch v-model="showOverview" />
<div class="text-aft">{{ showOverview ? '' : '' }}</div>
</div>
<div class="inner"> <div class="inner">
<div class="text-pre">实时交通流:</div> <div class="text-pre">实时交通流:</div>
<el-switch v-model="showFlow" /> <el-switch v-model="showFlow" />
<div class="text-aft">{{showFlow ? '' : ''}}</div> <div class="text-aft">{{ showFlow ? '' : '' }}</div>
</div>
<div class="inner">
<Scales
:isShowSceneMode="false"
@plate-change="plateChange"
v-model:useMaterial="useMaterial"
class="scale-ctrls"
/>
</div>
</div> </div>
<!-- 隧道统计 -->
<div class="overview-block" v-show="showOverview">
<Overview @pack-up="handlePackUp" />
</div> </div>
<div class="bef-block"> <!-- <div class="bef-block">
<flow-chart v-if="showFlow" :direction="0" /> <flow-chart v-if="showFlow" :direction="0" />
<cameras ref="cameraRef1" v-else :direction="0" /> <cameras ref="cameraRef1" v-else :direction="0" />
</div> </div> -->
<!-- 车流 --> <!-- 车流 -->
<Traffic /> <Traffic ref="trafficRef" :showFlow="showFlow" :useMaterial="useMaterial" />
<div class="aft-block"> <!-- <div class="aft-block">
<flow-chart v-if="showFlow" :direction="1" /> <flow-chart v-if="showFlow" :direction="1" />
<cameras ref="cameraRef2" v-else :direction="1" /> <cameras ref="cameraRef2" v-else :direction="1" />
</div> </div> -->
<!-- 系统跳转 --> <!-- 系统跳转 -->
<div @click="toSystem" class="sys-trans" v-if='menus.includes(8)'> <div @click="toSystem" class="sys-trans" v-if="menus.includes(8)">
<div class="icon"></div> <div class="icon"></div>
<div class="text">隧道管控</div> <div class="text">隧道管控</div>
</div> </div>
</div> <!-- 实时事件 -->
<real-time-events ref="realTimeEvents" @handle-event-alert="openEventAlert" />
<!-- 事件告警弹窗 -->
<event-alert-pop @update="handleEventAlert" ref="eventAlertPop" />
</div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import { ref } from 'vue' import { ref } from 'vue'
import Traffic from './components/Traffic/index.vue' import Traffic from './components/Traffic/index.vue'
import Cameras from './components/Cameras/index.vue' // import Cameras from './components/Cameras/index.vue'
import FlowChart from './components/FlowChart/index.vue' // import FlowChart from './components/FlowChart/index.vue'
import Overview from './components/Overview/index.vue'
import RealTimeEvents from './components/RealTimeEvents/index.vue'
import EventAlertPop from './components/EventAlertPop/index.vue'
import Scales from '@/views/traffic/components/Scales/index.vue'
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
const userStore = useUserStoreWithOut() const userStore = useUserStoreWithOut()
import { getOrgainzations } from '@/api/system/role' import { getOrgainzations } from '@/api/system/role'
import { import { getDetails } from '@/api/system/role'
getDetails,
} from '@/api/system/role'
import { getLoginPath } from '@/api/system/user' import { getLoginPath } from '@/api/system/user'
import { getState, setState } from '@/flash/Home' import { getState, setState } from '@/flash/Home'
// const showFlow = ref(false) // 模式切换开关 // const showFlow = ref(false) // 模式切换开关
const cameraRef1 = ref<any>(null) // 上行摄像头 const cameraRef1 = ref<any>(null) // 上行摄像头
const cameraRef2 = ref<any>(null) // 下行摄像头 const cameraRef2 = ref<any>(null) // 下行摄像头
const showOverview = ref(true) // 隧道统计显示开关
const realTimeEvents = ref<InstanceType<typeof RealTimeEvents> | null>(null)
const eventAlertPop = ref<InstanceType<typeof EventAlertPop> | null>(null)
const useMaterial = ref<boolean>(true) // 是否使用贴图
const trafficRef = ref<InstanceType<typeof Traffic> | null>(null)
// 权限菜单 // 权限菜单
const menus = ref([]) const menus = ref([])
// 模式切换开关 // 模式切换开关
const showFlow = computed({ const showFlow = computed({
get () { get() {
return getState() return getState()
}, },
set (e) { set(e) {
setState(e) setState(e)
} }
}) })
const cameraChange = (xcamera, direction) => { const cameraChange = (xcamera, direction) => {
if (direction === 0 && cameraRef1.value) { if (direction === 0 && cameraRef1.value) {
cameraRef1.value.cameraChange(xcamera) cameraRef1.value.cameraChange(xcamera)
} }
...@@ -75,7 +101,6 @@ const cameraChange = (xcamera, direction) => { ...@@ -75,7 +101,6 @@ const cameraChange = (xcamera, direction) => {
if (direction === 1 && cameraRef2.value) { if (direction === 1 && cameraRef2.value) {
cameraRef2.value.cameraChange(xcamera) cameraRef2.value.cameraChange(xcamera)
} }
} }
// 系统免登录 // 系统免登录
...@@ -86,26 +111,42 @@ const toSystem = async () => { ...@@ -86,26 +111,42 @@ const toSystem = async () => {
provide('cameraChange', cameraChange) provide('cameraChange', cameraChange)
const a = [{ const a = [{}]
}]
const setAuths = async () => { const setAuths = async () => {
const role = userStore.getRoles[0] const role = userStore.getRoles[0]
const res = await getOrgainzations({ code: role }) const res = await getOrgainzations({ code: role })
const resList = await getDetails({ id: res.list[0].id || '' }) const resList = await getDetails({ id: res.list[0].id || '' })
menus.value = resList.menuIds || [] menus.value = resList.menuIds || []
}
// 收起隧道统计
const handlePackUp = () => {
showOverview.value = false
}
// 打开事件告警
const openEventAlert = (id: string) => {
eventAlertPop.value?.open(id)
}
// 事件告警处置
const handleEventAlert = (isUpdate: boolean = false) => {
if (isUpdate) {
realTimeEvents.value?.search()
}
}
// 名牌切换
const plateChange = (e: number) => {
trafficRef.value?.plateChange(e)
} }
onBeforeMount(() => { onBeforeMount(() => {
setAuths() setAuths()
}) })
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
$Common_Url: '@/assets/images/common/'; $Common_Url: '@/assets/images/common/';
.flow-switch { .flow-switch {
...@@ -115,6 +156,7 @@ $Common_Url: '@/assets/images/common/'; ...@@ -115,6 +156,7 @@ $Common_Url: '@/assets/images/common/';
position: absolute; position: absolute;
right: 10px; right: 10px;
top: 100px; top: 100px;
display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
...@@ -122,17 +164,24 @@ $Common_Url: '@/assets/images/common/'; ...@@ -122,17 +164,24 @@ $Common_Url: '@/assets/images/common/';
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
margin-right: 42px; margin-right: 24px;
.text-aft { .text-aft {
margin-left: 6px; margin-left: 6px;
} }
.scale-ctrls {
margin-right: 20px;
}
} }
:deep(.el-switch) { :deep(.el-switch) {
--el-switch-on-color: #007DFA; --el-switch-on-color: #007dfa;
--el-switch-off-color: #203A60; --el-switch-off-color: #203a60;
} }
}
.overview-block {
margin: 33px 135px 30px;
overflow: hidden;
} }
.bef-block, .bef-block,
......
<template> <template>
<div :class="['ctrls', ctrlClass]">
<div :class="['ctrls', ctrlClass]">
<div class="title"> <div class="title">
<div class="text"> <div class="text">
{{ DIRECTION_NAMES[infos.direction] }} {{ DIRECTION_NAMES[infos.direction] + holeTypeName_l() }}
<div @click="directionChange" class="direction-change"> <div @click="holeTypeChange" class="direction-change">
<div class="icon"></div> <div class="icon"></div>
{{ DIRECTION_NAMES[1 - infos.direction].slice(0, -2) }} {{ DIRECTION_NAMES[infos.direction] + holeTypeName_r() }}
</div> </div>
</div> </div>
<div class="more"> <div class="more">
留存车辆:{{ infos.total }} 留存车辆:{{ infos.total }}
<div @click="goRemains" class="def-btn"> <div @click="goRemains" class="def-btn"> 详情 <Icon icon="ep:arrow-right" /> </div>
详情 <Icon icon="ep:arrow-right" />
</div>
</div> </div>
</div> </div>
...@@ -22,19 +18,16 @@ ...@@ -22,19 +18,16 @@
<div <div
v-for="item in filters" v-for="item in filters"
:key="item.key" :key="item.key"
:class="['x-car', {'act': item.key === act}]" :class="['x-car', { act: item.key === act }]"
@click="goFilter(item)" @click="goFilter(item)"
> >
{{ item.text }}({{ infos[item.key] }}) {{ item.text }}({{ infos[item.key] }})
</div> </div>
</div> </div>
</div>
</div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import { ref, defineProps, computed } from 'vue' import { ref, defineProps, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ASYNC_DIRECTIONS } from '@/config/dict' import { ASYNC_DIRECTIONS } from '@/config/dict'
...@@ -43,25 +36,31 @@ const props = defineProps<{ ...@@ -43,25 +36,31 @@ const props = defineProps<{
infos: any infos: any
}>() }>()
const emits = defineEmits(['car-filter', 'direction-change']) const emits = defineEmits(['car-filter', 'hole-type-change'])
const router = useRouter() const router = useRouter()
const ctrlClass = computed(() => props.infos.direction ? 'lower' : 'up') const ctrlClass = computed(() => (props.infos.direction ? 'lower' : 'up'))
const act = ref('') const act = ref('')
const filters = ref([ const filters = ref([
{ text: '危化品车', key: 'dangerousCount', type: '0'}, { text: '危化品车', key: 'dangerousCount', type: '0' },
{ text: '货车', key: 'truckCount', type: '3' }, { text: '货车', key: 'truckCount', type: '3' },
{ text: '客车', key: 'passengerCount', type: '1'}, { text: '客车', key: 'passengerCount', type: '1' },
{ text: '轿车', key: 'sedanCount', type: '2' }, { text: '轿车', key: 'sedanCount', type: '2' },
{ text: '其他', key: 'otherCount', type: '4' } { text: '其他', key: 'otherCount', type: '4' }
]) ])
const DIRECTION_NAMES = computed(() => { const DIRECTION_NAMES = computed(() => {
return ASYNC_DIRECTIONS().map((item) => item.name)
})
return ASYNC_DIRECTIONS().map(item => item.name) const holeTypeName_l = () => {
return props.infos.holeType === 'old' ? '老洞' : '新洞'
}
}) const holeTypeName_r = () => {
return props.infos.holeType === 'old' ? '新洞' : '老洞'
}
const goFilter = (e) => { const goFilter = (e) => {
if (act.value === e.key) { if (act.value === e.key) {
...@@ -88,15 +87,13 @@ const goRemains = () => { ...@@ -88,15 +87,13 @@ const goRemains = () => {
}) })
} }
// 方向切换 // 新老洞切换
const directionChange = () => { const holeTypeChange = () => {
emits('direction-change') emits('hole-type-change')
} }
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
$base-url: '@/assets/images/home/'; $base-url: '@/assets/images/home/';
$car-url: '@/assets/images/cars/'; $car-url: '@/assets/images/cars/';
$common-url: '@/assets/images/common/'; $common-url: '@/assets/images/common/';
...@@ -107,7 +104,7 @@ $common-url: '@/assets/images/common/'; ...@@ -107,7 +104,7 @@ $common-url: '@/assets/images/common/';
height: 42px; height: 42px;
.title { .title {
color: white; color: white;
width: 360px; width: 460px;
height: 38px; height: 38px;
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -121,7 +118,7 @@ $common-url: '@/assets/images/common/'; ...@@ -121,7 +118,7 @@ $common-url: '@/assets/images/common/';
align-items: center; align-items: center;
.direction-change { .direction-change {
font-size: 16px; font-size: 16px;
color: #42E7FF; color: #42e7ff;
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
...@@ -193,9 +190,8 @@ $common-url: '@/assets/images/common/'; ...@@ -193,9 +190,8 @@ $common-url: '@/assets/images/common/';
&.act { &.act {
background: rgba(124, 213, 255, 0.15); background: rgba(124, 213, 255, 0.15);
box-sizing: border-box; box-sizing: border-box;
border: 1px solid #7CD5FF; border: 1px solid #7cd5ff;
} }
} }
} }
</style> </style>
<template> <template>
<div class="traffic">
<div class="traffic"> <!-- <Scales
<Scales
@plate-change="plateChange" @plate-change="plateChange"
v-model:sceneMode="sceneMode" v-model:sceneMode="sceneMode"
v-model:useMaterial="commons.useMaterial" v-model:useMaterial="commons.useMaterial"
class="scale-ctrls" class="scale-ctrls"
/> /> -->
<FlowCarCtrl <FlowCarCtrl
@car-filter="carFilter" @car-filter="carFilter"
@direction-change="handleDirectionChange" @hole-type-change="handleHoleTypeChange"
:infos="{ ...summary, direction: commons.direction }" :infos="{ ...summary, direction: commons.direction, holeType: commons.holeType }"
/> />
<XDirectionSection <!-- 新洞 -->
v-bind="{...commons, traffic}" <XDirectionSectionRoad
ref="xDirectionSectionRoadRef"
v-bind="{
...commons,
traffic: newTraffic,
section: false,
roadHeight: newTraffic?.road_section_w,
act: commons.holeType === 'new'
}"
v-model:sectionAct="commons.sectionAct" v-model:sectionAct="commons.sectionAct"
@car-socket="carSocket" @car-socket="carSocket"
@section-change="sectionChange" @section-change="sectionChange"
@go-track="goTrack" @go-track="goTrack"
@camera-change="e => emits('camera-change', e)" @camera-change="(e) => emits('camera-change', e)"
@update-space-axis="(scrollLeft) => updateSpaceAxis('new', scrollLeft)"
/> />
</div> <!-- 老洞 -->
<XDirectionSection
ref="xDirectionSectionRef"
v-bind="{
...commons,
traffic,
section: false,
roadHeight: traffic?.road_section_w,
act: commons.holeType === 'old'
}"
v-model:sectionAct="commons.sectionAct"
@car-socket="carSocket"
@section-change="sectionChange"
@go-track="goTrack"
@camera-change="(e) => emits('camera-change', e)"
@update-space-axis="(scrollLeft) => updateSpaceAxis('old', scrollLeft)"
/>
<!-- 空间轴 -->
<div class="space-axis-block">
<span class="title">
<Icon icon="ep:info-filled" class="icon" :size="14" color="#0BC7FA" />
拖动滑块查看隧道局部
</span>
<SpaceAxis
ref="spaceAxisRoadRef"
@space-axis-scroll="(scrollLeft) => spaceAxisScroll('new', scrollLeft)"
v-bind="{
traffic: newTraffic,
direction: commons.direction,
holeType: 'new',
act: commons.holeType === 'new'
}"
/>
<SpaceAxis
ref="spaceAxisRef"
@space-axis-scroll="(scrollLeft) => spaceAxisScroll('old', scrollLeft)"
v-bind="{
traffic,
direction: commons.direction,
holeType: 'old',
act: commons.holeType === 'old'
}"
/>
</div>
</div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import { ref, reactive, onBeforeMount } from 'vue' import { ref, reactive, onBeforeMount } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import FlowCarCtrl from '../FlowCarCtrls/index.vue' import FlowCarCtrl from '../FlowCarCtrls/index.vue'
import XDirectionSection from '@/views/traffic/roads/XDirectionSection/index.vue' import XDirectionSection from '@/views/traffic/roads/XDirectionSectionV2/index.vue'
import XDirectionSectionRoad from '@/views/traffic/roads/XDirectionSectionV2/Road.vue'
import Scales from '@/views/traffic/components/Scales/index.vue' // import Scales from '@/views/traffic/components/Scales/index.vue'
import Cameras from '../components/Cameras/index.vue'
import TrafficEngine from '@/views/traffic/roads/Engine/index' import TrafficEngine from '@/views/traffic/roads/Engine/index'
import SpaceAxis from '@/views/traffic/roads/XDirection/comps/SpaceAxis/index.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
...@@ -56,6 +108,7 @@ const tunnelStore = useTunnelStoreWithOut() ...@@ -56,6 +108,7 @@ const tunnelStore = useTunnelStoreWithOut()
const commons = defineModel<any>() const commons = defineModel<any>()
const freshKey = ref(true) const freshKey = ref(true)
const traffic = ref(null) const traffic = ref(null)
const newTraffic = ref(null)
const summary = ref<any>({ const summary = ref<any>({
dangerousCount: 0, dangerousCount: 0,
...@@ -66,47 +119,47 @@ const summary = ref<any>({ ...@@ -66,47 +119,47 @@ const summary = ref<any>({
total: 0 total: 0
}) })
watch( // watch(
() => sceneMode.value, // () => sceneMode.value,
(e) => { // (e) => {
if (e === 'panorama') // if (e === 'panorama')
router.push({ // router.push({
path: 'home' // path: 'home'
}) // })
} // }
) // )
// 汇总同步 // 汇总同步
const carSocket = (e) => { const carSocket = (e) => {
const { dangerousCount, truckCount, passengerCount, sedanCount, otherCount } = e
const { dangerousCount, truckCount, passengerCount, sedanCount, otherCount } = e summary.value = {
summary.value = {
...summary.value, ...summary.value,
...{ ...{
dangerousCount, truckCount, passengerCount, sedanCount, otherCount, dangerousCount,
truckCount,
passengerCount,
sedanCount,
otherCount,
total: dangerousCount + dangerousCount + passengerCount + sedanCount + otherCount total: dangerousCount + dangerousCount + passengerCount + sedanCount + otherCount
} }
}
} }
}
// 车流筛选 // 车流筛选
const carFilter = ({ e }) => { const carFilter = ({ e }) => {
commons.value.filterType = e?.type || null commons.value.filterType = e?.type || null
} }
// 展示牌切换 // // 展示牌切换
const plateChange = (e) => { // const plateChange = (e) => {
commons.value.plateType = e // commons.value.plateType = e
} // }
// 方向切换
const handleDirectionChange = async () => {
commons.value.direction = 1 - commons.value.direction // 新/老洞切换
const handleHoleTypeChange = async () => {
// commons.value.direction = 1 - commons.value.direction
commons.value.holeType = commons.value.holeType === 'old' ? 'new' : 'old'
summary.value.dangerousCount = 0 summary.value.dangerousCount = 0
summary.value.truckCount = 0 summary.value.truckCount = 0
...@@ -115,17 +168,26 @@ const handleDirectionChange = async () => { ...@@ -115,17 +168,26 @@ const handleDirectionChange = async () => {
summary.value.otherCount = 0 summary.value.otherCount = 0
summary.value.total = 0 summary.value.total = 0
traffic.value.cleanRoad() traffic.value?.cleanRoad()
newTraffic.value?.cleanRoad()
const configs = await getConfigs(commons.value.direction) const configs = await getConfigs(commons.value.direction)
if (configs) { if (configs) {
if (commons.value.holeType === 'old') {
traffic.value = new TrafficEngine(sceneMode.value, configs) traffic.value = new TrafficEngine(sceneMode.value, configs)
traffic.value.startTraffic(res => { traffic.value.startTraffic((res) => {
carSocket(res) carSocket(res)
}) })
} } else {
newTraffic.value = new TrafficEngine(sceneMode.value, configs)
// 新洞还未对接,需后完善
newTraffic.value.startTraffic((res) => {
carSocket(res)
})
}
}
} }
// 段面切换 // 段面切换
...@@ -138,7 +200,8 @@ const goTrack = ({ id, direction }) => { ...@@ -138,7 +200,8 @@ const goTrack = ({ id, direction }) => {
router.push({ router.push({
path: 'track', path: 'track',
query: { query: {
id, direction, id,
direction,
from: 'event' from: 'event'
} }
}) })
...@@ -146,12 +209,10 @@ const goTrack = ({ id, direction }) => { ...@@ -146,12 +209,10 @@ const goTrack = ({ id, direction }) => {
// 获取隧道数据 // 获取隧道数据
const getConfigs = (direct) => { const getConfigs = (direct) => {
let wait = null let wait = null
let stop = null let stop = null
return new Promise(resolve => { return new Promise((resolve) => {
wait = setTimeout(() => { wait = setTimeout(() => {
stop && stop() stop && stop()
resolve(false) resolve(false)
...@@ -165,41 +226,90 @@ const getConfigs = (direct) => { ...@@ -165,41 +226,90 @@ const getConfigs = (direct) => {
direction: direct, direction: direct,
start, start,
end, end,
miles: long miles: long,
road_section_w: commons.value.holeType === 'old' ? 134 : 201,
holeType: commons.value.holeType // 新老洞类型 old、new
}) })
} }
}) })
}) })
}
const spaceAxisRef = ref<SpaceAxis>(null)
const spaceAxisRoadRef = ref<SpaceAxis>(null)
const xDirectionSectionRef = ref<XDirectionSection>(null)
const xDirectionSectionRoadRef = ref<XDirectionSectionRoad>(null)
// 空间轴滚动
const spaceAxisScroll = (holeType: string, scrollLeft: number) => {
if (holeType === 'old') {
xDirectionSectionRef.value.setRoadScroll(scrollLeft)
} else {
xDirectionSectionRoadRef.value.setRoadScroll(scrollLeft)
}
}
// 更新空间轴
const updateSpaceAxis = (holeType: string, scrollLeft: number) => {
if (holeType === 'old') {
spaceAxisRef.value.updateThumb({ scrollLeft })
} else {
spaceAxisRoadRef.value.updateThumb({ scrollLeft })
}
} }
onBeforeMount(async () => { onBeforeMount(async () => {
const { x, direction, holeType } = route.query
const { x, direction } = route.query
if (isSomeNone([x, direction])) { if (isSomeNone([x, direction])) {
return ElMessage.error('地址错误!') return ElMessage.error('地址错误!')
} }
commons.value.direction = +(direction as string) commons.value.direction = +(direction as string)
commons.value.holeType = holeType as string
const configs = await getConfigs(+direction) const configs = await getConfigs(+direction)
if (configs) { if (configs) {
traffic.value = new TrafficEngine(sceneMode.value, configs) traffic.value = new TrafficEngine(sceneMode.value, {
traffic.value.startTraffic(res => { ...configs,
road_section_w: 134,
holeType: 'old'
})
traffic.value.startTraffic((res) => {
carSocket(res) carSocket(res)
}) })
} newTraffic.value = new TrafficEngine(sceneMode.value, {
...configs,
road_section_w: 201,
holeType: 'new'
})
// 新洞还未对接,需后完善
// newTraffic.value.startTraffic((res) => {
// carSocket(res)
// })
carSocket({
dangerousCount: 0,
truckCount: 0,
passengerCount: 0,
sedanCount: 0,
otherCount: 0
})
}
}) })
onBeforeUnmount(() => {
// 清理所有隧道实例的 WebSocket 连接
[newTraffic.value, traffic.value].forEach(instance => {
if (instance) {
instance.cleanRoad() // 这会调用 socket.close()
}
})
})
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
.traffic { .traffic {
position: relative; position: relative;
text-align: center; text-align: center;
...@@ -209,6 +319,23 @@ onBeforeMount(async () => { ...@@ -209,6 +319,23 @@ onBeforeMount(async () => {
right: 85px; right: 85px;
top: 15px; top: 15px;
} }
}
.space-axis-block {
display: flex;
flex-direction: column;
// margin-top: 15px;
.title {
font-size: 14px;
line-height: 22px;
color: #fff;
display: flex;
align-items: center;
margin-left: 17px;
margin-bottom: 2px;
.icon {
margin-right: 5px;
}
}
}
}
</style> </style>
<template> <template>
<div class="section">
<div class="section"> <div class="flow-switch">
<div class="inner">
<Cameras ref="videosRef" :commons /> <Scales
:isShowSceneMode="false"
@plate-change="plateChange"
v-model:useMaterial="commons.useMaterial"
class="scale-ctrls"
/>
</div>
</div>
<!-- 返回全局视图 -->
<div class="back-home" @click="backHome">
<div class="text-bef">返回全局视图</div>
</div>
<!-- <Cameras ref="videosRef" :commons /> -->
<Traffic @camera-change="cameraChange" v-model="commons" /> <Traffic @camera-change="cameraChange" v-model="commons" />
</div>
</div> <!-- <Video ref="videoRef" /> -->
<!-- <Video ref="videoRef" /> -->
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
// import Cameras from './components/Cameras/index.vue'
import Cameras from './components/Cameras/index.vue'
import Traffic from './components/Traffic/index.vue' import Traffic from './components/Traffic/index.vue'
// import Video from '@/components/Video/index.vue' // import Video from '@/components/Video/index.vue'
import { useRoute } from 'vue-router' import Scales from '@/views/traffic/components/Scales/index.vue'
import { useRoute, useRouter } from 'vue-router'
import { ref } from 'vue' import { ref } from 'vue'
// const videoRef = ref<any>(null) // const videoRef = ref<any>(null)
const videosRef = ref<any>(null) const videosRef = ref<any>(null)
const route = useRoute() const route = useRoute()
const router = useRouter()
// 公共参数 // 公共参数
const commons = reactive({ const commons = reactive({
...@@ -31,30 +43,102 @@ const commons = reactive({ ...@@ -31,30 +43,102 @@ const commons = reactive({
useMaterial: false, // 是否使用贴图 useMaterial: false, // 是否使用贴图
plateType: 1, // 铭牌展示类型 plateType: 1, // 铭牌展示类型
direction: 0, // 车流方向 direction: 0, // 车流方向
sectionAct: 1 // 当前断面 sectionAct: 1, // 当前断面
scrollLeft: null, // 滚动轴位置
holeType: 'old' // 洞类型
}) })
const cameraChange = (e) => { const cameraChange = (e) => {
videosRef.value.cameraSet(e) // videosRef.value.cameraSet(e)
}
// 展示牌切换
const plateChange = (e) => {
commons.plateType = e
}
// 返回全局视图
const backHome = () => {
router.push({
name: 'Home'
})
} }
onBeforeMount(() => { onBeforeMount(() => {
const querys = route.query const querys = route.query
commons.sectionAct = +querys.x commons.sectionAct = +querys.x
commons.direction = +querys.direction commons.direction = +querys.direction
commons.scrollLeft = querys.scrollLeft ?? null
commons.holeType = querys.holeType
}) })
// provide('openVideoPop', (e) => { // provide('openVideoPop', (e) => {
// videoRef.value?.diaOpen(e) // videoRef.value?.diaOpen(e)
// }) // })
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
$base-url: '@/assets/images/section/';
.section { .section {
text-align: center; text-align: center;
}
.flow-switch {
color: white;
font-size: 14px;
z-index: 6;
position: absolute;
right: 10px;
top: 100px;
display: flex;
justify-content: space-between;
align-items: center;
.inner {
display: flex;
align-items: center;
justify-content: flex-end;
margin-right: 24px;
.text-aft {
margin-left: 6px;
}
.scale-ctrls {
margin-right: 20px;
}
}
:deep(.el-switch) {
--el-switch-on-color: #007dfa;
--el-switch-off-color: #203a60;
}
}
.back-home {
display: flex;
align-items: center;
justify-content: center;
width: 140px;
height: 32px;
border-radius: 4px;
// padding: 4px 12px;
// box-sizing: border-box;
background: rgba(0, 125, 250, 0.15);
font-size: 16px;
font-weight: normal;
line-height: 24px;
color: #ffffff;
margin: 30px 0 27px 44px;
cursor: pointer;
&::before {
display: inline-block;
content: '';
width: 16px;
height: 16px;
background: url($base-url + 'back.png') no-repeat;
background-size: cover;
margin-right: 5px;
}
}
}
</style> </style>
...@@ -36,12 +36,14 @@ ...@@ -36,12 +36,14 @@
</div> </div>
</el-popover> </el-popover>
<template v-if="isShowSceneMode">
<div @click="sceneChange('panorama')" :class="['x-btn', 'reduce', { 'disabled': sceneMode === 'panorama' }]"> <div @click="sceneChange('panorama')" :class="['x-btn', 'reduce', { 'disabled': sceneMode === 'panorama' }]">
<div class="inner"></div> <div class="inner"></div>
</div> </div>
<div @click="sceneChange('section')" :class="['x-btn', 'enlarge', { 'disabled': sceneMode === 'section' }]"> <div @click="sceneChange('section')" :class="['x-btn', 'enlarge', { 'disabled': sceneMode === 'section' }]">
<div class="inner"></div> <div class="inner"></div>
</div> </div>
</template>
<slot></slot> <slot></slot>
...@@ -53,6 +55,12 @@ ...@@ -53,6 +55,12 @@
import { ref, defineModel } from 'vue' import { ref, defineModel } from 'vue'
const props = withDefaults(defineProps<{
isShowSceneMode?: boolean
}>(), {
isShowSceneMode: true
})
const emits = defineEmits(['plate-change']) const emits = defineEmits(['plate-change'])
const carTip = ref(1) const carTip = ref(1)
......
...@@ -177,8 +177,10 @@ class TrafficEngine { ...@@ -177,8 +177,10 @@ class TrafficEngine {
ratex = 1 // 换算比 ratex = 1 // 换算比
miles = 1800 // 隧道总长 miles = 1800 // 隧道总长
road_w = 90 // 路面宽
// section = 300 // 段面长 // section = 300 // 段面长
section = 100 section = 100
road_section_w = 270 // 缎面模式路面宽
start = 0 start = 0
end = 1800 end = 1800
...@@ -218,12 +220,14 @@ class TrafficEngine { ...@@ -218,12 +220,14 @@ class TrafficEngine {
if (secne === 'panorama') { if (secne === 'panorama') {
this.ratex = (appStore.getDesign - 180) / this.miles this.ratex = (appStore.getDesign - 180) / this.miles
this.lane_w = 45 // this.lane_w = 45
this.lane_w = this.road_w / this.lanes
} }
if (secne === 'section') { if (secne === 'section') {
this.ratex = appStore.getDesign / this.section this.ratex = appStore.getDesign / this.section
this.lane_w = 125 // this.lane_w = 125
this.lane_w = this.road_section_w / this.lanes - 10
} }
this.cars = [] this.cars = []
...@@ -374,9 +378,11 @@ class TrafficEngine { ...@@ -374,9 +378,11 @@ class TrafficEngine {
// 设施换算 // 设施换算
facilitiesFormate (facilities) { facilitiesFormate (facilities) {
return facilities.map(({ start, end, type, direction }) => { return facilities.map(({ leftStart, rightStart, width, type, direction }) => {
return { return {
x: start * this.ratex, left_x: leftStart * this.ratex,
right_x: rightStart * this.ratex,
width: width * this.ratex,
type, type,
direction direction
} }
...@@ -387,8 +393,8 @@ class TrafficEngine { ...@@ -387,8 +393,8 @@ class TrafficEngine {
// 停车带换算 // 停车带换算
parkingFormate (packings) { parkingFormate (packings) {
return packings.map(({ start }) => { return packings.map(({ start, end }) => {
return { x: start * this.ratex } return { x: start * this.ratex, long: (end - start) * this.ratex }
}) })
} }
......
<template> <template>
<div :class="['facilities', act ? 'act' : 'disabled']">
<div :class="['facilities', act ? 'act' : 'disabled']">
<div <div
v-for="(item, x) in facilities" v-for="(item, x) in facilities"
:key="x" :key="x"
:class="['x-facility', item.type]" :class="['x-facility', item.type]"
:style="item.style" :style="item.style"
></div> ></div>
</div> </div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import { useTunnelStoreWithOut } from '@/store/modules/tunnel' import { useTunnelStoreWithOut } from '@/store/modules/tunnel'
const props = withDefaults(defineProps<{ const props = withDefaults(
defineProps<{
act: boolean act: boolean
}>(), { traffic: any
direction: number
}>(),
{
act: true act: true
}) }
)
const tunnelStore = useTunnelStoreWithOut() const tunnelStore = useTunnelStoreWithOut()
const tunnel = computed(() => {
return tunnelStore.getUpTunnel
})
const facilities = computed(() => { const facilities = computed(() => {
if (!props.traffic) return []
const rax = 1920 / +tunnel.value.long return props.traffic.facilitiesFormate(tunnelStore.getFacilities).map((item) => {
return tunnelStore.getFacilities.map(item => { const { left_x,right_x, width, type } = item
const { start, type } = item const style = {}
return { style.left = left_x + 'px'
style: { left: start * rax + 'px' }, // style.width = width + 'px'
type
}
})
}) const temp = { type, style }
return temp
})
})
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
$base-url: '@/assets/images/common/'; $base-url: '@/assets/images/common/';
.facilities { .facilities {
...@@ -79,8 +77,9 @@ $base-url: '@/assets/images/common/'; ...@@ -79,8 +77,9 @@ $base-url: '@/assets/images/common/';
width: 40px; width: 40px;
height: 100%; height: 100%;
&::after { &::after {
content: ""; content: '';
width: 40px; // width: 40px;
width: 100%;
height: 100%; height: 100%;
position: absolute; position: absolute;
left: 50%; left: 50%;
...@@ -110,8 +109,5 @@ $base-url: '@/assets/images/common/'; ...@@ -110,8 +109,5 @@ $base-url: '@/assets/images/common/';
// background-size: cover; // background-size: cover;
// } // }
} }
} }
</style> </style>
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
act ? 'act' : 'disabled' act ? 'act' : 'disabled'
]"> ]">
<template v-if="!isDownCtrls"> <template v-if="!isDownCtrls && section">
<!-- 段面控制器 --> <!-- 段面控制器 -->
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</template> </template>
<div class="main-road"> <div class="main-road" :style="roadStyle">
<!-- 停车带层 --> <!-- 停车带层 -->
<Parking v-bind="{ scene: 'panorama', direction, tunnel, _traffic }" /> <Parking v-bind="{ scene: 'panorama', direction, tunnel, _traffic }" />
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
</div> </div>
</div> </div>
<template v-if="isDownCtrls"> <template v-if="isDownCtrls && section">
<!-- 刻度线 --> <!-- 刻度线 -->
<Scale v-bind="{ data:_sections, isDownCtrls, direction, tunnel }" /> <Scale v-bind="{ data:_sections, isDownCtrls, direction, tunnel }" />
...@@ -121,16 +121,18 @@ const props = withDefaults( ...@@ -121,16 +121,18 @@ const props = withDefaults(
evtConfigs: any, // 事件详情 evtConfigs: any, // 事件详情
act: boolean, // 是否激活 act: boolean, // 是否激活
traffic?: any, // 车流引擎 traffic?: any, // 车流引擎
section?: boolean
filterType?: null | string, // 筛选类型 filterType?: null | string, // 筛选类型
isDownCtrls?: boolean, // 是否下方展示控制器 isDownCtrls?: boolean, // 是否下方展示控制器
trackers?: any trackers?: any
roadHeight?: number
}>(), }>(),
{ {
filterType: null, filterType: null,
isDownCtrls: false, isDownCtrls: false,
act: true, act: true,
trackers: {} trackers: {},
roadHeight: 120
} }
) )
...@@ -143,6 +145,11 @@ const _eventSelected= ref(null) // 选中车辆 ...@@ -143,6 +145,11 @@ const _eventSelected= ref(null) // 选中车辆
// const sectionAct = ref(null) // 当前段面 // const sectionAct = ref(null) // 当前段面
const start = ref(true) // 是否初次追踪 const start = ref(true) // 是否初次追踪
const _lines = ref(3) const _lines = ref(3)
const roadStyle = computed(() => {
return {
height: `${props.roadHeight}px`
}
})
const sectionAct = defineModel('sectionAct') // 选中段面 const sectionAct = defineModel('sectionAct') // 选中段面
...@@ -330,19 +337,16 @@ $base-common: '@/assets/images/common/'; ...@@ -330,19 +337,16 @@ $base-common: '@/assets/images/common/';
background: url($base-url + 'entry.png') no-repeat; background: url($base-url + 'entry.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
position: absolute; position: absolute;
left: 0;
top: 0; top: 0;
z-index: 1; z-index: 1;
} }
.export { .export {
height: 100%; height: 100%;
width: 90px; width: 90px;
background: url($base-url + 'entry.png') no-repeat; background: url($base-url + 'export.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
position: absolute; position: absolute;
right: 0;
top: 0; top: 0;
transform: rotateY(180deg);
z-index: 1; z-index: 1;
} }
.inner { .inner {
...@@ -433,6 +437,13 @@ $base-common: '@/assets/images/common/'; ...@@ -433,6 +437,13 @@ $base-common: '@/assets/images/common/';
margin-top: -60px; margin-top: -60px;
} }
&.in { &.in {
.entry {
right: 0;
transform: rotateY(180deg);
}
.export {
left: 0;
}
.running-car { .running-car {
right: -60px; right: -60px;
} }
...@@ -442,6 +453,13 @@ $base-common: '@/assets/images/common/'; ...@@ -442,6 +453,13 @@ $base-common: '@/assets/images/common/';
} }
&.out { &.out {
.entry {
left: 0;
}
.export {
right: 0;
transform: rotateY(180deg);
}
.running-car { .running-car {
left: -60px; left: -60px;
.core { .core {
......
...@@ -34,6 +34,7 @@ import { EVENT_TYPE } from '@/config/enum' ...@@ -34,6 +34,7 @@ import { EVENT_TYPE } from '@/config/enum'
import EVENT_IMAGE from '@/assets/images/event/event.png' import EVENT_IMAGE from '@/assets/images/event/event.png'
import TrafficSocket from '@/views/traffic/utils/socket' import TrafficSocket from '@/views/traffic/utils/socket'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useTrafficStoreOut } from '@/store/modules/traffic'
const props = defineProps<{ const props = defineProps<{
scene: string, scene: string,
...@@ -46,6 +47,7 @@ const props = defineProps<{ ...@@ -46,6 +47,7 @@ const props = defineProps<{
const events = defineModel<any[]>('events') // 实时事件 const events = defineModel<any[]>('events') // 实时事件
const selected = defineModel<any>('selected') // 选中事件 const selected = defineModel<any>('selected') // 选中事件
const socketEvent = ref<any>(null) // 事件流 const socketEvent = ref<any>(null) // 事件流
const trafficStore = useTrafficStoreOut()
const route = useRoute() const route = useRoute()
...@@ -109,7 +111,9 @@ const evtFlowStart = async () => { ...@@ -109,7 +111,9 @@ const evtFlowStart = async () => {
) )
socketEvent.value.start(res => { socketEvent.value.start(res => {
events.value = props._traffic.eventFormate(res) trafficStore.setEventCount(res.eventCount)
trafficStore.setEventNotify(res.eventDetailRespVO)
events.value = props._traffic.eventFormate(res.eventThirdEventStatusNotFinishList)
}) })
} }
...@@ -170,6 +174,11 @@ onMounted(() => { ...@@ -170,6 +174,11 @@ onMounted(() => {
}) })
onBeforeUnmount(() => {
// 清理事件实例的 WebSocket 连接
evtFlowClose()
})
</script> </script>
......
...@@ -37,6 +37,7 @@ const packings = computed(() => { ...@@ -37,6 +37,7 @@ const packings = computed(() => {
const style = {} const style = {}
style[props.direction ? 'left' : 'right'] = item.x + 'px' style[props.direction ? 'left' : 'right'] = item.x + 'px'
style.width = item.long + 'px'
const temp = { const temp = {
...item, ...item,
......
<template>
<div
class="custom-scrollbar-track"
ref="track"
@click="jumpToPosition"
:class="[act ? 'act' : 'disabled']"
>
<div class="custom-scrollbar-thumb" ref="thumb" @mousedown="startDrag">
<span class="bg"></span>
</div>
</div>
</template>
<script lang="ts" setup>
import { useAppStoreWithOut } from '@/store/modules/app'
const props = withDefaults(
defineProps<{
traffic: any // 车流引擎
direction: number
act?: boolean
}>(),
{
isDownCtrls: false,
act: true
}
)
const emits = defineEmits(['space-axis-scroll', 'drag-end'])
const appStore = useAppStoreWithOut()
const thumb = ref(null)
const track = ref(null)
const scrollContainerWidth = computed(() => {
let _sections = props.traffic?.sectionsCompute()
if (!_sections) return 0
return (_sections.length - 1 + _sections.slice(-1)[0]?.grow) * appStore.getDesign
})
let isDragging = false
// 计算滑块宽度
const updateThumbWidth = () => {
if (!thumb.value || !track.value) return
const visibleWidth = track.value.clientWidth
const thumbWidth = (visibleWidth / scrollContainerWidth.value) * visibleWidth
thumb.value.style.width = `${thumbWidth}px`
}
// 更新滑块位置
const updateThumb = ({ scrollLeft }) => {
if (!thumb.value || !track.value) return
const trackWidth = track.value.clientWidth
const thumbWidth = thumb.value.clientWidth
const maxLeft = trackWidth - thumbWidth
const thumbLeft = (scrollLeft / scrollContainerWidth.value) * trackWidth
thumb.value.style.left = `${Math.min(thumbLeft, maxLeft)}px`
}
// 点击轨道跳转
const jumpToPosition = (e) => {
if (!track.value) return
const trackWidth = track.value.clientWidth
const trackRect = track.value.getBoundingClientRect()
const clickPosition = e.clientX - trackRect.left
const thumbWidth = thumb.value.clientWidth
// 点击位置需要先进行缩放
const clickPositionCenter = clickPosition / appStore.zoom - thumbWidth / 2
const contentToTrackRatio = clickPositionCenter / trackWidth
const scrollLeft = contentToTrackRatio * scrollContainerWidth.value
emits('space-axis-scroll', scrollLeft)
emits('drag-end', scrollLeft)
}
// 拖动滑块
const startDrag = (e) => {
e.preventDefault()
isDragging = true
const startX = e.clientX
const startLeft = parseFloat(thumb.value.style.left || '0')
let actualScrollLeft = 0
const moveHandler = (e) => {
if (!isDragging || !thumb.value || !track.value) return
// 拖动需要先进行缩放
const deltaX = (e.clientX - startX) / appStore.zoom
const newLeft = Math.min(
Math.max(0, startLeft + deltaX),
track.value.clientWidth - thumb.value.clientWidth
)
thumb.value.style.left = `${newLeft}px`
const contentToTrackRatio = scrollContainerWidth.value / track.value.clientWidth
actualScrollLeft = newLeft * contentToTrackRatio
emits('space-axis-scroll', actualScrollLeft)
}
const upHandler = () => {
isDragging = false
emits('drag-end', actualScrollLeft)
document.removeEventListener('mousemove', moveHandler)
document.removeEventListener('mouseup', upHandler)
}
document.addEventListener('mousemove', moveHandler)
document.addEventListener('mouseup', upHandler)
}
// 设置滑块位置
const setThumbPosition = (scrollLeft: number) => {
thumb.value.style.left = `${scrollLeft}px`
}
watch(
scrollContainerWidth,
(e) => {
updateThumbWidth()
},
{ immediate: true }
)
onMounted(() => {
// updateThumbWidth()
})
defineExpose({
updateThumb,
setThumbPosition
})
</script>
<style lang="scss" scoped>
.custom-scrollbar-track {
position: relative;
// right: 0;
// top: 0;
width: 100%;
height: 16px;
background: #486794;
border-radius: 5px;
margin-bottom: 4px;
&.disabled {
opacity: 0.3;
pointer-events: none;
}
.custom-scrollbar-thumb {
position: absolute;
width: 110px;
height: 100%;
cursor: pointer;
transition: opacity 0.2s;
display: flex;
align-items: center;
justify-content: space-between;
opacity: 0.65;
.bg {
flex-grow: 1;
height: 100%;
margin: 0 2px;
background: rgba(255, 255, 255, 0.25);
}
&::before,
&::after {
display: inline-block;
content: '';
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-right: 5px solid #fff;
}
&::after {
border-left: 5px solid #fff;
border-right: none;
}
}
.custom-scrollbar-thumb:hover {
opacity: 1;
}
}
</style>
<template> <template>
<div
<div :class="[ :class="[
'x-direction', 'x-direction',
isDownCtrls ? 'down' : 'up', isDownCtrls ? 'down' : 'up',
+direction ? 'out' : 'in', +direction ? 'out' : 'in',
act ? 'act' : 'disabled' act ? 'act' : 'disabled'
]"> ]"
>
<template v-if="!isDownCtrls && section"> <template v-if="!isDownCtrls && section">
<!-- 段面控制器 --> <!-- 段面控制器 -->
<div class="section-box"> <div class="section-box">
<Section <Section
v-bind="{ data:_sections, scene: 'section', direction, trackers }" v-bind="{ data: _sections, scene: 'section', direction, trackers }"
v-model="sectionAct" v-model="sectionAct"
@go-section="goSection" @go-section="goSection"
/> />
...@@ -23,27 +21,27 @@ ...@@ -23,27 +21,27 @@
<!-- 刻度线 --> <!-- 刻度线 -->
<Scale class="scale-line" v-bind="{ data: _sections, isDownCtrls, direction, tunnel }" /> <Scale class="scale-line" v-bind="{ data: _sections, isDownCtrls, direction, tunnel }" />
</template> </template>
<div class="main-road"> <div class="main-road" :style="roadStyle">
<!-- 停车带层 --> <!-- 停车带层 -->
<Parking v-bind="{ scene: 'panorama', direction, tunnel, _traffic: traffic }" /> <Parking v-bind="{ scene: 'panorama', direction, tunnel, _traffic: traffic }" />
<!-- 路面层 --> <!-- 路面层 -->
<div class="level-road"> <div class="level-road">
<div class="entry"></div> <div class="entry"></div>
<div class="export"></div> <div class="export"></div>
<div class="inner"> <div class="inner">
<!-- 热力图层 --> <!-- 热力图层 -->
<HeatRoad class="dis-tag" :direction /> <HeatRoad class="dis-tag" :direction />
<!-- 摄像机层 --> <!-- 摄像机层 -->
<Camera class="dis-tag" v-bind="{ scene: 'panorama', direction, isDownCtrls, tunnel, _traffic: traffic }" @camera-change="e => emits('camera-change', e)" /> <Camera
class="dis-tag"
v-bind="{ scene: 'panorama', direction, isDownCtrls, tunnel, _traffic: traffic }"
@camera-change="(e) => emits('camera-change', e)"
/>
<!-- 虚实线 --> <!-- 虚实线 -->
<Line v-bind="{ direction, tunnel, _traffic: traffic }" /> <Line v-bind="{ direction, tunnel, _traffic: traffic }" />
...@@ -58,11 +56,17 @@ ...@@ -58,11 +56,17 @@
v-model:selected="_carSelected" v-model:selected="_carSelected"
v-bind="{ v-bind="{
scene: 'panorama', scene: 'panorama',
_traffic: traffic, _events, tunnel, filterType, _traffic: traffic,
direction, plateType, useMaterial, _events,
trackers, evtConfigs tunnel,
filterType,
direction,
plateType,
useMaterial,
trackers,
evtConfigs
}" }"
@car-socket="e => emits('car-socket', e)" @car-socket="(e) => emits('car-socket', e)"
/> />
<!-- 事件层 --> <!-- 事件层 -->
...@@ -71,49 +75,39 @@ ...@@ -71,49 +75,39 @@
v-model:selected="_eventSelected" v-model:selected="_eventSelected"
v-bind="{ v-bind="{
scene: 'panorama', scene: 'panorama',
_traffic: traffic, direction, evtConfigs, tunnel _traffic: traffic,
direction,
evtConfigs,
tunnel
}" }"
/> />
</div> </div>
</div> </div>
</div> </div>
<template v-if="isDownCtrls && section"> <template v-if="isDownCtrls && section">
<!-- 刻度线 --> <!-- 刻度线 -->
<Scale v-bind="{ data:_sections, isDownCtrls, direction, tunnel }" /> <Scale v-bind="{ data: _sections, isDownCtrls, direction, tunnel }" />
<!-- 段面控制器 --> <!-- 段面控制器 -->
<Section <Section
v-bind="{ data:_sections, isDownCtrls, scene: 'panorama', direction }" v-bind="{ data: _sections, isDownCtrls, scene: 'panorama', direction }"
@go-section="goSection" @go-section="goSection"
/> />
</template> </template>
</div> </div>
<CarCollect <CarCollect
v-model="_carSelected" v-model="_carSelected"
:cars="_cars" :cars="_cars"
ref="collectRef" ref="collectRef"
@go-track="e => emits('go-track', e)" @go-track="(e) => emits('go-track', e)"
/>
<EventPop
:events="_events"
v-model="_eventSelected"
ref="eventRef"
/> />
<EventPop :events="_events" v-model="_eventSelected" ref="eventRef" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import Section from './comps/Section/index.vue' import Section from './comps/Section/index.vue'
import Scale from './comps/Scale/index.vue' import Scale from './comps/Scale/index.vue'
import Parking from './comps/Parking/index.vue' import Parking from './comps/Parking/index.vue'
...@@ -132,7 +126,14 @@ import tunnelConfigs from '@/config/tunnel' ...@@ -132,7 +126,14 @@ import tunnelConfigs from '@/config/tunnel'
import { useTunnelStoreWithOut } from '@/store/modules/tunnel' import { useTunnelStoreWithOut } from '@/store/modules/tunnel'
const emits = defineEmits(['go-section', 'cars-update', 'open-event', 'go-track', 'car-socket', 'camera-change']) const emits = defineEmits([
'go-section',
'cars-update',
'open-event',
'go-track',
'car-socket',
'camera-change'
])
const tunnelStore = useTunnelStoreWithOut() const tunnelStore = useTunnelStoreWithOut()
const collectRef = ref<any>(null) // 收藏弹窗实例 const collectRef = ref<any>(null) // 收藏弹窗实例
...@@ -140,46 +141,49 @@ const eventRef = ref<any>(null) // 事件弹窗实例 ...@@ -140,46 +141,49 @@ const eventRef = ref<any>(null) // 事件弹窗实例
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
direction: number, // 道路方向 direction: number // 道路方向
plateType: number, // 铭牌类型 plateType: number // 铭牌类型
useMaterial: boolean, // 是否使用动态车辆贴图 useMaterial: boolean // 是否使用动态车辆贴图
evtConfigs: any, // 事件详情 evtConfigs: any // 事件详情
act: boolean, // 是否激活 act: boolean // 是否激活
traffic?: any, // 车流引擎 traffic?: any // 车流引擎
section?: boolean, section?: boolean
filterType?: null | string, // 筛选类型 filterType?: null | string // 筛选类型
isDownCtrls?: boolean, // 是否下方展示控制器 isDownCtrls?: boolean // 是否下方展示控制器
trackers?: any trackers?: any
roadHeight?: number // 路面高度
}>(), }>(),
{ {
section: true, section: true,
filterType: null, filterType: null,
isDownCtrls: false, isDownCtrls: false,
act: true, act: true,
trackers: {} trackers: {},
roadHeight: 90
} }
) )
const _traffic = ref<any>(null) // 车流引擎 const _traffic = ref<any>(null) // 车流引擎
const _sections = ref<any[]>([]) // 段面控制 const _sections = ref<any[]>([]) // 段面控制
const _cars = ref<any[]>([]) // 车流 const _cars = ref<any[]>([]) // 车流
const _carSelected= ref(null) // 选中车辆 const _carSelected = ref(null) // 选中车辆
const _events = ref<any[]>([]) // 事件列表 const _events = ref<any[]>([]) // 事件列表
const _eventSelected= ref(null) // 选中车辆 const _eventSelected = ref(null) // 选中车辆
// const sectionAct = ref(null) // 当前段面 // const sectionAct = ref(null) // 当前段面
const start = ref(true) // 是否初次追踪 const start = ref(true) // 是否初次追踪
const _lines = ref(3) const _lines = ref(3)
const roadStyle = computed(() => {
return {
height: `${props.roadHeight}px`
}
})
const sectionAct = defineModel('sectionAct') // 选中段面 const sectionAct = defineModel('sectionAct') // 选中段面
const tunnel = computed(() => { const tunnel = computed(() => {
return [ return [tunnelStore.getUpTunnel, tunnelStore.getDownTunnel][props.direction]
tunnelStore.getUpTunnel,
tunnelStore.getDownTunnel
][props.direction]
}) })
watch( watch(
() => props.traffic, () => props.traffic,
(e) => { (e) => {
...@@ -206,10 +210,10 @@ watch( ...@@ -206,10 +210,10 @@ watch(
watch( watch(
() => props.trackers.car, () => props.trackers.car,
(xcar) => { (xcar) => {
if (xcar && props.trackers.tracking) { if (xcar && props.trackers.tracking) {
const distance = props.direction
const distance = props.direction ? xcar.originalCar.carMilestone - tunnel.value.start : tunnel.value.end - xcar.originalCar.carMilestone ? xcar.originalCar.carMilestone - tunnel.value.start
: tunnel.value.end - xcar.originalCar.carMilestone
// const left = distance % 300 // const left = distance % 300
// const sections = (distance - left) / 300 + (left > 0 ? 1 : 0) // const sections = (distance - left) / 300 + (left > 0 ? 1 : 0)
...@@ -234,9 +238,6 @@ const goSection = (x) => { ...@@ -234,9 +238,6 @@ const goSection = (x) => {
direction: props.direction direction: props.direction
}) })
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -265,8 +266,6 @@ $base-common: '@/assets/images/common/'; ...@@ -265,8 +266,6 @@ $base-common: '@/assets/images/common/';
} }
} }
.ctrls { .ctrls {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
...@@ -332,25 +331,23 @@ $base-common: '@/assets/images/common/'; ...@@ -332,25 +331,23 @@ $base-common: '@/assets/images/common/';
position: absolute; position: absolute;
background: url($base-url + 'road.png'); background: url($base-url + 'road.png');
background-size: auto 100%; background-size: auto 100%;
.entry { .entry {
height: 100%; height: 100%;
width: 90px; width: 90px;
background: url($base-url + 'entry.png') no-repeat; background: url($base-url + 'entry.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
position: absolute; position: absolute;
left: 0;
top: 0; top: 0;
z-index: 1; z-index: 1;
} }
.export { .export {
height: 100%; height: 100%;
width: 90px; width: 90px;
background: url($base-url + 'entry.png') no-repeat; background: url($base-url + 'export.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
position: absolute; position: absolute;
right: 0;
top: 0; top: 0;
transform: rotateY(180deg);
z-index: 1; z-index: 1;
} }
.inner { .inner {
...@@ -418,7 +415,6 @@ $base-common: '@/assets/images/common/'; ...@@ -418,7 +415,6 @@ $base-common: '@/assets/images/common/';
} }
.down { .down {
.stone-num { .stone-num {
bottom: 12px; bottom: 12px;
} }
...@@ -433,6 +429,13 @@ $base-common: '@/assets/images/common/'; ...@@ -433,6 +429,13 @@ $base-common: '@/assets/images/common/';
margin-top: -60px; margin-top: -60px;
} }
&.in { &.in {
.entry {
right: 0;
transform: rotateY(180deg);
}
.export {
left: 0;
}
.running-car { .running-car {
right: -60px; right: -60px;
} }
...@@ -442,6 +445,13 @@ $base-common: '@/assets/images/common/'; ...@@ -442,6 +445,13 @@ $base-common: '@/assets/images/common/';
} }
&.out { &.out {
.entry {
left: 0;
}
.export {
right: 0;
transform: rotateY(180deg);
}
.running-car { .running-car {
left: -60px; left: -60px;
.core { .core {
......
This diff is collapsed.
<template>
<div :class="['x-direction-section-camera', direction ? 'out' : 'in']">
<div
v-for="(item, x) in cameras"
:key="x"
:style="item.style"
:class="['x-camera', { act: cameraSelected.includes(x) || camerasFocus.includes(item.id) }]"
>
<div class="video-view">
<VideoPlayer
@click="() => videoFocus(x)"
v-bind="{
frameW: 368,
frameH: 234,
vcode: item.cameraIndexCode,
pileNumber: item.pileNumber
}"
/>
</div>
<div class="footer">
<div class="pile">{{ item.pileNumber }}</div>
<!-- <div class="speed">平均车速:80km/h</div> -->
</div>
<div @click="openVideo(item)" class="full-screen"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import { getCurrentInstance } from 'vue'
import { useRoute } from 'vue-router'
import bus from '@/utils/eventBus'
import VideoPlayer from '@/views/components/VideoPlayer/index.vue'
// import { useCamera as useCameraHome } from '@/flash/Home'
import { getCameras, setCameras } from '@/flash/Home'
// store
const app = getCurrentInstance()
const route = useRoute()
// 事件
const emits = defineEmits(['camera-change', 'click-trigger'])
// 参数
const props = withDefaults(
defineProps<{
_traffic: any
tunnel: any
direction: number
}>(),
{}
)
const camerasFocus = ref<string[]>([])
// 选中摄像头计算
const cameraSelected = computed(() => {
const block = route.name
if (block === 'Home') {
return getCameras(props.direction)
}
return camerasFocus.value
})
// 路面摄像头铺设
const cameras = computed(() => {
if (!props._traffic) return []
return props._traffic.cameraFormate(props.tunnel.cameras, props.direction).map((item) => {
return {
...item,
style: {
left: item.x + 'px'
}
}
})
})
// 摄像头手动切换
const goSetCameras = (xcamera) => {
const block = route.name
if (block === 'Home') {
return setCameras(xcamera, props.direction)
} else {
emits('camera-change', xcamera)
}
}
onMounted(() => {
bus.on('focus-camera', (ids) => {
camerasFocus.value = ids
})
})
</script>
<style lang="scss" scoped>
$base-url: '@/assets/images/home/';
.x-direction-section-camera {
position: relative;
// left: 0;
width: 100%;
height: 256px;
margin: 27px 0 15px;
// z-index: 2;
.x-camera {
width: 368px;
height: 256px;
cursor: pointer;
position: absolute;
transform: translateX(-50%);
.video-view {
position: relative;
height: 234px;
box-sizing: border-box;
border: 1px solid #8eb8d1;
border-bottom: none;
border-radius: 6px 6px 0 0;
overflow: hidden;
}
.footer {
height: 24px;
display: flex;
align-items: center;
justify-content: space-between;
// position: absolute;
padding: 3px 8px;
box-sizing: border-box;
// left: 0;
// bottom: 0;
background: #35669c;
font-size: 12px;
font-weight: 600;
line-height: 18px;
color: rgba(240, 245, 255, 0.95);
border-radius: 0 0 6px 6px;
}
}
}
</style>
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment