视频管理
This commit is contained in:
parent
5879a4d054
commit
cad34775f7
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟保存成功(延迟500ms模拟API调用)
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue