From dd26ebfe3ae138b50f4f6d9ac5ae7493c7a57547 Mon Sep 17 00:00:00 2001 From: g82tt Date: Sun, 17 May 2026 15:42:00 +0800 Subject: [PATCH] =?UTF-8?q?W2:=20=E8=A7=86=E9=A2=91=E5=A2=99=E5=A4=9A?= =?UTF-8?q?=E8=B7=AF=E6=92=AD=E6=94=BE+HLS=E5=9B=9E=E6=94=BE=20=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E5=AF=B9=E6=8E=A5=E7=BD=91=E5=85=B3=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- warehouse/src/view/video/History.vue | 509 +-------- warehouse/src/view/video/VideoWall.vue | 1370 ++---------------------- 2 files changed, 174 insertions(+), 1705 deletions(-) diff --git a/warehouse/src/view/video/History.vue b/warehouse/src/view/video/History.vue index beeaac3..670e36a 100644 --- a/warehouse/src/view/video/History.vue +++ b/warehouse/src/view/video/History.vue @@ -3,25 +3,17 @@

历史回放

- 刷新 + 刷新 全屏显示
-

时间段选择

- + 查询 重置
@@ -33,9 +25,7 @@
{{ camera.name }}
{{ camera.location }}
-
- {{ camera.status === 'online' ? '在线' : '离线' }} -
+
{{ camera.status === 'online' ? '在线' : '离线' }}
@@ -43,29 +33,12 @@

历史回放画面

-
+
@@ -79,8 +52,6 @@ {{ onlineCount }} {{ offlineCount }} {{ currentTime }} - {{ isPlaying ? '播放中' : '已暂停' }} - {{ formatDate(dateTimeRange[0]) }}
@@ -90,438 +61,70 @@ import { ref, computed, onMounted } from 'vue' import { VideoPlay } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' +import { fetchCameras, gwGet, type Camera } from '@/api/gateway' -interface Camera { - id: string - name: string - location: string - status: 'online' | 'offline' - streamUrl?: string -} - -// 摄像头数据 -const cameras = ref([ - { - id: 'camera1', - name: '仓库入口', - location: '仓库正门', - status: 'online' - }, - { - id: 'camera2', - name: '货架区域A', - location: '仓库东区', - status: 'online' - }, - { - id: 'camera3', - name: '货架区域B', - location: '仓库西区', - status: 'online' - }, - { - id: 'camera4', - name: '装卸平台', - location: '仓库后门', - status: 'offline' - }, - { - id: 'camera5', - name: '办公区域', - location: '办公楼', - status: 'online' - } -]) - -// 当前选中的摄像头 +const cameras = ref([]) const selectedCamera = ref(null) - -// 日期时间范围 const dateTimeRange = ref([]) +const playbackUrl = ref('') +const playbackLoading = ref(false) +const currentTime = ref('') -// 播放进度 -const playProgress = ref(0) -const isPlaying = ref(false) -const playSpeed = ref(1) - -// 总时长(模拟值) -const totalDuration = ref('00:30:00') - -// 计算在线和离线设备数量 const onlineCount = computed(() => cameras.value.filter(c => c.status === 'online').length) const offlineCount = computed(() => cameras.value.filter(c => c.status === 'offline').length) -// 当前时间 -const currentTime = ref('') +const loadCameras = async () => { try { cameras.value = await fetchCameras('Owl:main') } catch {} } -// 选择摄像头 const selectCamera = (camera: Camera) => { selectedCamera.value = camera - playProgress.value = 0 - isPlaying.value = false - - // 设置默认日期时间范围(前一天到今天) - if (!dateTimeRange.value || dateTimeRange.value.length === 0) { - const yesterday = new Date() - yesterday.setDate(yesterday.getDate() - 1) - const today = new Date() - - dateTimeRange.value = [ - yesterday.toISOString().slice(0, 19).replace('T', ' '), - today.toISOString().slice(0, 19).replace('T', ' ') - ] - } - - ElMessage.success(`已选择 ${camera.name}`) -} - -// 搜索录像记录 -const searchRecordings = () => { - if (!selectedCamera.value) { - ElMessage.warning('请先选择摄像头') - return - } - - if (!dateTimeRange.value || dateTimeRange.value.length !== 2) { - ElMessage.warning('请选择有效的日期时间范围') - return - } - - ElMessage.success(`正在查询 ${selectedCamera.value.name} 的历史记录`) - // 重置播放状态 - playProgress.value = 0 - isPlaying.value = false -} - -// 重置日期时间范围 -const resetDateTimeRange = () => { - dateTimeRange.value = [] -} - -// 切换播放/暂停 -const togglePlay = () => { - isPlaying.value = !isPlaying.value - ElMessage.info(isPlaying.value ? '开始播放' : '暂停播放') - - // 模拟播放进度更新 - if (isPlaying.value) { - simulatePlayback() + if (!dateTimeRange.value.length) { + const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1) + dateTimeRange.value = [formatISOLocal(yesterday), formatISOLocal(new Date())] } } -// 快退10秒 -const stepBackward = () => { - playProgress.value = Math.max(0, playProgress.value - 5) - ElMessage.info('后退10秒') +const searchRecordings = async () => { + if (!selectedCamera.value) return ElMessage.warning('请先选择摄像头') + if (!dateTimeRange.value || dateTimeRange.value.length !== 2) return ElMessage.warning('请选择时间段') + playbackLoading.value = true; playbackUrl.value = '' + try { + const cam = selectedCamera.value + const start = new Date(dateTimeRange.value[0]).toISOString() + const end = new Date(dateTimeRange.value[1]).toISOString() + const data = await gwGet(`/api/gateway/streams/${cam.adapterCode}/${cam.sourceId}/playback?start=${start}&end=${end}`) + playbackUrl.value = data.hls || '' + if (!playbackUrl.value) ElMessage.warning('未获取到回放流地址') + } catch { ElMessage.error('获取回放流失败') } + finally { playbackLoading.value = false } } -// 快进10秒 -const stepForward = () => { - playProgress.value = Math.min(100, playProgress.value + 5) - ElMessage.info('前进10秒') -} +const resetDateTimeRange = () => { dateTimeRange.value = []; playbackUrl.value = '' } +const formatISOLocal = (d: Date) => d.toISOString().slice(0, 19).replace('T', ' ') +const formatDateTime = (s: string) => s -// 截图 -const screenshot = () => { - if (!selectedCamera.value) { - ElMessage.warning('请先选择摄像头') - return - } - ElMessage.success('截图成功') -} - -// 切换全屏 -const toggleFullScreen = () => { - ElMessage.info('全屏显示') - // 实际项目中需要实现全屏逻辑 -} - -// 模拟播放进度 -let playbackInterval: number | null = null -const simulatePlayback = () => { - if (playbackInterval) { - clearInterval(playbackInterval) - } - - playbackInterval = window.setInterval(() => { - if (isPlaying.value && playProgress.value < 100) { - playProgress.value += 0.1 * playSpeed.value - - // 播放结束 - if (playProgress.value >= 100) { - playProgress.value = 100 - isPlaying.value = false - if (playbackInterval) { - clearInterval(playbackInterval) - } - ElMessage.info('播放结束') - } - } - }, 100) -} - -// 格式化日期时间 -const formatDateTime = (dateTimeStr: string) => { - const date = new Date(dateTimeStr) - return date.toLocaleString('zh-CN') -} - -// 格式化日期 -const formatDate = (dateTimeStr: string) => { - const date = new Date(dateTimeStr) - return date.toLocaleDateString('zh-CN') -} - -// 格式化进度时间 -const formatProgressTime = () => { - const [hours, minutes, seconds] = totalDuration.value.split(':') - const totalSeconds = parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds) - const currentSeconds = Math.floor(totalSeconds * (playProgress.value / 100)) - - const h = Math.floor(currentSeconds / 3600).toString().padStart(2, '0') - const m = Math.floor((currentSeconds % 3600) / 60).toString().padStart(2, '0') - const s = (currentSeconds % 60).toString().padStart(2, '0') - - return `${h}:${m}:${s}` -} - -// 更新时间 -const updateTime = () => { - currentTime.value = new Date().toLocaleString('zh-CN') -} - -// 组件挂载时初始化 -onMounted(() => { - updateTime() - setInterval(updateTime, 1000) - - // 设置默认日期时间范围 - const yesterday = new Date() - yesterday.setDate(yesterday.getDate() - 1) - const today = new Date() - - dateTimeRange.value = [ - yesterday.toISOString().slice(0, 19).replace('T', ' '), - today.toISOString().slice(0, 19).replace('T', ' ') - ] -}) +const updateTime = () => { currentTime.value = new Date().toLocaleString('zh-CN') } +onMounted(() => { loadCameras(); updateTime(); setInterval(updateTime, 1000) }) \ No newline at end of file +.history-playback { height: 100%; display: flex; flex-direction: column; padding: 20px; } +.monitor-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } +.monitor-header h2 { margin: 0; color: #fff; font-size: 20px; font-weight: 500; } +.monitor-controls { display: flex; gap: 10px; } +.date-filter h3 { color: #fff; margin-bottom: 10px; } +.date-filter-controls { display: flex; gap: 10px; align-items: center; margin-bottom: 20px; } +.camera-list { margin-bottom: 20px; } +.camera-list h3 { color: #fff; } +.camera-item { background: rgba(255,255,255,0.05); cursor: pointer; } +.camera-name { font-size: 14px; color: #fff; } +.camera-location { font-size: 12px; color: #999; } +.camera-status.online { color: #67c23a; } +.camera-status.offline { color: #f56c6c; } +.main-view { flex: 1; } +.main-view h3 { color: #fff; margin-bottom: 10px; } +.video-container { background: #000; border-radius: 8px; overflow: hidden; } +.video-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 400px; color: #fff; } +.no-selection { flex: 1; display: flex; align-items: center; justify-content: center; } +.system-info { margin-top: 20px; } +.system-info h3 { color: #fff; margin-bottom: 10px; } + diff --git a/warehouse/src/view/video/VideoWall.vue b/warehouse/src/view/video/VideoWall.vue index 5e5fda9..2d7df17 100644 --- a/warehouse/src/view/video/VideoWall.vue +++ b/warehouse/src/view/video/VideoWall.vue @@ -2,7 +2,6 @@
-
@@ -12,1311 +11,178 @@
- -
- - 轮巡模式 - - - 追踪模式 - - - 特情模式 - + 轮巡模式 + 追踪模式 + 特情模式
- -
- 当前版块: - - {{ currentSection === 'patrol' ? '轮巡' : currentSection === 'track' ? '追踪' : '特情' }} - + 当前版块:{{ sectionLabel }}
- +
-
-
- -
-
-
- -
- 监控画面 -
摄像头 {{ index + 1 }}
-
在线
+
+
+
+ +
+
{{ cam.name }}
+
{{ cam.status === 'online' ? '在线' : '离线' }}
+
-
- - -
- -
-
-
- 主监控画面 -
主摄像头
-
在线
-
-
-
- - -
-
-
-
- 副监控画面 -
副摄像头 {{ index }}
+
+
+
+ +
+
{{ mainCam?.name || '主摄像头' }}
在线
+
+
+
+ +
+
{{ cam.name }}
+
+
+
+
-
- - -
- + + +
-

{{ selectedCamera ? selectedCamera.name : '未选择摄像机' }}

-

{{ selectedCamera ? selectedCamera.id : '' }}

+

{{ selectedCamera.name }}

+

{{ selectedCamera.id }}

- - -
- 时画面 - 回放 - 抓拍 - 下载 - 告警记录 -
- - -
-
- - - {{ exposure }} -
-
- - - {{ saturation }} -
-
- - - {{ color }} -
-
- -

云台控制

- +
- - - -
- - -
- - - - 停止 - - - -
- - -
- - - -
- - -
- - - - - - + + 停止 +
+
- +
-
- -
- -
- -
-
-
- 主监控画面 -
主摄像头
-
在线
-
-
-
- - -
-
-
-
- 副监控画面 -
副摄像头 {{ index }}
-
在线
-
-
-
-
- - -
-
-
-
- 副监控画面 -
副摄像头 {{ index + 4 }}
-
在线
-
-
-
-
-
-
- - -
- -
-

{{ selectedCamera ? selectedCamera.name : '未选择摄像机' }}

-

{{ selectedCamera ? selectedCamera.id : '' }}

-
- - -
- 时画面 - 回放 - 抓拍 - 下载 - 告警记录 -
- - -
-
- - - {{ exposure }} -
-
- - - {{ saturation }} -
-
- - - {{ color }} -
-
- - -
-

云台控制

-
- -
- - - -
- - -
- - - - 停止 - - - -
- - -
- - - -
- - -
- - - - - - -
+
+
+
+
+
- +
- -
- -
- -
- -
- -
- -
-
-
- 主监控画面 -
主摄像头
-
在线
-
-
-
- - -
-
-
-
- 副监控画面 -
副摄像头 {{ index }}
-
在线
-
-
-
-
- - -
-
-
-
- 副监控画面 -
副摄像头 {{ index + 4 }}
-
在线
-
-
-
-
-
-
- - -
- -
-

{{ selectedCamera ? selectedCamera.name : '未选择摄像机' }}

-

{{ selectedCamera ? selectedCamera.id : '' }}

-
- - -
- 时画面 - 回放 - 抓拍 - 下载 - 告警记录 -
- - -
-
- - - {{ exposure }} -
-
- - - {{ saturation }} -
-
- - - {{ color }} -
-
- - -
-

云台控制

-
- -
- - - -
- - -
- - - - 停止 - - - -
- - -
- - - -
- - -
- - - - - - -
-
-
-
-
-
- - -
-
-

特情处置预案

-
-

{{ emergencyPlan.title }}

-

{{ emergencyPlan.description }}

-
-
- {{ index + 1 }} - {{ step }} -
-
- -
-
-
+
+

特情模式 — 自动切换到告警设备画面

\ No newline at end of file +.video-wall-container { padding: 20px; } +.settings-bar { display: flex; gap: 20px; align-items: center; margin-bottom: 20px; } +.section-status { color: #fff; } +.main-content { display: flex; gap: 12px; } +.video-grid { flex: 1; } +.standard-grid { display: grid; gap: 4px; } +.grid-3x3 { grid-template-columns: repeat(3, 1fr); } +.grid-3x4 { grid-template-columns: repeat(4, 1fr); } +.grid-4x4 { grid-template-columns: repeat(4, 1fr); } +.grid-5x5 { grid-template-columns: repeat(5, 1fr); } +.video-item, .sub-video { aspect-ratio: 16/9; } +.video-frame { width: 100%; height: 100%; background: #000; position: relative; border-radius: 4px; overflow: hidden; } +.video-real { width: 100%; height: 100%; object-fit: cover; } +.video-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #fff; } +.video-name { font-size: 14px; color: #fff; } +.video-status.online { color: #67c23a; } +.video-status.offline { color: #f56c6c; } +.main-sub-layout { display: flex; gap: 4px; } +.main-video { flex: 2; } +.sub-videos { flex: 1; display: flex; flex-direction: column; gap: 4px; } +.track-sub-videos { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; } +.track-layout { display: flex; } +.camera-control-panel { width: 200px; padding: 10px; background: rgba(255,255,255,0.05); border-radius: 8px; } +.camera-info h3 { color: #fff; margin: 0; } +.ptz-controls h4 { color: #fff; text-align: center; margin: 8px 0; } +.ptz-buttons { display: flex; flex-direction: column; align-items: center; gap: 4px; } +.ptz-row { display: flex; gap: 4px; } +.ptz-btn { width: 36px; height: 36px; } +.emergency-section { flex: 1; } +