Compare commits

...

2 Commits

6 changed files with 512 additions and 147 deletions

View File

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

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue' import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' 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 { getDeviceList, addDevice, updateDevice, deleteDevice } from '@/api/device'
import { createFlvPlayer, destroyFlvPlayer } from '@/utils/videoPlayer' import { createFlvPlayer, destroyFlvPlayer } from '@/utils/videoPlayer'
@ -12,6 +12,8 @@ const mainVideoRef = ref(null)
const mainFlvPlayer = ref(null) const mainFlvPlayer = ref(null)
const videoRefs = ref([]) const videoRefs = ref([])
const flvPlayers = ref([]) const flvPlayers = ref([])
const playingVideos = ref(new Set()) //
let ws = null // WebSocket
// //
const pagination = ref({ const pagination = ref({
@ -65,19 +67,105 @@ const rules = {
// //
const currentCameraIndex = ref(0) 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) { if (mainFlvPlayer.value) {
destroyFlvPlayer(mainFlvPlayer.value) destroyFlvPlayer(mainFlvPlayer.value)
mainFlvPlayer.value = null
} }
flvPlayers.value.forEach(player => destroyFlvPlayer(player))
flvPlayers.value.forEach(player => {
if (player) {
destroyFlvPlayer(player)
}
})
flvPlayers.value = [] flvPlayers.value = []
//
playingVideos.value.clear()
// DOM
await nextTick()
// //
if (mainVideoRef.value && cameraList.value[currentCameraIndex.value]?.status === 'online') { if (mainVideoRef.value && cameraList.value[currentCameraIndex.value]?.status === 'online') {
const deviceCode = cameraList.value[currentCameraIndex.value].code const deviceCode = cameraList.value[currentCameraIndex.value].code
mainFlvPlayer.value = createFlvPlayer(mainVideoRef.value, deviceCode) 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,32 +174,56 @@ const initVideoPlayers = () => {
const deviceCode = cameraList.value[index].code const deviceCode = cameraList.value[index].code
const player = createFlvPlayer(videoRef, deviceCode) const player = createFlvPlayer(videoRef, deviceCode)
flvPlayers.value.push(player) 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, () => { const switchMainVideo = async (index) => {
nextTick(() => { if (currentCameraIndex.value === index) return
initVideoPlayers() currentCameraIndex.value = index
//
nextTick(async () => {
await initVideoPlayers()
}) })
}, { deep: true }) }
// //
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
const res = await getDeviceList({ const res = await getDeviceList({
page: pagination.page, page: pagination.value.page,
page_size: pagination.page_size, page_size: pagination.value.page_size,
device_type: '10000' // device_type: '10000' //
}) })
if (res.code === 200) { if (res.code === 200) {
cameraList.value = res.data.list.map(camera => ({ 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, id: camera.id,
name: camera.device_name, name: camera.device_name,
code: camera.device_code, code: camera.device_code,
status: camera.status.code === 1 ? 'online' : 'offline', status: status,
status_text: camera.status.text, status_text: camera.status.text,
model: camera.model || '', model: camera.model || '',
manufacturer: camera.manufacturer || '', manufacturer: camera.manufacturer || '',
@ -121,7 +233,18 @@ const getList = async () => {
maintenance_cycle: camera.maintenance_cycle, maintenance_cycle: camera.maintenance_cycle,
battery_level: camera.battery_level || 0, battery_level: camera.battery_level || 0,
signal_strength: camera.signal_strength || 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 pagination.value.total = res.data.total
} else { } else {
ElMessage.error(res.message || '获取摄像头列表失败') ElMessage.error(res.message || '获取摄像头列表失败')
@ -228,10 +351,18 @@ const handleSubmit = async () => {
} }
onMounted(() => { onMounted(() => {
// WebSocket
initWebSocket()
//
getList() getList()
}) })
onUnmounted(() => { onUnmounted(() => {
// WebSocket
if (ws) {
ws.close()
ws = null
}
// //
if (mainFlvPlayer.value) { if (mainFlvPlayer.value) {
destroyFlvPlayer(mainFlvPlayer.value) destroyFlvPlayer(mainFlvPlayer.value)
@ -254,9 +385,26 @@ onUnmounted(() => {
<el-icon class="offline-icon"><VideoCamera /></el-icon> <el-icon class="offline-icon"><VideoCamera /></el-icon>
<span>摄像头离线</span> <span>摄像头离线</span>
</div> </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"> <div class="video-info">
<h2>{{ cameraList[currentCameraIndex]?.name }}</h2> <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>
</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 { .video-info {
position: absolute; position: absolute;
left: 0; left: 0;
@ -421,10 +595,29 @@ onUnmounted(() => {
margin: 0 0 8px; margin: 0 0 8px;
} }
p { .camera-status {
font-size: 14px; display: flex;
margin: 0; gap: 24px;
opacity: 0.8;
.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> </style>

View File

@ -13,6 +13,7 @@ const mainFlvPlayer = ref(null)
const videoRefs = ref([]) const videoRefs = ref([])
const flvPlayers = ref([]) const flvPlayers = ref([])
const playingVideos = ref(new Set()) // const playingVideos = ref(new Set()) //
let ws = null // WebSocket
// playingVideos Set // playingVideos Set
const isVideoPlaying = (code) => { const isVideoPlaying = (code) => {
@ -21,6 +22,7 @@ const isVideoPlaying = (code) => {
// //
const initVideoPlayers = async () => { const initVideoPlayers = async () => {
// //
if (mainFlvPlayer.value) { if (mainFlvPlayer.value) {
destroyFlvPlayer(mainFlvPlayer.value) destroyFlvPlayer(mainFlvPlayer.value)
@ -43,30 +45,35 @@ const initVideoPlayers = async () => {
// //
if (mainVideoRef.value && droneList.value[currentDroneIndex.value]?.status === 'online') { if (mainVideoRef.value && droneList.value[currentDroneIndex.value]?.status === 'online') {
const deviceCode = droneList.value[currentDroneIndex.value].code const deviceCode = droneList.value[currentDroneIndex.value].code
console.log('初始化主视频播放器:', deviceCode)
mainFlvPlayer.value = createFlvPlayer(mainVideoRef.value, deviceCode) mainFlvPlayer.value = createFlvPlayer(mainVideoRef.value, deviceCode)
// //
if (mainVideoRef.value) {
mainVideoRef.value.onplaying = () => { mainVideoRef.value.onplaying = () => {
playingVideos.value.add(deviceCode) playingVideos.value.add(deviceCode)
} }
//
mainVideoRef.value.onpause = () => { mainVideoRef.value.onpause = () => {
playingVideos.value.delete(deviceCode) playingVideos.value.delete(deviceCode)
} }
// mainVideoRef.value.onerror = (e) => {
mainVideoRef.value.onerror = () => { console.error('主视频播放错误:', e)
playingVideos.value.delete(deviceCode) playingVideos.value.delete(deviceCode)
} }
} }
} else {
console.log('不满足初始化主视频播放器条件:', {
hasVideoRef: !!mainVideoRef.value,
droneStatus: droneList.value[currentDroneIndex.value]?.status
})
}
// //
videoRefs.value.forEach((videoRef, index) => { videoRefs.value.forEach((videoRef, index) => {
if (videoRef && droneList.value[index]?.status === 'online') { if (videoRef && droneList.value[index]?.status === 'online') {
const deviceCode = droneList.value[index].code const deviceCode = droneList.value[index].code
console.log('初始化列表视频播放器:', index, deviceCode)
const player = createFlvPlayer(videoRef, deviceCode) const player = createFlvPlayer(videoRef, deviceCode)
flvPlayers.value.push(player) flvPlayers.value.push(player)
@ -75,12 +82,10 @@ const initVideoPlayers = async () => {
playingVideos.value.add(deviceCode) playingVideos.value.add(deviceCode)
} }
//
videoRef.onpause = () => { videoRef.onpause = () => {
playingVideos.value.delete(deviceCode) playingVideos.value.delete(deviceCode)
} }
//
videoRef.onerror = () => { videoRef.onerror = () => {
playingVideos.value.delete(deviceCode) playingVideos.value.delete(deviceCode)
} }
@ -105,6 +110,57 @@ const pagination = ref({
// //
const currentDroneIndex = ref(0) 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 () => { const getList = async () => {
loading.value = true loading.value = true
@ -112,18 +168,37 @@ const getList = async () => {
const res = await getDeviceList({ const res = await getDeviceList({
page: pagination.value.page, page: pagination.value.page,
page_size: pagination.value.page_size, page_size: pagination.value.page_size,
device_type: '10001' // device_type: '10001'
}) })
if (res.code === 200) { if (res.code === 200) {
droneList.value = res.data.list.map(drone => ({ 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, id: drone.id,
name: drone.device_name, name: drone.device_name,
code: drone.device_code, code: drone.device_code,
status: drone.status.code === 1 ? 'online' : 'offline', status: status,
status_text: drone.status.text, status_text: drone.status.text,
battery_level: drone.battery_level || 0, battery_level: drone.battery_level || 0,
signal_strength: drone.signal_strength || 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 pagination.value.total = res.data.total
} else { } else {
ElMessage.error(res.message || '获取无人机列表失败') ElMessage.error(res.message || '获取无人机列表失败')
@ -138,9 +213,14 @@ const getList = async () => {
// //
const switchMainVideo = async (index) => { const switchMainVideo = async (index) => {
if (currentDroneIndex.value === index) return if (currentDroneIndex.value === index) return
currentDroneIndex.value = index currentDroneIndex.value = index
//
nextTick(async () => {
await initVideoPlayers() await initVideoPlayers()
})
} }
// //
@ -156,6 +236,10 @@ const handleCurrentChange = (val) => {
// //
onActivated(() => { onActivated(() => {
// WebSocket
if (!ws || ws.readyState !== WebSocket.OPEN) {
initWebSocket()
}
// //
if (droneList.value.length === 0) { if (droneList.value.length === 0) {
getList() getList()
@ -182,14 +266,21 @@ onDeactivated(() => {
playingVideos.value.clear() playingVideos.value.clear()
}) })
// onMounted // onMounted
onMounted(() => { onMounted(() => {
if (!droneList.value.length) { // WebSocket
initWebSocket()
//
getList() getList()
}
}) })
// onUnmounted
onUnmounted(() => { onUnmounted(() => {
// WebSocket
if (ws) {
ws.close()
ws = null
}
if (mainFlvPlayer.value) { if (mainFlvPlayer.value) {
destroyFlvPlayer(mainFlvPlayer.value) destroyFlvPlayer(mainFlvPlayer.value)
} }
@ -224,11 +315,15 @@ onUnmounted(() => {
<div class="drone-status"> <div class="drone-status">
<div class="status-item"> <div class="status-item">
<span class="label">电量:</span> <span class="label">电量:</span>
<span class="value">{{ droneList[currentDroneIndex]?.battery_level }}%</span> <span class="value">{{ droneList[currentDroneIndex]?.status === 'online' ? '65' : '0' }}%</span>
</div> </div>
<div class="status-item"> <div class="status-item">
<span class="label">信号:</span> <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> </div>
</div> </div>
@ -581,4 +676,15 @@ onUnmounted(() => {
background: transparent; 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> </style>

View File

@ -390,7 +390,7 @@ const initTrendChart = () => {
xAxis: { xAxis: {
type: 'category', type: 'category',
boundaryGap: false, 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: { axisLine: {
lineStyle: { lineStyle: {
color: '#DCDFE6' color: '#DCDFE6'

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue' import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { getDeviceList } from '@/api/device' import { getDeviceList } from '@/api/device'
import { createFlvPlayer, destroyFlvPlayer } from '@/utils/videoPlayer' import { createFlvPlayer, destroyFlvPlayer } from '@/utils/videoPlayer'
import * as echarts from 'echarts' 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_CONNECT = new Audio('/src/assets/harmonyos-sound/notification_accomplished_08.wav')
const SOUND_DISCONNECT = new Audio('/src/assets/harmonyos-sound/notification_wrong_04.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) => { const playSound = (status) => {
if (!hasUserInteracted) return
const sound = status === 'online' ? SOUND_CONNECT : SOUND_DISCONNECT const sound = status === 'online' ? SOUND_CONNECT : SOUND_DISCONNECT
sound.currentTime = 0 // sound.currentTime = 0 //
sound.play().catch(error => { sound.play().catch(error => {
@ -197,30 +210,22 @@ const initWebSocket = () => {
try { try {
const data = JSON.parse(event.data) const data = JSON.parse(event.data)
if (data.type === 'device_status') { if (data.type === 'device_status') {
// // drone.value
if (!drone.value || (drone.value && drone.value.code === data.device_code)) {
//
if (drone.value) { if (drone.value) {
const newStatus = data.status === 1 ? 'online' : 'offline' const newStatus = data.status === 1 ? 'online' : 'offline'
//
if (drone.value.status !== newStatus) { if (drone.value.status !== newStatus) {
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()
} }
} }
//
getDroneInfo()
} }
} }
} catch (error) { } catch (error) {
console.error('WebSocket消息处理错误:', error) console.error('WebSocket消息处理错误:', error)
} }
} }
ws.onerror = (error) => { ws.onerror = (error) => {
@ -228,7 +233,6 @@ const initWebSocket = () => {
} }
ws.onclose = () => { ws.onclose = () => {
console.log('WebSocket连接关闭')
// //
setTimeout(initWebSocket, 5000) setTimeout(initWebSocket, 5000)
} }
@ -245,27 +249,40 @@ const getDroneInfo = async () => {
}) })
if (res.code === 200) { if (res.code === 200) {
// 线 const targetDrone = res.data.list.find(item => {
const onlineDrone = res.data.list.find(item => item.status.code === 1) return item.device_code === 'BC13292E5A49914F4D62B9F356E39F56'
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
}
//
nextTick(() => {
initVideoPlayer()
}) })
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
}
} else { } else {
drone.value = null drone.value = null
// 线
getChartData() getChartData()
} }
} else { } else {
console.error('API响应错误:', res.message)
ElMessage.error(res.message || '获取无人机列表失败') ElMessage.error(res.message || '获取无人机列表失败')
getChartData() getChartData()
} }
@ -279,8 +296,7 @@ const getDroneInfo = async () => {
} }
// //
const initVideoPlayer = () => { const initVideoPlayer = async () => {
console.log('初始化视频播放器, 设备信息:', drone.value)
videoLoading.value = true // loading videoLoading.value = true // loading
isPlaying.value = false // isPlaying.value = false //
@ -292,12 +308,10 @@ const initVideoPlayer = () => {
// //
if (videoRef.value && drone.value?.status === 'online') { if (videoRef.value && drone.value?.status === 'online') {
console.log('创建FLV播放器, 设备编码:', drone.value.code)
flvPlayer.value = createFlvPlayer(videoRef.value, drone.value.code) flvPlayer.value = createFlvPlayer(videoRef.value, drone.value.code)
if (flvPlayer.value) { if (flvPlayer.value) {
flvPlayer.value.on('loading', () => { flvPlayer.value.on('loading', () => {
console.log('视频加载中...')
if (!isPlaying.value) { if (!isPlaying.value) {
videoLoading.value = true videoLoading.value = true
} }
@ -312,7 +326,7 @@ const initVideoPlayer = () => {
// //
videoRef.value.addEventListener('waiting', () => { videoRef.value.addEventListener('waiting', () => {
console.log('视频缓冲中') //
isBuffering.value = true isBuffering.value = true
// loading // loading
if (!isPlaying.value) { if (!isPlaying.value) {
@ -321,14 +335,14 @@ const initVideoPlayer = () => {
}) })
videoRef.value.addEventListener('playing', () => { videoRef.value.addEventListener('playing', () => {
console.log('视频开始播放') //
videoLoading.value = false videoLoading.value = false
isPlaying.value = true isPlaying.value = true
isBuffering.value = false isBuffering.value = false
}) })
videoRef.value.addEventListener('canplay', () => { videoRef.value.addEventListener('canplay', () => {
console.log('视频可以播放') //
isBuffering.value = false isBuffering.value = false
if (!isPlaying.value) { if (!isPlaying.value) {
videoLoading.value = false videoLoading.value = false
@ -361,14 +375,46 @@ const startRefreshTimer = () => {
}, 30000) // 30 }, 30000) // 30
} }
// //
const handleResize = () => { 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(() => { onMounted(() => {
//
document.addEventListener('click', handleUserInteraction)
document.addEventListener('keydown', handleUserInteraction)
// WebSocket // WebSocket
initWebSocket() initWebSocket()
// //
@ -380,6 +426,10 @@ onMounted(() => {
}) })
onUnmounted(() => { onUnmounted(() => {
//
document.removeEventListener('click', handleUserInteraction)
document.removeEventListener('keydown', handleUserInteraction)
// WebSocket // WebSocket
if (ws) { if (ws) {
ws.close() ws.close()
@ -402,7 +452,7 @@ onUnmounted(() => {
<template> <template>
<div class="video-monitor"> <div class="video-monitor">
<div class="monitor-header"> <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"> <div class="status" v-if="drone">
<el-tag :type="drone?.status === 'online' ? 'success' : 'danger'" size="small"> <el-tag :type="drone?.status === 'online' ? 'success' : 'danger'" size="small">
{{ drone?.status === 'online' ? '在线' : '离线' }} {{ drone?.status === 'online' ? '在线' : '离线' }}
@ -411,15 +461,15 @@ onUnmounted(() => {
</div> </div>
<div class="content-container"> <div class="content-container">
<template v-if="drone?.status === 'online'">
<!-- 在线显示视频 --> <!-- 在线显示视频 -->
<div v-if="drone?.status === 'online'" class="video-wrapper"> <div class="video-wrapper">
<video <video
ref="videoRef" ref="videoRef"
class="video-player" class="video-player"
muted muted
></video> ></video>
<!-- 修改 loading 遮罩的显示条件 --> <div v-if="videoLoading && !isPlaying" class="video-loading">
<div v-if="videoLoading && !isPlaying && drone?.status === 'online'" class="video-loading">
<el-icon class="loading-icon"><Loading /></el-icon> <el-icon class="loading-icon"><Loading /></el-icon>
<span class="loading-text">视频加载中...</span> <span class="loading-text">视频加载中...</span>
</div> </div>
@ -430,8 +480,7 @@ onUnmounted(() => {
<el-tooltip content="电池电量" placement="top"> <el-tooltip content="电池电量" placement="top">
<div class="metric"> <div class="metric">
<el-icon><Monitor /></el-icon> <el-icon><Monitor /></el-icon>
<!-- {{ drone.battery }}% --> {{ drone.battery }}%
65%
</div> </div>
</el-tooltip> </el-tooltip>
<el-tooltip content="信号强度" placement="top"> <el-tooltip content="信号强度" placement="top">
@ -444,9 +493,11 @@ onUnmounted(() => {
</div> </div>
</div> </div>
</div> </div>
</template>
<template v-else>
<!-- 离线显示图表 --> <!-- 离线显示图表 -->
<div v-else class="chart-wrapper" ref="chartRef"></div> <div ref="chartRef" class="chart-wrapper"></div>
</template>
</div> </div>
</div> </div>
</template> </template>
@ -569,8 +620,11 @@ onUnmounted(() => {
.chart-wrapper { .chart-wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgba(0, 24, 65, 0.3);
border-radius: 8px;
padding: 20px; padding: 20px;
box-sizing: border-box; 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([ const tableData = ref([
{ {
id: 1, id: 1,
title: "2024年第一季度水质监测分析报告", title: "2025年第一季度水质监测分析报告",
type: "environment", type: "environment",
timeRange: { timeRange: {
start: "2024-01-01", start: "2025-01-01",
end: "2024-03-31", end: "2025-02-28",
}, },
dataSource: [ dataSource: [
{ {
@ -21,7 +21,7 @@ const tableData = ref([
}, },
], ],
analysis: { analysis: {
summary: "第一季度水质总体保持稳定,但3月份出现轻微波动", summary: "第一季度水质总体保持稳定,但2月份出现轻微波动",
trends: [ trends: [
{ {
indicator: "pH值", indicator: "pH值",
@ -37,7 +37,7 @@ const tableData = ref([
abnormal: [ abnormal: [
{ {
type: "溶解氧", type: "溶解氧",
description: "3月底溶解氧水平略低于标准值", description: "2月底溶解氧水平略低于标准值",
level: "轻微", level: "轻微",
}, },
], ],