Bläddra i källkod

完善mqtt组件和增加使用实例

jiuling 8 månader sedan
förälder
incheckning
4b5fb43819
2 ändrade filer med 194 tillägg och 101 borttagningar
  1. 144 85
      src/components/Mqtt/mqttComp.vue
  2. 50 16
      src/views/map/maplist/index.vue

+ 144 - 85
src/components/Mqtt/mqttComp.vue

@@ -1,121 +1,180 @@
-<!--
- * @Description: mqtt连接和消息发送组件 页面
- * @Author: mhf
- * @Date: 2024-05-25 00:49:23
- * @params:
--->
+<!-- MqttComp.vue -->
 <template>
-  <div></div>
+  <div class="mqtt-comp" style="display: none"></div>
 </template>
 
 <script>
-import mqtt from "mqtt";
+import mqtt from "mqtt/dist/mqtt.min.js";
 
 export default {
-  name: "mqttComp",
+  name: "MqttComp",
   props: {
-    topic: String, // 订阅主题
     mqttUrl: {
       type: Object,
-      default: () => {
-        return {
-          head: "ws", // 必须是 ws 或 wss (mqtt:// 或 mqtts:// 必须让后端开放websocket服务)
-          host: "8.148.78.124", // ip地址
-          port: 8083, // 服务端口
-          tailPath: "mqtt", // 服务路径
-        };
-      },
-    }, // 服务地址
+      default: () => ({
+        head: "ws", // ws 或 wss
+        host: "8.148.78.124",
+        port: 8083,
+        path: "/mqtt",
+      }),
+    },
     mqttOpts: {
       type: Object,
-      default: () => {
-        return {
-          keepalive: 60,
-          clientId: "clientId-" + Math.random().toString(16).substr(2, 8),
-          username: "username",
-          password: "password",
-          connectTimeout: 10 * 1000,
-          path: "/mqtt"
-        };
-      },
-    }, // 连接配置
+      default: () => ({
+        keepalive: 60,
+        clientId: "clientId-" + Math.random().toString(16).substr(2, 8),
+        username: "",
+        password: "",
+        clean: true,
+        connectTimeout: 10 * 1000,
+        reconnectPeriod: 2000,
+      }),
+    },
+    topics: {
+      type: Array,
+      default: () => [], // 可一次性传入多个 topic
+    },
   },
   data() {
     return {
-      client: "",
-      clientCount: 0,
-      receivedMessage: null, // 用于存储接收到的消息
+      client: null,
+      isConnected: false,
+      subscribedTopics: [], // 内部管理的已订阅 topic
     };
   },
   watch: {
-    topic(newTopic) {
-      if (newTopic && this.client) {
-        this.client.unsubscribe(this.topic);
-        this.client.subscribe(newTopic);
-      }
+    topics: {
+      handler(newTopics) {
+        if (!this.client || !this.isConnected) return;
+
+        // 取消订阅已经移除的 topic
+        this.subscribedTopics.forEach((t) => {
+          if (!newTopics.includes(t)) {
+            this.unsubscribeTopic(t);
+          }
+        });
+
+        // 订阅新增 topic
+        newTopics.forEach((t) => {
+          if (!this.subscribedTopics.includes(t)) {
+            this.subscribeTopic(t);
+          }
+        });
+      },
+      deep: true,
+      immediate: true,
     },
   },
   methods: {
-    async initMqtt() {
-      let opts = JSON.parse(JSON.stringify(this.mqttOpts));
-      this.client = mqtt.connect(
-        `${this.mqttUrl.head}://${this.mqttUrl.host}:${this.mqttUrl.port}/${this.mqttUrl.tailPath}`,
-        opts
-      );
-      this.client.on("connect", this.handleConnect);
-      this.client.on("message", this.handleMessage);
-      this.client.on("reconnect", this.handleReconnect);
-      this.client.on("error", this.handleError);
-    },
+    // 初始化 MQTT 连接
+    initMqtt() {
+      const { head, host, port, path } = this.mqttUrl;
+      const connectUrl = `${head}://${host}:${port}${path || ""}`;
+      console.log("MQTT连接地址:", connectUrl);
 
-    handleConnect() {
-      console.log("mqtt_连接成功");
-      this.client.subscribe(this.topic);
-    },
+      this.client = mqtt.connect(connectUrl, this.mqttOpts);
+
+      this.client.on("connect", () => {
+        this.isConnected = true;
+        console.log("✅ MQTT 已连接");
+        this.$emit("mqtt-connected");
+
+        // 自动订阅传入的 topic
+        this.topics.forEach((topic) => this.subscribeTopic(topic));
+      });
+
+      this.client.on("message", (topic, message) => {
+        let msg;
+        try {
+          msg = JSON.parse(message.toString());
+        } catch {
+          msg = message.toString();
+        }
+        this.$emit("message-received", { topic, message: msg });
+      });
 
-    handleMessage(topic, message) {
-      this.receivedMessage = JSON.parse(message?.toString() || {});
-      this.$emit("message-received", this.receivedMessage);
+      this.client.on("reconnect", () => {
+        console.log("♻️ MQTT 正在重连...");
+        this.$emit("mqtt-reconnect");
+      });
+
+      this.client.on("error", (err) => {
+        console.error("❌ MQTT连接失败:", err);
+        this.$emit("mqtt-error", err);
+      });
+
+      this.client.on("close", () => {
+        this.isConnected = false;
+        console.log("🔌 MQTT连接已关闭");
+        this.$emit("mqtt-close");
+      });
     },
 
-    handleReconnect(error) {
-      console.log(`正在第${this.clientCount}重连`, error);
-      this.clientCount++;
-      if (this.clientCount >= 10) {
-        this.client.end();
+    // 订阅单个 topic
+    subscribeTopic(topic) {
+      if (!this.client || !this.isConnected) {
+        console.warn("MQTT 未连接,无法订阅:", topic);
+        return;
       }
+      if (!topic || this.subscribedTopics.includes(topic)) return;
+
+      this.client.subscribe(topic, (err) => {
+        if (!err) {
+          console.log("订阅成功:", topic);
+          this.subscribedTopics.push(topic);
+          this.$emit("topic-subscribed", topic);
+        }
+      });
     },
 
-    handleError(error) {
-      console.log("连接失败", error);
+    // 取消订阅单个 topic
+    unsubscribeTopic(topic) {
+      if (!this.client || !this.isConnected) return;
+      if (!topic || !this.subscribedTopics.includes(topic)) return;
+
+      this.client.unsubscribe(topic, (err) => {
+        if (!err) {
+          console.log("取消订阅:", topic);
+          this.subscribedTopics = this.subscribedTopics.filter((t) => t !== topic);
+          this.$emit("topic-unsubscribed", topic);
+        }
+      });
     },
 
-    /**
-     * MQTT实现发送消息
-     * @param: topic: 主题
-     * @param: message: 消息体
-     * @author: mhf
-     * @time: 2024-05-24 14:26:51
-     **/
-    mqttPublish(topic, message) {
-      this.client.publish(topic, JSON.stringify(message));
+    // 发布消息
+    publish(topic, message) {
+      if (!this.client || !this.isConnected) {
+        console.warn("MQTT 未连接,消息未发送:", topic);
+        return;
+      }
+      console.log("sdfdsf",typeof message === "object");
+      
+      const payload = typeof message === "object" ? JSON.stringify(message) : String(message);
+      this.client.publish(topic, payload);
+    },
+
+    // 断开连接,取消所有订阅
+    disconnect() {
+      if (this.client) {
+        // 先取消所有订阅
+        this.subscribedTopics.forEach((t) => this.unsubscribeTopic(t));
+        this.client.end(true, () => {
+          console.log("🔌 MQTT手动断开成功");
+        });
+      }
     },
   },
-  async mounted() {
-    await this.initMqtt();
+  mounted() {
+    this.initMqtt();
   },
-
-  beforeDestroy() {  
-  	this.$emit("mqtt-close") // 关闭mqtt链接需要的前置操作
-    // 使用延迟确保消息发送完成后再关闭连接
-    setTimeout(() => {
-      this.client.end(true, {}, () => {
-        console.log("MQTT连接已成功关闭");
-      });
-    }, 100); // 延迟时间根据实际情况调整,确保消息发送完成
-    // this.client.end();
+  beforeDestroy() {
+    this.disconnect();
   },
 };
 </script>
 
-<style lang="scss" scoped></style>
+<style scoped>
+.mqtt-comp {
+  display: none;
+}
+</style>

+ 50 - 16
src/views/map/maplist/index.vue

@@ -384,17 +384,24 @@
         <el-button @click="constructOpen = false">取 消</el-button>
       </div>
     </el-dialog>
+    <div>
+        <button @click="publishMsg">发布消息</button>
+        <button @click="addTopic">动态订阅 topic3</button>
+        <button @click="removeTopic">动态取消 topic2</button>
+      </div>
+
+      <MqttComp ref="mqtt" :topics="topics" @message-received="onMessage" />
   </div>
 </template>
 
 <script>
+import MqttComp from "@/components/Mqtt/mqttComp.vue";
 // 导入地图卡片组件
 import XtMapCard from '@/components/XtMapCard'
 // 导入日期格式化工具
 import { formatDateTimeCompat, pickUpdatedAt } from '@/utils/datefmt'
 // 导入路由辅助函数
 import { buildNavTo, buildEditTo, buildCalibrateTo, buildConstructTo, pickId } from '@/utils/route-helpers'
-
 // 安全获取数组函数
 function pickArray(...arrs){ 
   for(const a of arrs){ 
@@ -407,7 +414,7 @@ function pickArray(...arrs){
 let mapApi
 try {
   // 尝试导入真实 API(如果存在)
-  mapApi = require('@/api/map/map')
+  mapApi = require('@/api/map/index.js')
 } catch (error) {
   // Fallback 到 Mock API
   mapApi = require('@/api/mock/maps')
@@ -420,11 +427,13 @@ export default {
   name: "MapList",
   
   components: {
-    XtMapCard
+    XtMapCard,
+     MqttComp,
   },
   
   data() {
     return {
+       topics: ["/robot4inspection/a477be75f66fe3cb/task/target/action/goto/reply","/robot4inspection/a477be75f66fe3cb/task/target/event/arrive"], // 初始订阅的 topic
       // 常量
       MAP_FILE_BASE,
       
@@ -746,31 +755,56 @@ export default {
     this.removeKeyboardListeners()
   },
   methods: {
+    onMessage({ topic, message }) {
+        console.log("收到消息:", topic, message);
+      },
+
+      publishMsg() {
+        this.$refs.mqtt.publish("/robot4inspection/508b02dc5bcdca22/task/target/action/goto", {
+    "timestamp" : 12345678,
+    "args": [
+        {
+            "roadmap": "demo",
+            "nid": [10]
+        }
+    ]
+});
+      },
+
+      addTopic() {
+        if (!this.topics.includes("topic3")) this.topics.push("topic3");
+      },
+
+      removeTopic() {
+        this.topics = this.topics.filter((t) => t !== "/robot4inspection/508b02dc5bcdca22/localization/pose");
+      },
     // 获取地图列表
     async getList() {
       this.loading = true
       
       // 模拟加载延迟
-      await new Promise(resolve => setTimeout(resolve, 300))
+      // await new Promise(resolve => setTimeout(resolve, 300))
       
       try {
         // TODO: 后续接入真实接口时,取消注释以下代码
-        /*
-        const params = {
-          page: this.currentPage,
-          pageSize: this.pageSize,
-          keyword: this.searchKeyword,
-          status: this.statusFilter,
-          sort: this.sortField,
-          order: this.sortOrder
-        }
-
-        const response = await mapApi.listMaps(params)
+        
+        // const params = {
+        //   page: this.currentPage,
+        //   pageSize: this.pageSize,
+        //   keyword: this.searchKeyword,
+        //   status: this.statusFilter,
+        //   sort: this.sortField,
+        //   order: this.sortOrder
+        // }
+
+        const response = await mapApi.getMapList()
+        console.log('Fetched map list:', response);
+        
         if (response.code === 200) {
           this.mapList = this.processMapList(response.data.list)
           this.total = response.data.total
         }
-        */
+       
         
         // 使用 Mock 数据 - 确保 mapList 被设置
         this.mapList = [...this.mockMapList]