zhaohuanhuan 3 місяців тому
батько
коміт
74af105aa5
1 змінених файлів з 299 додано та 18 видалено
  1. 299 18
      src/views/test.vue

+ 299 - 18
src/views/test.vue

@@ -38,9 +38,29 @@
       <button v-if="!scanMode" @click="connectBluetooth" :disabled="isConnecting || device" class="btn-connect">
         {{ isConnecting ? '连接中...' : '连接重量秤' }}
       </button>
-      <button v-else @click="scanAllDevices" :disabled="isScanning" class="btn-scan">
-        {{ isScanning ? '扫描中...' : '扫描所有设备' }}
-      </button>
+
+      <!-- 扫描模式按钮组 -->
+      <div v-else class="scan-controls">
+        <button @click="scanAllDevices" :disabled="isScanning" class="btn-scan">
+          {{ isScanning ? '扫描中...' : '扫描设备' }}
+        </button>
+
+        <div class="batch-scan-section">
+          <label class="batch-scan-toggle">
+            <input type="checkbox" v-model="batchScanMode">
+            批量扫描模式
+          </label>
+
+          <button v-if="batchScanMode" @click="startBatchScan" :disabled="isScanning" class="btn-batch-scan">
+            🚀 开始批量扫描
+          </button>
+
+          <button v-if="batchScanMode && batchScanCount > 0" @click="stopBatchScan" class="btn-stop-batch">
+            ⏹️ 停止批量扫描
+          </button>
+        </div>
+      </div>
+
       <button @click="disconnectBluetooth" :disabled="!device" class="btn-disconnect">
         断开连接
       </button>
@@ -50,15 +70,47 @@
 
     <!-- 扫描提示 -->
     <div v-if="scanMode" class="scan-info">
-      <p>📱 <strong>扫描说明:</strong> 每次点击扫描按钮只能发现一个设备。如需发现更多设备,请多次点击"扫描所有设备"按钮。</p>
-      <p v-if="availableDevices.length === 0">💡 提示: 确保蓝牙设备已开启并在附近,然后点击扫描按钮。</p>
+      <div class="scan-header">
+        <h4>🔍 设备扫描说明</h4>
+        <p><strong>Web Bluetooth API 限制:</strong> 每次只能选择一个设备,无法自动发现所有设备。</p>
+      </div>
+
+      <div class="scan-guide">
+        <div class="guide-step">
+          <span class="step-number">1</span>
+          <span>点击"扫描所有设备"按钮</span>
+        </div>
+        <div class="guide-step">
+          <span class="step-number">2</span>
+          <span>在弹出的设备列表中选择一个设备</span>
+        </div>
+        <div class="guide-step">
+          <span class="step-number">3</span>
+          <span>重复步骤1-2来发现更多设备</span>
+        </div>
+      </div>
+
+      <div class="scan-tips">
+        <p v-if="availableDevices.length === 0">💡 <strong>提示:</strong> 如果没有看到设备,请确保蓝牙设备已开启并在附近。</p>
+        <p v-if="availableDevices.length > 0">✅ 已发现 <strong>{{ availableDevices.length }}</strong> 个设备,可以继续扫描更多设备。</p>
+      </div>
     </div>
 
     <!-- 设备列表 -->
     <div v-if="scanMode && availableDevices.length > 0" class="device-list">
-      <h3>发现的设备 ({{ availableDevices.length }})</h3>
+      <div class="device-list-header">
+        <h3>发现的设备 ({{ availableDevices.length }})</h3>
+        <div class="device-search">
+          <input type="text" v-model="deviceSearchQuery" placeholder="搜索设备名称..." class="search-input">
+          <button @click="deviceSearchQuery = ''" class="btn-clear-search" v-if="deviceSearchQuery">清除</button>
+        </div>
+      </div>
+      <div v-if="filteredDevices.length === 0 && deviceSearchQuery" class="no-results">
+        没有找到匹配的设备
+      </div>
+
       <div class="device-grid">
-        <div v-for="(device, index) in availableDevices" :key="device.id || index"
+        <div v-for="(device, index) in filteredDevices" :key="device.id || index"
              class="device-card"
              :class="{ connected: device.id === (this.device && this.device.id) }"
              @click="connectToDevice(device)">
@@ -110,6 +162,9 @@ export default {
       availableDevices: [],
       isScanning: false,
       scanMode: false, // true: 扫描所有设备, false: 连接指定设备
+      batchScanMode: false, // 是否启用批量扫描模式
+      batchScanCount: 0, // 批量扫描计数
+      deviceSearchQuery: '', // 设备搜索查询
       // 重量秤设备UUID (HM-10模块)
       SERVICE_UUID: '0000ffe0-0000-1000-8000-00805f9b34fb',
       CHARACTERISTIC_UUID: '0000ffe1-0000-1000-8000-00805f9b34fb',
@@ -120,6 +175,21 @@ export default {
   mounted() {
     this.checkBrowserSupport();
   },
+
+  computed: {
+    // 过滤设备列表
+    filteredDevices() {
+      if (!this.deviceSearchQuery) {
+        return this.availableDevices;
+      }
+
+      const query = this.deviceSearchQuery.toLowerCase();
+      return this.availableDevices.filter(device =>
+        device.name.toLowerCase().includes(query) ||
+        device.id.toLowerCase().includes(query)
+      );
+    }
+  },
   methods: {
     async connectBluetooth() {
       // 检查浏览器支持
@@ -353,16 +423,70 @@ export default {
       console.log('设备列表已清除');
     },
 
+    // 开始批量扫描
+    async startBatchScan() {
+      if (this.isScanning) return;
+
+      this.batchScanCount = 0;
+      this.logBatchScan('开始批量扫描设备...');
+
+      // 开始第一次扫描
+      await this.performBatchScan();
+    },
+
+    // 执行批量扫描
+    async performBatchScan() {
+      if (!this.batchScanMode) return;
+
+      this.batchScanCount++;
+      this.logBatchScan(`执行第 ${this.batchScanCount} 次扫描...`);
+
+      try {
+        await this.scanAllDevices();
+
+        // 如果仍在批量扫描模式且没有达到最大次数,延迟后继续扫描
+        if (this.batchScanMode && this.batchScanCount < 10) { // 最多扫描10次
+          setTimeout(() => {
+            this.performBatchScan();
+          }, 2000); // 2秒延迟,避免过于频繁
+        } else if (this.batchScanCount >= 10) {
+          this.logBatchScan('已达到最大扫描次数 (10次),自动停止批量扫描');
+          this.batchScanMode = false;
+        }
+      } catch (error) {
+        this.logBatchScan(`批量扫描出错: ${error.message}`);
+        this.batchScanMode = false;
+      }
+    },
+
+    // 停止批量扫描
+    stopBatchScan() {
+      this.batchScanMode = false;
+      this.logBatchScan('批量扫描已手动停止');
+    },
+
+    // 批量扫描日志
+    logBatchScan(message) {
+      console.log(`[批量扫描] ${message}`);
+      // 可以在这里添加UI日志显示
+    },
+
     // 扫描单个蓝牙设备(避免浏览器卡死)
     async scanAllDevices() {
       if (this.isScanning) return;
 
       this.isScanning = true;
-      this.connectionStatus = '正在扫描设备...';
+      const scanMessage = this.batchScanMode ? `正在扫描设备 (${this.batchScanCount})...` : '正在扫描设备...';
+      this.connectionStatus = scanMessage;
 
       try {
         console.log('开始扫描蓝牙设备...');
 
+        // 在批量扫描模式下给出不同的提示
+        if (this.batchScanMode) {
+          alert(`请在设备列表中选择第 ${this.batchScanCount} 个设备。\n\n如果没有更多设备可以选择,请点击"取消"或关闭对话框来停止批量扫描。`);
+        }
+
         // 创建超时Promise,避免无限等待
         const timeoutPromise = new Promise((_, reject) => {
           setTimeout(() => reject(new Error('设备选择超时,请重试')), 30000);
@@ -378,23 +502,38 @@ export default {
 
         // 处理发现的设备
         this.addDeviceToList(device);
-        this.connectionStatus = `已发现设备: ${device.name || '未知设备'}`;
+        const deviceName = device.name || '未知设备';
+        this.connectionStatus = `已发现设备: ${deviceName}`;
+
+        if (this.batchScanMode) {
+          this.logBatchScan(`成功发现设备: ${deviceName}`);
+        }
 
-        console.log('设备扫描成功:', device.name);
+        console.log('设备扫描成功:', deviceName);
 
       } catch (error) {
         console.error('扫描设备失败:', error);
 
         if (error.name === 'NotFoundError') {
-          // 用户取消了设备选择,这是正常行为
-          this.connectionStatus = '扫描已取消';
+          // 用户取消了设备选择
+          if (this.batchScanMode) {
+            this.connectionStatus = '批量扫描已停止';
+            this.batchScanMode = false;
+            this.logBatchScan('用户取消扫描,批量扫描已停止');
+          } else {
+            this.connectionStatus = '扫描已取消';
+          }
           console.log('用户取消了设备选择');
         } else if (error.message.includes('超时')) {
           this.connectionStatus = '扫描超时';
-          alert('设备选择超时,请重试');
+          if (!this.batchScanMode) {
+            alert('设备选择超时,请重试');
+          }
         } else {
           this.connectionStatus = '扫描失败';
-          alert(`扫描失败: ${error.message}`);
+          if (!this.batchScanMode) {
+            alert(`扫描失败: ${error.message}`);
+          }
         }
       } finally {
         this.isScanning = false;
@@ -727,18 +866,117 @@ button {
 /* 扫描信息提示 */
 .scan-info {
   margin: 15px 0;
-  padding: 12px;
+  padding: 15px;
   background-color: #d1ecf1;
   border: 1px solid #bee5eb;
   border-radius: 8px;
   color: #0c5460;
 }
 
-.scan-info p {
+.scan-header h4 {
+  margin: 0 0 10px 0;
+  color: #0c5460;
+  font-size: 16px;
+}
+
+.scan-guide {
+  margin: 15px 0;
+}
+
+.guide-step {
+  display: flex;
+  align-items: center;
+  margin: 8px 0;
+  padding: 8px;
+  background-color: rgba(255, 255, 255, 0.7);
+  border-radius: 6px;
+}
+
+.step-number {
+  display: inline-block;
+  width: 24px;
+  height: 24px;
+  line-height: 24px;
+  text-align: center;
+  background-color: #17a2b8;
+  color: white;
+  border-radius: 50%;
+  margin-right: 12px;
+  font-weight: bold;
+  font-size: 12px;
+}
+
+.scan-tips {
+  margin-top: 15px;
+  padding-top: 15px;
+  border-top: 1px solid #bee5eb;
+}
+
+.scan-tips p {
   margin: 5px 0;
   font-size: 14px;
 }
 
+/* 扫描控制 */
+.scan-controls {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  align-items: flex-start;
+}
+
+.batch-scan-section {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  flex-wrap: wrap;
+}
+
+.batch-scan-toggle {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: bold;
+  cursor: pointer;
+}
+
+.batch-scan-toggle input[type="checkbox"] {
+  transform: scale(1.2);
+}
+
+.btn-batch-scan {
+  background-color: #28a745;
+  color: white;
+  padding: 8px 16px;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.btn-batch-scan:hover:not(:disabled) {
+  background-color: #218838;
+}
+
+.btn-batch-scan:disabled {
+  background-color: #6c757d;
+  cursor: not-allowed;
+}
+
+.btn-stop-batch {
+  background-color: #dc3545;
+  color: white;
+  padding: 8px 16px;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.btn-stop-batch:hover {
+  background-color: #c82333;
+}
+
 .mode-selection label {
   margin: 0 15px;
   font-size: 16px;
@@ -759,10 +997,53 @@ button {
   border-radius: 8px;
 }
 
-.device-list h3 {
-  margin-top: 0;
+.device-list-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15px;
+  flex-wrap: wrap;
+  gap: 15px;
+}
+
+.device-list-header h3 {
+  margin: 0;
   color: #333;
+}
+
+.device-search {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.search-input {
+  padding: 8px 12px;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  font-size: 14px;
+  min-width: 200px;
+}
+
+.btn-clear-search {
+  padding: 8px 12px;
+  background-color: #6c757d;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 12px;
+}
+
+.btn-clear-search:hover {
+  background-color: #545b62;
+}
+
+.no-results {
   text-align: center;
+  padding: 20px;
+  color: #666;
+  font-style: italic;
 }
 
 .device-grid {