视频播放修改
This commit is contained in:
parent
6bb7da638f
commit
b081921d0c
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="video-monitor-container">
|
||||
<!-- 顶部状态栏 -->
|
||||
<el-card class="top-bar-card" shadow="never">
|
||||
<div class="top-bar">
|
||||
<div class="status-bar">
|
||||
|
|
@ -14,8 +13,12 @@
|
|||
<i class="el-icon-time"></i>
|
||||
{{ currentTime }}
|
||||
</div>
|
||||
<!-- 画线按钮组 -->
|
||||
<div class="draw-line-buttons">
|
||||
<el-button type="default" plain class="fullscreen-btn" @click="toggleFullscreen"
|
||||
icon="el-icon-full-screen">
|
||||
{{ isFullscreen ? '退出全屏' : '全屏监控' }}
|
||||
</el-button>
|
||||
|
||||
<el-button v-if="!isDrawingMode" type="primary" class="draw-line-btn" @click="handleDrawLine"
|
||||
icon="el-icon-edit">
|
||||
画线
|
||||
|
|
@ -35,25 +38,19 @@
|
|||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 视频播放区域 -->
|
||||
<el-card class="video-card" shadow="never" :body-style="{ padding: '0', height: '100%' }">
|
||||
<div class="video-wrapper">
|
||||
<div class="video-container" ref="videoContainer">
|
||||
<video ref="videoPlayer" class="video-player" :src="videoUrl" autoplay muted controls
|
||||
@loadedmetadata="handleVideoLoaded"
|
||||
@error="handleVideoError"
|
||||
>
|
||||
@loadedmetadata="handleVideoLoaded" @error="handleVideoError" controlsList="nofullscreen">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
|
||||
<!-- 画线画布覆盖层 -->
|
||||
<canvas ref="drawCanvas" class="draw-canvas" :class="{ active: isDrawingMode }"
|
||||
@mousedown="startDrawing" @mousemove="draw" @mouseup="stopDrawing"
|
||||
@mouseleave="stopDrawing"></canvas>
|
||||
<!-- 已保存的画线显示层(只读) -->
|
||||
<canvas ref="savedLineCanvas" class="saved-line-canvas"></canvas>
|
||||
|
||||
<!-- 视频加载提示 -->
|
||||
<div v-if="!videoUrl" class="video-placeholder">
|
||||
<i class="el-icon-video-camera"></i>
|
||||
<p>暂无视频流</p>
|
||||
|
|
@ -72,21 +69,27 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
currentTime: '',
|
||||
videoUrl: '', // 视频流地址,可以从接口获取
|
||||
isDrawingMode: false, // 是否开启画线模式
|
||||
videoUrl: '',
|
||||
isDrawingMode: false,
|
||||
isDrawing: false,
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
endX: 0,
|
||||
endY: 0,
|
||||
line: null, // 保存画线的数据(只允许一条线)
|
||||
savedLine: null, // 已保存的画线数据(保存成功后保留)
|
||||
initialLine: null, // 初始的线(默认线)
|
||||
currentLine: null, // 当前正在画的线
|
||||
detectedVehicles: [], // 检测到的车辆数据
|
||||
line: null,
|
||||
savedLine: null,
|
||||
initialLine: null,
|
||||
currentLine: null,
|
||||
detectedVehicles: [],
|
||||
timer: null,
|
||||
saving: false, // 保存中状态
|
||||
videoErrorShown: false, // 是否已显示视频错误提示
|
||||
saving: false,
|
||||
videoErrorShown: false,
|
||||
// 新增:全屏状态标记
|
||||
isFullscreen: false,
|
||||
// 新增:记录上一次容器尺寸,用于计算缩放比例
|
||||
lastContainerWidth: 0,
|
||||
lastContainerHeight: 0,
|
||||
resizeObserver: null,
|
||||
lineInfo: {
|
||||
equipmentName: '',
|
||||
equipmentStatus: '',
|
||||
|
|
@ -114,23 +117,130 @@ export default {
|
|||
this.updateTime()
|
||||
}, 1000)
|
||||
|
||||
// 初始化画布
|
||||
// 初始化 ResizeObserver 监听容器大小变化
|
||||
this.initResizeObserver()
|
||||
|
||||
// 监听全屏变化事件(处理 ESC 退出全屏的情况)
|
||||
document.addEventListener('fullscreenchange', this.onFullscreenChange)
|
||||
document.addEventListener('webkitfullscreenchange', this.onFullscreenChange)
|
||||
document.addEventListener('mozfullscreenchange', this.onFullscreenChange)
|
||||
document.addEventListener('MSFullscreenChange', this.onFullscreenChange)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.initCanvas()
|
||||
// 初始化默认线(可以从接口获取,这里使用示例数据)
|
||||
this.initInitialLine()
|
||||
})
|
||||
|
||||
// 模拟加载视频(实际应该从接口获取)
|
||||
this.loadVideoStream()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
// 销毁监听器
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect()
|
||||
}
|
||||
document.removeEventListener('fullscreenchange', this.onFullscreenChange)
|
||||
document.removeEventListener('webkitfullscreenchange', this.onFullscreenChange)
|
||||
document.removeEventListener('mozfullscreenchange', this.onFullscreenChange)
|
||||
document.removeEventListener('MSFullscreenChange', this.onFullscreenChange)
|
||||
},
|
||||
methods: {
|
||||
/** 更新时间 */
|
||||
/** 初始化 ResizeObserver */
|
||||
initResizeObserver() {
|
||||
const container = this.$refs.videoContainer
|
||||
if (!container) return
|
||||
|
||||
// 记录初始尺寸
|
||||
this.lastContainerWidth = container.offsetWidth
|
||||
this.lastContainerHeight = container.offsetHeight
|
||||
|
||||
this.resizeObserver = new ResizeObserver(entries => {
|
||||
for (let entry of entries) {
|
||||
// 使用 requestAnimationFrame 避免频繁触发
|
||||
requestAnimationFrame(() => {
|
||||
this.handleResize(entry.contentRect)
|
||||
})
|
||||
}
|
||||
})
|
||||
this.resizeObserver.observe(container)
|
||||
},
|
||||
|
||||
/** 处理容器大小变化(全屏或窗口缩放)核心逻辑 */
|
||||
handleResize(rect) {
|
||||
const newWidth = rect.width
|
||||
const newHeight = rect.height
|
||||
|
||||
// 防止为0的情况
|
||||
if (newWidth === 0 || newHeight === 0) return
|
||||
|
||||
// 1. 计算缩放比例
|
||||
const scaleX = newWidth / this.lastContainerWidth
|
||||
const scaleY = newHeight / this.lastContainerHeight
|
||||
|
||||
// 2. 更新 Canvas 物理分辨率以匹配新的 CSS 尺寸
|
||||
const drawCanvas = this.$refs.drawCanvas
|
||||
const savedCanvas = this.$refs.savedLineCanvas
|
||||
|
||||
if (drawCanvas) {
|
||||
drawCanvas.width = newWidth
|
||||
drawCanvas.height = newHeight
|
||||
}
|
||||
if (savedCanvas) {
|
||||
savedCanvas.width = newWidth
|
||||
savedCanvas.height = newHeight
|
||||
}
|
||||
|
||||
// 3. 按比例更新所有线的坐标
|
||||
const scaleLine = (l) => {
|
||||
if (!l) return null
|
||||
return {
|
||||
startX: l.startX * scaleX,
|
||||
startY: l.startY * scaleY,
|
||||
endX: l.endX * scaleX,
|
||||
endY: l.endY * scaleY
|
||||
}
|
||||
}
|
||||
|
||||
if (this.initialLine) this.initialLine = scaleLine(this.initialLine)
|
||||
if (this.savedLine) this.savedLine = scaleLine(this.savedLine)
|
||||
if (this.line) this.line = scaleLine(this.line)
|
||||
if (this.currentLine) this.currentLine = scaleLine(this.currentLine)
|
||||
|
||||
// 4. 更新记录的尺寸
|
||||
this.lastContainerWidth = newWidth
|
||||
this.lastContainerHeight = newHeight
|
||||
|
||||
// 5. 重绘
|
||||
this.redrawCanvas()
|
||||
},
|
||||
|
||||
/** 全屏切换 */
|
||||
toggleFullscreen() {
|
||||
const container = this.$refs.videoContainer
|
||||
if (!document.fullscreenElement) {
|
||||
// 进入全屏
|
||||
if (container.requestFullscreen) {
|
||||
container.requestFullscreen()
|
||||
} else if (container.webkitRequestFullscreen) {
|
||||
container.webkitRequestFullscreen()
|
||||
} else if (container.msRequestFullscreen) {
|
||||
container.msRequestFullscreen()
|
||||
}
|
||||
} else {
|
||||
// 退出全屏
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** 监听全屏状态变化 */
|
||||
onFullscreenChange() {
|
||||
this.isFullscreen = !!document.fullscreenElement
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
|
|
@ -142,12 +252,15 @@ export default {
|
|||
this.currentTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
/** 初始化画布 */
|
||||
initCanvas() {
|
||||
const canvas = this.$refs.drawCanvas
|
||||
const savedCanvas = this.$refs.savedLineCanvas
|
||||
const container = this.$refs.videoContainer
|
||||
if (container) {
|
||||
// 确保更新 lastContainerWidth
|
||||
this.lastContainerWidth = container.offsetWidth
|
||||
this.lastContainerHeight = container.offsetHeight
|
||||
|
||||
if (canvas) {
|
||||
canvas.width = container.offsetWidth
|
||||
canvas.height = container.offsetHeight
|
||||
|
|
@ -155,7 +268,6 @@ export default {
|
|||
if (savedCanvas) {
|
||||
savedCanvas.width = container.offsetWidth
|
||||
savedCanvas.height = container.offsetHeight
|
||||
// 如果有已保存的线或初始线,重新绘制
|
||||
if (this.savedLine || this.line || this.initialLine) {
|
||||
this.redrawCanvas()
|
||||
}
|
||||
|
|
@ -163,7 +275,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/** 初始化默认线 */
|
||||
async initInitialLine() {
|
||||
try {
|
||||
const container = this.$refs.videoContainer
|
||||
|
|
@ -172,14 +283,11 @@ export default {
|
|||
const containerWidth = container.offsetWidth || 1920
|
||||
const containerHeight = container.offsetHeight || 1080
|
||||
|
||||
// 调用接口获取画线详情
|
||||
const res = await getLineDetailAPI({})
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data
|
||||
this.lineInfo = data
|
||||
|
||||
// 如果有画线数据,将百分比坐标转换为像素坐标
|
||||
if (data.startX && data.startY && data.endX && data.endY) {
|
||||
this.initialLine = {
|
||||
startX: (parseFloat(data.startX) / 100) * containerWidth,
|
||||
|
|
@ -187,8 +295,6 @@ export default {
|
|||
endX: (parseFloat(data.endX) / 100) * containerWidth,
|
||||
endY: (parseFloat(data.endY) / 100) * containerHeight
|
||||
}
|
||||
|
||||
// 设置为已保存的线并显示
|
||||
this.savedLine = { ...this.initialLine }
|
||||
this.redrawCanvas()
|
||||
}
|
||||
|
|
@ -198,95 +304,71 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/** 处理画线按钮点击 */
|
||||
handleDrawLine() {
|
||||
this.isDrawingMode = true
|
||||
// 不清除已保存的线,保留旧线显示
|
||||
this.line = null // 重置当前画线数据
|
||||
this.clearCanvas() // 清除当前画布(只清除正在画的线)
|
||||
// 如果有已保存的线,重新绘制
|
||||
this.line = null
|
||||
this.clearCanvas()
|
||||
if (this.savedLine) {
|
||||
this.redrawCanvas()
|
||||
}
|
||||
this.$message.success('画线模式已开启,在视频上按住鼠标左键拖动即可画线(只能画一条线)')
|
||||
this.$message.success('画线模式已开启,在视频上按住鼠标左键拖动即可画线')
|
||||
},
|
||||
|
||||
/** 保存画线 */
|
||||
async handleSaveLines() {
|
||||
// 如果有已保存的线,可以继续保存(更新保存)
|
||||
// 如果没有新画的线,但有已保存的线,也可以保存(更新保存)
|
||||
const lineToSave = this.line || this.savedLine
|
||||
|
||||
if (!lineToSave) {
|
||||
this.$message.warning('请先画线后再保存')
|
||||
return
|
||||
}
|
||||
|
||||
this.saving = true
|
||||
try {
|
||||
// 获取视频容器的尺寸,用于计算相对坐标
|
||||
const container = this.$refs.videoContainer
|
||||
const containerWidth = container ? container.offsetWidth : 1920
|
||||
const containerHeight = container ? container.offsetHeight : 1080
|
||||
|
||||
// 准备保存的数据,将像素坐标转换为相对坐标(百分比),并转换为字符串
|
||||
const params = {
|
||||
startX: String(((lineToSave.startX / containerWidth) * 100).toFixed(2)),
|
||||
startY: String(((lineToSave.startY / containerHeight) * 100).toFixed(2)),
|
||||
endX: String(((lineToSave.endX / containerWidth) * 100).toFixed(2)),
|
||||
endY: String(((lineToSave.endY / containerHeight) * 100).toFixed(2))
|
||||
}
|
||||
|
||||
const res = await updateLineAPI(params)
|
||||
|
||||
if (res.code === 200) {
|
||||
this.$message.success('画线数据保存成功')
|
||||
// 保存成功后,将当前线保存到已保存的线,同时更新初始线
|
||||
if (this.line) {
|
||||
const newLine = { ...this.line }
|
||||
this.savedLine = newLine
|
||||
// 保存成功后,同时更新初始线为新保存的线
|
||||
this.initialLine = { ...newLine }
|
||||
this.line = null
|
||||
}
|
||||
// 关闭画线模式,但保留画线在画布上
|
||||
this.isDrawingMode = false
|
||||
// 清除当前画布
|
||||
this.clearCanvas()
|
||||
// 重绘已保存的画线(只显示最新的线)
|
||||
this.redrawCanvas()
|
||||
} else {
|
||||
this.$message.error(res.msg || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存画线数据失败:', error)
|
||||
// this.$message.error('保存失败,请稍后重试')
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
|
||||
/** 取消画线 */
|
||||
handleCancelLines() {
|
||||
// 如果初始线还在,使用初始线
|
||||
if (this.initialLine) {
|
||||
this.isDrawingMode = false
|
||||
this.line = null // 清除当前正在画的线
|
||||
this.clearCanvas() // 清除当前画布
|
||||
// 恢复使用初始线
|
||||
this.line = null
|
||||
this.clearCanvas()
|
||||
this.savedLine = { ...this.initialLine }
|
||||
this.redrawCanvas()
|
||||
this.$message.info('已取消画线模式,恢复初始线')
|
||||
} else if (this.savedLine) {
|
||||
// 如果有已保存的线(但不是初始线),只关闭画线模式,不清空线
|
||||
this.isDrawingMode = false
|
||||
this.line = null // 清除当前正在画的线
|
||||
this.clearCanvas() // 清除当前画布
|
||||
// 保留已保存的画线,重新绘制
|
||||
this.line = null
|
||||
this.clearCanvas()
|
||||
this.redrawCanvas()
|
||||
this.$message.info('已取消画线模式')
|
||||
} else {
|
||||
// 如果没有已保存的线,清除所有画线
|
||||
this.$confirm('确定要取消画线吗?画线将被清除', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
|
|
@ -295,20 +377,16 @@ export default {
|
|||
this.isDrawingMode = false
|
||||
this.line = null
|
||||
this.clearCanvas()
|
||||
// 清除已保存的画线显示层
|
||||
const savedCanvas = this.$refs.savedLineCanvas
|
||||
if (savedCanvas) {
|
||||
const ctx = savedCanvas.getContext('2d')
|
||||
ctx.clearRect(0, 0, savedCanvas.width, savedCanvas.height)
|
||||
}
|
||||
this.$message.info('已取消画线')
|
||||
}).catch(() => {
|
||||
// 用户取消操作
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
},
|
||||
|
||||
/** 清除画布 */
|
||||
clearCanvas() {
|
||||
const canvas = this.$refs.drawCanvas
|
||||
if (canvas) {
|
||||
|
|
@ -317,20 +395,16 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/** 开始画线 */
|
||||
startDrawing(e) {
|
||||
if (!this.isDrawingMode) return
|
||||
// 如果已经存在已保存的线,清除它(使用最新的线)
|
||||
if (this.savedLine) {
|
||||
this.savedLine = null
|
||||
// 清除已保存的画线显示层
|
||||
const savedCanvas = this.$refs.savedLineCanvas
|
||||
if (savedCanvas) {
|
||||
const ctx = savedCanvas.getContext('2d')
|
||||
ctx.clearRect(0, 0, savedCanvas.width, savedCanvas.height)
|
||||
}
|
||||
}
|
||||
// 如果正在画新线,清除当前线(只允许一条线)
|
||||
if (this.line) {
|
||||
this.line = null
|
||||
}
|
||||
|
|
@ -347,7 +421,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/** 画线 */
|
||||
draw(e) {
|
||||
if (!this.isDrawing) return
|
||||
const canvas = this.$refs.drawCanvas
|
||||
|
|
@ -356,18 +429,15 @@ export default {
|
|||
const currentX = e.clientX - rect.left
|
||||
const currentY = e.clientY - rect.top
|
||||
|
||||
// 更新当前线的终点
|
||||
if (this.currentLine) {
|
||||
this.currentLine.endX = currentX
|
||||
this.currentLine.endY = currentY
|
||||
}
|
||||
|
||||
// 重绘画布:只绘制当前正在画的线(已保存的线在另一个画布上显示,不会被清除)
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.strokeStyle = '#ff0000'
|
||||
ctx.lineWidth = 3
|
||||
|
||||
// 绘制当前正在画的线
|
||||
if (this.currentLine) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(this.currentLine.startX, this.currentLine.startY)
|
||||
|
|
@ -376,32 +446,24 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/** 停止画线 */
|
||||
stopDrawing() {
|
||||
if (this.isDrawing && this.currentLine) {
|
||||
// 只有当起点和终点不同时才保存
|
||||
if (this.currentLine.startX !== this.currentLine.endX ||
|
||||
this.currentLine.startY !== this.currentLine.endY) {
|
||||
// 如果已经存在已保存的线,清除它(使用最新的线)
|
||||
if (this.savedLine) {
|
||||
this.savedLine = null
|
||||
// 清除已保存的画线显示层
|
||||
const savedCanvas = this.$refs.savedLineCanvas
|
||||
if (savedCanvas) {
|
||||
const ctx = savedCanvas.getContext('2d')
|
||||
ctx.clearRect(0, 0, savedCanvas.width, savedCanvas.height)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前画的线(只保存一条线)
|
||||
this.line = {
|
||||
startX: this.currentLine.startX,
|
||||
startY: this.currentLine.startY,
|
||||
endX: this.currentLine.endX,
|
||||
endY: this.currentLine.endY
|
||||
}
|
||||
|
||||
// 重绘画布,显示当前画的线
|
||||
this.redrawCanvas()
|
||||
}
|
||||
this.currentLine = null
|
||||
|
|
@ -409,21 +471,13 @@ export default {
|
|||
this.isDrawing = false
|
||||
},
|
||||
|
||||
/** 重绘画布 */
|
||||
redrawCanvas() {
|
||||
// 绘制到已保存的画线显示层
|
||||
const savedCanvas = this.$refs.savedLineCanvas
|
||||
if (savedCanvas) {
|
||||
const container = this.$refs.videoContainer
|
||||
if (container) {
|
||||
savedCanvas.width = container.offsetWidth
|
||||
savedCanvas.height = container.offsetHeight
|
||||
}
|
||||
|
||||
const ctx = savedCanvas.getContext('2d')
|
||||
// 确保清空整个画布
|
||||
ctx.clearRect(0, 0, savedCanvas.width, savedCanvas.height)
|
||||
|
||||
// 绘制已保存的线(优先显示已保存的线,如果没有则显示当前线,最后显示初始线)
|
||||
const lineToDraw = this.savedLine || this.line || this.initialLine
|
||||
if (lineToDraw) {
|
||||
ctx.strokeStyle = '#ff0000'
|
||||
|
|
@ -436,49 +490,30 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/** 视频加载完成 */
|
||||
handleVideoLoaded() {
|
||||
console.log('视频加载完成')
|
||||
// 可以在这里获取视频尺寸并调整画布大小
|
||||
this.$nextTick(() => {
|
||||
this.initCanvas()
|
||||
// 如果有已保存的线或初始线,重新绘制
|
||||
if (this.savedLine || this.line || this.initialLine) {
|
||||
this.redrawCanvas()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 视频加载错误 */
|
||||
handleVideoError(e) {
|
||||
// 如果视频URL为空,不显示错误(因为还没有加载视频)
|
||||
if (!this.videoUrl) {
|
||||
return
|
||||
}
|
||||
// 避免重复显示错误提示
|
||||
if (this.videoErrorShown) {
|
||||
return
|
||||
}
|
||||
if (!this.videoUrl) return
|
||||
if (this.videoErrorShown) return
|
||||
console.error('视频加载失败:', e)
|
||||
this.videoErrorShown = true
|
||||
this.$message.error('视频加载失败,请检查视频源地址')
|
||||
},
|
||||
|
||||
/** 加载视频流 */
|
||||
async loadVideoStream() {
|
||||
try {
|
||||
const res = await getVideoStreamAPI()
|
||||
if (res.code === 200 && res.data && res.data.streamUrl) {
|
||||
// 重置错误状态
|
||||
this.videoErrorShown = false
|
||||
this.videoUrl = res.data.streamUrl
|
||||
} else {
|
||||
// 如果没有视频流地址,不显示错误(可能是正常的)
|
||||
console.log('未获取到视频流地址')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取视频流失败:', error)
|
||||
// 接口调用失败时不显示错误,因为可能是正常的(没有配置视频流)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -486,6 +521,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 保持原有样式不变,仅为 video-container 增加全屏时的样式处理 */
|
||||
.video-monitor-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 84px);
|
||||
|
|
@ -581,57 +617,6 @@ export default {
|
|||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.draw-line-btn {
|
||||
background: #1f72ea;
|
||||
border: none;
|
||||
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
|
||||
border-radius: 4px;
|
||||
padding: 10px 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #4a8bff;
|
||||
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.save-line-btn {
|
||||
background: #67c23a;
|
||||
border: none;
|
||||
box-shadow: 0px 4px 8px 0px rgba(103, 194, 58, 0.5);
|
||||
border-radius: 4px;
|
||||
padding: 10px 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #85ce61;
|
||||
box-shadow: 0px 6px 12px 0px rgba(103, 194, 58, 0.6);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-line-btn {
|
||||
background: #909399;
|
||||
border: none;
|
||||
box-shadow: 0px 4px 8px 0px rgba(144, 147, 153, 0.5);
|
||||
border-radius: 4px;
|
||||
padding: 10px 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #a6a9ad;
|
||||
box-shadow: 0px 6px 12px 0px rgba(144, 147, 153, 0.6);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -666,11 +651,25 @@ export default {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* 关键:确保全屏时背景为黑色 */
|
||||
background-color: #000;
|
||||
|
||||
/* 兼容浏览器的全屏伪类 */
|
||||
&:fullscreen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
&:-webkit-full-screen {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
.video-player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 保持宽高比,或者使用 fill 铺满 */
|
||||
object-fit: fill;
|
||||
background: #000;
|
||||
}
|
||||
|
|
@ -723,74 +722,6 @@ export default {
|
|||
z-index: 1.5;
|
||||
}
|
||||
|
||||
.detection-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vehicle-box {
|
||||
position: absolute;
|
||||
border: 2px solid #00ff00;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
animation: pulse 2s infinite;
|
||||
|
||||
.box-corner {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #00ff00;
|
||||
border: 2px solid #000;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.corner-tl {
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.corner-tr {
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
border-left: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.corner-bl {
|
||||
bottom: -6px;
|
||||
left: -6px;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&.corner-br {
|
||||
bottom: -6px;
|
||||
right: -6px;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式调整
|
||||
@media (max-width: 768px) {
|
||||
.video-monitor-container {
|
||||
padding: 8px;
|
||||
|
|
@ -815,4 +746,19 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
/* 隐藏 Chrome, Edge, Safari 的全屏按钮 */
|
||||
video::-webkit-media-controls-fullscreen-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 隐藏部分旧版浏览器的全屏功能(如果存在) */
|
||||
video::-webkit-media-controls-enclosure {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* 针对特定移动端(如某些安卓浏览器) */
|
||||
video::-internal-media-controls-download-button {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue