uni-app实战:WebSocket实时投票系统搭建与状态同步

2026-05-12 0 207

在移动互联网应用中,实时互动功能已成为刚需。本文将以“实时投票”为案例,演示如何在uni-app中集成WebSocket,实现跨端(iOS/Android/小程序/Web)的投票数据实时同步。全文从连接建立、消息协议、心跳机制到UI更新,提供可直接运行的代码片段与设计思路。

一、为什么选择uni-app + WebSocket

uni-app基于Vue.js,一套代码发布多端,降低开发成本。WebSocket实现全双工通信,相比轮询更省流量、延迟更低。两者结合特别适合直播投票、在线问卷、实时竞价等场景。本案例采用原生WebSocket API(uni-app已封装),无需额外依赖。

二、项目结构与准备工作

新建uni-app项目(推荐使用HBuilder X),在pages目录下创建vote文件夹,包含vote.vue页面。同时准备一个简单的Node.js WebSocket服务端(用于演示,生产环境可替换为云函数或Java/Go服务)。

    // 服务端示例 (Node.js + ws)
    const WebSocket = require('ws');
    const wss = new WebSocket.Server({ port: 3001 });
    let votes = { option1: 0, option2: 0, option3: 0 };
    
    wss.on('connection', (ws) => {
        // 发送当前票数
        ws.send(JSON.stringify({ type: 'init', data: votes }));
        
        ws.on('message', (message) => {
            const { type, option } = JSON.parse(message);
            if (type === 'vote' && votes[option] !== undefined) {
                votes[option]++;
                // 广播给所有客户端
                wss.clients.forEach(client => {
                    if (client.readyState === WebSocket.OPEN) {
                        client.send(JSON.stringify({ type: 'update', data: votes }));
                    }
                });
            }
        });
    });
    

服务端维护一个votes对象,收到投票消息后更新并广播。客户端连接后立即获取初始数据。

三、uni-app页面核心实现

在vote.vue中,我们需要处理:WebSocket连接、心跳保活、数据渲染、投票交互。以下为完整代码(已去除冗余样式,仅保留逻辑与结构)。

    <template>
        <view class="vote-container">
            <view class="vote-title">实时投票:你最喜欢哪个框架?</view>
            <view class="options">
                <view class="option-item" v-for="(item, index) in options" :key="index" @click="handleVote(item.key)">
                    <text>{{ item.label }}</text>
                    <text class="count">{{ item.count }} 票</text>
                    <view class="progress-bar">
                        <view class="progress-fill" :style="'width:' + (item.percent || 0) + '%'"></view>
                    </view>
                </view>
            </view>
            <view class="status">{{ connectionStatus }}</view>
        </view>
    </template>

    <script>
    export default {
        data() {
            return {
                options: [
                    { key: 'option1', label: 'Vue', count: 0, percent: 0 },
                    { key: 'option2', label: 'React', count: 0, percent: 0 },
                    { key: 'option3', label: 'Angular', count: 0, percent: 0 }
                ],
                socketTask: null,
                heartbeatTimer: null,
                connectionStatus: '正在连接...'
            }
        },
        mounted() {
            this.connectWebSocket();
        },
        beforeDestroy() {
            this.closeSocket();
        },
        methods: {
            connectWebSocket() {
                // uni-app 的 WebSocket 连接
                this.socketTask = uni.connectSocket({
                    url: 'ws://localhost:3001', // 替换为实际服务器地址
                    success: () => {
                        console.log('连接发起成功');
                    },
                    fail: (err) => {
                        console.error('连接失败', err);
                        this.connectionStatus = '连接失败,请检查网络';
                    }
                });

                // 监听打开
                this.socketTask.onOpen(() => {
                    console.log('WebSocket 已打开');
                    this.connectionStatus = '已连接';
                    this.startHeartbeat();
                });

                // 监听消息
                this.socketTask.onMessage((res) => {
                    const msg = JSON.parse(res.data);
                    if (msg.type === 'init' || msg.type === 'update') {
                        this.updateVotes(msg.data);
                    } else if (msg.type === 'pong') {
                        // 心跳回应,不做额外处理
                    }
                });

                // 监听错误
                this.socketTask.onError((err) => {
                    console.error('WebSocket 错误', err);
                    this.connectionStatus = '连接异常';
                });

                // 监听关闭
                this.socketTask.onClose(() => {
                    console.log('WebSocket 关闭');
                    this.connectionStatus = '已断开';
                    this.stopHeartbeat();
                    // 可在此处添加重连逻辑
                    setTimeout(() => {
                        this.connectWebSocket();
                    }, 3000);
                });
            },
            // 更新票数并计算百分比
            updateVotes(data) {
                const total = data.option1 + data.option2 + data.option3;
                this.options.forEach(opt => {
                    opt.count = data[opt.key] || 0;
                    opt.percent = total > 0 ? ((opt.count / total) * 100).toFixed(1) : 0;
                });
            },
            // 发起投票
            handleVote(key) {
                if (this.socketTask && this.connectionStatus === '已连接') {
                    this.socketTask.send({
                        data: JSON.stringify({ type: 'vote', option: key }),
                        success() {
                            console.log('投票消息发送成功');
                        }
                    });
                } else {
                    uni.showToast({ title: '连接未就绪', icon: 'none' });
                }
            },
            // 心跳机制:每15秒发送ping
            startHeartbeat() {
                this.heartbeatTimer = setInterval(() => {
                    if (this.socketTask) {
                        this.socketTask.send({
                            data: JSON.stringify({ type: 'ping' }),
                            fail() {
                                console.error('心跳发送失败');
                            }
                        });
                    }
                }, 15000);
            },
            stopHeartbeat() {
                if (this.heartbeatTimer) {
                    clearInterval(this.heartbeatTimer);
                    this.heartbeatTimer = null;
                }
            },
            closeSocket() {
                this.stopHeartbeat();
                if (this.socketTask) {
                    this.socketTask.close();
                    this.socketTask = null;
                }
            }
        }
    }
    </script>
    

上述代码实现了完整的WebSocket生命周期管理。注意:uni-app的WebSocket API与浏览器略有不同,需使用uni.connectSocket,并通过onMessage、onOpen等回调处理。

四、核心机制解析

1. 连接与重连

在mounted中建立连接,并在onClose中设置3秒后自动重连,保证网络波动后恢复。生产环境建议使用指数退避策略。

2. 心跳维持

每隔15秒发送{type:’ping’},服务端需回应{type:’pong’}。如果服务端没有回应,客户端可以在连续几次未收到pong后主动断开并重连。本案例简化了心跳响应,实际开发应增加超时检测。

3. 数据同步与广播

服务端收到投票后更新votes并广播update消息。客户端收到后直接替换本地数据,并重新计算百分比。这种设计保证所有客户端看到一致的结果。

4. 进度条动态展示

每个选项的.percent绑定内联宽度,实时反映投票比例。由于不使用style标签,我们直接在元素上使用:style绑定,这也是uni-app支持的动态样式写法。

五、多端适配与注意事项

uni-app的WebSocket在微信小程序中需要设置合法域名(在微信公众平台配置socket合法域名)。在App端,Android和iOS均支持原生WebSocket,无需额外配置。H5端则使用浏览器原生WebSocket。本案例代码在H5和小程序端测试通过。

另外,当应用进入后台时(尤其是移动端),WebSocket可能被系统挂起。建议在onshow/onhide中重新连接或暂停心跳,避免无效消耗。

六、完整服务端示例(补充)

为了让读者快速运行,这里给出更健壮的服务端代码(包含心跳响应)。

    const WebSocket = require('ws');
    const wss = new WebSocket.Server({ port: 3001 });
    let votes = { option1: 0, option2: 0, option3: 0 };

    wss.on('connection', (ws) => {
        ws.isAlive = true;
        ws.send(JSON.stringify({ type: 'init', data: votes }));

        ws.on('pong', () => {
            ws.isAlive = true;
        });

        ws.on('message', (message) => {
            const msg = JSON.parse(message);
            if (msg.type === 'ping') {
                ws.send(JSON.stringify({ type: 'pong' }));
            } else if (msg.type === 'vote' && votes[msg.option] !== undefined) {
                votes[msg.option]++;
                wss.clients.forEach(client => {
                    if (client.readyState === WebSocket.OPEN) {
                        client.send(JSON.stringify({ type: 'update', data: votes }));
                    }
                });
            }
        });

        ws.on('close', () => {
            console.log('客户端断开');
        });
    });

    // 心跳检测:每30秒检查一次,关闭未pong的客户端
    const interval = setInterval(() => {
        wss.clients.forEach((ws) => {
            if (ws.isAlive === false) return ws.terminate();
            ws.isAlive = false;
            ws.ping();
        });
    }, 30000);

    wss.on('close', () => clearInterval(interval));
    

服务端通过ping/pong机制检测客户端存活,并自动清理死连接。

七、总结与扩展

本文通过一个实时投票案例,完整展示了uni-app中WebSocket的使用方法,包括连接管理、心跳、数据广播与动态UI更新。读者可以在此基础上扩展为在线问卷、弹幕、协同编辑等更复杂的实时应用。

关键要点回顾:

  • 使用uni.connectSocket建立连接,注意各端差异
  • 心跳机制是长连接稳定的基础
  • 数据同步采用全量替换,适合数据量小的场景;大数据量可考虑增量更新
  • uni-app的:style绑定可实现动态进度条,无需额外样式标签

希望本文能帮助你快速搭建属于自己的实时交互应用。如果你有任何问题或改进建议,欢迎在评论区讨论(本页面为演示,不设评论区)。


本文为原创技术教程,基于uni-app 3.x版本,WebSocket协议标准。服务端使用Node.js ws库,客户端兼容H5、小程序、App端。

uni-app实战:WebSocket实时投票系统搭建与状态同步
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 uniapp uni-app实战:WebSocket实时投票系统搭建与状态同步 https://www.taomawang.com/web/uniapp/1785.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务