MQTT状态管理Vuex全局状态管理

1.uniapp集成MQTT协议,参考我的文章:https://www.cnblogs.com/zhangyouwu/p/19439905

2.创建store/index.js

复制代码
// 页面路径:store/index.js
import { createStore } from 'vuex'
const store = createStore({
    state:{//存放状态
        "xcx_zt":0,//程序是否在线
        "m_zt":0,//设备是否在线
        'm_id':'',//设备ID
    },
    mutations: {
        chang_xcx_zt(state, n) {
            state.xcx_zt = n
        },
        chang_m_zt(state, n) {
            state.m_zt = n
        },
        chang_m_id(state, n) {
            state.m_id = n
        },
    }
})

export default store

3.在 main.js 中导入文件,主要这2句

复制代码
import store from './store'
复制代码
app.use(store)

4.修改mqtt.js

复制代码
import mqtt from 'mqtt/dist/mqtt.min'
import store from '@/store/index.js'
// MQTT 配置(根据实际服务端修改)
const MQTT_CONFIG = {
    // 不同端的连接协议:
    // H5: ws://xxx:8083/mqtt 或 wss://xxx:8084/mqtt
    // 微信小程序: wxs://xxx:8084/mqtt(需配置域名白名单)
    // App: tcp://xxx:1883(需配置网络权限)
    host: 'wxs://xxx:8084/mqtt', 
    // host: 'ws://xxx:8083/mqtt', 
    clientId: `wine-${Math.random().toString(16).substr(2, 8)}`, // 唯一客户端ID
    username: 'zyw', // 服务端认证用户名(无则留空)
    password: 'zyw123456', // 服务端认证密码(无则留空)
    keepalive: 60, // 心跳间隔(秒)
    reconnectPeriod: 5000, // 重连间隔(毫秒)
    clean: true // 清除会话(true:断开后不保留订阅)
}

// 全局 MQTT 客户端实例
let client = null
// 消息回调函数(供页面监听)
let messageCallback = null

/**
 * 连接 MQTT 服务器
 * @param {Function} callback 消息接收回调 (topic, message) => {}
 */
export function connectMQTT(callback) {
    // 保存消息回调
    messageCallback = callback

    // 已连接则直接返回
    if (client && client.connected){
        store.commit('chang_xcx_zt', 1)//在线
        return false;
    } 

    // 创建连接
    client = mqtt.connect(MQTT_CONFIG.host, {
        clientId: MQTT_CONFIG.clientId,
        username: MQTT_CONFIG.username,
        password: MQTT_CONFIG.password,
        keepalive: MQTT_CONFIG.keepalive,
        reconnectPeriod: MQTT_CONFIG.reconnectPeriod,
        clean: MQTT_CONFIG.clean
    })

    // 连接成功回调
    client.on('connect', () => {
        console.log('MQTT 连接成功:', MQTT_CONFIG.clientId);
        if(MQTT_CONFIG.clientId){
            store.commit('chang_xcx_zt', 1)//在线
            if (messageCallback) {
                var p_obj={};
                var p_data={};
                
                p_data.keyword="connect";
                p_data.clientId=MQTT_CONFIG.clientId;
                p_data.msg="MQTT 连接成功";
                
                p_obj.code=1;
                p_obj.data=p_data;
                messageCallback(p_obj) // 转字符串便于处理
            }
        }
    })

    // 接收消息回调
    client.on('message', (topic, message) => {
        console.log(`收到消息1:topic=${topic}, message=${message.toString()}`)
        if (messageCallback) {
            messageCallback(topic, message.toString()) // 转字符串便于处理
        }
    })

    // 连接错误回调
    client.on('error', (error) => {
        console.error('MQTT 连接错误:', error)
        store.commit('chang_xcx_zt', 0)//离线
        if (messageCallback) {
            var p_obj={};
            var p_data={};
            p_data.keyword="error";
            p_data.error=error;
            p_data.msg="MQTT 连接错误";
            
            p_obj.code=1;
            p_obj.data=p_data;
            messageCallback(p_obj) // 转字符串便于处理
        }
        
    })

    // 断开连接回调
    client.on('close', () => {
        console.log('MQTT 连接断开');
        store.commit('chang_xcx_zt', 0)//离线

        if (messageCallback) {
            var p_obj={};
            var p_data={};
            p_data.keyword="close";
            p_data.msg="MQTT 连接断开";
            
            p_obj.code=1;
            p_obj.data=p_data;
            messageCallback(p_obj) // 转字符串便于处理
        }
    })

    // 重连回调
    client.on('reconnect', () => {
        console.log('MQTT 正在重连...')
        store.commit('chang_xcx_zt', 0)//离线
        if (messageCallback) {
            var p_obj={};
            var p_data={};
            p_data.keyword="reconnect";
            p_data.msg="MQTT 正在重连";
            
            p_obj.code=1;
            p_obj.data=p_data;
            messageCallback(p_obj) // 转字符串便于处理
        }
    })
}
/**
 * 断开 MQTT 连接
 */
export function disconnectMQTT(callback) {
    if (client && client.connected) {
        // 保存消息回调
        messageCallback = callback
        client.end()
        client = null
        console.log('MQTT 主动断开连接')
        store.commit('chang_xcx_zt', 0)//离线
        if (messageCallback) {
            var p_obj={};
            var p_data={};
            p_data.keyword="disconnect";
            p_data.msg="MQTT 主动断开连接";
            
            p_obj.code=1;
            p_obj.data=p_data;
            messageCallback(p_obj) // 转字符串便于处理
        }
        
    }
}

/**
 * 订阅 MQTT 主题
 * @param {String|Array} topic 主题(单个字符串或数组)
 * @param {Number} qos 服务质量(0/1/2,默认0)
 */
export function subscribeMQTT(topic, qos = 0) {
  if (!client || !client.connected) {
    console.error('MQTT 未连接,无法订阅')
    return
  }
  client.subscribe(topic, { qos }, (error) => {
    if (error) {
      console.error(`订阅主题 ${topic} 失败:`, error)
    } else {
      console.log(`订阅主题 ${topic} 成功`)
    }
  })
}

/**
 * 发布 MQTT 消息
 * @param {String} topic 主题
 * @param {String|Object} message 消息内容(对象会转为JSON字符串)
 * @param {Number} qos 服务质量(0/1/2,默认0)
 */
export function publishMQTT(topic, message, qos = 0) {
  if (!client || !client.connected) {
    console.error('MQTT 未连接,无法发布消息')
    return
  }
  // 统一转为字符串
  const msg = typeof message === 'object' ? JSON.stringify(message) : message
  client.publish(topic, msg, { qos }, (error) => {
    if (error) {
      console.error(`发布消息到 ${topic} 失败:`, error)
    } else {
      console.log(`发布消息到 ${topic} 成功:`, msg)
    }
  })
}

/**
 * 取消订阅 MQTT 主题
 * @param {String|Array} topic 主题(单个字符串或数组)
 */
export function unsubscribeMQTT(topic) {
  if (!client || !client.connected) {
    console.error('MQTT 未连接,无法取消订阅')
    return
  }
  client.unsubscribe(topic, (error) => {
    if (error) {
      console.error(`取消订阅 ${topic} 失败:`, error)
    } else {
      console.log(`取消订阅 ${topic} 成功`)
    }
  })
}


mqtt.js

mqtt.js

5.创建在线组件/components/online/online.vue,其中2个getApp().add_log方法是上传连接日志到服务器,请自行删除

复制代码
<template>
    <view>
        <view class="topfixed">
            <view class="topfixed_content">
                <!-- <view :style="'height:'+statusBarHeight+'px;'"></view> -->
                <view class="topNavBox">
                    <view class="leftBtnBox">
                        <view class="gobackBox color-white font-28">
                            <view>设备:</view>
                            <block v-if="m_zt==1">
                                <view class="flex">
                                    <u-icon name="pause-circle-fill" color="#00AA59" size="18"></u-icon>
                                    <view class="ml6">在线</view>
                                </view>
                            </block>
                            
                            <block v-else>
                                <view class="flex">
                                    <u-icon name="minus-circle-fill" color="#ff0000" size="18"></u-icon>
                                    <view class="ml6">离线</view>
                                </view>
                            </block>
                        </view>
                        
                    </view>
                </view>
                
                <view class="topNavBox">
                    <view class="leftBtnBox">
                        <view class="gobackBox color-white font-28">
                            <view>程序:</view>
                            <block v-if="xcx_zt==1">
                                <view class="flex" @click="disconnect">
                                    <u-icon name="pause-circle-fill" color="#00AA59" size="18"></u-icon>
                                    <view class="ml6">在线</view>
                                </view>
                            </block>
                            
                            <block v-else>
                                <view class="flex" @click="connect">
                                    <u-icon name="minus-circle-fill" color="#ff0000" size="18"></u-icon>
                                    <view class="ml6">离线</view>
                                </view>
                            </block>
                        </view>
                        
                    </view>
                </view>
            </view>
        </view>
    </view>
</template>

<script>
    import { connectMQTT, subscribeMQTT, publishMQTT, disconnectMQTT } from '@/common/mqtt.js'
    export default {
        name:"online",
        props: {
        },
        
        data() {
            return {
                statusBarHeight: uni.getSystemInfoSync ().statusBarHeight,//状态栏高度
                receiveMessage: '' // 接收的消息
            }
        },
        computed: {
            //程序:0离线 1在线
            xcx_zt() {
                return this.$store.state.xcx_zt;
            },
            //设备:0离线 1在线
            m_zt() {
                return this.$store.state.m_zt;
            }
        },

        methods: {
            
        
            // 连接MQTT
            connect() {
                var _this=this;
                connectMQTT((res) => {
                    
                    //console.log(res);
                    if(res.code==1){
                        getApp().add_log(res.data).then((resolve, reject) => {//创建日志
                            //console.log(resolve);
                            if(resolve.code==1){
                            }
                        })
                    }
                    // var str=`主题:${topic},内容:${message}`;
                    // console.log(str)
                    // // 监听接收的消息
                    // _this.receiveMessage = `主题:${topic},内容:${message}`
                })
            },
            
            // 订阅主题(示例:/uni-app/test)
            subscribe() {
                subscribeMQTT('/uni-app/test')
            },
        
            // 发布消息(示例:向/uni-app/test发布消息)
            publish() {
                publishMQTT('/uni-app/test', {
                    content: 'Hello Uni-app MQTT',
                    time: new Date().toLocaleString()
                })
            },
        
            // 断开连接
            disconnect() {
                var _this=this;
                disconnectMQTT((res) => {
                    if(res.code==1){
                        getApp().add_log(res.data).then((resolve, reject) => {//创建日志
                            //console.log(resolve);
                            if(resolve.code==1){
                                this.receiveMessage = ''
                            }
                        })
                    }
                
                });                
            }
    
            
        }
    }
</script>

<style>
.topfixed{
        position: fixed;
        top: 0;
        left: 0;
        z-index: 20;
        /* // background-color: #fff; */
        width: 100%;
        padding: 0 20rpx;
        box-sizing: border-box;
    }
    .topfixed_content{
        width: 100%;
    }
    /* #ifdef H5 */
    .topfixed_content{
        max-width: 420px;
        margin: auto;
    }
    /* #endif */
    .topfixed .topNavBox{
        width:100%;
        height:40px;
        display: flex;
        padding: 4px 0;
        box-sizing: border-box;
    }
    .topfixed .topNavBox .leftBtnBox{
        flex: none;
        background-color: rgba(0,0,0,0.7);
        display: flex;
        height: 32px;
        border-radius: 100px;
    }
    .topfixed .topNavBox .leftBtnBox .gobackBox{
        padding: 0 25rpx;
        display: flex;
        align-items: center;
        position: relative;
    }
    .topfixed .topNavBox .leftBtnBox .gobackBox.goHomeBox::before{
        content: '';
        width: 1px;
        height: 20px;
        position: absolute;
        left: 0;
        top: 50%;
        margin-top: -10px;
        background-color: #fff;
    }
    
    
    
</style>

online.vue

6.创建测试文件/pages/index/index.vue,其中tabBar是我自定义的菜单组件,请自行删除

复制代码
<template>
    <view class="">
        <view>
            <online></online>
        </view>
        <map style="width: 100%; height: 300px;" :latitude="latitude" :longitude="longitude" :style="'height:'+windowHeight*2+'rpx;'">
            
        </map>
        
        <view>
            <tabBar :pageIndex="100" :textType="1" @toTab="toTab"></tabBar>
        </view>
    </view>
</template>

<script>
    import tabBar from '@/components/tabBar/tabBar.vue';
    import online from '@/components/online/online.vue';//是否在线
    export default {
        components: {
            tabBar,
            online,//是否在线
        },
        data() {
            return {
                windowHeight : uni.getSystemInfoSync().windowHeight,//屏幕高度
                title: 'Hello',
                latitude:'34.259428',
                longitude:'108.947040',
            }
        },
        onLoad() {
            // 隐藏默认菜单栏
            uni.hideTabBar({animation: false});
        },

        onShow() {
            // 隐藏默认菜单栏
            uni.hideTabBar({animation: false});
        },
        methods: {
            //菜单跳转
            toTab(item){
                //console.log(item);
                var id =item.id;//1购买记录 2扫码饮酒 3我的
                //扫码饮酒
                if(id==1){
                    uni.navigateTo({
                        url:'/pages_a/order/list',
                        // url:'/pages_a/pour/pouring'
                    })
                }else if(id==2){
                    uni.navigateTo({
                        url:'/pages/sao/index?m_id=1'
                        //url:'/pages/sao/index'
                    })
                    
                    // uni.scanCode({
                    //     scanType: ['qrCode'], //只支持二维码
                    //     onlyFromCamera: true,
                    //     success: function(res) {
                    //         console.log('条码类型:' + res.scanType);
                    //         console.log('条码内容:' + res.result);
                    //     }
                    // });
                }else if(id==3){
                    uni.switchTab({
                        url:'/pages/my/index'
                    })
                }
                
            }
        }
    }
</script>

<style>
    .content {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }

    .logo {
        height: 200rpx;
        width: 200rpx;
        margin-top: 200rpx;
        margin-left: auto;
        margin-right: auto;
        margin-bottom: 50rpx;
    }

    .text-area {
        display: flex;
        justify-content: center;
    }

    .title {
        font-size: 36rpx;
        color: #8f8f94;
    }
</style>

index.vue

结果