基于 Vue3 + WebSocket 实现的平板控制端与大屏展示端联动方案

包含断线重连逻辑,采用「控制端 - 服务器 - 展示端」架构,通过 Vue 的响应式特性管理连接状态和指令交互。

技术栈

  • 服务器:Node.js + ws 库(WebSocket 服务)
  • 控制端 / 展示端:Vue3(Composition API) + 原生 WebSocket API
  • 构建工具:Vite(快速开发体验)

实现步骤

1. 服务器端(转发与连接管理)

与前方案核心逻辑一致,负责维护连接、转发指令和心跳检测(复用 server.js):

javascript 复制代码
// 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 的 refonMounted 管理连接状态,通过按钮发送控制指令,包含断线自动重连逻辑。

(1)创建控制端项目
bash 复制代码
npm create vite@latest controller -- --template vue
cd controller
npm install
(2)核心组件 src/App.vue
javascript 复制代码
<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)创建展示端项目
bash 复制代码
npm create vite@latest display -- --template vue
cd display
npm install
(2)核心组件 src/App.vue
javascript 复制代码
<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)等,界面实时响应状态变化。
  • 展示端的 displayTextbgColor 与 DOM 绑定,接收指令后自动更新视图,体现 Vue 数据驱动的优势。
2. 断线重连机制
  • 自动重连 :两端在 onclose 事件中触发重连逻辑,通过 setTimeout 实现 3 秒间隔重试,直到连接成功。
  • 连接清理 :组件卸载时(onUnmounted)关闭 WebSocket 连接并清除重连定时器,避免内存泄漏。
  • 状态可视化:通过颜色区分连接状态(绿色 = 已连接,红色 = 未连接 / 错误),用户可直观感知。
3. 指令交互流程
  1. 控制端点击按钮 → 调用 sendControl 发送指令(包含 actionpayload)。
  2. 服务器接收指令 → 转发给展示端。
  3. 展示端 onmessage 接收指令 → 调用 handleControl 执行对应操作(更新文本 / 颜色)。

运行方式

  1. 启动 WebSocket 服务器:

    bash 复制代码
    node server.js
  2. 分别启动控制端和展示端:

    bash 复制代码
    # 控制端
    cd controller
    npm run dev
    # 访问 http://localhost:5173(平板或浏览器模拟)
    
    # 展示端
    cd display
    npm run dev
    # 访问 http://localhost:5174(大屏或浏览器模拟)
  3. 操作控制端按钮,观察大屏是否同步响应;断开服务器再重启,测试重连功能。

扩展性优化

  • 多设备支持 :修改服务器 clients 为数组,存储多个控制端 / 展示端,通过设备 ID 区分指令目标。
  • 指令加密:对传输的指令进行简单加密(如 Base64),防止恶意指令干扰。
  • 心跳响应 :展示端 / 控制端收到心跳包后回复 heartbeat_ack,增强连接稳定性检测。

此方案充分利用 Vue3 的响应式和组件化特性,结合 WebSocket 实现了低延迟的跨设备联动,适合会议、展览等场景的实时控制需求。

相关推荐
运维行者_4 小时前
DDI 与 OpManager 集成对企业 IT 架构的全维度优化
运维·网络·数据库·华为·架构·1024程序员节·snmp监控
元智启4 小时前
AI开发工具实战解析:如何实现企业数据处理流程自动化
1024程序员节
Tiandaren4 小时前
自用提示词01 || Prompt Engineering || 学习路线大纲 || 作用:通过启发式的问题来带动学习
人工智能·pytorch·深度学习·nlp·prompt·1024程序员节
遥远_4 小时前
电商履约大促峰值应对:核心业务数据预热方案详解
java·spring·1024程序员节·电商大促·数据预热
国科安芯5 小时前
AS32S601ZIT2抗辐照MCU在商业卫星飞轮系统中的可靠性分析
服务器·网络·人工智能·单片机·嵌入式硬件·fpga开发·1024程序员节
xiaopengbc5 小时前
新买的笔记本电脑为什么风扇声音一直很大?怎样解决?
电脑·1024程序员节
zyq99101_15 小时前
树与二叉树的奥秘全解析
c语言·数据结构·学习·1024程序员节
艾莉丝努力练剑5 小时前
【C++:继承】C++面向对象继承全面解析:派生类构造、多继承、菱形虚拟继承与设计模式实践
linux·开发语言·c++·人工智能·stl·1024程序员节
ezreal_pan5 小时前
架构权衡与实践:基于“约束大于规范”的缓存组件封装
redis·cache·1024程序员节