|
@@ -4,84 +4,91 @@
|
|
|
class="video-container"
|
|
class="video-container"
|
|
|
:class="{ 'is-fullscreen': isFullscreen }"
|
|
:class="{ 'is-fullscreen': isFullscreen }"
|
|
|
>
|
|
>
|
|
|
- <!-- PTZ 球机控制面板(左侧) -->
|
|
|
|
|
- <div class="ptz-wrapper">
|
|
|
|
|
- <div class="ptz-panel" :class="{ 'is-collapsed': ptzCollapsed }">
|
|
|
|
|
- <div class="ptz-title">PTZ Control</div>
|
|
|
|
|
- <div class="ptz-grid">
|
|
|
|
|
- <div class="ptz-placeholder" />
|
|
|
|
|
- <button
|
|
|
|
|
- class="ptz-btn"
|
|
|
|
|
- title="Up"
|
|
|
|
|
- @mousedown="handlePtzStart(PTZDirection.Up)"
|
|
|
|
|
- @mouseup="handlePtzStop"
|
|
|
|
|
- >
|
|
|
|
|
- <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
- <path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>
|
|
|
|
|
- </svg>
|
|
|
|
|
- </button>
|
|
|
|
|
- <div class="ptz-placeholder" />
|
|
|
|
|
-
|
|
|
|
|
- <button
|
|
|
|
|
- class="ptz-btn"
|
|
|
|
|
- title="Left"
|
|
|
|
|
- @mousedown="handlePtzStart(PTZDirection.Left)"
|
|
|
|
|
- @mouseup="handlePtzStop"
|
|
|
|
|
- >
|
|
|
|
|
- <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
- <path fill="currentColor" d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
|
|
|
|
|
- </svg>
|
|
|
|
|
- </button>
|
|
|
|
|
- <button
|
|
|
|
|
- class="ptz-btn ptz-btn-center"
|
|
|
|
|
- title="Reset"
|
|
|
|
|
- @click="handlePtzCenter"
|
|
|
|
|
- >
|
|
|
|
|
- <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
- <path fill="currentColor" d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26L6.7 14.8A5.87 5.87 0 0 1 6 12c0-3.31 2.69-6 6-6zm6.76 1.74L17.3 9.2c.44.84.7 1.79.7 2.8 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"/>
|
|
|
|
|
- </svg>
|
|
|
|
|
- </button>
|
|
|
|
|
- <button
|
|
|
|
|
- class="ptz-btn"
|
|
|
|
|
- title="Right"
|
|
|
|
|
- @mousedown="handlePtzStart(PTZDirection.Right)"
|
|
|
|
|
- @mouseup="handlePtzStop"
|
|
|
|
|
- >
|
|
|
|
|
- <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
- <path fill="currentColor" d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
|
|
|
|
|
- </svg>
|
|
|
|
|
- </button>
|
|
|
|
|
-
|
|
|
|
|
- <div class="ptz-placeholder" />
|
|
|
|
|
- <button
|
|
|
|
|
- class="ptz-btn"
|
|
|
|
|
- title="Down"
|
|
|
|
|
- @mousedown="handlePtzStart(PTZDirection.Down)"
|
|
|
|
|
- @mouseup="handlePtzStop"
|
|
|
|
|
- >
|
|
|
|
|
- <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
- <path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/>
|
|
|
|
|
- </svg>
|
|
|
|
|
- </button>
|
|
|
|
|
- <div class="ptz-placeholder" />
|
|
|
|
|
|
|
+ <!-- Left sidebar control panel -->
|
|
|
|
|
+ <div v-if="showChannelSelector || ptzEnabled" class="control-sidebar">
|
|
|
|
|
+ <div class="control-panel" :class="{ 'is-collapsed': ptzCollapsed }">
|
|
|
|
|
+ <!-- Channel selector -->
|
|
|
|
|
+ <div v-if="showChannelSelector" class="channel-selector">
|
|
|
|
|
+ <div class="control-label">Channel</div>
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="selectedChn"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @change="handleChnChange"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="ch in channelOptions"
|
|
|
|
|
+ :key="ch.value"
|
|
|
|
|
+ :label="ch.label"
|
|
|
|
|
+ :value="ch.value"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- PTZ control -->
|
|
|
|
|
+ <template v-if="ptzEnabled">
|
|
|
|
|
+ <div v-if="showChannelSelector" class="control-divider" />
|
|
|
|
|
+ <div class="control-label">PTZ Control</div>
|
|
|
|
|
+ <div class="ptz-grid">
|
|
|
|
|
+ <div class="ptz-placeholder" />
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="ptz-btn"
|
|
|
|
|
+ title="Up"
|
|
|
|
|
+ @mousedown="handlePtzStart(PTZDirection.Up)"
|
|
|
|
|
+ @mouseup="handlePtzStop"
|
|
|
|
|
+ >
|
|
|
|
|
+ <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
+ <path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <div class="ptz-placeholder" />
|
|
|
|
|
+
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="ptz-btn"
|
|
|
|
|
+ title="Left"
|
|
|
|
|
+ @mousedown="handlePtzStart(PTZDirection.Left)"
|
|
|
|
|
+ @mouseup="handlePtzStop"
|
|
|
|
|
+ >
|
|
|
|
|
+ <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
+ <path fill="currentColor" d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="ptz-btn ptz-btn-center"
|
|
|
|
|
+ title="Reset"
|
|
|
|
|
+ @click="handlePtzCenter"
|
|
|
|
|
+ >
|
|
|
|
|
+ <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
+ <path fill="currentColor" d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26L6.7 14.8A5.87 5.87 0 0 1 6 12c0-3.31 2.69-6 6-6zm6.76 1.74L17.3 9.2c.44.84.7 1.79.7 2.8 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"/>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="ptz-btn"
|
|
|
|
|
+ title="Right"
|
|
|
|
|
+ @mousedown="handlePtzStart(PTZDirection.Right)"
|
|
|
|
|
+ @mouseup="handlePtzStop"
|
|
|
|
|
+ >
|
|
|
|
|
+ <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
+ <path fill="currentColor" d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </button>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="ptz-placeholder" />
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="ptz-btn"
|
|
|
|
|
+ title="Down"
|
|
|
|
|
+ @mousedown="handlePtzStart(PTZDirection.Down)"
|
|
|
|
|
+ @mouseup="handlePtzStop"
|
|
|
|
|
+ >
|
|
|
|
|
+ <svg viewBox="0 0 24 24" width="22" height="22">
|
|
|
|
|
+ <path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/>
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <div class="ptz-placeholder" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <!-- 速度控制 -->
|
|
|
|
|
-<!-- <div class="ptz-speed">-->
|
|
|
|
|
-<!-- <span class="ptz-speed-label">Speed</span>-->
|
|
|
|
|
-<!-- <el-slider-->
|
|
|
|
|
-<!-- v-model="ptzSpeed"-->
|
|
|
|
|
-<!-- :min="1"-->
|
|
|
|
|
-<!-- :max="8"-->
|
|
|
|
|
-<!-- :step="1"-->
|
|
|
|
|
-<!-- :show-tooltip="false"-->
|
|
|
|
|
-<!-- size="small"-->
|
|
|
|
|
-<!-- />-->
|
|
|
|
|
-<!-- <span class="ptz-speed-value">{{ ptzSpeed }}</span>-->
|
|
|
|
|
-<!-- </div>-->
|
|
|
|
|
- </div>
|
|
|
|
|
- <!-- 折叠/展开按钮 -->
|
|
|
|
|
- <button class="ptz-toggle" :title="ptzCollapsed ? 'Expand PTZ' : 'Collapse PTZ'" @click="ptzCollapsed = !ptzCollapsed">
|
|
|
|
|
|
|
+ <!-- Collapse toggle -->
|
|
|
|
|
+ <button class="panel-toggle" :title="ptzCollapsed ? 'Expand' : 'Collapse'" @click="ptzCollapsed = !ptzCollapsed">
|
|
|
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
|
<path fill="currentColor" :d="ptzCollapsed ? 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z' : 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'"/>
|
|
<path fill="currentColor" :d="ptzCollapsed ? 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z' : 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'"/>
|
|
|
</svg>
|
|
</svg>
|
|
@@ -93,25 +100,8 @@
|
|
|
<MyVideo
|
|
<MyVideo
|
|
|
ref="videoRef"
|
|
ref="videoRef"
|
|
|
:drag-flag="true"
|
|
:drag-flag="true"
|
|
|
- :channel="currentChannel"
|
|
|
|
|
@video-error="handleVideoError"
|
|
@video-error="handleVideoError"
|
|
|
/>
|
|
/>
|
|
|
- <!-- Channel selector -->
|
|
|
|
|
- <div v-if="channelOptions.length > 1" class="channel-selector">
|
|
|
|
|
- <el-select
|
|
|
|
|
- v-model="currentChannel"
|
|
|
|
|
- size="small"
|
|
|
|
|
- placeholder="Channel"
|
|
|
|
|
- @change="handleChannelChange"
|
|
|
|
|
- >
|
|
|
|
|
- <el-option
|
|
|
|
|
- v-for="ch in channelOptions"
|
|
|
|
|
- :key="ch.value"
|
|
|
|
|
- :label="ch.label"
|
|
|
|
|
- :value="ch.value"
|
|
|
|
|
- />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </div>
|
|
|
|
|
<button
|
|
<button
|
|
|
class="fullscreen-btn"
|
|
class="fullscreen-btn"
|
|
|
:title="isFullscreen ? 'Exit Fullscreen (F)' : 'Fullscreen (F)'"
|
|
:title="isFullscreen ? 'Exit Fullscreen (F)' : 'Fullscreen (F)'"
|
|
@@ -140,8 +130,9 @@
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
import MyVideo from '@/components/myVideo.vue'
|
|
import MyVideo from '@/components/myVideo.vue'
|
|
|
-import { ptzControl, PTZDirection, getDeviceNum } from '@/api/ptz'
|
|
|
|
|
|
|
+import { ptzControl, PTZDirection, getDeviceNum, putDeviceNum } from '@/api/ptz'
|
|
|
|
|
|
|
|
const videoRef = ref()
|
|
const videoRef = ref()
|
|
|
const isFullscreen = ref(false)
|
|
const isFullscreen = ref(false)
|
|
@@ -151,32 +142,49 @@ const ptzSpeed = ref(4)
|
|
|
const isPtzMoving = ref(false)
|
|
const isPtzMoving = ref(false)
|
|
|
const ptzCollapsed = ref(false)
|
|
const ptzCollapsed = ref(false)
|
|
|
|
|
|
|
|
-// Channel
|
|
|
|
|
-const currentChannel = ref('ch1')
|
|
|
|
|
-const channelOptions = ref<{ label: string; value: string }[]>([
|
|
|
|
|
- { label: 'Channel 1', value: 'ch1' }
|
|
|
|
|
-])
|
|
|
|
|
-
|
|
|
|
|
-// Fetch lens count and build channel options
|
|
|
|
|
-const fetchChannelCount = async () => {
|
|
|
|
|
|
|
+// Device state
|
|
|
|
|
+const showChannelSelector = ref(false)
|
|
|
|
|
+const selectedChn = ref(0) // 0: Fixed, 1: PTZ
|
|
|
|
|
+const ptzEnabled = ref(false)
|
|
|
|
|
+const channelOptions = [
|
|
|
|
|
+ { label: 'Fixed Camera', value: 0 },
|
|
|
|
|
+ { label: 'PTZ Camera', value: 1 }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+// Fetch device info
|
|
|
|
|
+const fetchDeviceInfo = async () => {
|
|
|
try {
|
|
try {
|
|
|
- // const res = await getDeviceNum()
|
|
|
|
|
- // const lensNumber = res?.data?.LensNumber ?? 1
|
|
|
|
|
- const lensNumber = 2
|
|
|
|
|
- const options = []
|
|
|
|
|
- for (let i = 1; i <= lensNumber; i++) {
|
|
|
|
|
- options.push({ label: `Channel ${i}`, value: `ch${i}` })
|
|
|
|
|
- }
|
|
|
|
|
- channelOptions.value = options
|
|
|
|
|
|
|
+ const res = await getDeviceNum()
|
|
|
|
|
+ // const res = {"data":{"LensNumber":2,"ChnNumber":0}}
|
|
|
|
|
+ const lensNumber = res?.data?.LensNumber ?? 1
|
|
|
|
|
+ const chnNumber = res?.data?.ChnNumber ?? 0
|
|
|
|
|
+
|
|
|
|
|
+ // Dual lens: show channel selector
|
|
|
|
|
+ showChannelSelector.value = lensNumber >= 2
|
|
|
|
|
+
|
|
|
|
|
+ // Sync current selection and PTZ state
|
|
|
|
|
+ selectedChn.value = chnNumber
|
|
|
|
|
+ ptzEnabled.value = chnNumber === 1
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- console.error('Failed to get device channel count:', error)
|
|
|
|
|
|
|
+ console.error('Failed to get device info:', error)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Switch channel: refresh video stream
|
|
|
|
|
-const handleChannelChange = () => {
|
|
|
|
|
- if (videoRef.value?.refreshWebSocket) {
|
|
|
|
|
- videoRef.value.refreshWebSocket()
|
|
|
|
|
|
|
+// User switches between Fixed / PTZ camera via dropdown
|
|
|
|
|
+const handleChnChange = async (val: number) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await putDeviceNum({ ChnNumber: val })
|
|
|
|
|
+ // Re-fetch to get confirmed state from device
|
|
|
|
|
+ await fetchDeviceInfo()
|
|
|
|
|
+ // Refresh video stream after switch
|
|
|
|
|
+ if (videoRef.value?.refreshWebSocket) {
|
|
|
|
|
+ videoRef.value.refreshWebSocket()
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Failed to switch channel:', error)
|
|
|
|
|
+ ElMessage.error('Failed to switch channel, please try again')
|
|
|
|
|
+ // Revert selection on failure
|
|
|
|
|
+ await fetchDeviceInfo()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -247,7 +255,7 @@ const handleVideoError = (error: any) => {
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
|
document.addEventListener('keydown', handleKeydown)
|
|
document.addEventListener('keydown', handleKeydown)
|
|
|
- fetchChannelCount()
|
|
|
|
|
|
|
+ fetchDeviceInfo()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
onUnmounted(() => {
|
|
@@ -283,12 +291,6 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
.video-wrapper {
|
|
.video-wrapper {
|
|
|
position: relative;
|
|
position: relative;
|
|
|
- /*
|
|
|
|
|
- * 用固定的 calc 尺寸而非 flex:1,
|
|
|
|
|
- * 这样 video.load() 清空内容时容器不会收缩。
|
|
|
|
|
- * 宽度 = 总宽 - PTZ面板(180px) - gap(16px) - 两侧padding(32px) - 留白(20px)
|
|
|
|
|
- * 高度按 16:9 从宽度推算,同时不超过容器高度
|
|
|
|
|
- */
|
|
|
|
|
width: calc(100vw - 208px);
|
|
width: calc(100vw - 208px);
|
|
|
height: calc((100vw - 208px) / 16 * 9);
|
|
height: calc((100vw - 208px) / 16 * 9);
|
|
|
max-height: calc(100vh - 110px);
|
|
max-height: calc(100vh - 110px);
|
|
@@ -300,7 +302,6 @@ onUnmounted(() => {
|
|
|
height: 100%;
|
|
height: 100%;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 全屏时视频撑满整个屏幕
|
|
|
|
|
.video-container.is-fullscreen & {
|
|
.video-container.is-fullscreen & {
|
|
|
width: 100vw;
|
|
width: 100vw;
|
|
|
height: 100vh;
|
|
height: 100vh;
|
|
@@ -308,11 +309,6 @@ onUnmounted(() => {
|
|
|
max-height: none;
|
|
max-height: none;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- :deep(.preview-container) {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
:deep(.video_container) {
|
|
:deep(.video_container) {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
height: 100%;
|
|
height: 100%;
|
|
@@ -342,45 +338,6 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* Channel selector */
|
|
|
|
|
-.channel-selector {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- bottom: 48px;
|
|
|
|
|
- right: 16px;
|
|
|
|
|
- z-index: 10;
|
|
|
|
|
- opacity: 0;
|
|
|
|
|
- transition: opacity 0.3s ease;
|
|
|
|
|
-
|
|
|
|
|
- .video-wrapper:hover & {
|
|
|
|
|
- opacity: 1;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-select) {
|
|
|
|
|
- width: 120px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-input__wrapper) {
|
|
|
|
|
- background: rgba(0, 0, 0, 0.6);
|
|
|
|
|
- border: none;
|
|
|
|
|
- box-shadow: none;
|
|
|
|
|
- backdrop-filter: blur(8px);
|
|
|
|
|
- border-radius: 6px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-input__inner) {
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
-
|
|
|
|
|
- &::placeholder {
|
|
|
|
|
- color: rgba(255, 255, 255, 0.6);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- :deep(.el-select__caret) {
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
.fullscreen-btn {
|
|
.fullscreen-btn {
|
|
|
position: absolute;
|
|
position: absolute;
|
|
|
top: 16px;
|
|
top: 16px;
|
|
@@ -415,22 +372,28 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* PTZ 云台控制面板 */
|
|
|
|
|
-.ptz-wrapper {
|
|
|
|
|
- position: relative;
|
|
|
|
|
|
|
+/* Control sidebar */
|
|
|
|
|
+.control-sidebar {
|
|
|
flex-shrink: 0;
|
|
flex-shrink: 0;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
|
|
+
|
|
|
|
|
+ .video-container.is-fullscreen & {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ left: 20px;
|
|
|
|
|
+ top: 50%;
|
|
|
|
|
+ transform: translateY(-50%);
|
|
|
|
|
+ z-index: 10;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.ptz-panel {
|
|
|
|
|
|
|
+.control-panel {
|
|
|
width: 140px;
|
|
width: 140px;
|
|
|
background: #f5f7fa;
|
|
background: #f5f7fa;
|
|
|
border-radius: 10px;
|
|
border-radius: 10px;
|
|
|
padding: 12px;
|
|
padding: 12px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- align-items: center;
|
|
|
|
|
gap: 10px;
|
|
gap: 10px;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
transition: width 0.3s ease, padding 0.3s ease, opacity 0.3s ease;
|
|
transition: width 0.3s ease, padding 0.3s ease, opacity 0.3s ease;
|
|
@@ -446,7 +409,9 @@ onUnmounted(() => {
|
|
|
background: rgba(0, 0, 0, 0.7);
|
|
background: rgba(0, 0, 0, 0.7);
|
|
|
backdrop-filter: blur(12px);
|
|
backdrop-filter: blur(12px);
|
|
|
|
|
|
|
|
- .ptz-title { color: #fff; }
|
|
|
|
|
|
|
+ .control-label { color: #fff; }
|
|
|
|
|
+ .control-divider { border-color: rgba(255, 255, 255, 0.15); }
|
|
|
|
|
+
|
|
|
.ptz-btn {
|
|
.ptz-btn {
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
color: #fff;
|
|
color: #fff;
|
|
@@ -459,11 +424,40 @@ onUnmounted(() => {
|
|
|
border-color: rgba(64, 158, 255, 0.5) !important;
|
|
border-color: rgba(64, 158, 255, 0.5) !important;
|
|
|
&:hover { background: rgba(64, 158, 255, 0.5) !important; }
|
|
&:hover { background: rgba(64, 158, 255, 0.5) !important; }
|
|
|
}
|
|
}
|
|
|
- .ptz-speed-label, .ptz-speed-value { color: #fff; }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ box-shadow: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ :deep(.el-input__inner) { color: #fff; }
|
|
|
|
|
+ :deep(.el-select__caret) { color: #fff; }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.control-label {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.control-divider {
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-top: 1px solid #e4e7ed;
|
|
|
|
|
+ margin: 2px 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.channel-selector {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-select) {
|
|
|
|
|
+ width: 100%;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.ptz-toggle {
|
|
|
|
|
|
|
+.panel-toggle {
|
|
|
width: 24px;
|
|
width: 24px;
|
|
|
height: 48px;
|
|
height: 48px;
|
|
|
border: 1px solid #e4e7ed;
|
|
border: 1px solid #e4e7ed;
|
|
@@ -498,21 +492,6 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.video-container.is-fullscreen .ptz-wrapper {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- left: 20px;
|
|
|
|
|
- top: 50%;
|
|
|
|
|
- transform: translateY(-50%);
|
|
|
|
|
- z-index: 10;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.ptz-title {
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #303133;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
.ptz-grid {
|
|
.ptz-grid {
|
|
|
display: grid;
|
|
display: grid;
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
grid-template-columns: repeat(3, 1fr);
|
|
@@ -547,17 +526,6 @@ onUnmounted(() => {
|
|
|
&:hover { background: #d9ecff !important; }
|
|
&:hover { background: #d9ecff !important; }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.ptz-speed {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
-
|
|
|
|
|
- .ptz-speed-label { font-size: 12px; color: #909399; white-space: nowrap; }
|
|
|
|
|
- .ptz-speed-value { font-size: 12px; color: #606266; font-weight: 600; min-width: 14px; text-align: center; }
|
|
|
|
|
- :deep(.el-slider) { flex: 1; }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
@media (max-width: 768px) {
|
|
@media (max-width: 768px) {
|
|
|
.video-container {
|
|
.video-container {
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
@@ -565,7 +533,8 @@ onUnmounted(() => {
|
|
|
padding: 0;
|
|
padding: 0;
|
|
|
gap: 12px;
|
|
gap: 12px;
|
|
|
}
|
|
}
|
|
|
- .ptz-panel { width: 100%; max-width: 200px; order: 2; }
|
|
|
|
|
|
|
+ .control-sidebar { order: 2; }
|
|
|
|
|
+ .control-panel { width: 100%; max-width: 200px; }
|
|
|
.video-wrapper {
|
|
.video-wrapper {
|
|
|
order: 1;
|
|
order: 1;
|
|
|
width: 100vw;
|
|
width: 100vw;
|