W2: 视频墙多路播放+HLS回放 全部对接网关真实数据
This commit is contained in:
@@ -3,25 +3,17 @@
|
|||||||
<div class="monitor-header">
|
<div class="monitor-header">
|
||||||
<h2>历史回放</h2>
|
<h2>历史回放</h2>
|
||||||
<div class="monitor-controls">
|
<div class="monitor-controls">
|
||||||
<el-button type="primary" size="small">刷新</el-button>
|
<el-button type="primary" size="small" @click="loadCameras">刷新</el-button>
|
||||||
<el-button size="small" plain>全屏显示</el-button>
|
<el-button size="small" plain>全屏显示</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 日期时间选择 -->
|
|
||||||
<div class="date-filter">
|
<div class="date-filter">
|
||||||
<h3>时间段选择</h3>
|
<h3>时间段选择</h3>
|
||||||
<div class="date-filter-controls">
|
<div class="date-filter-controls">
|
||||||
<el-date-picker
|
<el-date-picker v-model="dateTimeRange" type="daterange" range-separator="至"
|
||||||
v-model="dateTimeRange"
|
start-placeholder="开始日期时间" end-placeholder="结束日期时间"
|
||||||
type="daterange"
|
value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" style="width: 400px;" />
|
||||||
range-separator="至"
|
|
||||||
start-placeholder="开始日期时间"
|
|
||||||
end-placeholder="结束日期时间"
|
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
style="width: 400px;"
|
|
||||||
></el-date-picker>
|
|
||||||
<el-button type="primary" size="small" @click="searchRecordings">查询</el-button>
|
<el-button type="primary" size="small" @click="searchRecordings">查询</el-button>
|
||||||
<el-button size="small" plain @click="resetDateTimeRange">重置</el-button>
|
<el-button size="small" plain @click="resetDateTimeRange">重置</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,9 +25,7 @@
|
|||||||
<div class="camera-info">
|
<div class="camera-info">
|
||||||
<div class="camera-name">{{ camera.name }}</div>
|
<div class="camera-name">{{ camera.name }}</div>
|
||||||
<div class="camera-location">{{ camera.location }}</div>
|
<div class="camera-location">{{ camera.location }}</div>
|
||||||
<div class="camera-status" :class="camera.status">
|
<div class="camera-status" :class="camera.status">{{ camera.status === 'online' ? '在线' : '离线' }}</div>
|
||||||
{{ camera.status === 'online' ? '在线' : '离线' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,29 +33,12 @@
|
|||||||
<div class="main-view">
|
<div class="main-view">
|
||||||
<h3>历史回放画面</h3>
|
<h3>历史回放画面</h3>
|
||||||
<div class="video-container" v-if="selectedCamera">
|
<div class="video-container" v-if="selectedCamera">
|
||||||
<div class="video-placeholder">
|
<video v-if="playbackUrl" :src="playbackUrl" autoplay controls style="width:100%;height:400px;background:#000" />
|
||||||
|
<div v-else class="video-placeholder">
|
||||||
<el-icon><VideoPlay /></el-icon>
|
<el-icon><VideoPlay /></el-icon>
|
||||||
<p>正在加载 {{ selectedCamera.name }} 的历史画面...</p>
|
<p>{{ playbackLoading ? '正在加载...' : '' }} {{ selectedCamera.name }} 的历史画面</p>
|
||||||
<p v-if="dateTimeRange && dateTimeRange.length === 2">{{ formatDateTime(dateTimeRange[0]) }} - {{ formatDateTime(dateTimeRange[1]) }}</p>
|
<p v-if="dateTimeRange && dateTimeRange.length === 2">{{ formatDateTime(dateTimeRange[0]) }} - {{ formatDateTime(dateTimeRange[1]) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- 播放进度条 -->
|
|
||||||
<div class="video-progress">
|
|
||||||
<el-slider v-model="playProgress" :min="0" :max="100" show-tooltip="always" />
|
|
||||||
<div class="time-display">{{ formatProgressTime() }} / {{ totalDuration }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="video-controls">
|
|
||||||
<el-button size="small" icon="el-icon-video-play" @click="togglePlay">{{ isPlaying ? '暂停' : '播放' }}</el-button>
|
|
||||||
<el-button size="small" icon="el-icon-refresh-right" @click="stepForward">前进10秒</el-button>
|
|
||||||
<el-button size="small" icon="el-icon-refresh-left" @click="stepBackward">后退10秒</el-button>
|
|
||||||
<el-button size="small" icon="el-icon-camera" @click="screenshot">截图</el-button>
|
|
||||||
<el-select v-model="playSpeed" placeholder="播放速度" size="small">
|
|
||||||
<el-option label="0.5x" :value="0.5"></el-option>
|
|
||||||
<el-option label="1.0x" :value="1"></el-option>
|
|
||||||
<el-option label="1.5x" :value="1.5"></el-option>
|
|
||||||
<el-option label="2.0x" :value="2"></el-option>
|
|
||||||
</el-select>
|
|
||||||
<el-button size="small" icon="el-icon-full-screen" @click="toggleFullScreen">全屏</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="no-selection" v-else>
|
<div class="no-selection" v-else>
|
||||||
<el-empty description="请从左侧选择摄像头查看历史画面" />
|
<el-empty description="请从左侧选择摄像头查看历史画面" />
|
||||||
@@ -79,8 +52,6 @@
|
|||||||
<el-descriptions-item label="在线设备">{{ onlineCount }}</el-descriptions-item>
|
<el-descriptions-item label="在线设备">{{ onlineCount }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="离线设备">{{ offlineCount }}</el-descriptions-item>
|
<el-descriptions-item label="离线设备">{{ offlineCount }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="当前时间">{{ currentTime }}</el-descriptions-item>
|
<el-descriptions-item label="当前时间">{{ currentTime }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="回放状态" v-if="selectedCamera">{{ isPlaying ? '播放中' : '已暂停' }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="选择时间" v-if="dateTimeRange && dateTimeRange.length === 2">{{ formatDate(dateTimeRange[0]) }}</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,438 +61,70 @@
|
|||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { VideoPlay } from '@element-plus/icons-vue'
|
import { VideoPlay } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { fetchCameras, gwGet, type Camera } from '@/api/gateway'
|
||||||
|
|
||||||
interface Camera {
|
const cameras = ref<Camera[]>([])
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
location: string
|
|
||||||
status: 'online' | 'offline'
|
|
||||||
streamUrl?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 摄像头数据
|
|
||||||
const cameras = ref<Camera[]>([
|
|
||||||
{
|
|
||||||
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 selectedCamera = ref<Camera | null>(null)
|
const selectedCamera = ref<Camera | null>(null)
|
||||||
|
|
||||||
// 日期时间范围
|
|
||||||
const dateTimeRange = ref<string[]>([])
|
const dateTimeRange = ref<string[]>([])
|
||||||
|
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 onlineCount = computed(() => cameras.value.filter(c => c.status === 'online').length)
|
||||||
const offlineCount = computed(() => cameras.value.filter(c => c.status === 'offline').length)
|
const offlineCount = computed(() => cameras.value.filter(c => c.status === 'offline').length)
|
||||||
|
|
||||||
// 当前时间
|
const loadCameras = async () => { try { cameras.value = await fetchCameras('Owl:main') } catch {} }
|
||||||
const currentTime = ref('')
|
|
||||||
|
|
||||||
// 选择摄像头
|
|
||||||
const selectCamera = (camera: Camera) => {
|
const selectCamera = (camera: Camera) => {
|
||||||
selectedCamera.value = camera
|
selectedCamera.value = camera
|
||||||
playProgress.value = 0
|
if (!dateTimeRange.value.length) {
|
||||||
isPlaying.value = false
|
const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1)
|
||||||
|
dateTimeRange.value = [formatISOLocal(yesterday), formatISOLocal(new Date())]
|
||||||
// 设置默认日期时间范围(前一天到今天)
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 快退10秒
|
const searchRecordings = async () => {
|
||||||
const stepBackward = () => {
|
if (!selectedCamera.value) return ElMessage.warning('请先选择摄像头')
|
||||||
playProgress.value = Math.max(0, playProgress.value - 5)
|
if (!dateTimeRange.value || dateTimeRange.value.length !== 2) return ElMessage.warning('请选择时间段')
|
||||||
ElMessage.info('后退10秒')
|
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 resetDateTimeRange = () => { dateTimeRange.value = []; playbackUrl.value = '' }
|
||||||
const stepForward = () => {
|
const formatISOLocal = (d: Date) => d.toISOString().slice(0, 19).replace('T', ' ')
|
||||||
playProgress.value = Math.min(100, playProgress.value + 5)
|
const formatDateTime = (s: string) => s
|
||||||
ElMessage.info('前进10秒')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 截图
|
const updateTime = () => { currentTime.value = new Date().toLocaleString('zh-CN') }
|
||||||
const screenshot = () => {
|
onMounted(() => { loadCameras(); updateTime(); setInterval(updateTime, 1000) })
|
||||||
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', ' ')
|
|
||||||
]
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.history-playback {
|
.history-playback { height: 100%; display: flex; flex-direction: column; padding: 20px; }
|
||||||
height: 100%;
|
.monitor-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||||
display: flex;
|
.monitor-header h2 { margin: 0; color: #fff; font-size: 20px; font-weight: 500; }
|
||||||
flex-direction: column;
|
.monitor-controls { display: flex; gap: 10px; }
|
||||||
padding: 20px;
|
.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; }
|
||||||
.monitor-header {
|
.camera-list h3 { color: #fff; }
|
||||||
display: flex;
|
.camera-item { background: rgba(255,255,255,0.05); cursor: pointer; }
|
||||||
justify-content: space-between;
|
.camera-name { font-size: 14px; color: #fff; }
|
||||||
align-items: center;
|
.camera-location { font-size: 12px; color: #999; }
|
||||||
margin-bottom: 20px;
|
.camera-status.online { color: #67c23a; }
|
||||||
}
|
.camera-status.offline { color: #f56c6c; }
|
||||||
|
.main-view { flex: 1; }
|
||||||
.monitor-header h2 {
|
.main-view h3 { color: #fff; margin-bottom: 10px; }
|
||||||
margin: 0;
|
.video-container { background: #000; border-radius: 8px; overflow: hidden; }
|
||||||
color: #fff;
|
.video-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 400px; color: #fff; }
|
||||||
font-size: 20px;
|
.no-selection { flex: 1; display: flex; align-items: center; justify-content: center; }
|
||||||
font-weight: 500;
|
.system-info { margin-top: 20px; }
|
||||||
}
|
.system-info h3 { color: #fff; margin-bottom: 10px; }
|
||||||
|
</style>
|
||||||
.monitor-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-filter {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-filter h3 {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-filter-controls {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-list {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-list h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-item {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-item:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-name {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-location {
|
|
||||||
font-size: 12px;
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-status {
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-status.online {
|
|
||||||
background: rgba(103, 194, 58, 0.2);
|
|
||||||
color: #67c23a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.camera-status.offline {
|
|
||||||
background: rgba(245, 108, 108, 0.2);
|
|
||||||
color: #f56c6c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-view {
|
|
||||||
flex: 1;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-view h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-container {
|
|
||||||
flex: 1;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-placeholder {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-placeholder .el-icon {
|
|
||||||
font-size: 64px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-placeholder p {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-progress {
|
|
||||||
padding: 10px;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-display {
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-controls {
|
|
||||||
padding: 10px;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-selection {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-info {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-info h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-descriptions {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-descriptions__label {
|
|
||||||
color: rgba(255, 255, 255, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-descriptions__content {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-card.is-hover-shadow:hover {
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-slider__runway {
|
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-slider__bar {
|
|
||||||
background-color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-slider__button-wrapper {
|
|
||||||
border-color: #409eff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user