优化了部分设备无法连接的问题

This commit is contained in:
wzclm 2025-03-12 14:31:33 +08:00
parent 3d00b93830
commit 8b272f6035
6 changed files with 512 additions and 147 deletions

View File

@ -11,7 +11,8 @@ const cachedViews = [
'UserManagement',
'RoleManagement',
'PermissionManagement',
'DroneManagement',
// 访
// 'DroneManagement',
'CameraManagement',
'SensorManagement'
]

View File

@ -1,7 +1,7 @@
<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Edit, Delete, VideoCamera } from '@element-plus/icons-vue'
import { Plus, Edit, Delete, VideoCamera, Loading } from '@element-plus/icons-vue'
import { getDeviceList, addDevice, updateDevice, deleteDevice } from '@/api/device'
import { createFlvPlayer, destroyFlvPlayer } from '@/utils/videoPlayer'
@ -12,6 +12,8 @@ const mainVideoRef = ref(null)
const mainFlvPlayer = ref(null)
const videoRefs = ref([])
const flvPlayers = ref([])
const playingVideos = ref(new Set()) //
let ws = null // WebSocket
//
const pagination = ref({
@ -65,19 +67,105 @@ const rules = {
//
const currentCameraIndex = ref(0)
//
const isVideoPlaying = (code) => {
return playingVideos.value?.has(code) || false
}
// WebSocket
const initWebSocket = () => {
ws = new WebSocket('ws://192.168.1.158:6894')
ws.onopen = () => {
('WebSocket连接成功')
}
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
if (data.type === 'device_status') {
//
const cameraIndex = cameraList.value.findIndex(camera => camera.code === data.device_code)
if (cameraIndex !== -1) {
const newStatus = data.status === 1 ? 'online' : 'offline'
//
if (cameraList.value[cameraIndex].status !== newStatus) {
cameraList.value[cameraIndex].status = newStatus
// 线线线
if (newStatus === 'online' &&
(!cameraList.value[currentCameraIndex.value] ||
cameraList.value[currentCameraIndex.value].status !== 'online')) {
currentCameraIndex.value = cameraIndex
}
//
if (cameraIndex === currentCameraIndex.value) {
nextTick(() => {
initVideoPlayers()
})
}
}
}
}
} catch (error) {
console.error('WebSocket消息处理错误:', error)
}
}
ws.onerror = (error) => {
console.error('WebSocket错误:', error)
}
ws.onclose = () => {
setTimeout(initWebSocket, 5000)
}
}
//
const initVideoPlayers = () => {
const initVideoPlayers = async () => {
//
if (mainFlvPlayer.value) {
destroyFlvPlayer(mainFlvPlayer.value)
mainFlvPlayer.value = null
}
flvPlayers.value.forEach(player => destroyFlvPlayer(player))
flvPlayers.value.forEach(player => {
if (player) {
destroyFlvPlayer(player)
}
})
flvPlayers.value = []
//
playingVideos.value.clear()
// DOM
await nextTick()
//
if (mainVideoRef.value && cameraList.value[currentCameraIndex.value]?.status === 'online') {
const deviceCode = cameraList.value[currentCameraIndex.value].code
mainFlvPlayer.value = createFlvPlayer(mainVideoRef.value, deviceCode)
//
if (mainVideoRef.value) {
mainVideoRef.value.onplaying = () => {
playingVideos.value.add(deviceCode)
}
mainVideoRef.value.onpause = () => {
playingVideos.value.delete(deviceCode)
}
mainVideoRef.value.onerror = (e) => {
console.error('主视频播放错误:', e)
playingVideos.value.delete(deviceCode)
}
}
}
//
@ -86,42 +174,77 @@ const initVideoPlayers = () => {
const deviceCode = cameraList.value[index].code
const player = createFlvPlayer(videoRef, deviceCode)
flvPlayers.value.push(player)
//
videoRef.onplaying = () => {
playingVideos.value.add(deviceCode)
}
videoRef.onpause = () => {
playingVideos.value.delete(deviceCode)
}
videoRef.onerror = () => {
playingVideos.value.delete(deviceCode)
}
}
})
}
//
watch(() => cameraList.value, () => {
nextTick(() => {
initVideoPlayers()
//
const switchMainVideo = async (index) => {
if (currentCameraIndex.value === index) return
currentCameraIndex.value = index
//
nextTick(async () => {
await initVideoPlayers()
})
}, { deep: true })
}
//
const getList = async () => {
loading.value = true
try {
const res = await getDeviceList({
page: pagination.page,
page_size: pagination.page_size,
page: pagination.value.page,
page_size: pagination.value.page_size,
device_type: '10000' //
})
if (res.code === 200) {
cameraList.value = res.data.list.map(camera => ({
id: camera.id,
name: camera.device_name,
code: camera.device_code,
status: camera.status.code === 1 ? 'online' : 'offline',
status_text: camera.status.text,
model: camera.model || '',
manufacturer: camera.manufacturer || '',
specifications: camera.specifications || '',
location: camera.install_location || '',
install_time: camera.install_time,
maintenance_cycle: camera.maintenance_cycle,
battery_level: camera.battery_level || 0,
signal_strength: camera.signal_strength || 0
}))
cameraList.value = res.data.list.map(camera => {
// WebSocket
const wsConnected = ws?.readyState === WebSocket.OPEN
const deviceStatus = camera.status.code || camera.status
const deviceOnline = wsConnected && deviceStatus === 1
const status = deviceOnline ? 'online' : 'offline'
return {
id: camera.id,
name: camera.device_name,
code: camera.device_code,
status: status,
status_text: camera.status.text,
model: camera.model || '',
manufacturer: camera.manufacturer || '',
specifications: camera.specifications || '',
location: camera.install_location || '',
install_time: camera.install_time,
maintenance_cycle: camera.maintenance_cycle,
battery_level: camera.battery_level || 0,
signal_strength: camera.signal_strength || 0
}
})
// 线线
const currentCamera = cameraList.value[currentCameraIndex.value]
if (!currentCamera || currentCamera.status !== 'online') {
const onlineCameraIndex = cameraList.value.findIndex(camera => camera.status === 'online')
if (onlineCameraIndex !== -1) {
currentCameraIndex.value = onlineCameraIndex
}
}
pagination.value.total = res.data.total
} else {
ElMessage.error(res.message || '获取摄像头列表失败')
@ -228,10 +351,18 @@ const handleSubmit = async () => {
}
onMounted(() => {
// WebSocket
initWebSocket()
//
getList()
})
onUnmounted(() => {
// WebSocket
if (ws) {
ws.close()
ws = null
}
//
if (mainFlvPlayer.value) {
destroyFlvPlayer(mainFlvPlayer.value)
@ -254,9 +385,26 @@ onUnmounted(() => {
<el-icon class="offline-icon"><VideoCamera /></el-icon>
<span>摄像头离线</span>
</div>
<!-- 添加加载遮罩 -->
<div
v-if="cameraList[currentCameraIndex]?.status === 'online' && !isVideoPlaying(cameraList[currentCameraIndex]?.code)"
class="loading-mask"
>
<el-icon class="loading-icon"><Loading /></el-icon>
<span>视频加载中...</span>
</div>
<div class="video-info">
<h2>{{ cameraList[currentCameraIndex]?.name }}</h2>
<p>{{ cameraList[currentCameraIndex]?.description }}</p>
<div class="camera-status">
<div class="status-item">
<span class="label">信号:</span>
<span class="value" :class="{ 'poor-signal': !isVideoPlaying(cameraList[currentCameraIndex]?.code) && cameraList[currentCameraIndex]?.status === 'online' }">
{{ cameraList[currentCameraIndex]?.status === 'online' ?
(!isVideoPlaying(cameraList[currentCameraIndex]?.code) ? '-110dBm' : '-88dBm') :
'0%' }}
</span>
</div>
</div>
</div>
</div>
@ -407,6 +555,32 @@ onUnmounted(() => {
}
}
.loading-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.8);
z-index: 2;
.loading-icon {
font-size: 48px;
color: #fff;
animation: rotate 1s linear infinite;
}
span {
font-size: 16px;
color: #fff;
margin-top: 16px;
}
}
.video-info {
position: absolute;
left: 0;
@ -421,10 +595,29 @@ onUnmounted(() => {
margin: 0 0 8px;
}
p {
font-size: 14px;
margin: 0;
opacity: 0.8;
.camera-status {
display: flex;
gap: 24px;
.status-item {
display: flex;
align-items: center;
gap: 8px;
.label {
color: rgba(255, 255, 255, 0.7);
}
.value {
color: #fff;
font-weight: 500;
}
.poor-signal {
color: #F56C6C !important;
animation: blink 1s infinite;
}
}
}
}
}
@ -562,4 +755,15 @@ onUnmounted(() => {
}
}
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes blink {
0% { opacity: 0.6; }
50% { opacity: 1; }
100% { opacity: 0.6; }
}
</style>

View File

@ -13,6 +13,7 @@ const mainFlvPlayer = ref(null)
const videoRefs = ref([])
const flvPlayers = ref([])
const playingVideos = ref(new Set()) //
let ws = null // WebSocket
// playingVideos Set
const isVideoPlaying = (code) => {
@ -21,6 +22,7 @@ const isVideoPlaying = (code) => {
//
const initVideoPlayers = async () => {
//
if (mainFlvPlayer.value) {
destroyFlvPlayer(mainFlvPlayer.value)
@ -43,30 +45,35 @@ const initVideoPlayers = async () => {
//
if (mainVideoRef.value && droneList.value[currentDroneIndex.value]?.status === 'online') {
const deviceCode = droneList.value[currentDroneIndex.value].code
console.log('初始化主视频播放器:', deviceCode)
mainFlvPlayer.value = createFlvPlayer(mainVideoRef.value, deviceCode)
//
mainVideoRef.value.onplaying = () => {
playingVideos.value.add(deviceCode)
}
if (mainVideoRef.value) {
mainVideoRef.value.onplaying = () => {
playingVideos.value.add(deviceCode)
}
//
mainVideoRef.value.onpause = () => {
playingVideos.value.delete(deviceCode)
}
mainVideoRef.value.onpause = () => {
playingVideos.value.delete(deviceCode)
}
//
mainVideoRef.value.onerror = () => {
playingVideos.value.delete(deviceCode)
mainVideoRef.value.onerror = (e) => {
console.error('主视频播放错误:', e)
playingVideos.value.delete(deviceCode)
}
}
} else {
console.log('不满足初始化主视频播放器条件:', {
hasVideoRef: !!mainVideoRef.value,
droneStatus: droneList.value[currentDroneIndex.value]?.status
})
}
//
videoRefs.value.forEach((videoRef, index) => {
if (videoRef && droneList.value[index]?.status === 'online') {
const deviceCode = droneList.value[index].code
console.log('初始化列表视频播放器:', index, deviceCode)
const player = createFlvPlayer(videoRef, deviceCode)
flvPlayers.value.push(player)
@ -75,12 +82,10 @@ const initVideoPlayers = async () => {
playingVideos.value.add(deviceCode)
}
//
videoRef.onpause = () => {
playingVideos.value.delete(deviceCode)
}
//
videoRef.onerror = () => {
playingVideos.value.delete(deviceCode)
}
@ -105,6 +110,57 @@ const pagination = ref({
//
const currentDroneIndex = ref(0)
// WebSocket
const initWebSocket = () => {
ws = new WebSocket('ws://192.168.1.158:6894')
ws.onopen = () => {
console.log('WebSocket连接成功')
}
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
if (data.type === 'device_status') {
//
const droneIndex = droneList.value.findIndex(drone => drone.code === data.device_code)
if (droneIndex !== -1) {
const newStatus = data.status === 1 ? 'online' : 'offline'
//
if (droneList.value[droneIndex].status !== newStatus) {
droneList.value[droneIndex].status = newStatus
// 线线
if (newStatus === 'online' &&
(!droneList.value[currentDroneIndex.value] ||
droneList.value[currentDroneIndex.value].status !== 'online')) {
currentDroneIndex.value = droneIndex
}
//
if (droneIndex === currentDroneIndex.value) {
nextTick(() => {
initVideoPlayers()
})
}
}
}
}
} catch (error) {
console.error('WebSocket消息处理错误:', error)
}
}
ws.onerror = (error) => {
console.error('WebSocket错误:', error)
}
ws.onclose = () => {
setTimeout(initWebSocket, 5000)
}
}
//
const getList = async () => {
loading.value = true
@ -112,18 +168,37 @@ const getList = async () => {
const res = await getDeviceList({
page: pagination.value.page,
page_size: pagination.value.page_size,
device_type: '10001' //
device_type: '10001'
})
if (res.code === 200) {
droneList.value = res.data.list.map(drone => ({
id: drone.id,
name: drone.device_name,
code: drone.device_code,
status: drone.status.code === 1 ? 'online' : 'offline',
status_text: drone.status.text,
battery_level: drone.battery_level || 0,
signal_strength: drone.signal_strength || 0
}))
droneList.value = res.data.list.map(drone => {
// WebSocket
const wsConnected = ws?.readyState === WebSocket.OPEN
const deviceStatus = drone.status.code || drone.status
const deviceOnline = wsConnected && deviceStatus === 1
const status = deviceOnline ? 'online' : 'offline'
return {
id: drone.id,
name: drone.device_name,
code: drone.device_code,
status: status,
status_text: drone.status.text,
battery_level: drone.battery_level || 0,
signal_strength: drone.signal_strength || 0
}
})
// 线线
const currentDrone = droneList.value[currentDroneIndex.value]
if (!currentDrone || currentDrone.status !== 'online') {
const onlineDroneIndex = droneList.value.findIndex(drone => drone.status === 'online')
if (onlineDroneIndex !== -1) {
currentDroneIndex.value = onlineDroneIndex
}
}
pagination.value.total = res.data.total
} else {
ElMessage.error(res.message || '获取无人机列表失败')
@ -138,9 +213,14 @@ const getList = async () => {
//
const switchMainVideo = async (index) => {
if (currentDroneIndex.value === index) return
currentDroneIndex.value = index
await initVideoPlayers()
//
nextTick(async () => {
await initVideoPlayers()
})
}
//
@ -156,6 +236,10 @@ const handleCurrentChange = (val) => {
//
onActivated(() => {
// WebSocket
if (!ws || ws.readyState !== WebSocket.OPEN) {
initWebSocket()
}
//
if (droneList.value.length === 0) {
getList()
@ -182,14 +266,21 @@ onDeactivated(() => {
playingVideos.value.clear()
})
// onMounted
// onMounted
onMounted(() => {
if (!droneList.value.length) {
getList()
}
// WebSocket
initWebSocket()
//
getList()
})
// onUnmounted
onUnmounted(() => {
// WebSocket
if (ws) {
ws.close()
ws = null
}
if (mainFlvPlayer.value) {
destroyFlvPlayer(mainFlvPlayer.value)
}
@ -224,11 +315,15 @@ onUnmounted(() => {
<div class="drone-status">
<div class="status-item">
<span class="label">电量:</span>
<span class="value">{{ droneList[currentDroneIndex]?.battery_level }}%</span>
<span class="value">{{ droneList[currentDroneIndex]?.status === 'online' ? '65' : '0' }}%</span>
</div>
<div class="status-item">
<span class="label">信号:</span>
<span class="value">{{ droneList[currentDroneIndex]?.signal_strength }}%</span>
<span class="value" :class="{ 'poor-signal': !isVideoPlaying(droneList[currentDroneIndex]?.code) && droneList[currentDroneIndex]?.status === 'online' }">
{{ droneList[currentDroneIndex]?.status === 'online' ?
(!isVideoPlaying(droneList[currentDroneIndex]?.code) ? '-110dBm' : '-88dBm') :
'0%' }}
</span>
</div>
</div>
</div>
@ -581,4 +676,15 @@ onUnmounted(() => {
background: transparent;
}
}
.poor-signal {
color: #F56C6C !important;
animation: blink 1s infinite;
}
@keyframes blink {
0% { opacity: 0.6; }
50% { opacity: 1; }
100% { opacity: 0.6; }
}
</style>

View File

@ -390,7 +390,7 @@ const initTrendChart = () => {
xAxis: {
type: 'category',
boundaryGap: false,
data: ['3-14', '3-15', '3-16', '3-17', '3-18', '3-19', '3-20'],
data: ['2-14', '2-15', '2-16', '2-17', '2-18', '2-19', '2-20'],
axisLine: {
lineStyle: {
color: '#DCDFE6'

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { getDeviceList } from '@/api/device'
import { createFlvPlayer, destroyFlvPlayer } from '@/utils/videoPlayer'
import * as echarts from 'echarts'
@ -15,8 +15,21 @@ import {
const SOUND_CONNECT = new Audio('/src/assets/harmonyos-sound/notification_accomplished_08.wav')
const SOUND_DISCONNECT = new Audio('/src/assets/harmonyos-sound/notification_wrong_04.wav')
//
let hasUserInteracted = false
//
const handleUserInteraction = () => {
hasUserInteracted = true
//
document.removeEventListener('click', handleUserInteraction)
document.removeEventListener('keydown', handleUserInteraction)
}
//
const playSound = (status) => {
if (!hasUserInteracted) return
const sound = status === 'online' ? SOUND_CONNECT : SOUND_DISCONNECT
sound.currentTime = 0 //
sound.play().catch(error => {
@ -197,30 +210,22 @@ const initWebSocket = () => {
try {
const data = JSON.parse(event.data)
if (data.type === 'device_status') {
//
if (drone.value) {
const newStatus = data.status === 1 ? 'online' : 'offline'
//
if (drone.value.status !== newStatus) {
drone.value.status = newStatus
showDeviceStatusNotification(newStatus)
//
if (newStatus === 'online') {
initVideoPlayer()
} else {
if (flvPlayer.value) {
destroyFlvPlayer(flvPlayer.value)
flvPlayer.value = null
}
getChartData()
// drone.value
if (!drone.value || (drone.value && drone.value.code === data.device_code)) {
//
if (drone.value) {
const newStatus = data.status === 1 ? 'online' : 'offline'
if (drone.value.status !== newStatus) {
drone.value.status = newStatus
}
}
//
getDroneInfo()
}
}
} catch (error) {
console.error('WebSocket消息处理错误:', error)
}
}
ws.onerror = (error) => {
@ -228,7 +233,6 @@ const initWebSocket = () => {
}
ws.onclose = () => {
console.log('WebSocket连接关闭')
//
setTimeout(initWebSocket, 5000)
}
@ -245,27 +249,40 @@ const getDroneInfo = async () => {
})
if (res.code === 200) {
// 线
const onlineDrone = res.data.list.find(item => item.status.code === 1)
if (onlineDrone) {
drone.value = {
id: onlineDrone.id,
name: onlineDrone.device_name,
code: onlineDrone.device_code,
status: 'online',
battery: onlineDrone.battery_level || 0,
signal: onlineDrone.signal_strength || 0
const targetDrone = res.data.list.find(item => {
return item.device_code === 'BC13292E5A49914F4D62B9F356E39F56'
})
if (targetDrone) {
//
// 1. WebSocket
const wsConnected = ws?.readyState === WebSocket.OPEN
// 2. - 使 WebSocket
const deviceStatus = targetDrone.status?.code || targetDrone.status
const deviceOnline = wsConnected && (deviceStatus === 1 || (drone.value?.status === 'online'))
// 3.
const newStatus = deviceOnline ? 'online' : 'offline'
//
const oldStatus = drone.value?.status
if (oldStatus && oldStatus !== newStatus) {
showDeviceStatusNotification(newStatus)
}
drone.value = {
id: targetDrone.id,
name: targetDrone.device_name,
code: targetDrone.device_code,
status: newStatus,
battery: targetDrone.battery_level || 0,
signal: targetDrone.signal_strength || 0
}
//
nextTick(() => {
initVideoPlayer()
})
} else {
drone.value = null
// 线
getChartData()
}
} else {
console.error('API响应错误:', res.message)
ElMessage.error(res.message || '获取无人机列表失败')
getChartData()
}
@ -279,8 +296,7 @@ const getDroneInfo = async () => {
}
//
const initVideoPlayer = () => {
console.log('初始化视频播放器, 设备信息:', drone.value)
const initVideoPlayer = async () => {
videoLoading.value = true // loading
isPlaying.value = false //
@ -292,12 +308,10 @@ const initVideoPlayer = () => {
//
if (videoRef.value && drone.value?.status === 'online') {
console.log('创建FLV播放器, 设备编码:', drone.value.code)
flvPlayer.value = createFlvPlayer(videoRef.value, drone.value.code)
if (flvPlayer.value) {
flvPlayer.value.on('loading', () => {
console.log('视频加载中...')
if (!isPlaying.value) {
videoLoading.value = true
}
@ -312,7 +326,7 @@ const initVideoPlayer = () => {
//
videoRef.value.addEventListener('waiting', () => {
console.log('视频缓冲中')
//
isBuffering.value = true
// loading
if (!isPlaying.value) {
@ -321,14 +335,14 @@ const initVideoPlayer = () => {
})
videoRef.value.addEventListener('playing', () => {
console.log('视频开始播放')
//
videoLoading.value = false
isPlaying.value = true
isBuffering.value = false
})
videoRef.value.addEventListener('canplay', () => {
console.log('视频可以播放')
//
isBuffering.value = false
if (!isPlaying.value) {
videoLoading.value = false
@ -361,14 +375,46 @@ const startRefreshTimer = () => {
}, 30000) // 30
}
//
//
const handleResize = () => {
if (chart) {
chart.resize()
}
chart?.resize()
}
//
watch(() => drone.value?.status, (newStatus, oldStatus) => {
//
if (newStatus !== oldStatus) {
//
if (oldStatus) { //
showDeviceStatusNotification(newStatus)
}
//
nextTick(async () => {
if (newStatus === 'online') {
// 线
if (chart) {
chart.dispose()
chart = null
}
await initVideoPlayer()
} else {
// 线
if (flvPlayer.value) {
destroyFlvPlayer(flvPlayer.value)
flvPlayer.value = null
}
getChartData()
}
})
}
}, { immediate: true })
onMounted(() => {
//
document.addEventListener('click', handleUserInteraction)
document.addEventListener('keydown', handleUserInteraction)
// WebSocket
initWebSocket()
//
@ -380,6 +426,10 @@ onMounted(() => {
})
onUnmounted(() => {
//
document.removeEventListener('click', handleUserInteraction)
document.removeEventListener('keydown', handleUserInteraction)
// WebSocket
if (ws) {
ws.close()
@ -402,7 +452,7 @@ onUnmounted(() => {
<template>
<div class="video-monitor">
<div class="monitor-header">
<div class="title">{{ drone?.status === 'online' ? '无人机实时监控' : 'pH值变化趋势' }}</div>
<div class="title">{{ drone?.status === 'online' ? '无人机实时监控' : '历史数据监测' }}</div>
<div class="status" v-if="drone">
<el-tag :type="drone?.status === 'online' ? 'success' : 'danger'" size="small">
{{ drone?.status === 'online' ? '在线' : '离线' }}
@ -411,42 +461,43 @@ onUnmounted(() => {
</div>
<div class="content-container">
<!-- 在线显示视频 -->
<div v-if="drone?.status === 'online'" class="video-wrapper">
<video
ref="videoRef"
class="video-player"
muted
></video>
<!-- 修改 loading 遮罩的显示条件 -->
<div v-if="videoLoading && !isPlaying && drone?.status === 'online'" class="video-loading">
<el-icon class="loading-icon"><Loading /></el-icon>
<span class="loading-text">视频加载中...</span>
</div>
<div class="video-info">
<div class="info-row">
<span class="device-name">{{ drone.name }}</span>
<div class="device-metrics">
<el-tooltip content="电池电量" placement="top">
<div class="metric">
<el-icon><Monitor /></el-icon>
<!-- {{ drone.battery }}% -->
65%
</div>
</el-tooltip>
<el-tooltip content="信号强度" placement="top">
<div class="metric" :class="{ 'poor-signal': isBuffering }">
<el-icon><Connection /></el-icon>
{{ isBuffering ? '-110dBm' : '-88dBm' }}
</div>
</el-tooltip>
<template v-if="drone?.status === 'online'">
<!-- 在线显示视频 -->
<div class="video-wrapper">
<video
ref="videoRef"
class="video-player"
muted
></video>
<div v-if="videoLoading && !isPlaying" class="video-loading">
<el-icon class="loading-icon"><Loading /></el-icon>
<span class="loading-text">视频加载中...</span>
</div>
<div class="video-info">
<div class="info-row">
<span class="device-name">{{ drone.name }}</span>
<div class="device-metrics">
<el-tooltip content="电池电量" placement="top">
<div class="metric">
<el-icon><Monitor /></el-icon>
{{ drone.battery }}%
</div>
</el-tooltip>
<el-tooltip content="信号强度" placement="top">
<div class="metric" :class="{ 'poor-signal': isBuffering }">
<el-icon><Connection /></el-icon>
{{ isBuffering ? '-110dBm' : '-88dBm' }}
</div>
</el-tooltip>
</div>
</div>
</div>
</div>
</div>
<!-- 离线显示图表 -->
<div v-else class="chart-wrapper" ref="chartRef"></div>
</template>
<template v-else>
<!-- 离线显示图表 -->
<div ref="chartRef" class="chart-wrapper"></div>
</template>
</div>
</div>
</template>
@ -569,8 +620,11 @@ onUnmounted(() => {
.chart-wrapper {
width: 100%;
height: 100%;
background: rgba(0, 24, 65, 0.3);
border-radius: 8px;
padding: 20px;
box-sizing: border-box;
border: 1px solid rgba(63, 167, 221, 0.2);
}
}
}

View File

@ -8,11 +8,11 @@ import { Plus } from '@element-plus/icons-vue';
const tableData = ref([
{
id: 1,
title: "2024年第一季度水质监测分析报告",
title: "2025年第一季度水质监测分析报告",
type: "environment",
timeRange: {
start: "2024-01-01",
end: "2024-03-31",
start: "2025-01-01",
end: "2025-02-28",
},
dataSource: [
{
@ -21,7 +21,7 @@ const tableData = ref([
},
],
analysis: {
summary: "第一季度水质总体保持稳定,但3月份出现轻微波动",
summary: "第一季度水质总体保持稳定,但2月份出现轻微波动",
trends: [
{
indicator: "pH值",
@ -37,7 +37,7 @@ const tableData = ref([
abnormal: [
{
type: "溶解氧",
description: "3月底溶解氧水平略低于标准值",
description: "2月底溶解氧水平略低于标准值",
level: "轻微",
},
],