feat(dashboard1): 支持摄像头画面原生全屏

This commit is contained in:
2026-09-21 16:42:27 +08:00
parent 37c67ce25b
commit 6e31cc10f2
2 changed files with 127 additions and 5 deletions

View File

@@ -1372,6 +1372,36 @@ function closeMapPreview() {
mapPreviewVisible.value = false;
}
// 视频画面全屏:使用浏览器原生 Fullscreen API,进入浏览器顶层,避免被页面布局/包含块影响
const whiteScreenRef = ref<HTMLElement | null>(null);
const infraredScreenRef = ref<HTMLElement | null>(null);
const videoScreenRefs: Record<CameraChannelKey, Ref<HTMLElement | null>> = {
white: whiteScreenRef,
infrared: infraredScreenRef,
};
// 当前处于全屏的摄像头通道,null 表示未全屏
const videoFullscreenKey = ref<CameraChannelKey | null>(null);
// 进入/退出指定摄像头画面的全屏
async function toggleVideoFullscreen(key: CameraChannelKey) {
const element = videoScreenRefs[key].value;
if (!element) {
return;
}
if (document.fullscreenElement === element) {
await document.exitFullscreen();
return;
}
await element.requestFullscreen();
}
// 同步原生全屏状态,用于切换按钮图标
function handleVideoFullscreenChange() {
const current = document.fullscreenElement;
videoFullscreenKey.value =
cameraChannelKeys.find((key) => videoScreenRefs[key].value === current) ?? null;
}
// Esc 关闭预览
function handleMapPreviewKeydown(event: KeyboardEvent) {
if (event.key === "Escape" && mapPreviewVisible.value) {
@@ -1386,6 +1416,7 @@ onMounted(() => {
// startInspectionPolling();
window.addEventListener("blur", stopContinuousControl);
window.addEventListener("keydown", handleMapPreviewKeydown);
document.addEventListener("fullscreenchange", handleVideoFullscreenChange);
});
onBeforeUnmount(() => {
@@ -1395,6 +1426,7 @@ onBeforeUnmount(() => {
closeControlSocket();
window.removeEventListener("blur", stopContinuousControl);
window.removeEventListener("keydown", handleMapPreviewKeydown);
document.removeEventListener("fullscreenchange", handleVideoFullscreenChange);
});
// 切换机器人或视频地址变化时,两路摄像头各自重新拉流
@@ -1814,25 +1846,43 @@ function getStatusClass(status: DeviceItem["status"]) {
</div>
<div class="video-panel">
<!-- 白光镜头:16:9 -->
<div class="video-screen">
<div ref="whiteScreenRef" class="video-screen">
<video v-if="cameraUrls.white" ref="whiteVideoRef" :key="cameraUrls.white"
class="monitor-video" autoplay muted playsinline></video>
<div class="scan-line"></div>
<div class="video-overlay top-left">实时视频流</div>
<div class="video-overlay top-right">白光镜头</div>
<div class="video-overlay bottom-left">{{ currentRobot.id }}</div>
<!-- 全屏按钮:点击后进入/退出浏览器原生全屏 -->
<button v-if="videoFullscreenKey !== 'white'" class="video-fullscreen-btn" type="button" title="全屏"
aria-label="全屏" @click.stop="toggleVideoFullscreen('white')">
<svg class="video-fullscreen-icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 4H4v5M15 4h5v5M9 20H4v-5M15 20h5v-5" fill="none" stroke="currentColor"
stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<button v-else class="video-fullscreen-close" type="button" title="退出全屏" aria-label="退出全屏"
@click.stop="toggleVideoFullscreen('white')">✕</button>
<div v-if="cameraLoading.white" class="video-empty">视频流连接中...</div>
<div v-else-if="cameraError.white" class="video-empty">{{ cameraError.white }}</div>
<div v-else-if="!cameraUrls.white" class="video-empty">当前机器人暂无白光视频地址</div>
</div>
<!-- 红外镜头:16:9 -->
<div class="video-screen">
<div ref="infraredScreenRef" class="video-screen">
<video v-if="cameraUrls.infrared" ref="infraredVideoRef" :key="cameraUrls.infrared"
class="monitor-video infrared" autoplay muted playsinline></video>
<div class="scan-line"></div>
<div class="video-overlay top-left">实时视频流</div>
<div class="video-overlay top-right">红外镜头</div>
<div class="video-overlay bottom-left">{{ currentRobot.id }}</div>
<!-- 全屏按钮:点击后进入/退出浏览器原生全屏 -->
<button v-if="videoFullscreenKey !== 'infrared'" class="video-fullscreen-btn" type="button" title="全屏"
aria-label="全屏" @click.stop="toggleVideoFullscreen('infrared')">
<svg class="video-fullscreen-icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M9 4H4v5M15 4h5v5M9 20H4v-5M15 20h5v-5" fill="none" stroke="currentColor"
stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<button v-else class="video-fullscreen-close" type="button" title="退出全屏" aria-label="退出全屏"
@click.stop="toggleVideoFullscreen('infrared')">✕</button>
<div v-if="cameraLoading.infrared" class="video-empty">视频流连接中...</div>
<div v-else-if="cameraError.infrared" class="video-empty">{{ cameraError.infrared }}</div>
<div v-else-if="!cameraUrls.infrared" class="video-empty">当前机器人暂无红外视频地址</div>