视频管理

This commit is contained in:
cwchen 2025-12-24 17:09:56 +08:00
parent 5879a4d054
commit cad34775f7
2 changed files with 75 additions and 123 deletions

View File

@ -1,11 +1,11 @@
import request from '@/utils/request' import request from '@/utils/request'
// 设备管理->视频监控->保存画线数据 // 设备管理->视频监控->保存画线数据
export function getLineDetailAPI(data) { export function getLineDetailAPI(params) {
return request({ return request({
url: '/smartCar/data/line/getLineDetail', url: '/smartCar/data/line/getLineDetail',
method: 'POST', method: 'get',
data params
}) })
} }

View File

@ -4,8 +4,8 @@
<el-card class="top-bar-card" shadow="never"> <el-card class="top-bar-card" shadow="never">
<div class="top-bar"> <div class="top-bar">
<div class="status-bar"> <div class="status-bar">
<span class="status-text">博诺思车辆道路计数仪</span> <span class="status-text">{{ lineInfo.equipmentName }}</span>
<span class="status-indicator" :class="deviceStatus"> <span class="status-indicator">
{{ statusText }} {{ statusText }}
</span> </span>
</div> </div>
@ -16,31 +16,17 @@
</div> </div>
<!-- 画线按钮组 --> <!-- 画线按钮组 -->
<div class="draw-line-buttons"> <div class="draw-line-buttons">
<el-button <el-button v-if="!isDrawingMode" type="primary" class="draw-line-btn" @click="handleDrawLine"
v-if="!isDrawingMode" icon="el-icon-edit">
type="primary"
class="draw-line-btn"
@click="handleDrawLine"
icon="el-icon-edit"
>
画线 画线
</el-button> </el-button>
<template v-else> <template v-else>
<el-button <el-button type="success" class="save-line-btn" @click="handleSaveLines"
type="success" icon="el-icon-check" :loading="saving">
class="save-line-btn"
@click="handleSaveLines"
icon="el-icon-check"
:loading="saving"
>
保存 保存
</el-button> </el-button>
<el-button <el-button type="info" class="cancel-line-btn" @click="handleCancelLines"
type="info" icon="el-icon-close">
class="cancel-line-btn"
@click="handleCancelLines"
icon="el-icon-close"
>
取消 取消
</el-button> </el-button>
</template> </template>
@ -53,54 +39,17 @@
<el-card class="video-card" shadow="never" :body-style="{ padding: '0', height: '100%' }"> <el-card class="video-card" shadow="never" :body-style="{ padding: '0', height: '100%' }">
<div class="video-wrapper"> <div class="video-wrapper">
<div class="video-container" ref="videoContainer"> <div class="video-container" ref="videoContainer">
<video <video ref="videoPlayer" class="video-player" :src="videoUrl" autoplay muted controls
ref="videoPlayer" @loadedmetadata="handleVideoLoaded" @error="handleVideoError">
class="video-player"
:src="videoUrl"
autoplay
muted
controls
@loadedmetadata="handleVideoLoaded"
@error="handleVideoError"
>
您的浏览器不支持视频播放 您的浏览器不支持视频播放
</video> </video>
<!-- 画线画布覆盖层 --> <!-- 画线画布覆盖层 -->
<canvas <canvas ref="drawCanvas" class="draw-canvas" :class="{ active: isDrawingMode }"
ref="drawCanvas" @mousedown="startDrawing" @mousemove="draw" @mouseup="stopDrawing"
class="draw-canvas" @mouseleave="stopDrawing"></canvas>
:class="{ active: isDrawingMode }"
@mousedown="startDrawing"
@mousemove="draw"
@mouseup="stopDrawing"
@mouseleave="stopDrawing"
></canvas>
<!-- 已保存的画线显示层只读 --> <!-- 已保存的画线显示层只读 -->
<canvas <canvas ref="savedLineCanvas" class="saved-line-canvas"></canvas>
ref="savedLineCanvas"
class="saved-line-canvas"
></canvas>
<!-- 车辆检测框覆盖层 -->
<div class="detection-overlay">
<div
v-for="(vehicle, index) in detectedVehicles"
:key="index"
class="vehicle-box"
:style="{
left: vehicle.x + '%',
top: vehicle.y + '%',
width: vehicle.width + '%',
height: vehicle.height + '%'
}"
>
<div class="box-corner corner-tl"></div>
<div class="box-corner corner-tr"></div>
<div class="box-corner corner-bl"></div>
<div class="box-corner corner-br"></div>
</div>
</div>
<!-- 视频加载提示 --> <!-- 视频加载提示 -->
<div v-if="!videoUrl" class="video-placeholder"> <div v-if="!videoUrl" class="video-placeholder">
@ -120,7 +69,6 @@ export default {
name: 'VideoMonitor', name: 'VideoMonitor',
data() { data() {
return { return {
deviceStatus: 'online', // online, offline, standby, upgrading
currentTime: '', currentTime: '',
videoUrl: '', // videoUrl: '', //
isDrawingMode: false, // 线 isDrawingMode: false, // 线
@ -136,17 +84,25 @@ export default {
detectedVehicles: [], // detectedVehicles: [], //
timer: null, timer: null,
saving: false, // saving: false, //
lineInfo: {
equipmentName: '',
equipmentStatus: '',
startX: '',
startY: '',
endX: '',
endY: '',
}
} }
}, },
computed: { computed: {
statusText() { statusText() {
const statusMap = { const statusMap = {
online: '在线', '1': '在线',
offline: '离线', '0': '离线',
standby: '待机', '2': '待机',
upgrading: '升级中' '3': '升级中'
} }
return statusMap[this.deviceStatus] || '未知' return statusMap[this.lineInfo.equipmentStatus] || '未知'
} }
}, },
mounted() { mounted() {
@ -205,31 +161,38 @@ export default {
}, },
/** 初始化默认线 */ /** 初始化默认线 */
initInitialLine() { async initInitialLine() {
// 线 try {
// 使线 const container = this.$refs.videoContainer
const container = this.$refs.videoContainer if (!container) return
if (container) {
const containerWidth = container.offsetWidth || 1920 const containerWidth = container.offsetWidth || 1920
const containerHeight = container.offsetHeight || 1080 const containerHeight = container.offsetHeight || 1080
// 20%80% // 线
this.initialLine = { const res = await getLineDetailAPI({})
startX: containerWidth * 0.2,
startY: containerHeight * 0.2, if (res.code === 200 && res.data) {
endX: containerWidth * 0.8, const data = res.data
endY: containerHeight * 0.8 this.lineInfo = data
}
// 线
// 线线 if (data.startX && data.startY && data.endX && data.endY) {
if (this.initialLine) { this.initialLine = {
this.savedLine = { ...this.initialLine } startX: (parseFloat(data.startX) / 100) * containerWidth,
this.redrawCanvas() startY: (parseFloat(data.startY) / 100) * containerHeight,
endX: (parseFloat(data.endX) / 100) * containerWidth,
endY: (parseFloat(data.endY) / 100) * containerHeight
}
// 线
this.savedLine = { ...this.initialLine }
this.redrawCanvas()
}
} }
} catch (error) {
console.error('获取画线详情失败:', error)
} }
// 线
// this.loadInitialLine()
}, },
/** 处理画线按钮点击 */ /** 处理画线按钮点击 */
@ -250,7 +213,7 @@ export default {
// 线 // 线
// 线线 // 线线
const lineToSave = this.line || this.savedLine const lineToSave = this.line || this.savedLine
if (!lineToSave) { if (!lineToSave) {
this.$message.warning('请先画线后再保存') this.$message.warning('请先画线后再保存')
return return
@ -263,29 +226,16 @@ export default {
const containerWidth = container ? container.offsetWidth : 1920 const containerWidth = container ? container.offsetWidth : 1920
const containerHeight = container ? container.offsetHeight : 1080 const containerHeight = container ? container.offsetHeight : 1080
// //
const lineData = {
startX: (lineToSave.startX / containerWidth) * 100,
startY: (lineToSave.startY / containerHeight) * 100,
endX: (lineToSave.endX / containerWidth) * 100,
endY: (lineToSave.endY / containerHeight) * 100,
}
const params = { const params = {
line: lineData, startX: String(((lineToSave.startX / containerWidth) * 100).toFixed(2)),
videoUrl: this.videoUrl, startY: String(((lineToSave.startY / containerHeight) * 100).toFixed(2)),
deviceId: this.$route.query.deviceId || '', // ID endX: String(((lineToSave.endX / containerWidth) * 100).toFixed(2)),
endY: String(((lineToSave.endY / containerHeight) * 100).toFixed(2))
} }
// 500msAPI const res = await updateLineAPI(params)
await new Promise(resolve => setTimeout(resolve, 500))
// API
const res = { code: 200, msg: '保存成功' }
// API使
// const res = await saveVideoLineAPI(params)
if (res.code === 200) { if (res.code === 200) {
this.$message.success('画线数据保存成功') this.$message.success('画线数据保存成功')
// 线线线 // 线线线
@ -427,7 +377,7 @@ export default {
stopDrawing() { stopDrawing() {
if (this.isDrawing && this.currentLine) { if (this.isDrawing && this.currentLine) {
// //
if (this.currentLine.startX !== this.currentLine.endX || if (this.currentLine.startX !== this.currentLine.endX ||
this.currentLine.startY !== this.currentLine.endY) { this.currentLine.startY !== this.currentLine.endY) {
// 线使线 // 线使线
if (this.savedLine) { if (this.savedLine) {
@ -439,7 +389,7 @@ export default {
ctx.clearRect(0, 0, savedCanvas.width, savedCanvas.height) ctx.clearRect(0, 0, savedCanvas.width, savedCanvas.height)
} }
} }
// 线线 // 线线
this.line = { this.line = {
startX: this.currentLine.startX, startX: this.currentLine.startX,
@ -447,7 +397,7 @@ export default {
endX: this.currentLine.endX, endX: this.currentLine.endX,
endY: this.currentLine.endY endY: this.currentLine.endY
} }
// 线 // 线
this.redrawCanvas() this.redrawCanvas()
} }
@ -466,10 +416,10 @@ export default {
savedCanvas.width = container.offsetWidth savedCanvas.width = container.offsetWidth
savedCanvas.height = container.offsetHeight savedCanvas.height = container.offsetHeight
} }
const ctx = savedCanvas.getContext('2d') const ctx = savedCanvas.getContext('2d')
ctx.clearRect(0, 0, savedCanvas.width, savedCanvas.height) ctx.clearRect(0, 0, savedCanvas.width, savedCanvas.height)
// 线线线线 // 线线线线
const lineToDraw = this.savedLine || this.line || this.initialLine const lineToDraw = this.savedLine || this.line || this.initialLine
if (lineToDraw) { if (lineToDraw) {
@ -504,7 +454,6 @@ export default {
/** 加载视频流 */ /** 加载视频流 */
async loadVideoStream() { async loadVideoStream() {
// API
const res = await getVideoStreamAPI() const res = await getVideoStreamAPI()
if (res.code === 200) { if (res.code === 200) {
this.videoUrl = res.data.streamUrl this.videoUrl = res.data.streamUrl
@ -808,9 +757,12 @@ export default {
} }
@keyframes pulse { @keyframes pulse {
0%, 100% {
0%,
100% {
opacity: 1; opacity: 1;
} }
50% { 50% {
opacity: 0.7; opacity: 0.7;
} }