BodyBalanceEvaluation/vue_websocket_example.vue
2025-07-29 18:28:40 +08:00

421 lines
8.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="websocket-example">
<div class="header">
<h2>Vue WebSocket连接示例</h2>
<div class="status" :class="connectionStatus">
{{ statusText }}
</div>
</div>
<div class="controls">
<button
@click="connectWebSocket"
:disabled="isConnected"
class="btn btn-primary"
>
连接WebSocket
</button>
<button
@click="disconnectWebSocket"
:disabled="!isConnected"
class="btn btn-secondary"
>
断开连接
</button>
<button
@click="startRtsp"
:disabled="!isConnected || isRtspRunning"
class="btn btn-success"
>
启动RTSP
</button>
<button
@click="stopRtsp"
:disabled="!isConnected || !isRtspRunning"
class="btn btn-danger"
>
停止RTSP
</button>
</div>
<div class="video-container">
<h3>RTSP视频流</h3>
<div v-if="rtspImageSrc" class="video-wrapper">
<img :src="rtspImageSrc" alt="RTSP视频流" class="rtsp-image" />
<div class="frame-info">已接收帧数: {{ frameCount }}</div>
</div>
<div v-else class="no-video">
暂无视频流
</div>
</div>
<div class="log-container">
<h3>连接日志</h3>
<div class="log-content" ref="logContainer">
<div v-for="(log, index) in logs" :key="index" class="log-item">
<span class="log-time">[{{ log.time }}]</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { io } from 'socket.io-client'
// 响应式数据
const socket = ref(null)
const isConnected = ref(false)
const isRtspRunning = ref(false)
const rtspImageSrc = ref('')
const frameCount = ref(0)
const logs = ref([])
const logContainer = ref(null)
// 后端服务器配置
const BACKEND_URL = 'http://localhost:5000' // 根据实际情况修改
// 如果是远程服务器,使用: 'http://192.168.1.173:5000'
// 计算属性
const connectionStatus = computed(() => {
if (isConnected.value) return 'connected'
return 'disconnected'
})
const statusText = computed(() => {
if (isConnected.value) {
return isRtspRunning.value ? '已连接 - RTSP运行中' : '已连接'
}
return '未连接'
})
// 日志记录函数
const addLog = (message) => {
const now = new Date()
const time = now.toLocaleTimeString()
logs.value.push({ time, message })
// 限制日志数量
if (logs.value.length > 100) {
logs.value.shift()
}
// 自动滚动到底部
nextTick(() => {
if (logContainer.value) {
logContainer.value.scrollTop = logContainer.value.scrollHeight
}
})
}
// WebSocket连接函数
const connectWebSocket = () => {
try {
addLog(`正在连接到 ${BACKEND_URL}`)
// 创建Socket.IO连接
socket.value = io(BACKEND_URL, {
transports: ['websocket', 'polling'],
timeout: 10000,
forceNew: true
})
// 连接成功事件
socket.value.on('connect', () => {
isConnected.value = true
addLog(`WebSocket连接成功Socket ID: ${socket.value.id}`)
})
// 连接失败事件
socket.value.on('connect_error', (error) => {
isConnected.value = false
addLog(`连接失败: ${error.message}`)
})
// 断开连接事件
socket.value.on('disconnect', (reason) => {
isConnected.value = false
isRtspRunning.value = false
rtspImageSrc.value = ''
addLog(`连接断开: ${reason}`)
})
// 监听RTSP状态事件
socket.value.on('rtsp_status', (data) => {
addLog(`RTSP状态: ${JSON.stringify(data)}`)
if (data.status === 'started') {
isRtspRunning.value = true
frameCount.value = 0
} else if (data.status === 'stopped') {
isRtspRunning.value = false
rtspImageSrc.value = ''
} else if (data.status === 'already_running') {
addLog('RTSP已在运行中')
}
})
// 监听RTSP帧数据
socket.value.on('rtsp_frame', (data) => {
if (data.image) {
frameCount.value++
rtspImageSrc.value = 'data:image/jpeg;base64,' + data.image
// 每30帧记录一次
if (frameCount.value % 30 === 0) {
addLog(`已接收 ${frameCount.value}`)
}
}
})
} catch (error) {
addLog(`连接异常: ${error.message}`)
}
}
// 断开WebSocket连接
const disconnectWebSocket = () => {
if (socket.value) {
socket.value.disconnect()
socket.value = null
isConnected.value = false
isRtspRunning.value = false
rtspImageSrc.value = ''
addLog('主动断开连接')
}
}
// 启动RTSP
const startRtsp = () => {
if (socket.value && isConnected.value) {
addLog('发送start_rtsp事件')
socket.value.emit('start_rtsp')
} else {
addLog('WebSocket未连接无法启动RTSP')
}
}
// 停止RTSP
const stopRtsp = () => {
if (socket.value && isConnected.value) {
addLog('发送stop_rtsp事件')
socket.value.emit('stop_rtsp')
} else {
addLog('WebSocket未连接无法停止RTSP')
}
}
// 生命周期钩子
onMounted(() => {
addLog('组件已挂载可以开始连接WebSocket')
addLog(`后端地址: ${BACKEND_URL}`)
addLog('请确保后端服务已启动')
})
onUnmounted(() => {
// 组件卸载时清理连接
if (socket.value) {
socket.value.disconnect()
}
})
</script>
<style scoped>
.websocket-example {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #e9ecef;
}
.header h2 {
margin: 0;
color: #333;
}
.status {
padding: 8px 16px;
border-radius: 20px;
font-weight: bold;
font-size: 14px;
}
.status.connected {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.disconnected {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover:not(:disabled) {
background-color: #545b62;
}
.btn-success {
background-color: #28a745;
color: white;
}
.btn-success:hover:not(:disabled) {
background-color: #1e7e34;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover:not(:disabled) {
background-color: #c82333;
}
.video-container {
margin-bottom: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
}
.video-container h3 {
margin-top: 0;
margin-bottom: 15px;
color: #333;
}
.video-wrapper {
text-align: center;
position: relative;
}
.rtsp-image {
max-width: 100%;
height: auto;
border: 2px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.frame-info {
margin-top: 10px;
font-size: 14px;
color: #666;
font-weight: 500;
}
.no-video {
text-align: center;
padding: 40px;
color: #6c757d;
font-style: italic;
background-color: #e9ecef;
border-radius: 8px;
}
.log-container {
margin-top: 20px;
}
.log-container h3 {
margin-bottom: 10px;
color: #333;
}
.log-content {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
height: 200px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
}
.log-item {
margin-bottom: 5px;
word-wrap: break-word;
}
.log-time {
color: #6c757d;
font-weight: bold;
}
.log-message {
margin-left: 8px;
color: #333;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.controls {
flex-direction: column;
}
.btn {
width: 100%;
}
}
</style>