Commit 3b7b696f authored by wangdonghao's avatar wangdonghao

功能修改

parent 9adbca23
......@@ -5,13 +5,18 @@ NODE_ENV=development
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://10.126.10.155:48080'
# VITE_BASE_URL='http://10.126.10.155:48080'
VITE_BASE_URL='http://12.1.89.111:48078'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://10.126.10.155:48080/admin-api/infra/file/upload'
# VITE_UPLOAD_URL='http://10.126.10.155:48080/admin-api/infra/file/upload'
VITE_UPLOAD_URL='http://12.1.89.111:48078/admin-api/infra/file/upload'
# 隧道上传路径
VITE_TUNNEL_UPLOAD_URL='http://12.1.89.111:48078/qxsd/tunnel-equipment/import'
# 接口地址
VITE_API_URL=/admin-api
......@@ -34,5 +39,14 @@ VITE_OUT_DIR=dist
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=true
# 车辆模型、事件图片地址
VITE_IMAGE_BASE=http://33.64.92.130:8800
# 车流、事件流 socket 地址
VITE_SOCKET_PATH=ws://12.1.89.111:48078
# 卡口图片地址
VITE_PORT_IMAGE_BASE=http://33.64.92.130:8800
# 模板下载地址
VITE_TEMPLATE_BASE=http://12.1.89.111:9880
......@@ -5,16 +5,16 @@ NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://12.1.89.111:48088'
VITE_BASE_URL='http://12.1.89.111:48078'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://12.1.89.111:48088/admin-api/infra/file/upload'
VITE_UPLOAD_URL='http://12.1.89.111:48078/admin-api/infra/file/upload'
# 隧道上传路径
VITE_TUNNEL_UPLOAD_URL='http://12.1.89.111:48088/qxsd/tunnel-equipment/import'
VITE_TUNNEL_UPLOAD_URL='http://12.1.89.111:48078/qxsd/tunnel-equipment/import'
# 接口前缀
VITE_API_BASEPATH=
......@@ -41,7 +41,7 @@ VITE_OUT_DIR=dist-prod
VITE_IMAGE_BASE=http://33.64.92.130:8800
# 车流、事件流 socket 地址
VITE_SOCKET_PATH=ws://12.1.89.111:48088
VITE_SOCKET_PATH=ws://12.1.89.111:48078
# 卡口图片地址
VITE_PORT_IMAGE_BASE=http://33.64.92.130:8800
......
{
"typescript.tsdk": "node_modules/typescript/lib"
}
\ No newline at end of file
This diff is collapsed.
src/assets/images/common/man-cross.png

1.53 KB | W: | H:

src/assets/images/common/man-cross.png

681 Bytes | W: | H:

src/assets/images/common/man-cross.png
src/assets/images/common/man-cross.png
src/assets/images/common/man-cross.png
src/assets/images/common/man-cross.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -38,7 +38,7 @@
</el-form-item> -->
<el-form-item v-if="type === 2" required label="原因:">
<el-input v-model="params.dispelCause" placeholder="请输入" :rows="5" resize="none" type="textarea" />
<el-input v-model="params.dispelCause" placeholder="请输入" :rows="5" resize="none" type="textarea" :maxlength="200" show-word-limit />
</el-form-item>
</el-form>
......@@ -106,6 +106,9 @@
// 消除事件
if (type.value === 2) {
if(!params.dispelCause) return ElMessage.warning('请先填写原因!')
questing.value = true
cancelEvent(params).then(res => {
if (res) {
......@@ -197,6 +200,9 @@
background: transparent;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
}
.el-input__count {
background: transparent;
}
}
.el-select__popper {
......
......@@ -45,6 +45,7 @@ import { useTunnelStore } from '@/store/modules/tunnel'
import { getEventDetail, eventRemain } from '@/api/event'
import { historyed, isMocking, ensign, direct, flows, flashClean, startFlows } from '@/flash/Detail'
import { CAR_TYPES } from '@/config/dict'
const evtPop = ref<any>(null) // 事件处理弹窗实例
......@@ -122,6 +123,29 @@ const getConfigs = (direct) => {
}
// 车辆类型
const CAR_MAP = Object.fromEntries(CAR_TYPES.map(item => {
return [item.type, item.name]
}))
// 事件情况
const eventDes = (info: any)=>{
const { eventStartTime, carInfoList, eventSituation, nonVehicleFlag } = info
let str = eventStartTime.slice(0, -3) + ' '
let carList = carInfoList?.map(t=>{
return `${CAR_MAP[t.vehicleType]}(${t.carPlate})`
}) || []
if(nonVehicleFlag) carList = []
str+=`${carList.join('')} ${eventSituation}`
return str
}
onMounted(async () => {
if (!route.query.id) {
......@@ -133,7 +157,7 @@ onMounted(async () => {
// 事件详情
const res1 = await getEventDetail({ id })
params.id = id
params.info = res1.eventSituation
params.info = eventDes(res1)
params.handled = res1.eventStatus !== 0
params.milestone = +res1.milestone
......
......@@ -101,7 +101,11 @@
{{ SOURCE_MAP[row.eventSource] }}
</template>
</el-table-column>
<el-table-column prop="eventCarPlate" label="关联车辆" width="250" />
<el-table-column prop="eventCarPlate" label="关联车辆" width="250" >
<template #default="{ row }">
{{ setEventCarPlate(row) }}
</template>
</el-table-column>
<el-table-column label="抓拍图片" width="180" align="center" >
<template #default="{ row }">
......@@ -372,7 +376,14 @@
// 状态计算
const typeCompute = (row) => {
const { eventCarPlate, eventType } = row
const { eventCarPlate, eventType, nonVehicleFlag } = row
if(nonVehicleFlag) {
return {
classStr: 'type-3',
nameStr: EVENT_MAP[eventType]
}
}
if(+eventType === 6) {
return {
......@@ -395,6 +406,17 @@
}
// 设置车牌号
const setEventCarPlate = (row) => {
const { eventCarPlate, nonVehicleFlag } = row
// true 非车辆事件 false 车辆相关事件
if(nonVehicleFlag) return '-'
return eventCarPlate.join('')
}
// 页跳转
const goJumper = () => {
params.pageNo = jumperNum.value || 1
......
......@@ -95,7 +95,11 @@
</template>
</el-table-column>
<el-table-column prop="eventCarPlate" label="关联车辆" width="250" />
<el-table-column prop="eventCarPlate" label="关联车辆" width="250" >
<template #default="{ row }">
{{ setEventCarPlate(row) }}
</template>
</el-table-column>
<el-table-column label="抓拍图片" width="230">
<template #default="{ row }">
<div class="image-group">
......@@ -348,7 +352,14 @@
// 状态计算
const typeCompute = (row) => {
const { eventCarPlate, eventType } = row
const { eventCarPlate, eventType, nonVehicleFlag } = row
if(nonVehicleFlag) {
return {
classStr: 'type-3',
nameStr: EVENT_MAP[eventType]
}
}
if(+eventType === 6) {
return {
......@@ -371,6 +382,17 @@
}
// 设置车牌号
const setEventCarPlate = (row) => {
const { eventCarPlate, nonVehicleFlag } = row
// true 非车辆事件 false 车辆相关事件
if(nonVehicleFlag) return '-'
return eventCarPlate.join('')
}
// 页跳转
const goJumper = () => {
params.pageNo = jumperNum.value || 1
......
......@@ -18,6 +18,7 @@
</template>
<script lang='ts' setup>
import { CAR_TYPES } from '@/config/dict'
const props = defineProps<{
eventData
......@@ -29,11 +30,35 @@ const params = reactive({
generated: false // 是否已生成回溯数据
})
// 车辆类型
const CAR_MAP = Object.fromEntries(CAR_TYPES.map(item => {
return [item.type, item.name]
}))
// 事件情况
const eventDes = (info: any)=>{
const { eventStartTime, carInfoList, eventSituation, nonVehicleFlag } = info
let str = eventStartTime.slice(0, -3) + ' '
let carList = carInfoList?.map(t=>{
return `${CAR_MAP[t.vehicleType]}(${t.carPlate})`
}) || []
if(nonVehicleFlag) carList = []
str+=`${carList.join('')} ${eventSituation}`
return str
}
watch(
() => props.eventData,
e => {
if (e) {
params.situation = e.eventSituation
// params.situation = e.eventSituation
params.situation = eventDes(e)
params.status = e.eventStatus === 0
params.generated = e.existHisData
}
......
<template>
<div class="cameras">
<div class="cameras">
<div
v-for="(item, x) in cameras"
:key="item.code"
......@@ -9,10 +7,12 @@
@click="goSetFocus(x)"
>
<div class="view">
<VideoPlayer v-bind="{
<VideoPlayer
v-bind="{
vcode: item.cameraIndexCode,
pileNumber: item.pileNumber
}" />
}"
/>
</div>
<div @click="openVideo(item)" class="full-screen"></div>
......@@ -22,13 +22,10 @@
<div class="avg-speed"></div>
</div>
</div>
</div>
</div>
</template>
<script lang='ts' setup>
<script lang="ts" setup>
import VideoPlayer from '@/views/components/VideoPlayer/index.vue'
import { defineProps, getCurrentInstance, defineExpose } from 'vue'
......@@ -43,20 +40,47 @@ const props = defineProps<{
const tunnelStore = useTunnelStoreWithOut()
const app = getCurrentInstance()
const focus = computed(() => {
if(props.holeType === 'new') {
// (新洞/老洞)新洞目前还没有实现,需后续补功能
return ''
}
return getFocus(props.direction)
})
// 新洞摄像头,暂时性写死,后续需要废弃重写 start
const NEWCAMERAS = [
[
{ cameraIndexCode: '33068100001310432117', pileNumber: 'K214+475' },
{ cameraIndexCode: '33068100001310172957', pileNumber: 'K214+590' },
{ cameraIndexCode: '33068100001310342185', pileNumber: 'K214+710' },
{ cameraIndexCode: '33068100001310583584', pileNumber: 'K214+870' },
{ cameraIndexCode: '33068100001310898523', pileNumber: 'K215+020' }
],
[
{ cameraIndexCode: '33068100001310763166', pileNumber: 'K214+200' },
{ cameraIndexCode: '33068100001310703492', pileNumber: 'K214+400' },
{ cameraIndexCode: '33068100001310818999', pileNumber: 'K214+450' },
{ cameraIndexCode: '33068100001310534239', pileNumber: 'K214+550' },
{ cameraIndexCode: '33068100001310053767', pileNumber: 'K214+765' },
]
]
// 新洞摄像头,暂时性写死,后续需要废弃重写 end
const cameras = computed(() => {
if( props.holeType === 'new' ) {
// (新洞/老洞)目前还没有实现,需后续补功能
return getRealCameras(props.direction)
if (props.holeType === 'new') {
// (新洞/老洞)新洞目前还没有实现,需后续补功能
// return getRealCameras(props.direction)
return NEWCAMERAS[props.direction]
}
return getRealCameras(props.direction)
})
const goSetFocus = (x) => {
if(props.holeType === 'new') {
// (新洞/老洞)新洞目前还没有实现,需后续补功能
return
}
setFocus(x, props.direction)
}
......@@ -65,31 +89,27 @@ const openVideo = (item) => {
}
const outSideClick = (e) => {
const videos = document.querySelectorAll('.cameras .video-x')
const cameras = document.querySelectorAll('.x-camera')
const inside = [...videos, ...cameras].some(item => {
const inside = [...videos, ...cameras].some((item) => {
return item === e.target || item.contains(e.target)
})
if (!inside) {
goSetFocus(null, props.direction)
}
}
// 切换摄像头
const cameraChange = (xcamera) => {
if (focus.value === null) {
return
}
const { cameras } = tunnelStore[['getUpTunnel', 'getDownTunnel'][props.direction]]
const x = cameras.findIndex(item => item.id === xcamera.id)
const x = cameras.findIndex((item) => item.id === xcamera.id)
cameraSelected.value[props.direction].splice(focus.value, 1, x)
}
onMounted(() => {
......@@ -100,16 +120,12 @@ onUnmounted(() => {
document.removeEventListener('click', outSideClick)
})
defineExpose({
cameraChange
})
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
$base-url: '@/assets/images/section/';
.cameras {
......@@ -123,7 +139,7 @@ $base-url: '@/assets/images/section/';
cursor: pointer;
&.focus {
transition-property: all;
transition-duration: .3s;
transition-duration: 0.3s;
border: 1px solid #00ddff;
box-shadow: 0 0 12px #00ddff;
overflow: hidden;
......@@ -131,7 +147,7 @@ $base-url: '@/assets/images/section/';
}
.view {
height: 152px;
border: 1px solid #8EB8D1;
border: 1px solid #8eb8d1;
border-bottom: none;
box-sizing: border-box;
border-bottom: none;
......@@ -147,12 +163,12 @@ $base-url: '@/assets/images/section/';
font-size: 12px;
font-weight: 600;
color: rgba(240, 245, 255, 0.95);
background: #35669C;
background: #35669c;
padding: 0 8px;
border-radius: 0 0 6px 6px;
}
}
.full-screen{
.full-screen {
width: 16px;
height: 16px;
background: url($base-url + 'full-screen.png') no-repeat;
......@@ -169,5 +185,4 @@ $base-url: '@/assets/images/section/';
}
}
}
</style>
......@@ -4,7 +4,7 @@
<div class="images">
<el-image
:style="{ height: '100%', width: '100%', transform: 'rotateY(180deg)' }"
:src="IMAGE_BASE + carInfo.carImageUrl"
:src="carInfo.carImageUrl"
fit="contain"
>
<template #error>
......@@ -20,7 +20,7 @@
<div class="ttl">{{ carName }}</div>
<div
@click="collectChange"
:class="['icon', { collected: carInfo.isFocusMark || carInfo.isDangerous }]"
:class="['icon', { collected: isFocusMark || carInfo.isDangerous }]"
></div>
</div>
......@@ -36,7 +36,7 @@
</div>
</div>
<div class="footer" v-if="carInfo.retainedMinutesTime">
<div class="footer" v-if="carInfo.isDriveTunnel">
<div class="cols">
<div class="col-x">
<div class="icon"></div>
......@@ -68,7 +68,7 @@ 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
// const IMAGE_BASE = import.meta.env.VITE_IMAGE_BASE
interface CarInfo {
carId: string
......@@ -83,31 +83,40 @@ interface CarInfo {
speed?: number
carMilestone: string
mook?: boolean
isDriveTunnel: boolean
}
const props = defineProps<{
carInfo: CarInfo
}>()
const isFocusMark = ref<boolean>(false)
// 车辆类型
const CAR_MAP = Object.fromEntries(CAR_TYPES.map(item => {
return [item.type, item.name]
}))
const carName = computed(() => {
return `${CAR_TYPES[props.carInfo.type].name}(${props.carInfo.carPlate})`
return `${CAR_MAP[props.carInfo.type]}(${props.carInfo.carPlate})`
})
const router = useRouter()
// 收藏与取消
const collectChange = async () => {
const { carId, isFocusMark, isDangerous } = props.carInfo
const { carId, isDangerous } = props.carInfo
if (isDangerous) {
return ElMessage.warning('危化品车不可取消关注!')
}
if (isFocusMark) {
if (isFocusMark.value) {
await cancelMark({ carId })
} else {
await carMark({ carId })
}
isFocusMark.value = !isFocusMark.value
}
// 方向
......@@ -119,6 +128,14 @@ const directionMap = computed(() => {
)
})
watch(
() => props.carInfo,
(e) => {
isFocusMark.value = e.isFocusMark || false
},
{ immediate: true, deep: true }
)
// 轨迹追踪
const goTracker = () => {
const { carId, direction } = props.carInfo
......@@ -222,7 +239,7 @@ $base_url: '@/assets/images/home/';
display: flex;
flex-grow: 1;
justify-content: space-between;
margin-right: 57px;
margin-right: 27px;
.col-x {
display: flex;
align-items: center;
......@@ -256,6 +273,7 @@ $base_url: '@/assets/images/home/';
}
.def-btn {
cursor: pointer;
width: 72px;
height: 26px;
border-radius: 4px;
......
<template>
<div class="image-some">
<div v-if="images.length === 0" class="null-text">
{{ emptyText }}
</div>
<el-image :src="images[0]" v-if="images.length === 1" class="x-image" >
<template #error><div class="error">加载失败...</div></template>
</el-image>
<el-carousel
v-if="images.length > 1"
class="x-carousel"
ref="carousel"
@change="handleChange"
indicator-position="none"
>
<el-carousel-item v-for="(item, x) in images" :key="x">
<el-image :src="item" class="x-image" >
<template #error>
<div class="error">加载失败...</div>
</template>
</el-image>
</el-carousel-item>
</el-carousel>
</div>
</template>
<script lang='ts' setup>
withDefaults(defineProps<{
images: string[],
autoplay?: boolean,
emptyText?: string
}>(), {
images: () => [],
autoplay: true,
emptyText: '图片加载中...'
})
const emits = defineEmits(['current-change'])
const carousel = ref<any>(null)
const handleChange = (current: number) => {
emits('current-change', current)
}
const setAct = (x) => {
carousel.value.setActiveItem(x)
}
const prevImg = () => {
carousel.value.prev()
}
const nextImg = () => {
carousel.value.next()
}
defineExpose({
setAct,
prevImg,
nextImg
})
</script>
<style lang='scss' scoped>
.image-some {
width: 100%;
height: 100%;
position: relative;
.error {
color: rgba($color: #ffffff, $alpha: 0.8);
font-size: 14px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.null-text {
position: absolute;
width: 100%;
height: 100%;
color: rgba($color: #ffffff, $alpha: 0.6);
display: flex;
align-items: center;
justify-content: center;
}
.x-image {
width: 100%;
height: 100%;
:deep(.el-image__error) {
background: transparent;
}
:deep(.el-image__placeholder) {
background: transparent;
}
}
.x-carousel {
height: 100%;
}
:deep(.el-carousel__container) {
height: 100%;
}
}
</style>
\ No newline at end of file
......@@ -21,6 +21,7 @@ import { ref, onMounted } from 'vue'
const props = defineProps<{
direction: number
holeType: string
}>()
const chartBox = ref(null)
......@@ -30,11 +31,17 @@ const appStore = useAppStoreWithOut()
const chartRender = async (options = TraficFlowBar()) => {
if(props.holeType === 'new') {
// 新洞数据暂未对接,需后续再调整
// todo
} else {
const res = await getAvargeFlow({tunnelId: props.direction ? 'xl_tunnel_down' : 'xl_tunnel_up'})
options.xAxis.data = res.map(item => dayjs(item.createTime).format('HH:mm'))
options.series[0].data = res.map(item => item.carCount)
options.series[1].data = res.map(item => item.maxAverageSpeed)
}
chartEl.value.setOption(options)
......@@ -54,6 +61,10 @@ onMounted(() => {
})
defineExpose({
chartRender
})
</script>
......
<template>
<div class="Overview">
<div v-for="info in tunnelHoles" :key="info.key">
<div class="item-block" 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>
<span class="name">新洞</span><span class="number">{{ formatNumber(info.newCount) }}</span>
</div>
<div class="flex-block m-width">
老洞<span class="number">{{ formatNumber(info.oldCount) }}</span>
<span class="name">老洞</span><span class="number">{{ formatNumber(info.oldCount) }}</span>
</div>
</div>
</div>
<div>
<div class="overview-right">
<div class="title">
事件统计
<div class="dateSelect-block">
......@@ -29,7 +29,7 @@
<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="name">{{ accident.name }}</span>
<span class="number">{{ formatNumber(accident.count) }}</span>
</div>
<div class="btn-block" @click="packUp">
......@@ -63,7 +63,7 @@ const dateList = ref<any[]>([
])
const dateSelected = ref('1')
// 延迟时间 30分钟
const delay = 30*60*1000
const delay = 30 * 60 * 1000
const formatNumber = (num: number) => {
return num.toLocaleString()
......@@ -158,7 +158,7 @@ $base-url: '@/assets/images/home/';
.Overview {
display: flex;
justify-content: space-between;
// justify-content: space-between;
align-items: center;
font-family: PingFang SC;
font-size: 18px;
......@@ -167,6 +167,13 @@ $base-url: '@/assets/images/home/';
letter-spacing: 0em;
color: #ffffff;
.item-block {
margin-right: 80px;
&:last-child {
margin-right: 0;
}
}
.title {
display: flex;
align-items: center;
......@@ -205,6 +212,10 @@ $base-url: '@/assets/images/home/';
}
}
.overview-right {
flex: 1;
}
.flex-block {
display: flex;
align-items: center;
......@@ -224,11 +235,15 @@ $base-url: '@/assets/images/home/';
&:first-child {
margin-left: 0;
}
.name {
flex-shrink: 0;
}
.number {
margin-left: 12px;
font-size: 24px;
font-weight: 600;
line-height: 32px;
flex: 1;
}
.icon {
width: 24px;
......
......@@ -16,7 +16,7 @@
<span class="i-name">关键字:</span>
<div class="i-content">
<el-input
v-model="params.keyWord2"
v-model="params.keyword2"
class="x-inp"
placeholder="输入隧道方向、桩号位置"
/>
......@@ -57,14 +57,14 @@
</span>
</div>
<div class="text ellipsis">
{{ carPlateStr(item.eventCarPlate) + ' ' + item.eventSituation }}
{{ carPlateStr(item) + item.eventSituation }}
</div>
<div class="foot">
<div :class="`status-${item.eventStatus}`">
{{ STATUS_MAP[item.eventStatus] }}
</div>
<div class="btn-block">
<span class="btn" @click="goEventDetail(item.id)">事件详情</span>
<span class="btn" @click="goEventDetail(item.id, false, true)">告警详情</span>
<span
class="btn-red"
v-if="[0, 3].includes(item.eventStatus)"
......@@ -133,12 +133,13 @@ const alarmTime = computed({
})
const params = reactive({
keyWord2: '',
keyword2: '',
eventStartTimeSearchStart: '', // 开始时间
eventStartTimeSearchEnd: '', // 结束时间
pageNo: 1,
pageSize: 10,
total: 0
total: 0,
eventStatusSearch: 0
})
const drawerShow = ref(false) // 实时事件弹窗打开开关
......@@ -163,7 +164,14 @@ const EVENT_MAP = Object.fromEntries(
// 状态计算
const typeCompute = (row) => {
const { eventCarPlate, eventType } = row
const { eventCarPlate, eventType, nonVehicleFlag } = row
if(nonVehicleFlag) {
return {
classStr: 'type-3',
nameStr: EVENT_MAP[eventType]
}
}
if (+eventType === 6) {
return {
......@@ -186,8 +194,12 @@ const typeCompute = (row) => {
}
// 车牌
const carPlateStr = (eventCarPlate) => {
return eventCarPlate?.join('')
const carPlateStr = (info) => {
const { eventCarPlate, nonVehicleFlag } = info
if(nonVehicleFlag) return ''
return eventCarPlate?.join('') + ' '
}
// 搜索
......@@ -198,7 +210,7 @@ const search = debounce(() => {
// 重置
const reset = () => {
params.keyWord2 = ''
params.keyword2 = ''
params.eventStartTimeSearchStart = ''
params.eventStartTimeSearchEnd = ''
search()
......@@ -221,8 +233,8 @@ const getList = async () => {
}
// 跳转事件详情
const goEventDetail = (id: string, isClose: boolean = false) => {
emit('handle-event-alert', id)
const goEventDetail = (id: string, isClose: boolean = false, disabled: boolean = false) => {
emit('handle-event-alert', { id, disabled })
if (isClose) {
closeNotification()
}
......@@ -246,7 +258,11 @@ const initList = () => {
// 获取事件数量
const getEventsCount = async () => {
const res = await getEventList({ pageNo: 1, pageSize: 1 })
const res = await getEventList({
pageNo: 1,
pageSize: 1,
eventStatusSearch: params.eventStatusSearch
})
trafficStore.setEventCount(res.total)
}
......@@ -286,12 +302,12 @@ const triggerEventNotify = (item) => {
h(
'div',
{ class: 'text ellipsis' },
carPlateStr(item.eventCarPlate) + ' ' + item.eventSituation
carPlateStr(item) + item.eventSituation
),
h('div', { class: 'foot' }, [
h('div', { class: `status-${item.eventStatus}` }, STATUS_MAP[item.eventStatus]),
h('div', { class: 'btn-block' }, [
h('span', { class: 'btn', onClick: () => goToEvent(item.id) }, '事件详情'),
h('span', { class: 'btn', onClick: () => goEventDetail(item.id, true, true) }, '告警详情'),
h('span', { class: 'btn-red', onClick: () => goEventDetail(item.id, true) }, '处置')
])
])
......@@ -623,6 +639,10 @@ defineExpose({
color: rgba(240, 245, 255, 0.95);
}
}
.el-button.is-text:not(.is-disabled):hover {
color: var(--el-button-disabled-text-color);
}
}
.event-notify {
border-radius: 8px;
......
<template>
<div class="traffic">
<FlowCarCtrl @car-filter="carFilter" holeTypeName="新洞" :infos="newSummary" />
<FlowCarCtrl
@car-filter="carFilter"
:holeTypeName="holeDirection ? '老洞' : '新洞'"
:infos="holeDirection ? oldSummary : newSummary"
/>
<div class="hole-select">
<div class="name">聚焦方向:</div>
......@@ -22,10 +26,22 @@
<!-- 新洞监控 -->
<div class="cameras-block">
<flow-chart v-if="showFlow" :direction="holeDirection" />
<cameras ref="cameraRef1" v-else holeType="new" :direction="holeDirection" />
<flow-chart
ref="flowChartRef1"
v-if="showFlow"
:direction="holeDirection"
:holeType="holeDirection ? 'old' : 'new'"
/>
<cameras
ref="cameraRef1"
v-else
:holeType="holeDirection ? 'old' : 'new'"
:direction="holeDirection"
/>
</div>
<!-- 左洞 -->
<template v-if="!holeDirection">
<Road
@go-section="goSection"
@go-track="goTrack"
......@@ -36,11 +52,12 @@
traffic: newTraffic,
act: true,
section: false,
roadHeight: newTraffic?.road_w
roadHeight: newTraffic?.road_w,
isNeedLeftCamera: true
}"
/>
<Facilities v-bind="{ traffic: newTraffic, direction: holeDirection, act: true }" :act="true" />
<Facilities v-bind="{ traffic: newTraffic, direction: holeDirection, act: true }" />
<XDirection
@go-section="goSection"
......@@ -52,63 +69,94 @@
traffic: oldTraffic,
act: true,
section: false,
roadHeight: oldTraffic?.road_w
roadHeight: oldTraffic?.road_w,
isNeedLeftCamera: true
}"
/>
</template>
<Facilities v-bind="{ traffic: oldTraffic, direction: holeDirection, act: true }" />
<!-- <XDirection
v-if="outTraffic"
<!-- 右洞 -->
<template v-if="holeDirection">
<XDirection
@go-section="goSection"
@go-track="goTrack"
@car-socket="(e) => carSocket(e, 1)"
@car-socket="(e) => carSocket(e, 'old')"
v-bind="{
...configs,
isDownCtrls: true,
direction: 1,
traffic: outTraffic,
act: holeAct === 'old',
section: false
direction: holeDirection,
traffic: oldTraffic,
act: true,
section: false,
roadHeight: oldTraffic?.road_w,
isNeedLeftCamera: true
}"
/>
<Facilities :act="holeAct === 'new'" />
<Facilities v-bind="{ traffic: newTraffic, direction: holeDirection, act: true }" />
<Road
@go-section="goSection"
@go-track="goTrack"
@car-socket="(e) => carSocket(e, 1)"
@car-socket="(e) => carSocket(e, 'new')"
v-bind="{
...configs,
isDownCtrls: true,
direction: 1,
traffic: inTraffic,
act: holeAct === 'new'
direction: holeDirection,
traffic: newTraffic,
act: true,
roadHeight: newTraffic?.road_w,
isNeedLeftCamera: true
}"
/> -->
/>
</template>
<div class="cameras-block">
<flow-chart v-if="showFlow" :direction="holeDirection" />
<cameras ref="cameraRef1" v-else holeType="old" :direction="holeDirection" />
<flow-chart
ref="flowChartRef2"
v-if="showFlow"
:direction="holeDirection"
:holeType="!holeDirection ? 'old' : 'new'"
/>
<cameras
ref="cameraRef2"
v-else
:holeType="!holeDirection ? 'old' : 'new'"
:direction="holeDirection"
/>
</div>
<FlowCarCtrl @car-filter="carFilter" holeTypeName="老洞" :infos="oldSummary" />
<FlowCarCtrl
@car-filter="carFilter"
:holeTypeName="!holeDirection ? '老洞' : '新洞'"
:infos="!holeDirection ? oldSummary : newSummary"
/>
<!-- 空间轴 -->
<div class="space-axis-block">
<span class="title">
<Icon icon="ep:info-filled" class="icon" :size="14" color="#0BC7FA" />
拖动滑块查看隧道局部
点击空间轴上对应位置放大隧道
</span>
<SpaceAxis
ref="spaceAxisHNRef"
@drag-end="(scrollLeft) => spaceAxisScroll(scrollLeft, 'new')"
v-bind="{ traffic: newTraffic, direction: holeDirection }"
@drag-end="(scrollLeft) => spaceAxisScroll(scrollLeft, holeDirection ? 'old' : 'new')"
v-bind="{
traffic: newTraffic,
direction: holeDirection,
holeType: holeDirection ? 'old' : 'new'
}"
/>
<SpaceAxis
ref="spaceAxisHORef"
@drag-end="(scrollLeft) => spaceAxisScroll(scrollLeft, 'old')"
v-bind="{ traffic: oldTraffic, direction: holeDirection }"
@drag-end="(scrollLeft) => spaceAxisScroll(scrollLeft, !holeDirection ? 'old' : 'new')"
v-bind="{
traffic: oldTraffic,
direction: holeDirection,
holeType: !holeDirection ? 'old' : 'new'
}"
/>
</div>
</div>
......@@ -171,6 +219,9 @@ const outNewTraffic = ref<TrafficEngine | null>(null) // 新洞下行隧道实
const inOldTraffic = ref<TrafficEngine | null>(null) // 老洞上行隧道实例
const ouOldTraffic = ref<TrafficEngine | null>(null) // 老洞下行隧道实例
const flowChartRef1 = ref<InstanceType<typeof FlowChart> | null>(null)
const flowChartRef2 = ref<InstanceType<typeof FlowChart> | null>(null)
const newSummary = ref<TrafficSummary>({
name: '杭州方向',
direction: 0,
......@@ -325,11 +376,39 @@ const spaceAxisScroll = (e: number, holeType: string) => {
watch(
() => holeDirection.value,
(e) => {
// 刷新流量图
nextTick(() => {
flowChartRef1.value?.chartRender()
flowChartRef2.value?.chartRender()
})
// 后续接新洞后再做修改
// newTraffic.value?.startTraffic((res) => {
// carSocket(res, 'new')
// })
newSummary.value.direction = e
oldSummary.value.direction = e
if (e === 1) {
inOldTraffic.value?.cleanRoad()
// 暂时解决老洞下行没有车流数据接入,后续接入后需要调整 start
carSocket(
{
name: '杭州方向',
direction: 1,
dangerousCount: 0,
truckCount: 0,
passengerCount: 0,
sedanCount: 0,
otherCount: 0
},
'old'
)
// 暂时解决老洞下行没有车流数据接入,后续接入后需要调整 end
} else {
ouOldTraffic.value?.cleanRoad()
}
oldTraffic.value?.startTraffic((res) => {
carSocket(res, 'old')
......
......@@ -74,8 +74,8 @@ 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 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)
......@@ -93,15 +93,15 @@ const showFlow = computed({
}
})
const cameraChange = (xcamera, direction) => {
if (direction === 0 && cameraRef1.value) {
cameraRef1.value.cameraChange(xcamera)
}
// const cameraChange = (xcamera, direction) => {
// if (direction === 0 && cameraRef1.value) {
// cameraRef1.value.cameraChange(xcamera)
// }
if (direction === 1 && cameraRef2.value) {
cameraRef2.value.cameraChange(xcamera)
}
}
// if (direction === 1 && cameraRef2.value) {
// cameraRef2.value.cameraChange(xcamera)
// }
// }
// 系统免登录
const toSystem = async () => {
......@@ -109,7 +109,7 @@ const toSystem = async () => {
window.open(freePath)
}
provide('cameraChange', cameraChange)
// provide('cameraChange', cameraChange)
const a = [{}]
const setAuths = async () => {
......@@ -125,10 +125,12 @@ const handlePackUp = () => {
}
// 打开事件告警
const openEventAlert = (id: string) => {
eventAlertPop.value?.open(id)
const openEventAlert = (info) => {
eventAlertPop.value?.open(info)
}
provide<(info) => void>('openEventAlert', openEventAlert)
// 事件告警处置
const handleEventAlert = (isUpdate: boolean = false) => {
if (isUpdate) {
......
......@@ -183,8 +183,15 @@ const handleHoleTypeChange = async () => {
newTraffic.value = new TrafficEngine(sceneMode.value, configs)
// 新洞还未对接,需后完善
newTraffic.value.startTraffic((res) => {
carSocket(res)
// newTraffic.value.startTraffic((res) => {
// carSocket(res)
// })
carSocket({
dangerousCount: 0,
truckCount: 0,
passengerCount: 0,
sedanCount: 0,
otherCount: 0
})
}
}
......@@ -275,15 +282,19 @@ onBeforeMount(async () => {
road_section_w: 134,
holeType: 'old'
})
traffic.value.startTraffic((res) => {
carSocket(res)
})
newTraffic.value = new TrafficEngine(sceneMode.value, {
...configs,
road_section_w: 201,
holeType: 'new'
})
if ( holeType === 'old' ) {
traffic.value.startTraffic((res) => {
carSocket(res)
})
} else {
// 新洞还未对接,需后完善
// newTraffic.value.startTraffic((res) => {
// carSocket(res)
......@@ -296,6 +307,7 @@ onBeforeMount(async () => {
otherCount: 0
})
}
}
})
onBeforeUnmount(() => {
......
......@@ -40,7 +40,7 @@ const router = useRouter()
// 公共参数
const commons = reactive({
filterType: null,
useMaterial: false, // 是否使用贴图
useMaterial: true, // 是否使用贴图
plateType: 1, // 铭牌展示类型
direction: 0, // 车流方向
sectionAct: 1, // 当前断面
......
......@@ -5,7 +5,7 @@ import { CAR_TYPES } from '@/config/dict'
const appStore = useAppStoreWithOut()
class Car {
export class Car {
id // 车辆 ID
type // 车型
......
......@@ -89,6 +89,7 @@ $base-url: '@/assets/images/common/';
}
.man-cross {
width: 18px;
background: url($base-url + 'man-cross.png') no-repeat;
background-size: cover;
// background: url($base-url + 'cross-back.png') no-repeat;
......
......@@ -11,7 +11,7 @@
v-for="(item, x) in cameras"
:key="x"
:style="item.style"
:class="['x-camera', { act: cameraSelected.includes(x) || camerasFocus.includes(item.id) }]"
:class="['x-camera', item.className , { act: isAllSelected || cameraSelected.includes(x) || camerasFocus.includes(item.id) }]"
@click="goSetCameras(item)"
>
<div class="pile">{{item.pileNumber}}</div>
......@@ -42,9 +42,13 @@ const props = withDefaults(defineProps<{
_traffic: any,
tunnel: any,
direction: number,
isDownCtrls?: boolean
isDownCtrls?: boolean,
isAllSelected?: boolean,
isNeedLeftCamera?: boolean
}>(), {
isDownCtrls: false
isDownCtrls: false,
isAllSelected: false,
isNeedLeftCamera: false
})
const camerasFocus = ref([])
......@@ -67,11 +71,23 @@ const cameras = computed(() => {
if (!props._traffic) return []
return props._traffic.cameraFormate(props.tunnel.cameras, props.direction).map(item => {
let style: any = { left: item.x + 'px' }
let className = ''
// camera_lable = 4紧急停车带相机
if (item.cameraLable === 4 && props.isNeedLeftCamera) {
if (props.direction) {
style.bottom = props._traffic.road_w - 24 + 'px'
}else {
style.top = props._traffic.road_w + 'px'
}
className = 'left-camera'
}
return {
...item,
style: {
left: item.x + 'px'
}
style,
className
}
})
......@@ -143,7 +159,7 @@ $base-url: '@/assets/images/home/';
display: none;
}
&:hover {
&:hover,&.act {
.pile {
display: block;
}
......@@ -156,6 +172,13 @@ $base-url: '@/assets/images/home/';
.pile {
top: -18px;
}
&.left-camera {
.pile {
bottom: -18px;
top: unset;
}
}
}
}
......@@ -169,6 +192,15 @@ $base-url: '@/assets/images/home/';
top: -18px;
transform: translateX(-50%);
}
&.left-camera {
transform: rotateY(180deg) translate(50%, -50%);
.pile {
transform: rotateY(180deg) translateX(50%);
bottom: -18px;
top: unset;
}
}
}
}
......@@ -179,6 +211,15 @@ $base-url: '@/assets/images/home/';
bottom: -18px;
transform: rotateY(180deg) translateX(50%);
}
&.left-camera {
transform: translate(50%, -50%);
.pile {
top: -18px;
bottom: unset;
transform: translateX(-50%);
}
}
}
}
......
<template>
<div class="events">
<div class="events">
<div
v-for="point in [currentEvents, nowEvent].flat()"
class="event-x"
:style="getStyle(point)"
:key="point.id"
@click="onClicked(point)"
@animationend="onAnimationEnd"
>
<div v-if="EVENT_TYPE[point.eventType]" class="chip">
<div class="text">{{ EVENT_TYPE[point.eventType] || '' }}</div>
<div :class="['type-icon', `type-${point.eventType}`]"></div>
......@@ -21,26 +19,23 @@
</div>
<img :src="EVENT_IMAGE" />
</div>
</div>
</div>
</template>
<script lang='ts' setup>
import { EVENT_TYPE } from '@/config/enum'
<script lang="ts" setup>
// import { EVENT_TYPE } from '@/config/enum'
import { EVENT_TYPES } from '@/config/dict'
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,
_traffic: any,
tunnel: any,
direction: number,
scene: string
_traffic: any
tunnel: any
direction: number
evtConfigs: any
}>()
......@@ -51,20 +46,29 @@ const trafficStore = useTrafficStoreOut()
const route = useRoute()
// 多车事件
const currentEvents = computed(() => {
const EVENT_TYPE = Object.fromEntries(
EVENT_TYPES.map((item) => {
return [item.key, item.name]
})
)
// 不自动隐藏的事件
const NO_HIDE_EVENT = [7, 14]
// 多车事件
const currentEvents = computed(() => {
if (!events.value || !props.tunnel || !props._traffic) {
return []
}
const codes = [5, 6, 7]
// const codes = [5, 6, 7]
const evts = events.value
.filter(item => codes.includes(item.eventType))
.map(item => {
const distance = props.direction ? +item.milestone - props.tunnel.start : props.tunnel.end - (+item.milestone)
// .filter(item => codes.includes(item.eventType))
.map((item) => {
const distance = !props.direction
? +item.milestone - props.tunnel.start
: props.tunnel.end - +item.milestone
return {
...item,
distance
......@@ -72,12 +76,10 @@ const currentEvents = computed(() => {
})
return props._traffic.eventFormate(evts)
})
// 当前事件
const nowEvent = computed(() => {
return []
// if (route.name !== 'Detail' || !props.evtConfigs.status || !props.tunnel) {
......@@ -95,27 +97,23 @@ const nowEvent = computed(() => {
// }])
// return evts
})
// 开启实时事件
const evtFlowStart = async () => {
if (socketEvent.value) {
socketEvent.value.close()
socketEvent.value = null
}
socketEvent.value = new TrafficSocket(
props.direction ? 'OUT_EVENT' : 'IN_EVENT'
)
socketEvent.value = new TrafficSocket(props.direction ? 'OUT_EVENT' : 'IN_EVENT')
socketEvent.value.start(res => {
socketEvent.value.start((res) => {
trafficStore.setEventCount(res.eventCount)
trafficStore.setEventNotify(res.eventDetailRespVO)
events.value = props._traffic.eventFormate(res.eventThirdEventStatusNotFinishList)
// events.value = props._traffic.eventFormate(res.eventThirdEventStatusNotFinishList)
events.value = res.eventThirdEventStatusNotFinishList
})
}
// 关闭事实事件
......@@ -127,23 +125,40 @@ const evtFlowClose = () => {
events.value = []
}
// 处理动画结束事件
const onAnimationEnd = (event) => {
if (event.animationName === 'fadeOut') {
const target = event.target as HTMLElement;
target.style.display = 'none';
}
}
// 距离计算
const getStyle = (point) => {
return {
let style: any = {
top: point.y + 'px',
left: point.x + 'px',
transform: point.transform
}
if (!NO_HIDE_EVENT.includes(point.eventType)) {
style.animation = 'fadeOut 0.5s 5s forwards'
}
return style
}
const openEventAlert = inject<(info) => void>('openEventAlert')
// 事件点击
const onClicked = (event) => {
// 回溯模式下不支持点击
if (props?.evtConfigs?.status) {
return
}
if (openEventAlert) {
openEventAlert({id: event.id})
return
}
selected.value = event.id
}
......@@ -151,7 +166,6 @@ const onClicked = (event) => {
watch(
() => [props._traffic, props.evtConfigs],
(e) => {
if (!props._traffic) return
if (route.name !== 'Detail') {
......@@ -160,30 +174,23 @@ watch(
}
e.status ? evtFlowClose() : evtFlowStart()
},
{
immediate: true
}
)
onMounted(() => {
// evtFlowStart()
})
onBeforeUnmount(() => {
// 清理事件实例的 WebSocket 连接
evtFlowClose()
})
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
$base-event: '@/assets/images/event/';
.event-x {
......@@ -227,6 +234,8 @@ $base-event: '@/assets/images/event/';
.type-icon {
width: 24px;
height: 24px;
background: url($base-event + 'event_01.png') no-repeat;
background-size: cover;
}
.type-0,
......@@ -239,7 +248,6 @@ $base-event: '@/assets/images/event/';
background-size: cover;
}
}
}
@keyframes wave {
......@@ -260,4 +268,14 @@ $base-event: '@/assets/images/event/';
}
}
@keyframes fadeOut {
from {
opacity: 1;
visibility: visible;
}
to {
opacity: 0;
visibility: hidden;
}
}
</style>
......@@ -5,20 +5,31 @@
@click="jumpToPosition"
:class="[act ? 'act' : 'disabled']"
>
<div :class="['tip', holeType]">
{{ holeTypeName }}
</div>
<!-- 滑块 -->
<div class="custom-scrollbar-thumb" ref="thumb" @mousedown="startDrag">
<span class="bg"></span>
</div>
<!-- 车流 -->
<div :class="['cars', direction ? 'out' : 'in']">
<div v-for="item in cars" :key="item.id" class="car" :style="item.style"> </div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useAppStoreWithOut } from '@/store/modules/app'
import { Car } from '@/views/traffic/roads/Engine/index'
import { ASYNC_DIRECTIONS } from '@/config/dict'
const props = withDefaults(
defineProps<{
traffic: any // 车流引擎
direction: number
act?: boolean
holeType: string // 新洞/老洞(new/old)
}>(),
{
isDownCtrls: false,
......@@ -30,8 +41,14 @@ const emits = defineEmits(['space-axis-scroll', 'drag-end'])
const appStore = useAppStoreWithOut()
const thumb = ref(null)
const track = ref(null)
const thumb = ref<HTMLDivElement | null>(null)
const track = ref<HTMLDivElement | null>(null)
let engine: any = null // 动画引擎
const holeTypeName = computed(() => {
const holeName = ASYNC_DIRECTIONS()[props.direction].name
return holeName + (props.holeType === 'new' ? '新洞' : '老洞')
})
const scrollContainerWidth = computed(() => {
let _sections = props.traffic?.sectionsCompute()
......@@ -39,6 +56,52 @@ const scrollContainerWidth = computed(() => {
return (_sections.length - 1 + _sections.slice(-1)[0]?.grow) * appStore.getDesign
})
// 车流
const cars = computed(() => {
// 新洞还没接,后续接好后需要去掉 start
if(props.holeType === 'new') return []
// 新洞还没接,后续接好后需要去掉 end
if (!props.traffic || !props.act) return []
const { cars, miles } = props.traffic
const _cars = cars
.filter((item) => item.distance <= miles)
.map((item) => {
return new Car({
ratex: appStore.getDesign / miles,
id: item.id,
type: item.type,
speed: item.speed,
distance: item.distance,
line: item.line,
direction: item.direction,
plate: item.plate,
originalCar: item.originalCar,
car_length: 6,
car_width: 6,
lanes: [4, 12]
})
})
return _cars
})
// 新增动画循环
const animate = () => {
cars.value.forEach((car) => {
car.run() // 更新每辆车的位置
})
engine = requestAnimationFrame(animate)
}
const stopAnimate = () => {
if (engine) {
cancelAnimationFrame(engine)
}
}
let isDragging = false
// 计算滑块宽度
......@@ -61,7 +124,7 @@ const updateThumb = ({ scrollLeft }) => {
// 点击轨道跳转
const jumpToPosition = (e) => {
if (!track.value) return
if (!track.value || !thumb.value) return
const trackWidth = track.value.clientWidth
const trackRect = track.value.getBoundingClientRect()
const clickPosition = e.clientX - trackRect.left
......@@ -79,7 +142,7 @@ const startDrag = (e) => {
e.preventDefault()
isDragging = true
const startX = e.clientX
const startLeft = parseFloat(thumb.value.style.left || '0')
const startLeft = parseFloat(thumb.value?.style.left || '0')
let actualScrollLeft = 0
const moveHandler = (e) => {
......@@ -110,6 +173,7 @@ const startDrag = (e) => {
// 设置滑块位置
const setThumbPosition = (scrollLeft: number) => {
if (!thumb.value) return
thumb.value.style.left = `${scrollLeft}px`
}
......@@ -123,6 +187,11 @@ watch(
onMounted(() => {
// updateThumbWidth()
animate()
})
onUnmounted(() => {
stopAnimate()
})
defineExpose({
......@@ -132,6 +201,8 @@ defineExpose({
</script>
<style lang="scss" scoped>
$base-url: '@/assets/images/section/';
.custom-scrollbar-track {
position: relative;
// right: 0;
......@@ -146,8 +217,32 @@ defineExpose({
pointer-events: none;
}
.tip {
font-size: 14px;
font-weight: normal;
line-height: 14px;
letter-spacing: 0.5em;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.75;
cursor: default;
&::before {
display: inline-block;
content: '';
margin-right: 10px;
width: 11px;
height: 12px;
background: url($base-url + 'hole-icon.png') no-repeat;
background-size: cover;
}
}
.custom-scrollbar-thumb {
position: absolute;
top: 0;
left: 0;
width: 110px;
height: 100%;
cursor: pointer;
......@@ -181,5 +276,28 @@ defineExpose({
.custom-scrollbar-thumb:hover {
opacity: 1;
}
.cars {
.car {
position: absolute;
width: 6px;
height: 6px;
border-radius: 2px;
background: #0bc7fa;
}
&.in {
.car {
right: 0px;
bottom: 0px;
}
}
&.out {
.car {
left: 0px;
top: 0px;
}
}
}
}
</style>
......@@ -39,7 +39,7 @@
<!-- 摄像机层 -->
<Camera
class="dis-tag"
v-bind="{ scene: 'panorama', direction, isDownCtrls, tunnel, _traffic: traffic }"
v-bind="{ scene: 'panorama', direction, isDownCtrls, tunnel, _traffic: traffic, isNeedLeftCamera }"
@camera-change="(e) => emits('camera-change', e)"
/>
......@@ -152,6 +152,7 @@ const props = withDefaults(
isDownCtrls?: boolean // 是否下方展示控制器
trackers?: any
roadHeight?: number // 路面高度
isNeedLeftCamera?: boolean // 是否需要显示左边摄像头
}>(),
{
section: true,
......@@ -159,7 +160,8 @@ const props = withDefaults(
isDownCtrls: false,
act: true,
trackers: {},
roadHeight: 90
roadHeight: 90,
isNeedLeftCamera: false
}
)
......
......@@ -20,7 +20,7 @@
<div class="container-block">
<div class="road-scroll" ref="roadScrollRef" @scroll="roadScroll">
<!-- 监控视频层 -->
<Cameras v-if="act" v-bind="{ direction, tunnel, _traffic: traffic }" ref="camerasRef" />
<Cameras v-if="act" v-bind="{ direction, tunnel, traffic }" ref="camerasRef" />
<div class="road-scale" :style="{ height: roadHeight + 'px' }">
<div :style="roadStyle" class="main-road">
......@@ -30,7 +30,7 @@
<!-- 摄像机层 -->
<Camera
v-if="act"
v-bind="{ direction, tunnel, _traffic: traffic }"
v-bind="{ direction, tunnel, _traffic: traffic, isAllSelected: true }"
@camera-change="(e) => emits('camera-change', e)"
/>
......@@ -119,11 +119,11 @@ import CarCollect from '@/views/traffic/components/CarCollect/index.vue'
import EventPop from '@/views/traffic/components/EventPop/index.vue'
import tunnelConfigs from '@/config/tunnel'
import TrafficEngine from '@/views/traffic/roads/Engine/index'
// import TrafficEngine from '@/views/traffic/roads/Engine/index'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useTunnelStoreWithOut } from '@/store/modules/tunnel'
import { useRoute } from 'vue-router'
// import { useRoute } from 'vue-router'
const props = withDefaults(
defineProps<{
......@@ -160,7 +160,7 @@ const emits = defineEmits([
const tunnelStore = useTunnelStoreWithOut()
const appStore = useAppStoreWithOut()
const route = useRoute()
// const route = useRoute()
const collectRef = ref<any>(null) // 车辆 pop
const eventRef = ref<any>(null) // 事件 pop
......@@ -170,7 +170,7 @@ const start = ref(true) // 是否初次追踪
const firstSetRoadScroll = ref<boolean>(true) // 是否初次设置滚动条
const roadScrollLeft = ref<number | null>(null) // 滚动条位置
const _traffic = ref<any>(null) // 车流引擎
// const _traffic = ref<any>(null) // 车流引擎
const _sections = ref<any[]>([]) // 段面控制
const _cars = ref<any>() // 车流
......@@ -179,34 +179,34 @@ const _carSelected = ref(null) // 选中车辆
const _events = ref<any[]>([]) // 事件
const _eventSelected = ref(null) // 选中车辆
const sectionAct = defineModel('sectionAct') // 选中段面
const sectionAct = defineModel<number>('sectionAct') // 选中段面
const tunnel = computed(() => {
return [tunnelStore.getUpTunnel, tunnelStore.getDownTunnel][props.direction]
})
// 车道初始化
const trafficInit = (e) => {
if (_traffic.value) return
if (props.traffic) {
_traffic.value = props.traffic
} else {
const { start, end, long } = tunnel.value
_traffic.value = new TrafficEngine('section', {
direction: props.direction,
start: +start,
end: +end,
miles: +long
})
}
// const trafficInit = (e) => {
// if (_traffic.value) return
// if (props.traffic) {
// _traffic.value = props.traffic
// } else {
// const { start, end, long } = tunnel.value
// _traffic.value = new TrafficEngine('section', {
// direction: props.direction,
// start: +start,
// end: +end,
// miles: +long
// })
// }
_sections.value = _traffic.value.sectionsCompute()
// _sections.value = _traffic.value.sectionsCompute()
nextTick(() => {
sectionChange(sectionAct.value, true)
})
}
// nextTick(() => {
// sectionChange(sectionAct.value, true)
// })
// }
// 车流切面
watch(
......@@ -254,7 +254,7 @@ const roadStyle = computed(() => {
})
// 缎面切换
const sectionChange = (x, direct?: boolean, isScrollLeft?: boolean = false) => {
const sectionChange = (x, direct?: boolean, isScrollLeft: boolean = false) => {
if (!props.act) return
emits('section-change', x)
......@@ -266,7 +266,7 @@ const sectionChange = (x, direct?: boolean, isScrollLeft?: boolean = false) => {
thumb_x = _sections.value.length - 1 + _sections.value.slice(-1)[0].grow
}
const dx = Math.abs(sectionAct.value - x)
// const dx = Math.abs(sectionAct.value - x)
// 跳过动画
// if (direct) {
......@@ -282,13 +282,13 @@ const sectionChange = (x, direct?: boolean, isScrollLeft?: boolean = false) => {
// }
// }
let scrollLeft = !props.direction
let scrollLeft: number = !props.direction
? (_sections.value.length - 1 + _sections.value.slice(-1)[0].grow - thumb_x) *
appStore.getDesign
: thumb_x * appStore.getDesign
if (isScrollLeft) {
scrollLeft = roadScrollLeft.value
scrollLeft = roadScrollLeft.value as number
}
// 滚动条定位
setRoadScroll(scrollLeft, direct)
......@@ -326,7 +326,7 @@ watch(
_sections.value = e.sectionsCompute()
nextTick(() => {
if (firstSetRoadScroll.value) {
roadScrollLeft.value = props.scrollLeft
roadScrollLeft.value = props.scrollLeft as number
}
// const isScrollLeft = props.scrollLeft != null && firstSetRoadScroll.value ? true : false
const isScrollLeft = props.scrollLeft != null ? true : false
......@@ -348,19 +348,23 @@ const handleChange = (x) => {
}
}
const roadScrollRef = ref<HTMLDivElement>(null)
const roadScrollRef = ref<HTMLDivElement | null>(null)
const camerasRef = ref<HTMLDivElement | null>(null)
const roadScroll = () => {
const scrollLeft = roadScrollRef.value?.scrollLeft || 0
emits('update-space-axis', scrollLeft)
camerasRef.value?.checkVisibility()
}
// 设置路面滚动位置
const setRoadScroll = (scrollLeft, direct?: boolean = true) => {
roadScrollRef.value.scrollLeft = scrollLeft
const setRoadScroll = (scrollLeft, direct: boolean = true) => {
if (roadScrollRef.value) roadScrollRef.value.scrollLeft = scrollLeft
roadScrollLeft.value = scrollLeft
// 是否平滑滚动
if (!direct) {
if (!direct && roadScrollRef.value) {
roadScrollRef.value.style['scroll-behavior'] = 'smooth'
}
}
......@@ -491,6 +495,7 @@ $base-common: '@/assets/images/common/';
.road-scroll {
overflow-x: scroll;
scrollbar-width: none;
padding-left: 50px;
}
.road-scale {
padding-top: 30px;
......@@ -510,6 +515,7 @@ $base-common: '@/assets/images/common/';
top: 50%;
width: 100%;
margin-top: -1px;
overflow: hidden;
.x-line {
height: 2px;
position: absolute;
......@@ -531,6 +537,26 @@ $base-common: '@/assets/images/common/';
position: absolute;
background: url($base-url + 'road.png');
background-size: auto 100%;
&::before {
position: absolute;
content: '';
width: 75px;
height: 100%;
left: -75px;
top: 0;
background: url($base-url + 'road.png');
background-size: auto 100%;
}
&::after {
position: absolute;
content: '';
width: 100px;
height: 100%;
right: -100px;
top: 0;
background: url($base-url + 'road.png');
background-size: auto 100%;
}
.entry {
height: 270px;
width: 267px;
......
......@@ -4,11 +4,12 @@
v-for="(item, x) in cameras"
:key="x"
:style="item.style"
:class="['x-camera', { act: cameraSelected.includes(x) || camerasFocus.includes(item.id) }]"
class="x-camera"
ref="cameraElements"
>
<div class="video-view">
<VideoPlayer
@click="() => videoFocus(x)"
v-if="showCameras.includes(x)"
v-bind="{
frameW: 368,
frameH: 234,
......@@ -29,17 +30,10 @@
<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'])
......@@ -47,59 +41,85 @@ const emits = defineEmits(['camera-change', 'click-trigger'])
// 参数
const props = withDefaults(
defineProps<{
_traffic: any
traffic: any
tunnel: any
direction: number
}>(),
{}
)
const camerasFocus = ref<string[]>([])
// 选中摄像头计算
const cameraSelected = computed(() => {
const block = route.name
if (block === 'Home') {
return getCameras(props.direction)
const cameraElements = ref<HTMLElement[]>([])
// const observer = ref<IntersectionObserver | null>(null)
const containerEl = ref<HTMLElement | null>(null)
const showCameras = ref<number[]>([])
// 处理坐标偏移
const adjustXPositions = (points: { x: number }[]) => {
const adjustedPoints = [...points];
let adjusted = true;
// 持续调整,直到所有相邻点的间隔都不小于 368
while (adjusted) {
adjusted = false;
for (let i = 1; i < adjustedPoints.length; i++) {
const prevPoint = adjustedPoints[i - 1];
const currentPoint = adjustedPoints[i];
const gap = currentPoint.x - prevPoint.x;
if (gap < 370) {
const offset = (370 - gap) / 2;
prevPoint.x -= offset;
currentPoint.x += offset;
adjusted = true;
}
}
}
return camerasFocus.value
})
return adjustedPoints;
};
// 路面摄像头铺设
const cameras = computed(() => {
if (!props._traffic) return []
return props._traffic.cameraFormate(props.tunnel.cameras, props.direction).map((item) => {
if (!props.traffic) return []
const cameraList = props.traffic.cameraFormate(props.tunnel.cameras, props.direction)
const adjustedList = adjustXPositions(cameraList);
return adjustedList.map((item) => {
return {
...item,
style: {
left: item.x + 'px'
}
},
isVisible: false // 初始不可见
}
})
})
// 摄像头手动切换
const goSetCameras = (xcamera) => {
const block = route.name
if (block === 'Home') {
return setCameras(xcamera, props.direction)
} else {
emits('camera-change', xcamera)
}
const openVideo = (item) => {
app?.proxy?.$openVideo(item)
}
onMounted(() => {
bus.on('focus-camera', (ids) => {
camerasFocus.value = ids
const checkVisibility = () => {
containerEl.value = document.querySelector('.road-scroll')
if (!containerEl.value) return
const containerRect = containerEl.value.getBoundingClientRect()
const showList: number[] = []
cameraElements.value.forEach((el, index) => {
const elRect = el.getBoundingClientRect()
const isVisible = elRect.right > containerRect.left && elRect.left < containerRect.right
isVisible && showList.push(index)
})
showCameras.value = [...showList]
}
defineExpose({
checkVisibility
})
</script>
<style lang="scss" scoped>
$base-url: '@/assets/images/home/';
$base-common: '@/assets/images/common/';
.x-direction-section-camera {
position: relative;
......@@ -143,5 +163,23 @@ $base-url: '@/assets/images/home/';
border-radius: 0 0 6px 6px;
}
}
.full-screen {
width: 20px;
height: 20px;
background: url($base-common + 'full-screen.png') no-repeat;
background-size: cover;
position: absolute;
right: 8px;
bottom: 40px;
transition-property: all;
transition-duration: 0.2s;
z-index: 3;
&:hover {
cursor: pointer;
box-shadow: 0 0 10px #aeaeae;
}
}
}
</style>
......@@ -20,7 +20,7 @@
<div class="container-block">
<div class="road-scroll" ref="roadScrollRef" @scroll="roadScroll">
<!-- 监控视频层 -->
<Cameras v-if="act" v-bind="{ direction, tunnel, _traffic: traffic }" ref="camerasRef" />
<Cameras v-if="act" v-bind="{ direction, tunnel, traffic }" ref="camerasRef" />
<div class="road-scale" :style="{ height: roadHeight + 'px' }">
<div :style="roadStyle" class="main-road">
......@@ -30,7 +30,7 @@
<!-- 摄像机层 -->
<Camera
v-if="act"
v-bind="{ direction, tunnel, _traffic: traffic }"
v-bind="{ direction, tunnel, _traffic: traffic, isAllSelected: true }"
@camera-change="(e) => emits('camera-change', e)"
/>
......@@ -117,11 +117,11 @@ import CarCollect from '@/views/traffic/components/CarCollect/index.vue'
import EventPop from '@/views/traffic/components/EventPop/index.vue'
import tunnelConfigs from '@/config/tunnel'
import TrafficEngine from '@/views/traffic/roads/Engine/index'
// import TrafficEngine from '@/views/traffic/roads/Engine/index'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useTunnelStoreWithOut } from '@/store/modules/tunnel'
import { useRoute } from 'vue-router'
// import { useRoute } from 'vue-router'
const props = withDefaults(
defineProps<{
......@@ -158,7 +158,7 @@ const emits = defineEmits([
const tunnelStore = useTunnelStoreWithOut()
const appStore = useAppStoreWithOut()
const route = useRoute()
// const route = useRoute()
const collectRef = ref<any>(null) // 车辆 pop
const eventRef = ref<any>(null) // 事件 pop
......@@ -168,7 +168,7 @@ const start = ref(true) // 是否初次追踪
const firstSetRoadScroll = ref<boolean>(true) // 是否初次设置滚动条
const roadScrollLeft = ref<number | null>(null) // 滚动条位置
const _traffic = ref<any>(null) // 车流引擎
// const _traffic = ref<any>(null) // 车流引擎
const _sections = ref<any[]>([]) // 段面控制
const _cars = ref<any>() // 车流
......@@ -177,34 +177,34 @@ const _carSelected = ref(null) // 选中车辆
const _events = ref<any[]>([]) // 事件
const _eventSelected = ref(null) // 选中车辆
const sectionAct = defineModel('sectionAct') // 选中段面
const sectionAct = defineModel<number>('sectionAct') // 选中段面
const tunnel = computed(() => {
return [tunnelStore.getUpTunnel, tunnelStore.getDownTunnel][props.direction]
})
// 车道初始化
const trafficInit = (e) => {
if (_traffic.value) return
if (props.traffic) {
_traffic.value = props.traffic
} else {
const { start, end, long } = tunnel.value
_traffic.value = new TrafficEngine('section', {
direction: props.direction,
start: +start,
end: +end,
miles: +long
})
}
// const trafficInit = (e) => {
// if (_traffic.value) return
// if (props.traffic) {
// _traffic.value = props.traffic
// } else {
// const { start, end, long } = tunnel.value
// _traffic.value = new TrafficEngine('section', {
// direction: props.direction,
// start: +start,
// end: +end,
// miles: +long
// })
// }
_sections.value = _traffic.value.sectionsCompute()
// _sections.value = _traffic.value.sectionsCompute()
nextTick(() => {
sectionChange(sectionAct.value, true)
})
}
// nextTick(() => {
// sectionChange(sectionAct.value, true)
// })
// }
// 车流切面
watch(
......@@ -264,7 +264,7 @@ const sectionChange = (x, direct?: boolean, isScrollLeft: boolean = false) => {
thumb_x = _sections.value.length - 1 + _sections.value.slice(-1)[0].grow
}
const dx = Math.abs(sectionAct.value - x)
// const dx = Math.abs(sectionAct.value - x)
// 跳过动画
// if (direct) {
......@@ -286,7 +286,7 @@ const sectionChange = (x, direct?: boolean, isScrollLeft: boolean = false) => {
: thumb_x * appStore.getDesign
if (isScrollLeft) {
scrollLeft = roadScrollLeft.value
scrollLeft = roadScrollLeft.value as number
}
// 滚动条定位
setRoadScroll(scrollLeft, direct)
......@@ -324,7 +324,7 @@ watch(
_sections.value = e.sectionsCompute()
nextTick(() => {
if (firstSetRoadScroll.value) {
roadScrollLeft.value = props.scrollLeft
roadScrollLeft.value = props.scrollLeft as number
}
const isScrollLeft = props.scrollLeft != null ? true : false
sectionChange(sectionAct.value, true, isScrollLeft)
......@@ -345,19 +345,23 @@ const handleChange = (x) => {
}
}
const roadScrollRef = ref<HTMLDivElement>(null)
const roadScrollRef = ref<HTMLDivElement | null>(null)
const camerasRef = ref<HTMLDivElement | null>(null)
const roadScroll = () => {
const scrollLeft = roadScrollRef.value?.scrollLeft || 0
emits('update-space-axis', scrollLeft)
camerasRef.value?.checkVisibility()
}
// 设置路面滚动位置
const setRoadScroll = (scrollLeft, direct?: boolean = true) => {
roadScrollRef.value.scrollLeft = scrollLeft
const setRoadScroll = (scrollLeft, direct: boolean = true) => {
if (roadScrollRef.value) roadScrollRef.value.scrollLeft = scrollLeft
roadScrollLeft.value = scrollLeft
// 是否平滑滚动
if (!direct) {
if (!direct && roadScrollRef.value) {
roadScrollRef.value.style['scroll-behavior'] = 'smooth'
}
}
......@@ -488,6 +492,7 @@ $base-common: '@/assets/images/common/';
.road-scroll {
overflow-x: scroll;
scrollbar-width: none;
padding-left: 50px;
}
.road-scale {
padding-top: 30px;
......@@ -507,6 +512,7 @@ $base-common: '@/assets/images/common/';
top: 50%;
width: 100%;
margin-top: -1px;
overflow: hidden;
.x-line {
height: 2px;
position: absolute;
......@@ -528,6 +534,26 @@ $base-common: '@/assets/images/common/';
position: absolute;
background: url($base-url + 'road.png');
background-size: auto 100%;
&::before {
position: absolute;
content: '';
width: 75px;
height: 100%;
left: -75px;
top: 0;
background: url($base-url + 'road.png');
background-size: auto 100%;
}
&::after {
position: absolute;
content: '';
width: 100px;
height: 100%;
right: -100px;
top: 0;
background: url($base-url + 'road.png');
background-size: auto 100%;
}
.entry {
height: 270px;
width: 267px;
......
......@@ -13,6 +13,7 @@ class TrafficSocket {
OUT_EVENT: SOCKET_BASE + '/qxsd/realTimeEvent1'
}
isManualClose = false
link = null
socket = null
callmap = {}
......@@ -34,9 +35,19 @@ class TrafficSocket {
fuc(JSON.parse(res.data))
})
this.socket.addEventListener('close', (e) => {
if (!this.isManualClose) {
console.log('🚀 ~ WebSocket异常断开,尝试重连...')
this.start(fuc)
} else {
console.log('🚀 ~ WebSocket手动关闭')
}
})
}
close () {
this.isManualClose = true // 设置手动关闭标志
this.socket.close()
}
......
......@@ -24,14 +24,15 @@
"@/*": ["src/*"]
},
"types": [
"@intlify/unplugin-vue-i18n/config/types",
// "@intlify/unplugin-vue-i18n/config/types",
"@intlify/unplugin-vue-i18n",
"vite/client",
"element-plus/global",
"@types/qrcode",
"vite-plugin-svg-icons/client"
],
"outDir": "target", // 请保留这个属性,防止tsconfig.json文件报错
"typeRoots": ["./node_modules/@types/", "./config/types"]
// "typeRoots": ["./node_modules/@types/", "./config/types"]
},
"include": [
"src",
......
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