基于 Vue3 + WebSocket 实现的平板控制端与大屏展示端联动方案
包含断线重连逻辑,采用「控制端 - 服务器 - 展示端」架构,通过 Vue 的响应式特性管理连接状态和指令交互。
技术栈
- 服务器:Node.js +
ws库(WebSocket 服务) - 控制端 / 展示端:Vue3(Composition API) + 原生 WebSocket API
- 构建工具:Vite(快速开发体验)
实现步骤
1. 服务器端(转发与连接管理)
与前方案核心逻辑一致,负责维护连接、转发指令和心跳检测(复用 server.js):
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });const clients = { controller: null, display: null }; // 存储两端连接
const HEARTBEAT_INTERVAL = 30000; // 30秒心跳检测wss.on('connection', (ws) => {let clientType = null;ws.on('message', (data) => {const msg = JSON.parse(data.toString());if (msg.type === 'register') {clientType = msg.role;clients[clientType] = ws;ws.send(JSON.stringify({ type: 'connected', msg: `已作为${clientType}连接` }));startHeartbeat(ws);} else if (clientType === 'controller' && msg.type === 'control') {// 转发控制指令到展示端if (clients.display?.readyState === WebSocket.OPEN) {clients.display.send(JSON.stringify({ type: 'control', data: msg.data }));} else {ws.send(JSON.stringify({ type: 'error', msg: '展示端未连接' }));}}});ws.on('close', () => {if (clientType) clients[clientType] = null;});function startHeartbeat(ws) {const timer = setInterval(() => {if (ws.readyState === WebSocket.OPEN) {ws.send(JSON.stringify({ type: 'heartbeat' }));} else {clearInterval(timer);}}, HEARTBEAT_INTERVAL);}
});console.log('WebSocket服务器运行于 ws://localhost:8080');2. 控制端(平板 Vue 应用)
使用 Vue3 的 ref 和 onMounted 管理连接状态,通过按钮发送控制指令,包含断线自动重连逻辑。
(1)创建控制端项目
npm create vite@latest controller -- --template vue
cd controller
npm install(2)核心组件 src/App.vue
<template><div class="controller"><h1>平板控制端</h1><p class="status" :class="{ connected: isConnected }">状态:{{ statusText }}</p><div class="controls"><button @click="sendControl('showText', 'Hello 大屏!')">显示文本</button><button @click="sendControl('changeColor', randomColor)">切换背景色</button><button @click="sendControl('clear', null)">清空内容</button></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted } from 'vue';// 状态管理
const ws = ref(null);
const isConnected = ref(false);
const statusText = ref('未连接');
const SERVER_URL = 'ws://localhost:8080';
const RECONNECT_DELAY = 3000; // 重连间隔3秒
let reconnectTimer = null;// 生成随机颜色
const randomColor = () => {return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
};// 初始化WebSocket连接
const initWebSocket = () => {// 关闭现有连接if (ws.value) {ws.value.close();}statusText.value = '连接中...';ws.value = new WebSocket(SERVER_URL);// 连接成功ws.value.onopen = () => {isConnected.value = true;statusText.value = '已连接';// 注册为控制端ws.value.send(JSON.stringify({ type: 'register', role: 'controller' }));// 清除重连定时器if (reconnectTimer) clearTimeout(reconnectTimer);};// 接收服务器消息ws.value.onmessage = (event) => {const msg = JSON.parse(event.data);if (msg.type === 'error') {statusText.value = `错误:${msg.msg}`;}};// 连接关闭(触发重连)ws.value.onclose = () => {isConnected.value = false;statusText.value = '已断开,重连中...';// 延迟重连,避免频繁尝试reconnectTimer = setTimeout(initWebSocket, RECONNECT_DELAY);};// 连接错误ws.value.onerror = (err) => {console.error('WebSocket错误:', err);statusText.value = '连接错误';};
};// 发送控制指令
const sendControl = (action, data) => {if (ws.value?.readyState === WebSocket.OPEN) {ws.value.send(JSON.stringify({type: 'control',data: { action, payload: data }}));} else {statusText.value = '未连接,无法发送指令';}
};// 组件挂载时初始化连接
onMounted(initWebSocket);// 组件卸载时清理
onUnmounted(() => {if (ws.value) ws.value.close();if (reconnectTimer) clearTimeout(reconnectTimer);
});
</script><style scoped>
.controller {padding: 20px;text-align: center;
}
.status {font-size: 18px;margin: 20px 0;color: #ff4444;
}
.status.connected {color: #00C851;
}
.controls {display: flex;flex-direction: column;gap: 15px;margin-top: 30px;
}
button {padding: 15px 20px;font-size: 16px;cursor: pointer;background: #2196F3;color: white;border: none;border-radius: 8px;
}
button:hover {background: #0b7dda;
}
</style>3. 展示端(大屏 Vue 应用)
接收控制端指令并执行对应操作(更新文本、切换样式等),同样包含断线重连逻辑。
(1)创建展示端项目
npm create vite@latest display -- --template vue
cd display
npm install(2)核心组件 src/App.vue
<template><div class="display"><h1>大屏展示端</h1><p class="status" :class="{ connected: isConnected }">状态:{{ statusText }}</p><div class="display-area" :style="{ backgroundColor: bgColor }">{{ displayText }}</div></div>
</template><script setup>
import { ref, onMounted, onUnmounted } from 'vue';// 状态管理
const ws = ref(null);
const isConnected = ref(false);
const statusText = ref('未连接');
const displayText = ref('等待控制指令...');
const bgColor = ref('');
const SERVER_URL = 'ws://localhost:8080';
const RECONNECT_DELAY = 3000;
let reconnectTimer = null;// 初始化WebSocket连接
const initWebSocket = () => {if (ws.value) ws.value.close();statusText.value = '连接中...';ws.value = new WebSocket(SERVER_URL);ws.value.onopen = () => {isConnected.value = true;statusText.value = '已连接';ws.value.send(JSON.stringify({ type: 'register', role: 'display' }));if (reconnectTimer) clearTimeout(reconnectTimer);};// 接收控制指令并处理ws.value.onmessage = (event) => {const msg = JSON.parse(event.data);if (msg.type === 'control') {handleControl(msg.data);}};ws.value.onclose = () => {isConnected.value = false;statusText.value = '已断开,重连中...';reconnectTimer = setTimeout(initWebSocket, RECONNECT_DELAY);};ws.value.onerror = (err) => {console.error('WebSocket错误:', err);statusText.value = '连接错误';};
};// 处理控制指令
const handleControl = (data) => {switch (data.action) {case 'showText':displayText.value = data.payload;break;case 'changeColor':bgColor.value = data.payload;break;case 'clear':displayText.value = '已清空';bgColor.value = '';break;}
};onMounted(initWebSocket);
onUnmounted(() => {if (ws.value) ws.value.close();if (reconnectTimer) clearTimeout(reconnectTimer);
});
</script><style scoped>
.display {padding: 30px;text-align: center;
}
.status {font-size: 20px;margin: 20px 0;color: #ff4444;
}
.status.connected {color: #00C851;
}
.display-area {margin: 50px auto;padding: 60px 30px;width: 80%;min-height: 300px;border: 5px solid #333;border-radius: 10px;font-size: 36px;transition: all 0.3s ease;
}
</style>核心特性说明
1. 响应式状态管理
- 控制端 / 展示端均通过 Vue 的
ref管理连接状态(isConnected)、状态文本(statusText)等,界面实时响应状态变化。 - 展示端的
displayText和bgColor与 DOM 绑定,接收指令后自动更新视图,体现 Vue 数据驱动的优势。
2. 断线重连机制
- 自动重连:两端在
onclose事件中触发重连逻辑,通过setTimeout实现 3 秒间隔重试,直到连接成功。 - 连接清理:组件卸载时(
onUnmounted)关闭 WebSocket 连接并清除重连定时器,避免内存泄漏。 - 状态可视化:通过颜色区分连接状态(绿色 = 已连接,红色 = 未连接 / 错误),用户可直观感知。
3. 指令交互流程
- 控制端点击按钮 → 调用
sendControl发送指令(包含action和payload)。 - 服务器接收指令 → 转发给展示端。
- 展示端
onmessage接收指令 → 调用handleControl执行对应操作(更新文本 / 颜色)。
运行方式
- 启动 WebSocket 服务器:
node server.js 分别启动控制端和展示端:
# 控制端 cd controller npm run dev # 访问 http://localhost:5173(平板或浏览器模拟)# 展示端 cd display npm run dev # 访问 http://localhost:5174(大屏或浏览器模拟)- 操作控制端按钮,观察大屏是否同步响应;断开服务器再重启,测试重连功能。
扩展性优化
- 多设备支持:修改服务器
clients为数组,存储多个控制端 / 展示端,通过设备 ID 区分指令目标。 - 指令加密:对传输的指令进行简单加密(如 Base64),防止恶意指令干扰。
- 心跳响应:展示端 / 控制端收到心跳包后回复
heartbeat_ack,增强连接稳定性检测。
此方案充分利用 Vue3 的响应式和组件化特性,结合 WebSocket 实现了低延迟的跨设备联动,适合会议、展览等场景的实时控制需求。
