欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3?+?MQTT實(shí)現(xiàn)前端與硬件設(shè)備直接通訊(附完整代碼解析)

 更新時(shí)間:2025年10月29日 09:57:58   作者:小錢在學(xué)前端  
MQTT作為一種低開銷、低帶寬占用的即時(shí)通訊協(xié)議,使其在物聯(lián)網(wǎng)、小型設(shè)備、移動(dòng)應(yīng)用等方面有著廣泛的應(yīng)用,這篇文章主要介紹了Vue3?+?MQTT實(shí)現(xiàn)前端與硬件設(shè)備直接通訊的相關(guān)資料,需要的朋友可以參考下

前言

在物聯(lián)網(wǎng)(IoT)開發(fā)場景中,前端頁面與硬件設(shè)備的實(shí)時(shí)通訊是核心需求之一。MQTT(Message Queuing Telemetry Transport)作為輕量級、低帶寬消耗的通訊協(xié)議,非常適合硬件設(shè)備與前端的雙向數(shù)據(jù)交互。本文將講解如何使用 Vue3(Composition API)+ MQTT.js 實(shí)現(xiàn)硬件設(shè)備的搜索、匹配、通訊配置管理等功能,附帶完整代碼解析。

一、項(xiàng)目背景與技術(shù)棧

1. 需求場景

我們需要開發(fā)一個(gè) “硬件配置頁面”,核心功能包括:

  • 展示 MQTT 通訊核心參數(shù)(通訊關(guān)鍵字、發(fā)送 / 接收主題、連接狀態(tài));
  • 觸發(fā)硬件搜索,實(shí)時(shí)顯示搜索進(jìn)度;
  • 展示搜索到的硬件列表,支持逐個(gè)匹配硬件;
  • 記錄已匹配的硬件地址,管理 MQTT 連接生命周期。

2. 技術(shù)棧選型

技術(shù) / 工具用途說明
Vue3 (script setup)前端框架核心,使用 Composition API 組織邏輯,腳本 setup 語法簡化代碼結(jié)構(gòu)
MQTT.js實(shí)現(xiàn) MQTT 客戶端功能,負(fù)責(zé)與 MQTT 服務(wù)器建立連接、訂閱 / 發(fā)布消息
SCSS樣式預(yù)處理器,支持嵌套、變量、動(dòng)畫,提升樣式可維護(hù)性

二、核心概念鋪墊:MQTT 基礎(chǔ)

在開始代碼實(shí)現(xiàn)前,先快速回顧 MQTT 的核心概念,幫助理解后續(xù)邏輯:

  • 發(fā)布 / 訂閱(Pub/Sub)模式:前端(客戶端)與硬件(客戶端)通過 MQTT 服務(wù)器中轉(zhuǎn)消息,雙方無需直接連接;
  • 主題(Topic):消息的 “地址”,客戶端通過訂閱指定主題接收消息,通過發(fā)布指定主題發(fā)送消息(例如 /topic1/1111111);
  • 客戶端(Client):前端和硬件都是 MQTT 客戶端,需通過唯一 clientId 標(biāo)識(shí);
  • 連接狀態(tài):客戶端與 MQTT 服務(wù)器的連接狀態(tài)(已連接 / 未連接),影響消息收發(fā)能力。

三、功能模塊拆解與實(shí)現(xiàn)

下面按 “頁面結(jié)構(gòu) → 核心邏輯 → 樣式優(yōu)化” 的順序,逐步解析完整實(shí)現(xiàn)過程。

模塊 1:頁面結(jié)構(gòu)設(shè)計(jì)(Template)

頁面采用 “卡片式布局”,分為「通訊配置卡片」和「硬件配置卡片」,結(jié)構(gòu)清晰。

<template>
  <div class="room-config-container">
    <!-- 1. 通訊配置卡片:展示MQTT核心參數(shù) -->
    <div class="card communication-card">
      <div class="card-header">
        <h2 class="card-title">通訊配置</h2>
        <span class="card-helper">MQTT通訊相關(guān)參數(shù)</span>
      </div>
      <div class="card-body">
        <div class="config-grid">
          <!-- 通訊關(guān)鍵字 -->
          <div class="config-item">
            <span class="config-label">通訊關(guān)鍵字:</span>
            <span class="config-value">{{ key || '未設(shè)置' }}</span>
          </div>
          <!-- 發(fā)送主題 -->
          <div class="config-item">
            <span class="config-label">發(fā)送主題:</span>
            <span class="config-value">{{ sendTopic || '未設(shè)置' }}</span>
          </div>
          <!-- 接收主題 -->
          <div class="config-item">
            <span class="config-label">接收主題:</span>
            <span class="config-value">{{ receiveTopic || '未設(shè)置' }}</span>
          </div>
          <!-- MQTT連接狀態(tài)(帶視覺指示器) -->
          <div class="config-item">
            <span class="config-label">MQTT連接狀態(tài):</span>
            <span class="config-value">
              <span
                :class="isConnected ? 'status-indicator connected' : 'status-indicator disconnected'"
                :title="isConnected ? '已連接' : '未連接'"></span>
              {{ isConnected ? '已連接' : '未連接' }}
            </span>
          </div>
        </div>
      </div>
    </div>

    <!-- 2. 硬件配置卡片:搜索、匹配硬件 -->
    <div class="card dev-config-card">
      <div class="card-header">
        <h2 class="card-title">硬件配置</h2>
        <span class="card-helper">管理與設(shè)備通訊的硬件</span>
      </div>
      <div class="card-body">
        <!-- 硬件操作區(qū):匹配按鈕 + 搜索進(jìn)度 -->
        <div class="dev-actions">
          <button @click="configdev" class="btn primary" :disabled="isProcessing || isListening">
            <template v-if="isProcessing">
              <span class="loading-spinner"></span>匹配中...
            </template>
            <template v-else-if="isListening">
              <span class="loading-spinner"></span>搜索中...
            </template>
            <template v-else>匹配硬件</template>
          </button>

          <!-- 搜索進(jìn)度條(僅搜索期顯示) -->
          <div v-if="isListening" class="search-progress">
            <span>正在搜索硬件設(shè)備...</span>
            <div class="progress-bar">
              <div class="progress" :style="{ width: searchProgress + '%' }"></div>
            </div>
          </div>
        </div>

        <!-- 已匹配硬件信息 -->
        <div v-if="devmac" class="matched-dev">
          <h3>已匹配硬件</h3>
          <div class="dev-address">
            <span class="address-label">硬件地址:</span>
            <span class="address-value">{{ devmac }}</span>
          </div>
        </div>

        <!-- 硬件列表(搜索結(jié)果) -->
        <div class="dev-list-section">
          <h3>硬件設(shè)備列表 <span class="count-badge">{{ devlist.length }}</span></h3>
          <!-- 空狀態(tài) -->
          <div v-if="devlist.length === 0" class="empty-state">
            <div class="empty-icon">??</div>
            <p>暫無硬件設(shè)備,請點(diǎn)擊"匹配硬件"按鈕搜索</p>
          </div>
          <!-- 硬件列表 -->
          <ul class="dev-list">
            <li 
              v-for="(item, index) in devlist" 
              :key="item" 
              :class="{
                'dev-item': true,
                'processing': isProcessing && currentIndex === index, // 正在處理的硬件
                'matched': devmac === item // 已匹配的硬件
              }"
            >
              <span class="dev-name">{{ item }}</span>
              <span v-if="isProcessing && currentIndex === index" class="processing-indicator">
                <span class="spinner"></span>處理中...
              </span>
              <span v-if="devmac === item" class="matched-indicator">? 已匹配</span>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</template>

模塊 2:核心邏輯實(shí)現(xiàn)(Script Setup)

這部分是整個(gè)功能的核心,包含 MQTT 連接管理、硬件搜索、硬件匹配、資源清理 四大關(guān)鍵邏輯,使用 Vue3 Composition API 組織代碼,邏輯更聚合。

2.1 初始化響應(yīng)式變量與常量

首先定義 MQTT 基礎(chǔ)配置、核心業(yè)務(wù)變量、流程控制變量,統(tǒng)一管理狀態(tài)。

<script setup>
import { ref, watch, onUnmounted } from 'vue'
import mqtt from 'mqtt'; // 引入MQTT.js

// 1. MQTT基礎(chǔ)配置(常量,可根據(jù)實(shí)際環(huán)境修改)
const MQTT_BASE_CONFIG = {
  server: '127.0.0.1', // MQTT服務(wù)器地址(本地測試)
  port: 3333           // MQTT服務(wù)器端口
}

// 2. MQTT核心變量
const client = ref(null); // MQTT客戶端實(shí)例
const isConnected = ref(false); // MQTT連接狀態(tài)
const mqttConfig = ref({
  ...MQTT_BASE_CONFIG,
  options: {
    clientId: `device-client-${Math.random().toString(16).slice(2, 8)}`, // 隨機(jī)生成客戶端ID
    clean: true, // 清理會(huì)話(斷開后不保留訂閱)
    connectTimeout: 4000 // 連接超時(shí)時(shí)間(4秒)
  }
});

// 3. 業(yè)務(wù)核心變量
const key = ref('1111111'); // 通訊關(guān)鍵字(用于生成唯一主題)
const sendTopic = ref('');  // MQTT發(fā)送主題
const receiveTopic = ref('');// MQTT接收主題
const devlist = ref([]); // 搜索到的硬件列表
const devmac = ref(''); // 已匹配的硬件地址(MAC地址或唯一標(biāo)識(shí))
const searchProgress = ref(0); // 硬件搜索進(jìn)度(0-100%)

// 4. 流程控制變量
const isListening = ref(false); // 是否處于硬件搜索期
const currentIndex = ref(0); // 當(dāng)前處理的硬件索引(用于逐個(gè)匹配)
const isProcessing = ref(false); // 是否正在匹配硬件
const messageHandlers = ref([]); // MQTT消息處理器(用于卸載時(shí)清理)

// 5. 定時(shí)器統(tǒng)一管理(避免分散聲明導(dǎo)致內(nèi)存泄漏)
const timers = ref({
  statusCheck: null,  // 搜索超時(shí)定時(shí)器
  timeout: null,      // 單個(gè)硬件匹配超時(shí)定時(shí)器
  searchInterval: null// 搜索進(jìn)度更新定時(shí)器
});
</script>

2.2 MQTT 主題動(dòng)態(tài)更新

通訊關(guān)鍵字(key)是硬件與前端通訊的 “唯一標(biāo)識(shí)”,需監(jiān)聽其變化,動(dòng)態(tài)生成發(fā)送 / 接收主題(避免不同設(shè)備消息沖突)。

// 更新MQTT主題:根據(jù)通訊關(guān)鍵字生成唯一主題
const updateTopics = (newKey) => {
  if (!newKey) return; // 關(guān)鍵字為空時(shí)不更新
  sendTopic.value = `/topic1/${newKey}`; // 前端→硬件的主題
  receiveTopic.value = `/topic2/${newKey}`; // 硬件→前端的主題
}

// 監(jiān)聽關(guān)鍵字變化,立即更新主題(immediate: true 初始加載時(shí)執(zhí)行)
watch(key, updateTopics, { immediate: true })

2.3 MQTT 連接生命周期管理

實(shí)現(xiàn) MQTT 客戶端的連接、重連、關(guān)閉、消息監(jiān)聽邏輯,確保通訊穩(wěn)定性。

// 連接MQTT服務(wù)器
const connectMqtt = () => {
  // 1. 斷開現(xiàn)有連接(避免重復(fù)連接)
  if (client.value) client.value.end();

  // 2. 拼接MQTT連接地址(WebSocket協(xié)議,格式:ws://server:port/mqtt)
  const url = `ws://${mqttConfig.value.server}:${mqttConfig.value.port}/mqtt`;
  client.value = mqtt.connect(url, mqttConfig.value.options);

  // 3. 連接成功回調(diào)
  client.value.on('connect', () => {
    console.log('MQTT連接成功');
    isConnected.value = true;
    // 訂閱接收主題(接收硬件發(fā)送的消息)
    client.value.subscribe(receiveTopic.value, (err) => {
      err ? console.error('訂閱失敗:', err) : console.log(`訂閱成功: ${receiveTopic.value}`);
    });
  });

  // 4. 接收硬件消息(僅在搜索期處理硬件列表)
  client.value.on('message', (topic, message) => {
    if (isListening.value && topic === receiveTopic.value) {
      const msg = JSON.parse(message.toString());
      // 若消息包含硬件地址且未在列表中,加入列表
      if (msg.config && !devlist.value.includes(msg.config)) {
        devlist.value.push(msg.config);
        console.log(`新增硬件: ${msg.config}`);
      }
    }
  });

  // 5. 錯(cuò)誤與斷開處理
  client.value.on('error', (err) => {
    console.error('MQTT連接錯(cuò)誤:', err);
    isConnected.value = false;
  });
  client.value.on('reconnect', () => console.log('MQTT正在重連...'));
  client.value.on('close', () => {
    console.log('MQTT連接關(guān)閉');
    isConnected.value = false;
  });
};

// 監(jiān)聽主題變化,重新連接MQTT(確保訂閱最新主題)
watch(
  [sendTopic, receiveTopic],
  ([newSend, newReceive], [oldSend, oldReceive]) => {
    if (newSend && newReceive && newSend !== oldSend && newReceive !== oldReceive) {
      connectMqtt();
    }
  },
  { immediate: true } // 初始加載時(shí)連接MQTT
);

2.4 硬件搜索與匹配邏輯

這是最復(fù)雜的部分,需實(shí)現(xiàn) “觸發(fā)搜索 → 顯示進(jìn)度 → 逐個(gè)匹配 → 確認(rèn)結(jié)果” 的完整流程,核心是 定時(shí)器控制 和 異步遞歸處理。

// 發(fā)送MQTT消息(通用函數(shù),封裝發(fā)布邏輯)
const sendMessage = (topic, message) => {
  if (!isConnected.value || !client.value) return; // 未連接時(shí)不發(fā)送
  client.value.publish(topic, message, (err) => {
    err ? console.error('消息發(fā)送失敗:', err) : console.log('消息發(fā)送成功:', message);
  });
};

// 處理單個(gè)硬件匹配(返回Promise,成功true/失敗false)
const processSingledev = (dev) => {
  return new Promise((resolve) => {
    // 1. 清理上一輪殘留資源(定時(shí)器、消息監(jiān)聽)
    if (timers.value.timeout) clearTimeout(timers.value.timeout);
    messageHandlers.value.forEach(handler => client.value?.off('message', handler));
    messageHandlers.value = [];

    // 2. 訂閱當(dāng)前硬件的專屬主題(用于接收匹配響應(yīng))
    client.value.subscribe(`/topic2/${dev}`, (err) => {
      if (err) {
        console.error(`訂閱硬件 ${dev} 失敗:`, err);
        resolve(false);
        return;
      }
      console.log(`已訂閱硬件 ${dev} 主題: /topic2/${dev}`);

      // 3. 向前端發(fā)送“匹配指令”(pressdown為自定義指令,需與硬件協(xié)商)
      client.value.publish(`/topic1/${dev}`, JSON.stringify({ operation: 'pressdown' }), (err) => {
        if (err) {
          console.error(`向硬件 ${dev} 發(fā)送指令失敗:`, err);
          resolve(false);
          return;
        }
        console.log(`已向硬件 ${dev} 發(fā)送匹配指令`);
      });

      // 4. 監(jiān)聽硬件的匹配響應(yīng)
      const messageHandler = (topic, message) => {
        if (topic !== `/topic2/${dev}`) return; // 只處理當(dāng)前硬件的消息
        
        const msg = JSON.parse(message.toString());
        console.log('收到硬件響應(yīng):', msg);
        // 硬件返回“confirm: confirm”表示匹配成功
        if (msg.confirm === 'confirm') {
          devmac.value = dev; // 記錄已匹配硬件地址
          client.value.publish(`/topic1/${dev}`, JSON.stringify({ confirm: 'ok' })); // 發(fā)送確認(rèn)
          console.log(`硬件 ${dev} 匹配成功`);
          clearTimeout(timers.value.timeout); // 清除超時(shí)定時(shí)器
          resolve(true);
        }
      };
      client.value.on('message', messageHandler);
      messageHandlers.value.push(messageHandler); // 記錄處理器,用于后續(xù)清理

      // 5. 10秒超時(shí)控制(硬件未響應(yīng)則視為匹配失?。?
      timers.value.timeout = setTimeout(() => {
        console.log(`硬件 ${dev} 超時(shí)未響應(yīng)`);
        resolve(false);
      }, 10000);
    });
  });
};

// 遞歸處理硬件隊(duì)列(逐個(gè)匹配,直到找到目標(biāo)硬件或遍歷完成)
const processdevQueue = async () => {
  // 終止條件:已匹配到硬件 或 所有硬件處理完畢
  if (devmac.value || currentIndex.value >= devlist.value.length) {
    isProcessing.value = false;
    console.log(devmac.value ? '匹配成功' : '所有硬件處理完畢,未找到匹配項(xiàng)');
    return;
  }

  isProcessing.value = true;
  const currentdev = devlist.value[currentIndex.value];
  console.log(`處理第 ${currentIndex.value + 1} 個(gè)硬件: ${currentdev}`);

  // 處理當(dāng)前硬件,成功則終止,失敗則繼續(xù)下一個(gè)
  const isSuccess = await processSingledev(currentdev);
  if (isSuccess) {
    isProcessing.value = false;
    return;
  }

  currentIndex.value++; // 索引+1,處理下一個(gè)硬件
  processdevQueue(); // 遞歸調(diào)用
};

// 監(jiān)聽搜索狀態(tài)與硬件列表,自動(dòng)觸發(fā)匹配(搜索到硬件后立即開始匹配)
watch([isListening, devlist], () => {
  if (devlist.value.length > 0 && !devmac.value && !isProcessing.value) {
    console.log('開始逐個(gè)匹配硬件...');
    currentIndex.value = 0; // 重置索引
    processdevQueue();
  }
});

// 「匹配硬件」按鈕點(diǎn)擊事件(觸發(fā)搜索流程)
const configdev = () => {
  // 1. 清理所有殘留資源(避免上一輪影響)
  Object.values(timers.value).forEach(timer => timer && clearTimeout(timer));
  messageHandlers.value.forEach(handler => client.value?.off('message', handler));
  
  // 2. 重置狀態(tài)
  devmac.value = '';
  devlist.value = [];
  currentIndex.value = 0;
  isProcessing.value = false;
  searchProgress.value = 0;

  // 3. 發(fā)送“搜索硬件”指令(status: configdev 為自定義指令)
  sendMessage(sendTopic.value, JSON.stringify({ status: 'configdev' }));

  // 4. 啟動(dòng)10秒搜索期
  isListening.value = true;
  console.log('開始10秒硬件搜索...');

  // 5. 實(shí)時(shí)更新搜索進(jìn)度(每100ms更新一次)
  let elapsed = 0;
  timers.value.searchInterval = setInterval(() => {
    elapsed += 100;
    searchProgress.value = Math.min(100, (elapsed / 10000) * 100); // 進(jìn)度不超過100%
  }, 100);

  // 6. 搜索超時(shí)處理(10秒后結(jié)束搜索)
  timers.value.statusCheck = setTimeout(() => {
    isListening.value = false;
    clearInterval(timers.value.searchInterval);
    console.log(`搜索結(jié)束,共發(fā)現(xiàn) ${devlist.value.length} 個(gè)硬件`);
  }, 10000);
};

2.5 資源清理(避免內(nèi)存泄漏)

Vue3 組件卸載時(shí),需清理 定時(shí)器、MQTT 消息監(jiān)聽、MQTT 連接,防止內(nèi)存泄漏。

// 組件卸載鉤子:清理所有資源
onUnmounted(() => {
  // 清理所有定時(shí)器
  Object.values(timers.value).forEach(timer => timer && clearTimeout(timer));
  // 移除所有MQTT消息監(jiān)聽
  messageHandlers.value.forEach(handler => client.value?.off('message', handler));
  // 斷開MQTT連接
  client.value?.end();
});

四、常見問題與解決方案

在實(shí)際開發(fā)中,可能會(huì)遇到以下問題,這里提供對應(yīng)的解決方案:

1. MQTT 連接失敗

  • 原因 1:服務(wù)器地址 / 端口錯(cuò)誤:確認(rèn) MQTT 服務(wù)器是否啟動(dòng),地址(127.0.0.1)和端口(3333)是否與實(shí)際一致;
  • 原因 2:跨域問題:若前端與 MQTT 服務(wù)器不在同一域名,需在 MQTT 服務(wù)器配置跨域允許(例如 EMQ X 服務(wù)器在 Dashboard 中設(shè)置 CORS);
  • 原因 3:客戶端 ID 重復(fù):代碼中通過 Math.random() 生成隨機(jī) clientId,避免重復(fù),若需固定 ID,需確保唯一性。

2. 硬件搜索不到

  • 原因 1:指令不匹配:確認(rèn) sendMessage 發(fā)送的指令(status: 'configdev')與硬件端的指令解析邏輯一致;
  • 原因 2:主題錯(cuò)誤:檢查硬件發(fā)送的消息主題是否與前端 receiveTopic 一致(需與硬件端協(xié)商統(tǒng)一);
  • 原因 3:硬件未聯(lián)網(wǎng):確保硬件已連接到與 MQTT 服務(wù)器同一網(wǎng)絡(luò)。

3. 硬件匹配超時(shí)

  • 解決方案 1:延長超時(shí)時(shí)間:將 processSingledev 中的 10000(10 秒)改為更長時(shí)間(如 20000);
  • 解決方案 2:增加重試機(jī)制:在匹配失敗后,增加 1-2 次重試邏輯,避免網(wǎng)絡(luò)波動(dòng)導(dǎo)致的誤判;
  • 解決方案 3:檢查指令格式:確認(rèn)發(fā)送的匹配指令(operation: 'pressdown')和硬件響應(yīng)格式(confirm: 'confirm')是否正確。

五、總結(jié)與優(yōu)化方向

1. 功能總結(jié)

本實(shí)例實(shí)現(xiàn)了 Vue3 與硬件設(shè)備的 MQTT 通訊全流程,核心亮點(diǎn)包括:


MQTT 生命周期管理:連接、重連、關(guān)閉、訂閱 / 發(fā)布消息的完整邏輯; 硬件搜索與匹配:定時(shí)器控制搜索進(jìn)度,異步遞歸處理硬件匹配,確保流程嚴(yán)謹(jǐn);

2. 后續(xù)優(yōu)化方向

  • 錯(cuò)誤提示可視化:當(dāng)前錯(cuò)誤僅在控制臺(tái)打印,可增加彈窗或 Toast 提示用戶(如 MQTT 連接失敗、硬件匹配超時(shí));
  • 硬件匹配重試:增加手動(dòng)重試按鈕,允許用戶重新匹配未成功的硬件;
  • MQTT 連接狀態(tài)持久化:使用 localStorage 保存 MQTT 配置,頁面刷新后自動(dòng)恢復(fù)連接;
  • 多硬件管理:支持匹配多個(gè)硬件,展示多個(gè)已匹配硬件地址,實(shí)現(xiàn)多設(shè)備通訊
  • 資源清理:組件卸載時(shí)清理定時(shí)器、消息監(jiān)聽、MQTT 連接,避免內(nèi)存泄漏;
  • 用戶體驗(yàn)優(yōu)化:狀態(tài)指示器、進(jìn)度條、空狀態(tài)提示,提升頁面交互友好性。

完整代碼:

<template>
    <div class="room-config-container">
        <!-- 通訊配置卡片 -->
        <div class="card communication-card">
            <div class="card-header">
                <h2 class="card-title">通訊配置</h2>
                <span class="card-helper">MQTT通訊相關(guān)參數(shù)</span>
            </div>
            <div class="card-body">
                <div class="config-grid">
                    <div class="config-item">
                        <span class="config-label">通訊關(guān)鍵字:</span>
                        <span class="config-value">{{ key || '未設(shè)置' }}</span>
                    </div>
                    <div class="config-item">
                        <span class="config-label">發(fā)送主題:</span>
                        <span class="config-value">{{ sendTopic || '未設(shè)置' }}</span>
                    </div>
                    <div class="config-item">
                        <span class="config-label">接收主題:</span>
                        <span class="config-value">{{ receiveTopic || '未設(shè)置' }}</span>
                    </div>
                    <div class="config-item">
                        <span class="config-label">MQTT連接狀態(tài):</span>
                        <span class="config-value">
                            <span
                                :class="isConnected ? 'status-indicator connected' : 'status-indicator disconnected'"
                                :title="isConnected ? '已連接' : '未連接'"></span>
                            {{ isConnected ? '已連接' : '未連接' }}
                        </span>
                    </div>
                </div>
            </div>
        </div>

        <!-- 硬件配置區(qū)域 -->
        <div class="card dev-config-card">
            <div class="card-header">
                <h2 class="card-title">硬件配置</h2>
                <span class="card-helper">管理與設(shè)備通訊的硬件</span>
            </div>
            <div class="card-body">
                <div class="dev-actions">
                    <button @click="configdev" class="btn primary" :disabled="isProcessing || isListening">
                        <template v-if="isProcessing">
                            <span class="loading-spinner"></span>
                            匹配中...
                        </template>
                        <template v-else-if="isListening">
                            <span class="loading-spinner"></span>
                            搜索中...
                        </template>
                        <template v-else>
                            匹配硬件
                        </template>
                    </button>

                    <div v-if="isListening" class="search-progress">
                        <span>正在搜索硬件設(shè)備...</span>
                        <div class="progress-bar">
                            <div class="progress" :style="{ width: searchProgress + '%' }"></div>
                        </div>
                    </div>
                </div>

                <!-- 已匹配硬件信息 -->
                <div v-if="devmac" class="matched-dev">
                    <h3>已匹配硬件</h3>
                    <div class="dev-address">
                        <span class="address-label">硬件地址:</span>
                        <span class="address-value">{{ devmac }}</span>
                    </div>
                </div>

                <!-- 硬件列表 -->
                <div class="dev-list-section">
                    <h3>硬件設(shè)備列表 <span class="count-badge">{{ devlist.length }}</span></h3>
                    <div v-if="devlist.length === 0" class="empty-state">
                        <div class="empty-icon">??</div>
                        <p>暫無硬件設(shè)備,請點(diǎn)擊"匹配硬件"按鈕搜索</p>
                    </div>
                    <ul class="dev-list">
                        <li v-for="(item, index) in devlist" :key="item" :class="{
                            'dev-item': true,
                            'processing': isProcessing && currentIndex === index,
                            'matched': devmac === item
                        }">
                            <span class="dev-name">{{ item }}</span>
                            <span v-if="isProcessing && currentIndex === index" class="processing-indicator">
                                <span class="spinner"></span>
                                處理中...
                            </span>
                            <span v-if="devmac === item" class="matched-indicator">? 已匹配</span>
                        </li>
                    </ul>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, watch, onUnmounted } from 'vue'
import mqtt from 'mqtt';

// MQTT基礎(chǔ)配置(常量)
const MQTT_BASE_CONFIG = {
    server: '127.0.0.1', // MQTT服務(wù)器地址
    port: 3333           // MQTT服務(wù)器端口
}

// MQTT核心變量(僅保留必要項(xiàng))
const client = ref(null);
const isConnected = ref(false);
const mqttConfig = ref({
    ...MQTT_BASE_CONFIG,
    options: {
        clientId: `device-client-${Math.random().toString(16).slice(2, 8)}`,
        clean: true,
        connectTimeout: 4000
    }
});

// 業(yè)務(wù)核心變量(刪除未使用的hasNewDevice)
const key = ref('1111111'); // 通訊關(guān)鍵字
const sendTopic = ref('');  // 發(fā)送主題
const receiveTopic = ref('');// 接收主題
const devlist = ref([]); // 硬件設(shè)備列表
const devmac = ref('');     // 已匹配硬件地址
const searchProgress = ref(0); // 搜索進(jìn)度

// 流程控制變量(僅保留必要項(xiàng))
const isListening = ref(false);    // 是否處于硬件搜索期
const currentIndex = ref(0);       // 當(dāng)前處理的硬件索引
const isProcessing = ref(false);   // 是否正在匹配硬件
const messageHandlers = ref([]);   // MQTT消息處理器(用于清理)

// 定時(shí)器統(tǒng)一管理(避免分散聲明)
const timers = ref({
    statusCheck: null,  // 搜索超時(shí)定時(shí)器
    timeout: null,      // 單個(gè)硬件超時(shí)定時(shí)器
    searchInterval: null// 進(jìn)度更新定時(shí)器
});

// 更新MQTT主題(核心邏輯保留)
const updateTopics = (newKey) => {
    if (!newKey) return;
    sendTopic.value = `/topic1/${newKey}`;
    receiveTopic.value = `/topic2/${newKey}`;
}

// 監(jiān)聽關(guān)鍵字變化,同步更新主題
watch(key, updateTopics, { immediate: true })

// 處理單個(gè)硬件匹配(核心邏輯保留,簡化定時(shí)器清理)
const processSingledev = (dev) => {
    return new Promise((resolve) => {
        // 1. 清理上一輪殘留資源
        if (timers.value.timeout) clearTimeout(timers.value.timeout);
        messageHandlers.value.forEach(handler => client.value?.off('message', handler));
        messageHandlers.value = [];

        // 2. 訂閱當(dāng)前硬件主題
        client.value.subscribe(`/topic2/${dev}`, (err) => {
            if (err) {
                console.error(`訂閱硬件 ${dev} 失敗:`, err);
                resolve(false);
                return;
            }
            console.log(`已訂閱硬件 ${dev} 主題: /topic2/${dev}`);

            // 3. 發(fā)送匹配指令
            client.value.publish(`/topic1/${dev}`, JSON.stringify({ operation: 'pressdown' }), (err) => {
                if (err) {
                    console.error(`向硬件 ${dev} 發(fā)送指令失敗:`, err);
                    resolve(false);
                    return;
                }
                console.log(`已向硬件 ${dev} 發(fā)送匹配指令`);
            });

            // 4. 監(jiān)聽硬件響應(yīng)
            const messageHandler = (topic, message) => {
                if (topic !== `/topic2/${dev}`) return;
                
                const msg = JSON.parse(message.toString());
                console.log('收到硬件響應(yīng):', msg);
                if (msg.confirm === 'confirm') {
                    devmac.value = dev;
                    client.value.publish(`/topic1/${dev}`, JSON.stringify({ confirm: 'ok' }));
                    console.log(`硬件 ${dev} 匹配成功`);
                    clearTimeout(timers.value.timeout);
                    resolve(true);
                }
            };
            client.value.on('message', messageHandler);
            messageHandlers.value.push(messageHandler);

            // 5. 10秒超時(shí)控制
            timers.value.timeout = setTimeout(() => {
                console.log(`硬件 ${dev} 超時(shí)未響應(yīng)`);
                resolve(false);
            }, 10000);
        });
    });
};

// 遞歸處理硬件隊(duì)列(核心邏輯保留)
const processdevQueue = async () => {
    if (devmac.value || currentIndex.value >= devlist.value.length) {
        isProcessing.value = false;
        console.log(devmac.value ? '匹配成功' : '所有硬件處理完畢,未找到匹配項(xiàng)');
        return;
    }

    isProcessing.value = true;
    const currentdev = devlist.value[currentIndex.value];
    console.log(`處理第 ${currentIndex.value + 1} 個(gè)硬件: ${currentdev}`);

    const isSuccess = await processSingledev(currentdev);
    if (isSuccess) {
        isProcessing.value = false;
        return;
    }

    currentIndex.value++;
    processdevQueue();
};

// 監(jiān)聽搜索狀態(tài)與硬件列表,自動(dòng)觸發(fā)匹配
watch([isListening, devlist], () => {
    if (devlist.value.length > 0 && !devmac.value && !isProcessing.value) {
        console.log('開始逐個(gè)匹配硬件...');
        currentIndex.value = 0;
        processdevQueue();
    }
});

// 連接MQTT(簡化冗余邏輯)
const connectMqtt = () => {
    // 斷開現(xiàn)有連接
    if (client.value) client.value.end();

    const url = `ws://${mqttConfig.value.server}:${mqttConfig.value.port}/mqtt`;
    client.value = mqtt.connect(url, mqttConfig.value.options);

    // 連接成功
    client.value.on('connect', () => {
        console.log('MQTT連接成功');
        isConnected.value = true;
        client.value.subscribe(receiveTopic.value, (err) => {
            err ? console.error('訂閱失敗:', err) : console.log(`訂閱成功: ${receiveTopic.value}`);
        });
    });

    // 接收硬件列表(僅在搜索期處理)
    client.value.on('message', (topic, message) => {
        if (isListening.value && topic === receiveTopic.value) {
            const msg = JSON.parse(message.toString());
            if (msg.config && !devlist.value.includes(msg.config)) {
                devlist.value.push(msg.config);
                console.log(`新增硬件: ${msg.config}`);
            }
        }
    });

    // 錯(cuò)誤與斷開處理
    client.value.on('error', (err) => {
        console.error('MQTT連接錯(cuò)誤:', err);
        isConnected.value = false;
    });
    client.value.on('reconnect', () => console.log('MQTT正在重連...'));
    client.value.on('close', () => {
        console.log('MQTT連接關(guān)閉');
        isConnected.value = false;
    });
};

// 監(jiān)聽主題變化,重連MQTT
watch(
    [sendTopic, receiveTopic],
    ([newSend, newReceive], [oldSend, oldReceive]) => {
        if (newSend && newReceive && newSend !== oldSend && newReceive !== oldReceive) {
            connectMqtt();
        }
    },
    { immediate: true }
);

// 發(fā)送MQTT消息(核心功能保留)
const sendMessage = (topic, message) => {
    if (!isConnected.value || !client.value) return;
    client.value.publish(topic, message, (err) => {
        err ? console.error('消息發(fā)送失敗:', err) : console.log('消息發(fā)送成功:', message);
    });
};

// 匹配硬件按鈕點(diǎn)擊事件(簡化定時(shí)器管理)
const configdev = () => {
    // 1. 清理所有殘留資源
    Object.values(timers.value).forEach(timer => timer && clearTimeout(timer));
    messageHandlers.value.forEach(handler => client.value?.off('message', handler));
    
    // 2. 重置狀態(tài)
    devmac.value = '';
    devlist.value = [];
    currentIndex.value = 0;
    isProcessing.value = false;
    searchProgress.value = 0;

    // 3. 發(fā)送搜索指令
    sendMessage(sendTopic.value, JSON.stringify({ status: 'configdev' }));

    // 4. 啟動(dòng)10秒搜索期
    isListening.value = true;
    console.log('開始10秒硬件搜索...');

    // 5. 更新搜索進(jìn)度
    let elapsed = 0;
    timers.value.searchInterval = setInterval(() => {
        elapsed += 100;
        searchProgress.value = Math.min(100, (elapsed / 10000) * 100);
    }, 100);

    // 6. 搜索超時(shí)處理
    timers.value.statusCheck = setTimeout(() => {
        isListening.value = false;
        clearInterval(timers.value.searchInterval);
        console.log(`搜索結(jié)束,共發(fā)現(xiàn) ${devlist.value.length} 個(gè)硬件`);
    }, 10000);
};

// 組件卸載:清理所有資源(避免內(nèi)存泄漏)
onUnmounted(() => {
    Object.values(timers.value).forEach(timer => timer && clearTimeout(timer));
    messageHandlers.value.forEach(handler => client.value?.off('message', handler));
    client.value?.end();
});
</script>

<style lang="scss" scoped>
// 容器基礎(chǔ)樣式
.room-config-container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 24px;
    box-sizing: border-box;
}

// 卡片通用樣式(升級視覺效果)
.card {
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
    margin-bottom: 24px;
    overflow: hidden;
    transition: box-shadow 0.3s ease, transform 0.2s ease;

    // 卡片懸浮效果
    &:hover {
        box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
        transform: translateY(-2px);
    }

    // 卡片頂部色條(區(qū)分類型)
    &.communication-card {
        border-top: 4px solid #722ED1;
    }
    &.dev-config-card {
        border-top: 4px solid #0FC6C2;
    }

    // 卡片頭部
    .card-header {
        padding: 16px 24px;
        border-bottom: 1px solid #f5f5f5;
        display: flex;
        justify-content: space-between;
        align-items: center;

        .card-title {
            margin: 0;
            font-size: 18px;
            font-weight: 600;
            color: #1D2129;
        }
        .card-helper {
            font-size: 14px;
            color: #86909C;
        }
    }

    // 卡片內(nèi)容區(qū)
    .card-body {
        padding: 24px;
    }
}

// 通訊配置 - 網(wǎng)格布局
.config-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
}

// 配置項(xiàng)樣式(優(yōu)化背景與間距)
.config-item {
    padding: 14px 18px;
    background-color: #F7F8FA;
    border-radius: 8px;
    transition: background-color 0.2s ease;

    &:hover {
        background-color: #F0F2F5;
    }

    .config-label {
        display: block;
        margin-bottom: 6px;
        font-size: 14px;
        color: #4E5969;
        font-weight: 500;
    }
    .config-value {
        font-family: 'Consolas', 'Monaco', monospace;
        font-size: 14px;
        color: #1D2129;
        word-break: break-all;
    }
}

// 狀態(tài)指示器(優(yōu)化大小與間距)
.status-indicator {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    margin-right: 8px;
    vertical-align: middle;
    transition: background-color 0.3s ease;

    &.connected {
        background-color: #00B42A;
        box-shadow: 0 0 0 2px rgba(0, 180, 42, 0.2);
    }
    &.disconnected {
        background-color: #F53F3F;
        box-shadow: 0 0 0 2px rgba(245, 63, 63, 0.2);
    }
}

// 硬件配置 - 操作區(qū)
.dev-actions {
    margin-bottom: 24px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

// 按鈕樣式(升級交互效果)
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 10px 24px;
    border-radius: 8px;
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
    border: none;
    outline: none;

    &.primary {
        background-color: #165DFF;
        color: #fff;

        &:hover {
            background-color: #0E42D2;
            transform: translateY(-1px);
        }
        &:active {
            transform: translateY(0);
        }
        &:disabled {
            background-color: #84ADFF;
            cursor: not-allowed;
            transform: none;
        }
    }
}

// 搜索進(jìn)度條(優(yōu)化色彩)
.search-progress {
    width: 100%;
    max-width: 500px;

    span {
        display: block;
        margin-bottom: 8px;
        font-size: 14px;
        color: #4E5969;
    }
    .progress-bar {
        height: 8px;
        background-color: #F2F3F5;
        border-radius: 4px;
        overflow: hidden;

        .progress {
            height: 100%;
            background-color: #165DFF;
            transition: width 0.1s linear;
        }
    }
}

// 已匹配硬件(優(yōu)化背景色與圖標(biāo))
.matched-dev {
    padding: 18px;
    background-color: #E8F3E8;
    border-radius: 8px;
    margin-bottom: 24px;

    h3 {
        margin-top: 0;
        margin-bottom: 12px;
        font-size: 16px;
        color: #00B42A;
        display: flex;
        align-items: center;

        &::before {
            content: "?";
            margin-right: 8px;
            font-size: 18px;
        }
    }
    .dev-address {
        display: flex;
        flex-wrap: wrap;

        .address-label {
            font-weight: 500;
            margin-right: 8px;
            color: #4E5969;
        }
        .address-value {
            font-family: 'Consolas', 'Monaco', monospace;
            color: #1D2129;
            word-break: break-all;
        }
    }
}

// 硬件列表區(qū)域
.dev-list-section {
    h3 {
        margin-top: 0;
        margin-bottom: 16px;
        font-size: 16px;
        color: #1D2129;
        display: flex;
        align-items: center;
    }
    .count-badge {
        margin-left: 8px;
        padding: 2px 8px;
        background-color: #F2F3F5;
        color: #86909C;
        border-radius: 12px;
        font-size: 12px;
        font-weight: normal;
    }
}

// 硬件列表樣式(優(yōu)化hover與選中效果)
.dev-list {
    list-style: none;
    padding: 0;
    margin: 0;
    border: 1px solid #F2F3F5;
    border-radius: 8px;
    overflow: hidden;
}
.dev-item {
    padding: 16px;
    border-bottom: 1px solid #F2F3F5;
    display: flex;
    justify-content: space-between;
    align-items: center;
    transition: background-color 0.2s ease;

    &:last-child {
        border-bottom: none;
    }
    &:hover {
        background-color: #F7F8FA;
    }
    &.processing {
        background-color: #FFF8E6;
        border-left: 3px solid #FF7D00;
    }
    &.matched {
        background-color: #E8F3E8;
        border-left: 3px solid #00B42A;
    }

    .dev-name {
        font-family: 'Consolas', 'Monaco', monospace;
        color: #1D2129;
        word-break: break-all;
    }
    .processing-indicator {
        font-size: 12px;
        color: #FF7D00;
        display: flex;
        align-items: center;
    }
    .matched-indicator {
        font-size: 12px;
        color: #00B42A;
        font-weight: 500;
    }
}

// 空狀態(tài)(優(yōu)化間距與透明度)
.empty-state {
    padding: 48px 24px;
    text-align: center;
    color: #86909C;

    .empty-icon {
        font-size: 48px;
        margin-bottom: 16px;
        opacity: 0.4;
    }
    p {
        margin: 0;
        font-size: 14px;
    }
}

// 加載動(dòng)畫(統(tǒng)一大小與色彩)
.loading-spinner,
.spinner {
    display: inline-block;
    width: 16px;
    height: 16px;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    border-top-color: white;
    animation: spin 1s ease-in-out infinite;
    margin-right: 8px;
}
// 處理中動(dòng)畫(區(qū)分顏色)
.spinner {
    border-top-color: #FF7D00;
    border-color: rgba(255, 125, 0, 0.3);
}

// 旋轉(zhuǎn)動(dòng)畫
@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

// 響應(yīng)式適配(簡化邏輯)
@media (max-width: 768px) {
    .room-config-container {
        padding: 16px;
    }
    .config-grid {
        grid-template-columns: 1fr;
    }
}
</style>

總結(jié)

到此這篇關(guān)于Vue3 + MQTT實(shí)現(xiàn)前端與硬件設(shè)備直接通訊的文章就介紹到這了,更多相關(guān)Vue3 MQTT前端與硬件設(shè)備通訊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue內(nèi)置組件transition簡單原理圖文詳解(小結(jié))

    vue內(nèi)置組件transition簡單原理圖文詳解(小結(jié))

    這篇文章主要介紹了vue內(nèi)置組件transition簡單原理圖文詳解(小結(jié)),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-07-07
  • Vue完整項(xiàng)目構(gòu)建(進(jìn)階篇)

    Vue完整項(xiàng)目構(gòu)建(進(jìn)階篇)

    這篇文章主要介紹了Vue完整項(xiàng)目構(gòu)建(進(jìn)階篇)的相關(guān)資料,需要的朋友可以參考下
    2018-02-02
  • Vue?echarts實(shí)例項(xiàng)目地區(qū)銷量趨勢堆疊折線圖實(shí)現(xiàn)詳解

    Vue?echarts實(shí)例項(xiàng)目地區(qū)銷量趨勢堆疊折線圖實(shí)現(xiàn)詳解

    Echarts,它是一個(gè)與框架無關(guān)的 JS 圖表庫,但是它基于Js,這樣很多框架都能使用它,例如Vue,估計(jì)IONIC也能用,因?yàn)槲业牧?xí)慣,每次新嘗試做一個(gè)功能的時(shí)候,總要新創(chuàng)建個(gè)小項(xiàng)目,做做Demo
    2022-09-09
  • vue中使用keep-alive動(dòng)態(tài)刪除已緩存組件方式

    vue中使用keep-alive動(dòng)態(tài)刪除已緩存組件方式

    這篇文章主要介紹了vue中使用keep-alive動(dòng)態(tài)刪除已緩存組件方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Vue學(xué)習(xí)筆記分享之Vue組件化編程

    Vue學(xué)習(xí)筆記分享之Vue組件化編程

    這篇文章主要介紹了Vue學(xué)習(xí)筆記分享之Vue組件化編程,文中把知識(shí)點(diǎn)都一一羅列出來了,方便整理學(xué)習(xí),需要的朋友可以參考下
    2023-03-03
  • Vue中使用ECharts與v-if的問題和解決方案

    Vue中使用ECharts與v-if的問題和解決方案

    在Vue項(xiàng)目中使用v-if指令控制ECharts圖表顯示時(shí),可能會(huì)遇到圖表無法正常渲染或顯示錯(cuò)誤的問題,下面這篇文章主要介紹了Vue中使用ECharts與v-if的問題和解決方案,需要的朋友可以參考下
    2024-10-10
  • Vue 數(shù)值改變頁面沒有刷新的問題解決(數(shù)據(jù)改變視圖不更新的問題)

    Vue 數(shù)值改變頁面沒有刷新的問題解決(數(shù)據(jù)改變視圖不更新的問題)

    這篇文章主要介紹了Vue 數(shù)值改變頁面沒有刷新的問題解決(數(shù)據(jù)改變視圖不更新的問題),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • Vue中Element?UI組件庫使用方法詳解

    Vue中Element?UI組件庫使用方法詳解

    ElementUI是Vue的UI框架,提供了豐富的組件,方便快速開發(fā)頁面,本文詳細(xì)介紹了ElementUI的安裝、使用方法以及常見組件的說明,包括基礎(chǔ)組件、布局組件、選擇框組件、輸入框組件、下拉框組件等,需要的朋友可以參考下
    2024-11-11
  • vue移動(dòng)端如何解決click事件延遲,封裝tap等事件

    vue移動(dòng)端如何解決click事件延遲,封裝tap等事件

    這篇文章主要介紹了vue移動(dòng)端如何解決click事件延遲,封裝tap等事件,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • typescript+vite項(xiàng)目配置別名的方法實(shí)現(xiàn)

    typescript+vite項(xiàng)目配置別名的方法實(shí)現(xiàn)

    我們?yōu)榱耸÷匀唛L的路徑,經(jīng)常喜歡配置路徑別名,本文主要介紹了typescript+vite項(xiàng)目配置別名的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評論