Commit 9adbca23 authored by wangdonghao's avatar wangdonghao

新增功能

parent fb2fac6c
......@@ -40,4 +40,19 @@ export const getVideoUrl = (params) => {
// 获取隧道段面拥堵情况
export const getFlowSections = (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 = {
'wallet-filled',
'warning',
'warning-filled',
'warn-triangle-filled',
'watch',
'watermelon',
'wind-power',
......
......@@ -13,13 +13,24 @@ export const EVENT_TYPES = [
{ name: '逆行', key: '4' },
{ name: '拥堵', key: '5' },
{ name: '可疑事件', key: '6' },
{ name: '追尾', key: '7' },
{ name: '停车', key: '8' },
{ name: '事故', key: '7' },
{ name: '违章停车', key: '8' },
{ name: '超速', key: '16' },
{ name: '其他', key: '100' },
{ name: '', key: '99' },
{ name: '机动车闯禁行', key: '22' },
{ 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 = [
......
import { store } from '@/store'
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 {
waves: string[], // 标注车辆,事件相关等
nowEvents: any[], // 当前事件标注
waves: string[] // 标注车辆,事件相关等
nowEvents: any[] // 当前事件标注
oldEvents: any[] // 回溯事件标注
eventCount: number // 事件数量
eventNotify: EventNotify | null // 事件通知
}
export const useTrafficStore = defineStore('traffic', {
state: (): TrafficType => ({
waves: [],
nowEvents: [],
oldEvents: []
oldEvents: [],
eventCount: 0,
eventNotify: null
}),
getters: {
getWaves () {
getWaves() {
return this.waves
},
getNowEvents () {
getNowEvents() {
return this.nowEvents
},
getOldEvents () {
getOldEvents() {
return this.oldEvents
}
},
getEventCount() {
return this.eventCount
},
getEventNotify() {
return this.eventNotify
}
},
actions: {
setWaves (e) {
setWaves(e) {
this.waves = e || []
},
setNowEvents (e) {
setNowEvents(e) {
this.nowEvents = e || []
},
setOldEvents (e) {
setOldEvents(e) {
this.oldEvents = e || []
}
},
}
setEventCount(e) {
this.eventCount = e || 0
},
setEventNotify(e) {
this.eventNotify = e || null
}
}
})
export const useTrafficStoreOut = () => {
return useTrafficStore(store)
}
\ No newline at end of file
}
......@@ -251,14 +251,16 @@ export const useTunnelStore = defineStore('tunnel', {
this.faclities = tunnelPassages.map(item => {
const { leftHole, type } = item
const { leftHole, rightHole, type, width } = item
const classMap = {
x0: 'man-cross',
x1: 'car-cross'
}
return {
start: +leftHole,
leftStart: +leftHole,
rightStart: +rightHole,
width: +width,
type: classMap[type]
}
......
......@@ -160,7 +160,7 @@ onMounted(async () => {
params.carImages = (res2.bayonetImage || []).map(item => {
return {
name: item.carPlate,
imgs: item.imagePath
images: item.imagePath
}
})
params.evtImages = res2.eventImagesPath || []
......
......@@ -272,7 +272,8 @@
retainedTime: null, // 留存时间
eventStartTimeSearchStart: '', // 开始时间
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,
pageSize: 10,
total: 0
......@@ -334,7 +335,8 @@
params.eventStartTimeSearchStart = ''
params.eventStartTimeSearchEnd = ''
// 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()
......@@ -400,7 +402,8 @@
}
onBeforeMount(() => {
params.eventType = EVENT_TYPES.map(item=> {return item.key})
// params.eventType = EVENT_TYPES.map(item=> {return item.key})
params.eventType = []
goGetList()
})
......
......@@ -36,7 +36,8 @@ import { useTunnelStoreWithOut } from '@/store/modules/tunnel'
import { getFocus, getRealCameras, setFocus } from '@/flash/Home'
const props = defineProps<{
direction
direction: number
holeType: string
}>()
const tunnelStore = useTunnelStoreWithOut()
......@@ -48,6 +49,10 @@ const focus = computed(() => {
})
const cameras = computed(() => {
if( props.holeType === 'new' ) {
// (新洞/老洞)目前还没有实现,需后续补功能
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>
<div :class="['ctrls', ctrlClass]">
<div :class="['ctrls', ctrlClass, holeCtrls]">
<div class="title">
<div class="text">
......@@ -38,7 +38,8 @@ import { useRouter } from 'vue-router'
import { ASYNC_DIRECTIONS } from '@/config/dict'
const props = defineProps<{
infos: any
infos: any,
holeTypeName: string
}>()
const emits = defineEmits(['car-filter'])
......@@ -46,6 +47,8 @@ const router = useRouter()
const ctrlClass = computed(() => props.infos.direction ? 'lower' : 'up')
const holeCtrls = computed(() => props.holeTypeName === '新洞' ? 'newTop' : 'oldButtom')
const act = ref('')
const filters = ref([
{ text: '危化品车', key: 'dangerousCount', type: '0'},
......@@ -58,7 +61,7 @@ const filters = ref([
const directionName = computed(() => {
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/';
.ctrls {
display: inline-flex;
align-items: center;
height: 42px;
height: 40px;
.title {
color: white;
width: 316px;
width: 350px;
height: 38px;
display: flex;
align-items: center;
......@@ -129,7 +132,6 @@ $car-url: '@/assets/images/cars/';
}
&.up {
margin: 0 auto 15px;
.title {
background-image: url($base-url + 'arrow_lft.png');
background-repeat: no-repeat;
......@@ -145,7 +147,6 @@ $car-url: '@/assets/images/cars/';
}
&.lower {
margin: 15px auto 0;
.title {
background-image: url($base-url + 'arrow_rgt.png');
background-repeat: no-repeat;
......@@ -156,6 +157,14 @@ $car-url: '@/assets/images/cars/';
margin-right: 32px;
}
}
&.newTop {
margin: 0 auto 10px;
}
&.oldButtom {
margin: 0 auto;
}
}
.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>
<div class="home">
<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="text-pre">实时交通流:</div>
<el-switch v-model="showFlow" />
<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 class="home">
<div class="flow-switch">
<div class="inner">
<div class="text-pre">实时交通流:</div>
<el-switch v-model="showFlow" />
<div class="text-aft">{{showFlow ? '' : ''}}</div>
<!-- 隧道统计 -->
<div class="overview-block" v-show="showOverview">
<Overview @pack-up="handlePackUp" />
</div>
</div>
<div class="bef-block">
<flow-chart v-if="showFlow" :direction="0" />
<cameras ref="cameraRef1" v-else :direction="0" />
</div>
<!-- <div class="bef-block">
<flow-chart v-if="showFlow" :direction="0" />
<cameras ref="cameraRef1" v-else :direction="0" />
</div> -->
<!-- 车流 -->
<Traffic />
<!-- 车流 -->
<Traffic ref="trafficRef" :showFlow="showFlow" :useMaterial="useMaterial" />
<div class="aft-block">
<flow-chart v-if="showFlow" :direction="1" />
<cameras ref="cameraRef2" v-else :direction="1" />
</div>
<!-- <div class="aft-block">
<flow-chart v-if="showFlow" :direction="1" />
<cameras ref="cameraRef2" v-else :direction="1" />
</div> -->
<!-- 系统跳转 -->
<div @click="toSystem" class="sys-trans" v-if='menus.includes(8)'>
<div class="icon"></div>
<div class="text">隧道管控</div>
</div>
<!-- 系统跳转 -->
<div @click="toSystem" class="sys-trans" v-if="menus.includes(8)">
<div class="icon"></div>
<div class="text">隧道管控</div>
</div>
</div>
<!-- 实时事件 -->
<real-time-events ref="realTimeEvents" @handle-event-alert="openEventAlert" />
<!-- 事件告警弹窗 -->
<event-alert-pop @update="handleEventAlert" ref="eventAlertPop" />
</div>
</template>
<script lang='ts' setup>
<script lang="ts" setup>
import { ref } from 'vue'
import Traffic from './components/Traffic/index.vue'
import Cameras from './components/Cameras/index.vue'
import FlowChart from './components/FlowChart/index.vue'
// import Cameras from './components/Cameras/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'
const userStore = useUserStoreWithOut()
import { getOrgainzations } from '@/api/system/role'
import {
getDetails,
} from '@/api/system/role'
import { getDetails } from '@/api/system/role'
import { getLoginPath } from '@/api/system/user'
import { getState, setState } from '@/flash/Home'
// const showFlow = ref(false) // 模式切换开关
const cameraRef1 = 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 showFlow = computed({
get () {
get() {
return getState()
},
set (e) {
set(e) {
setState(e)
}
})
const cameraChange = (xcamera, direction) => {
if (direction === 0 && cameraRef1.value) {
cameraRef1.value.cameraChange(xcamera)
}
......@@ -75,7 +101,6 @@ const cameraChange = (xcamera, direction) => {
if (direction === 1 && cameraRef2.value) {
cameraRef2.value.cameraChange(xcamera)
}
}
// 系统免登录
......@@ -86,26 +111,42 @@ const toSystem = async () => {
provide('cameraChange', cameraChange)
const a = [{
}]
const a = [{}]
const setAuths = async () => {
const role = userStore.getRoles[0]
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 || []
}
// 收起隧道统计
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(() => {
setAuths()
})
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
$Common_Url: '@/assets/images/common/';
.flow-switch {
......@@ -115,6 +156,7 @@ $Common_Url: '@/assets/images/common/';
position: absolute;
right: 10px;
top: 100px;
display: flex;
justify-content: space-between;
align-items: center;
......@@ -122,17 +164,24 @@ $Common_Url: '@/assets/images/common/';
display: flex;
align-items: center;
justify-content: flex-end;
margin-right: 42px;
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;
--el-switch-on-color: #007dfa;
--el-switch-off-color: #203a60;
}
}
.overview-block {
margin: 33px 135px 30px;
overflow: hidden;
}
.bef-block,
......@@ -169,4 +218,4 @@ $Common_Url: '@/assets/images/common/';
margin-right: 5px;
}
}
</style>
\ No newline at end of file
</style>
<template>
<div :class="['ctrls', ctrlClass]">
<div class="title">
<div class="text">
{{ DIRECTION_NAMES[infos.direction] }}
<div @click="directionChange" class="direction-change">
<div class="icon"></div>
{{ DIRECTION_NAMES[1 - infos.direction].slice(0, -2) }}
<div :class="['ctrls', ctrlClass]">
<div class="title">
<div class="text">
{{ DIRECTION_NAMES[infos.direction] + holeTypeName_l() }}
<div @click="holeTypeChange" class="direction-change">
<div class="icon"></div>
{{ DIRECTION_NAMES[infos.direction] + holeTypeName_r() }}
</div>
</div>
</div>
<div class="more">
留存车辆:{{ infos.total }}
<div @click="goRemains" class="def-btn">
详情 <Icon icon="ep:arrow-right" />
<div class="more">
留存车辆:{{ infos.total }}
<div @click="goRemains" class="def-btn"> 详情 <Icon icon="ep:arrow-right" /> </div>
</div>
</div>
</div>
<div class="filters">
<div
v-for="item in filters"
:key="item.key"
:class="['x-car', {'act': item.key === act}]"
@click="goFilter(item)"
>
{{ item.text }}({{ infos[item.key] }})
<div class="filters">
<div
v-for="item in filters"
:key="item.key"
:class="['x-car', { act: item.key === act }]"
@click="goFilter(item)"
>
{{ item.text }}({{ infos[item.key] }})
</div>
</div>
</div>
</div>
</template>
<script lang='ts' setup>
<script lang="ts" setup>
import { ref, defineProps, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ASYNC_DIRECTIONS } from '@/config/dict'
......@@ -43,25 +36,31 @@ const props = defineProps<{
infos: any
}>()
const emits = defineEmits(['car-filter', 'direction-change'])
const emits = defineEmits(['car-filter', 'hole-type-change'])
const router = useRouter()
const ctrlClass = computed(() => props.infos.direction ? 'lower' : 'up')
const ctrlClass = computed(() => (props.infos.direction ? 'lower' : 'up'))
const act = ref('')
const filters = ref([
{ text: '危化品车', key: 'dangerousCount', type: '0'},
{ text: '危化品车', key: 'dangerousCount', type: '0' },
{ text: '货车', key: 'truckCount', type: '3' },
{ text: '客车', key: 'passengerCount', type: '1'},
{ text: '客车', key: 'passengerCount', type: '1' },
{ text: '轿车', key: 'sedanCount', type: '2' },
{ text: '其他', key: 'otherCount', type: '4' }
])
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) => {
if (act.value === e.key) {
......@@ -88,15 +87,13 @@ const goRemains = () => {
})
}
// 方向切换
const directionChange = () => {
emits('direction-change')
// 新老洞切换
const holeTypeChange = () => {
emits('hole-type-change')
}
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
$base-url: '@/assets/images/home/';
$car-url: '@/assets/images/cars/';
$common-url: '@/assets/images/common/';
......@@ -107,7 +104,7 @@ $common-url: '@/assets/images/common/';
height: 42px;
.title {
color: white;
width: 360px;
width: 460px;
height: 38px;
display: flex;
align-items: center;
......@@ -121,7 +118,7 @@ $common-url: '@/assets/images/common/';
align-items: center;
.direction-change {
font-size: 16px;
color: #42E7FF;
color: #42e7ff;
display: flex;
align-items: center;
cursor: pointer;
......@@ -193,9 +190,8 @@ $common-url: '@/assets/images/common/';
&.act {
background: rgba(124, 213, 255, 0.15);
box-sizing: border-box;
border: 1px solid #7CD5FF;
border: 1px solid #7cd5ff;
}
}
}
</style>
\ No newline at end of file
</style>
<template>
<div class="section">
<Cameras ref="videosRef" :commons />
<Traffic @camera-change="cameraChange" v-model="commons" />
</div>
<!-- <Video ref="videoRef" /> -->
<div class="section">
<div class="flow-switch">
<div class="inner">
<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" />
</div>
<!-- <Video ref="videoRef" /> -->
</template>
<script lang='ts' setup>
import Cameras from './components/Cameras/index.vue'
<script lang="ts" setup>
// import Cameras from './components/Cameras/index.vue'
import Traffic from './components/Traffic/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'
// const videoRef = ref<any>(null)
const videosRef = ref<any>(null)
const route = useRoute()
const router = useRouter()
// 公共参数
const commons = reactive({
......@@ -31,30 +43,102 @@ const commons = reactive({
useMaterial: false, // 是否使用贴图
plateType: 1, // 铭牌展示类型
direction: 0, // 车流方向
sectionAct: 1 // 当前断面
sectionAct: 1, // 当前断面
scrollLeft: null, // 滚动轴位置
holeType: 'old' // 洞类型
})
const cameraChange = (e) => {
videosRef.value.cameraSet(e)
// videosRef.value.cameraSet(e)
}
// 展示牌切换
const plateChange = (e) => {
commons.plateType = e
}
// 返回全局视图
const backHome = () => {
router.push({
name: 'Home'
})
}
onBeforeMount(() => {
const querys = route.query
commons.sectionAct = +querys.x
commons.direction = +querys.direction
commons.scrollLeft = querys.scrollLeft ?? null
commons.holeType = querys.holeType
})
// provide('openVideoPop', (e) => {
// videoRef.value?.diaOpen(e)
// })
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
$base-url: '@/assets/images/section/';
.section {
text-align: center;
}
</style>
\ No newline at end of file
.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>
......@@ -35,13 +35,15 @@
</div>
</el-popover>
<div @click="sceneChange('panorama')" :class="['x-btn', 'reduce', { 'disabled': sceneMode === 'panorama' }]">
<div class="inner"></div>
</div>
<div @click="sceneChange('section')" :class="['x-btn', 'enlarge', { 'disabled': sceneMode === 'section' }]">
<div class="inner"></div>
</div>
<template v-if="isShowSceneMode">
<div @click="sceneChange('panorama')" :class="['x-btn', 'reduce', { 'disabled': sceneMode === 'panorama' }]">
<div class="inner"></div>
</div>
<div @click="sceneChange('section')" :class="['x-btn', 'enlarge', { 'disabled': sceneMode === 'section' }]">
<div class="inner"></div>
</div>
</template>
<slot></slot>
......@@ -53,6 +55,12 @@
import { ref, defineModel } from 'vue'
const props = withDefaults(defineProps<{
isShowSceneMode?: boolean
}>(), {
isShowSceneMode: true
})
const emits = defineEmits(['plate-change'])
const carTip = ref(1)
......
......@@ -177,8 +177,10 @@ class TrafficEngine {
ratex = 1 // 换算比
miles = 1800 // 隧道总长
road_w = 90 // 路面宽
// section = 300 // 段面长
section = 100
road_section_w = 270 // 缎面模式路面宽
start = 0
end = 1800
......@@ -218,12 +220,14 @@ class TrafficEngine {
if (secne === 'panorama') {
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') {
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 = []
......@@ -374,9 +378,11 @@ class TrafficEngine {
// 设施换算
facilitiesFormate (facilities) {
return facilities.map(({ start, end, type, direction }) => {
return facilities.map(({ leftStart, rightStart, width, type, direction }) => {
return {
x: start * this.ratex,
left_x: leftStart * this.ratex,
right_x: rightStart * this.ratex,
width: width * this.ratex,
type,
direction
}
......@@ -387,8 +393,8 @@ class TrafficEngine {
// 停车带换算
parkingFormate (packings) {
return packings.map(({ start }) => {
return { x: start * this.ratex }
return packings.map(({ start, end }) => {
return { x: start * this.ratex, long: (end - start) * this.ratex }
})
}
......
<template>
<div :class="['facilities', act ? 'act' : 'disabled']">
<div
v-for="(item, x) in facilities"
:key="x"
:class="['x-facility', item.type]"
:style="item.style"
></div>
</div>
<div :class="['facilities', act ? 'act' : 'disabled']">
<div
v-for="(item, x) in facilities"
:key="x"
:class="['x-facility', item.type]"
:style="item.style"
></div>
</div>
</template>
<script lang='ts' setup>
<script lang="ts" setup>
import { useTunnelStoreWithOut } from '@/store/modules/tunnel'
const props = withDefaults(defineProps<{
act: boolean
}>(), {
act: true
})
const props = withDefaults(
defineProps<{
act: boolean
traffic: any
direction: number
}>(),
{
act: true
}
)
const tunnelStore = useTunnelStoreWithOut()
const tunnel = computed(() => {
return tunnelStore.getUpTunnel
})
const facilities = computed(() => {
if (!props.traffic) return []
const rax = 1920 / +tunnel.value.long
return tunnelStore.getFacilities.map(item => {
const { start, type } = item
return {
style: { left: start * rax + 'px' },
type
}
})
return props.traffic.facilitiesFormate(tunnelStore.getFacilities).map((item) => {
const { left_x,right_x, width, type } = item
const style = {}
style.left = left_x + 'px'
// style.width = width + 'px'
})
const temp = { type, style }
return temp
})
})
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
$base-url: '@/assets/images/common/';
.facilities {
......@@ -79,8 +77,9 @@ $base-url: '@/assets/images/common/';
width: 40px;
height: 100%;
&::after {
content: "";
width: 40px;
content: '';
// width: 40px;
width: 100%;
height: 100%;
position: absolute;
left: 50%;
......@@ -110,8 +109,5 @@ $base-url: '@/assets/images/common/';
// background-size: cover;
// }
}
}
</style>
\ No newline at end of file
</style>
......@@ -7,7 +7,7 @@
act ? 'act' : 'disabled'
]">
<template v-if="!isDownCtrls">
<template v-if="!isDownCtrls && section">
<!-- 段面控制器 -->
......@@ -26,7 +26,7 @@
</template>
<div class="main-road">
<div class="main-road" :style="roadStyle">
<!-- 停车带层 -->
<Parking v-bind="{ scene: 'panorama', direction, tunnel, _traffic }" />
......@@ -55,7 +55,7 @@
</div>
</div>
<template v-if="isDownCtrls">
<template v-if="isDownCtrls && section">
<!-- 刻度线 -->
<Scale v-bind="{ data:_sections, isDownCtrls, direction, tunnel }" />
......@@ -121,16 +121,18 @@ const props = withDefaults(
evtConfigs: any, // 事件详情
act: boolean, // 是否激活
traffic?: any, // 车流引擎
section?: boolean
filterType?: null | string, // 筛选类型
isDownCtrls?: boolean, // 是否下方展示控制器
trackers?: any
roadHeight?: number
}>(),
{
filterType: null,
isDownCtrls: false,
act: true,
trackers: {}
trackers: {},
roadHeight: 120
}
)
......@@ -143,6 +145,11 @@ const _eventSelected= ref(null) // 选中车辆
// const sectionAct = ref(null) // 当前段面
const start = ref(true) // 是否初次追踪
const _lines = ref(3)
const roadStyle = computed(() => {
return {
height: `${props.roadHeight}px`
}
})
const sectionAct = defineModel('sectionAct') // 选中段面
......@@ -330,19 +337,16 @@ $base-common: '@/assets/images/common/';
background: url($base-url + 'entry.png') no-repeat;
background-size: 100% 100%;
position: absolute;
left: 0;
top: 0;
z-index: 1;
}
.export {
height: 100%;
width: 90px;
background: url($base-url + 'entry.png') no-repeat;
background: url($base-url + 'export.png') no-repeat;
background-size: 100% 100%;
position: absolute;
right: 0;
top: 0;
transform: rotateY(180deg);
z-index: 1;
}
.inner {
......@@ -433,6 +437,13 @@ $base-common: '@/assets/images/common/';
margin-top: -60px;
}
&.in {
.entry {
right: 0;
transform: rotateY(180deg);
}
.export {
left: 0;
}
.running-car {
right: -60px;
}
......@@ -442,6 +453,13 @@ $base-common: '@/assets/images/common/';
}
&.out {
.entry {
left: 0;
}
.export {
right: 0;
transform: rotateY(180deg);
}
.running-car {
left: -60px;
.core {
......
......@@ -34,6 +34,7 @@ import { EVENT_TYPE } from '@/config/enum'
import EVENT_IMAGE from '@/assets/images/event/event.png'
import TrafficSocket from '@/views/traffic/utils/socket'
import { useRoute } from 'vue-router'
import { useTrafficStoreOut } from '@/store/modules/traffic'
const props = defineProps<{
scene: string,
......@@ -46,6 +47,7 @@ const props = defineProps<{
const events = defineModel<any[]>('events') // 实时事件
const selected = defineModel<any>('selected') // 选中事件
const socketEvent = ref<any>(null) // 事件流
const trafficStore = useTrafficStoreOut()
const route = useRoute()
......@@ -109,7 +111,9 @@ const evtFlowStart = async () => {
)
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(() => {
})
onBeforeUnmount(() => {
// 清理事件实例的 WebSocket 连接
evtFlowClose()
})
</script>
......
......@@ -37,6 +37,7 @@ const packings = computed(() => {
const style = {}
style[props.direction ? 'left' : 'right'] = item.x + 'px'
style.width = item.long + 'px'
const temp = {
...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>
<div :class="[
'x-direction',
isDownCtrls ? 'down' : 'up',
+direction ? 'out' : 'in',
act ? 'act' : 'disabled'
]">
<div
:class="[
'x-direction',
isDownCtrls ? 'down' : 'up',
+direction ? 'out' : 'in',
act ? 'act' : 'disabled'
]"
>
<template v-if="!isDownCtrls && section">
<!-- 段面控制器 -->
<div class="section-box">
<Section
v-bind="{ data:_sections, scene: 'section', direction, trackers }"
v-bind="{ data: _sections, scene: 'section', direction, trackers }"
v-model="sectionAct"
@go-section="goSection"
/>
......@@ -23,33 +21,33 @@
<!-- 刻度线 -->
<Scale class="scale-line" v-bind="{ data: _sections, isDownCtrls, direction, tunnel }" />
</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="entry"></div>
<div class="export"></div>
<div class="inner">
<!-- 热力图层 -->
<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 }" />
<!-- 路标 -->
<Arrow v-bind="{ direction, tunnel, _traffic: traffic }" />
<Arrow v-bind="{ direction, tunnel, _traffic: traffic }" />
<!-- 车流层 -->
<Cars
......@@ -57,12 +55,18 @@
v-model:cars="_cars"
v-model:selected="_carSelected"
v-bind="{
scene: 'panorama',
_traffic: traffic, _events, tunnel, filterType,
direction, plateType, useMaterial,
trackers, evtConfigs
}"
@car-socket="e => emits('car-socket', e)"
scene: 'panorama',
_traffic: traffic,
_events,
tunnel,
filterType,
direction,
plateType,
useMaterial,
trackers,
evtConfigs
}"
@car-socket="(e) => emits('car-socket', e)"
/>
<!-- 事件层 -->
......@@ -71,52 +75,42 @@
v-model:selected="_eventSelected"
v-bind="{
scene: 'panorama',
_traffic: traffic, direction, evtConfigs, tunnel
_traffic: traffic,
direction,
evtConfigs,
tunnel
}"
/>
</div>
</div>
</div>
<template v-if="isDownCtrls && section">
<!-- 刻度线 -->
<Scale v-bind="{ data:_sections, isDownCtrls, direction, tunnel }" />
<Scale v-bind="{ data: _sections, isDownCtrls, direction, tunnel }" />
<!-- 段面控制器 -->
<Section
v-bind="{ data:_sections, isDownCtrls, scene: 'panorama', direction }"
v-bind="{ data: _sections, isDownCtrls, scene: 'panorama', direction }"
@go-section="goSection"
/>
</template>
</div>
<CarCollect
v-model="_carSelected"
:cars="_cars"
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>
<script lang="ts" setup>
import Section from './comps/Section/index.vue'
import Scale from './comps/Scale/index.vue'
import Parking from './comps/Parking/index.vue'
import Parking from './comps/Parking/index.vue'
import Camera from './comps/Camera/index.vue'
import Line from './comps/Line/index.vue'
import Arrow from './comps/Arrow/index.vue'
......@@ -132,7 +126,14 @@ import tunnelConfigs from '@/config/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 collectRef = ref<any>(null) // 收藏弹窗实例
......@@ -140,46 +141,49 @@ const eventRef = ref<any>(null) // 事件弹窗实例
const props = withDefaults(
defineProps<{
direction: number, // 道路方向
plateType: number, // 铭牌类型
useMaterial: boolean, // 是否使用动态车辆贴图
evtConfigs: any, // 事件详情
act: boolean, // 是否激活
traffic?: any, // 车流引擎
section?: boolean,
filterType?: null | string, // 筛选类型
isDownCtrls?: boolean, // 是否下方展示控制器
direction: number // 道路方向
plateType: number // 铭牌类型
useMaterial: boolean // 是否使用动态车辆贴图
evtConfigs: any // 事件详情
act: boolean // 是否激活
traffic?: any // 车流引擎
section?: boolean
filterType?: null | string // 筛选类型
isDownCtrls?: boolean // 是否下方展示控制器
trackers?: any
roadHeight?: number // 路面高度
}>(),
{
section: true,
filterType: null,
isDownCtrls: false,
act: true,
trackers: {}
trackers: {},
roadHeight: 90
}
)
const _traffic = ref<any>(null) // 车流引擎
const _sections = ref<any[]>([]) // 段面控制
const _cars = ref<any[]>([]) // 车流
const _carSelected= ref(null) // 选中车辆
const _carSelected = ref(null) // 选中车辆
const _events = ref<any[]>([]) // 事件列表
const _eventSelected= ref(null) // 选中车辆
const _eventSelected = ref(null) // 选中车辆
// const sectionAct = ref(null) // 当前段面
const start = ref(true) // 是否初次追踪
const _lines = ref(3)
const roadStyle = computed(() => {
return {
height: `${props.roadHeight}px`
}
})
const sectionAct = defineModel('sectionAct') // 选中段面
const tunnel = computed(() => {
return [
tunnelStore.getUpTunnel,
tunnelStore.getDownTunnel
][props.direction]
return [tunnelStore.getUpTunnel, tunnelStore.getDownTunnel][props.direction]
})
watch(
() => props.traffic,
(e) => {
......@@ -206,10 +210,10 @@ watch(
watch(
() => props.trackers.car,
(xcar) => {
if (xcar && props.trackers.tracking) {
const distance = props.direction ? xcar.originalCar.carMilestone - tunnel.value.start : tunnel.value.end - xcar.originalCar.carMilestone
const distance = props.direction
? xcar.originalCar.carMilestone - tunnel.value.start
: tunnel.value.end - xcar.originalCar.carMilestone
// const left = distance % 300
// const sections = (distance - left) / 300 + (left > 0 ? 1 : 0)
......@@ -234,9 +238,6 @@ const goSection = (x) => {
direction: props.direction
})
}
</script>
<style lang="scss" scoped>
......@@ -265,8 +266,6 @@ $base-common: '@/assets/images/common/';
}
}
.ctrls {
display: inline-flex;
align-items: center;
......@@ -332,25 +331,23 @@ $base-common: '@/assets/images/common/';
position: absolute;
background: url($base-url + 'road.png');
background-size: auto 100%;
.entry {
height: 100%;
width: 90px;
background: url($base-url + 'entry.png') no-repeat;
background-size: 100% 100%;
position: absolute;
left: 0;
top: 0;
z-index: 1;
}
.export {
height: 100%;
width: 90px;
background: url($base-url + 'entry.png') no-repeat;
background: url($base-url + 'export.png') no-repeat;
background-size: 100% 100%;
position: absolute;
right: 0;
top: 0;
transform: rotateY(180deg);
z-index: 1;
}
.inner {
......@@ -418,7 +415,6 @@ $base-common: '@/assets/images/common/';
}
.down {
.stone-num {
bottom: 12px;
}
......@@ -433,6 +429,13 @@ $base-common: '@/assets/images/common/';
margin-top: -60px;
}
&.in {
.entry {
right: 0;
transform: rotateY(180deg);
}
.export {
left: 0;
}
.running-car {
right: -60px;
}
......@@ -442,6 +445,13 @@ $base-common: '@/assets/images/common/';
}
&.out {
.entry {
left: 0;
}
.export {
right: 0;
transform: rotateY(180deg);
}
.running-car {
left: -60px;
.core {
......
This diff is collapsed.
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