视频管理

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'
// 设备管理->视频监控->保存画线数据
export function getLineDetailAPI(data) {
export function getLineDetailAPI(params) {
return request({
url: '/smartCar/data/line/getLineDetail',
method: 'POST',
data
method: 'get',
params
})
}

View File

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