zhaohuanhuan 3 mesiacov pred
rodič
commit
495029cba5
2 zmenil súbory, kde vykonal 834 pridanie a 57 odobranie
  1. 6 0
      src/router/index.ts
  2. 828 57
      src/views/test.vue

+ 6 - 0
src/router/index.ts

@@ -176,6 +176,12 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'面单识别'},
     component: () => import('@/views/inbound/photoOCR/index.vue')
   },
+  {
+    path: '/test',
+    name: 'Test',
+    meta:{title:'测试'},
+    component: () => import('@/views/test.vue')
+  },
 ];
 
 // 创建路由实例

+ 828 - 57
src/views/test.vue

@@ -1,84 +1,855 @@
 <template>
   <div>
-    <input ref="inputValueRef" v-model="inputValue" placeholder="请输入内容" @keydown.enter="handleInputKeydown" />
-    <p>扫描的条形码值: {{ barcode }}</p>
+    <h1>蓝牙重量秤连接</h1>
+
+    <!-- 浏览器支持检查 -->
+    <div v-if="!isBluetoothSupported" class="warning">
+      <p>⚠️ 您的浏览器不支持Web Bluetooth API</p>
+      <p>请使用Chrome、Edge或Opera浏览器,并确保在HTTPS环境下运行</p>
+    </div>
+
+    <!-- 环境检查 -->
+    <div v-if="!isHttpsEnvironment && isBluetoothSupported" class="warning">
+      <p>⚠️ Web Bluetooth API 必须在HTTPS环境下使用</p>
+      <p>请部署到HTTPS服务器或使用localhost进行开发</p>
+    </div>
+
+    <div class="status">
+      <p>连接状态: <span :class="['connection-status', getStatusClass()]">{{ connectionStatus }}</span></p>
+      <p v-if="deviceName">已连接设备: {{ deviceName }}</p>
+      <p v-if="receivedData">最新数据: {{ receivedData }}</p>
+      <p v-if="weightValue !== null">重量值: {{ weightValue }} kg</p>
+      <p v-if="isConnecting">请在弹出的设备选择对话框中选择您的重量秤设备...</p>
+    </div>
+
+    <!-- 模式选择 -->
+    <div class="mode-selection">
+      <label>
+        <input type="radio" v-model="scanMode" :value="false">
+        连接重量秤设备
+      </label>
+      <label>
+        <input type="radio" v-model="scanMode" :value="true">
+        扫描所有设备
+      </label>
+    </div>
+
+    <div class="controls">
+      <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>
+      <button @click="disconnectBluetooth" :disabled="!device" class="btn-disconnect">
+        断开连接
+      </button>
+      <button @click="clearData" class="btn-clear">清除数据</button>
+      <button v-if="scanMode" @click="clearDeviceList" class="btn-clear">清除设备列表</button>
+    </div>
+
+    <!-- 扫描提示 -->
+    <div v-if="scanMode" class="scan-info">
+      <p>📱 <strong>扫描说明:</strong> 每次点击扫描按钮只能发现一个设备。如需发现更多设备,请多次点击"扫描所有设备"按钮。</p>
+      <p v-if="availableDevices.length === 0">💡 提示: 确保蓝牙设备已开启并在附近,然后点击扫描按钮。</p>
+    </div>
+
+    <!-- 设备列表 -->
+    <div v-if="scanMode && availableDevices.length > 0" class="device-list">
+      <h3>发现的设备 ({{ availableDevices.length }})</h3>
+      <div class="device-grid">
+        <div v-for="(device, index) in availableDevices" :key="device.id || index"
+             class="device-card"
+             :class="{ connected: device.id === (this.device && this.device.id) }"
+             @click="connectToDevice(device)">
+          <div class="device-info">
+            <h4>{{ device.name || '未知设备' }}</h4>
+            <p><strong>ID:</strong> {{ device.id }}</p>
+            <p v-if="device.rssi !== undefined"><strong>信号强度:</strong> {{ device.rssi }} dBm</p>
+            <p v-if="device.txPower !== undefined"><strong>发射功率:</strong> {{ device.txPower }} dBm</p>
+            <div v-if="device.adData && device.adData.serviceUUIDs && device.adData.serviceUUIDs.length > 0" class="services">
+              <strong>服务UUID:</strong>
+              <ul>
+                <li v-for="uuid in device.adData.serviceUUIDs" :key="uuid">{{ uuid }}</li>
+              </ul>
+            </div>
+            <p v-if="device.timestamp" class="timestamp">发现时间: {{ device.timestamp }}</p>
+          </div>
+          <button class="btn-connect-device" :disabled="isConnecting">
+            {{ device.id === (this.device && this.device.id) ? '已连接' : '连接' }}
+          </button>
+        </div>
+      </div>
+    </div>
+
+    <div class="data-history" v-if="dataHistory.length > 0">
+      <h3>数据历史记录</h3>
+      <ul>
+        <li v-for="(data, index) in dataHistory.slice(-10)" :key="index">
+          {{ data.timestamp }}: {{ data.value }}
+        </li>
+      </ul>
+    </div>
   </div>
 </template>
 
 <script>
-import { ref, onMounted, onBeforeUnmount } from 'vue';
-import { showToast } from 'vant'
-
 export default {
-  setup() {
-    const barcode = ref('');
-    const inputValue = ref('');
-    const inputValueRef=ref(null);
-    let typingTimer = null; // 用来控制防抖
-    const debounceDelay = 300; // 防抖延迟,300ms足够让条形码扫描器输入完成
-
-    // 处理全局的键盘事件
-    const handleGlobalKeydown = (event) => {
-      // 判断焦点是否在输入框中
-      const focusedElement = document.activeElement;
-      if (focusedElement.tagName.toLowerCase() === 'input' || focusedElement.tagName.toLowerCase() === 'textarea') {
-        return; // 如果焦点在输入框内,不处理条形码输入
+  name: 'BluetoothScale',
+  data() {
+    return {
+      device: null,
+      deviceName: '',
+      receivedData: '',
+      weightValue: null,
+      connectionStatus: '未连接',
+      isConnecting: false,
+      dataHistory: [],
+      isBluetoothSupported: false,
+      isHttpsEnvironment: false,
+      availableDevices: [],
+      isScanning: false,
+      scanMode: false, // true: 扫描所有设备, false: 连接指定设备
+      // 重量秤设备UUID (HM-10模块)
+      SERVICE_UUID: '0000ffe0-0000-1000-8000-00805f9b34fb',
+      CHARACTERISTIC_UUID: '0000ffe1-0000-1000-8000-00805f9b34fb',
+      CLIENT_CHARACTERISTIC_CONFIG: '00002902-0000-1000-8000-00805f9b34fb'
+    };
+  },
+
+  mounted() {
+    this.checkBrowserSupport();
+  },
+  methods: {
+    async connectBluetooth() {
+      // 检查浏览器支持
+      if (!navigator.bluetooth) {
+        alert('您的浏览器不支持Web Bluetooth API,请使用Chrome、Edge或Opera浏览器');
+        return;
+      }
+
+      // 检查是否在HTTPS环境下(生产环境必需)
+      if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
+        alert('Web Bluetooth API 必须在HTTPS环境下使用,请部署到HTTPS服务器或使用localhost');
+        return;
       }
-      // 处理条形码扫描输入
-      if (event.key.length === 1) { // 只处理字符键
-        barcode.value += event.key;
 
-        // 如果有一个定时器,清除它,重新计时
-        if (typingTimer) {
-          clearTimeout(typingTimer);
+      this.isConnecting = true;
+      this.connectionStatus = '正在连接...';
+
+      try {
+        console.log('开始请求蓝牙设备...');
+
+        // 创建超时Promise
+        const timeoutPromise = new Promise((_, reject) => {
+          setTimeout(() => reject(new Error('设备选择超时,请重试')), 30000); // 30秒超时
+        });
+
+        // 请求设备选择(带超时)
+        const devicePromise = navigator.bluetooth.requestDevice({
+          filters: [{
+            services: [this.SERVICE_UUID]
+          }],
+          optionalServices: [this.SERVICE_UUID]
+        });
+
+        const device = await Promise.race([devicePromise, timeoutPromise]);
+        console.log('设备已选择:', device.name);
+
+        // 检查设备是否已断开
+        if (!device) {
+          throw new Error('未选择设备');
+        }
+
+        this.connectionStatus = '正在连接到设备...';
+
+        // 创建连接超时
+        const connectTimeoutPromise = new Promise((_, reject) => {
+          setTimeout(() => reject(new Error('连接超时')), 10000); // 10秒连接超时
+        });
+
+        // 连接到GATT服务器(带超时)
+        const server = await Promise.race([
+          device.gatt.connect(),
+          connectTimeoutPromise
+        ]);
+
+        console.log('GATT服务器连接成功');
+        this.device = device;
+        this.deviceName = device.name || '未知设备';
+        this.connectionStatus = '正在配置服务...';
+
+        // 监听断开连接事件
+        device.addEventListener('gattserverdisconnected', this.handleDisconnection);
+
+        // 获取重量秤服务
+        console.log('获取服务...');
+        const service = await server.getPrimaryService(this.SERVICE_UUID);
+
+        // 获取特征
+        console.log('获取特征...');
+        const characteristic = await service.getCharacteristic(this.CHARACTERISTIC_UUID);
+
+        this.connectionStatus = '正在启动通知...';
+
+        // 设置通知
+        await characteristic.startNotifications();
+        characteristic.addEventListener('characteristicvaluechanged', this.handleCharacteristicValueChanged);
+
+        // 读取初始数据
+        console.log('读取初始数据...');
+        await this.readCharacteristic(characteristic);
+
+        this.connectionStatus = '已连接';
+        console.log(`成功连接到设备: ${this.deviceName}`);
+        this.isConnecting = false;
+
+      } catch (error) {
+        console.error('连接蓝牙设备失败:', error);
+        this.connectionStatus = '连接失败';
+        this.isConnecting = false;
+        this.resetConnection();
+
+        // 处理不同类型的错误
+        if (error.name === 'NotFoundError' || error.message.includes('未选择设备')) {
+          alert('未找到蓝牙设备,请确保设备已开启并在附近,然后重新点击连接');
+        } else if (error.name === 'NotAllowedError') {
+          alert('蓝牙权限被拒绝,请允许网站访问蓝牙设备');
+        } else if (error.name === 'NetworkError') {
+          alert('蓝牙连接失败,请检查设备是否支持BLE模式');
+        } else if (error.message.includes('超时')) {
+          alert(error.message + ',请重试');
+        } else {
+          alert(`连接失败: ${error.message}`);
         }
+      }
+    },
 
-        // 设置一个新的定时器,在一定时间后处理条形码
-        typingTimer = setTimeout(() => {
-          // 处理条形码完成逻辑,比如发送请求或其他操作
-          console.log('扫描完成:', barcode.value);
-          // showToast(inputValue.value)
-          // barcode.value = ''; // 清空条形码值(根据实际需要决定是否清空)
-        }, debounceDelay);
+    async disconnectBluetooth() {
+      if (this.device && this.device.gatt.connected) {
+        await this.device.gatt.disconnect();
+        console.log(`已断开设备: ${this.deviceName}`);
       }
-    };
 
-    // 处理输入框的键盘事件
-    const handleInputKeydown = (event) => {
-        if (event.key === 'Enter') {
-          event.preventDefault();
-          setTimeout(() => {
-            inputValueRef.value?.blur()
-          }, 300)
+      this.resetConnection();
+    },
+
+    handleDisconnection() {
+      console.log('设备断开连接');
+      this.resetConnection();
+      this.connectionStatus = '连接已断开';
+      // 可以在这里添加自动重连逻辑
+      // this.autoReconnect();
+    },
+
+    resetConnection() {
+      // 清理事件监听器
+      if (this.device) {
+        try {
+          this.device.removeEventListener('gattserverdisconnected', this.handleDisconnection);
+        } catch (e) {
+          console.log('清理事件监听器失败:', e);
         }
+      }
 
+      this.device = null;
+      this.deviceName = '';
+      this.receivedData = '';
+      this.weightValue = null;
+      this.connectionStatus = '未连接';
+      this.isConnecting = false;
+    },
 
+    // 检查连接状态
+    isConnected() {
+      return this.device && this.device.gatt && this.device.gatt.connected;
+    },
 
-      // 在输入框内,避免条形码输入逻辑
-    };
+    // 自动重连(可选功能)
+    async autoReconnect() {
+      if (this.isConnected()) return;
+
+      console.log('尝试自动重连...');
+      this.connectionStatus = '正在重连...';
+
+      try {
+        if (this.device && this.device.gatt) {
+          await this.device.gatt.connect();
+          this.connectionStatus = '已重新连接';
 
-    // 初始化时绑定全局键盘事件
-    onMounted(() => {
-      window.addEventListener('keydown', handleGlobalKeydown);
-    });
+          // 重新设置服务和特征
+          const server = this.device.gatt;
+          const service = await server.getPrimaryService(this.SERVICE_UUID);
+          const characteristic = await service.getCharacteristic(this.CHARACTERISTIC_UUID);
 
-    // 组件销毁时移除全局键盘事件监听
-    onBeforeUnmount(() => {
-      window.removeEventListener('keydown', handleGlobalKeydown);
-      if (typingTimer) {
-        clearTimeout(typingTimer); // 清除防抖定时器
+          await characteristic.startNotifications();
+          characteristic.addEventListener('characteristicvaluechanged', this.handleCharacteristicValueChanged);
+        }
+      } catch (error) {
+        console.error('自动重连失败:', error);
+        this.connectionStatus = '重连失败';
+        this.resetConnection();
       }
-    });
+    },
 
-    return {
-      barcode,
-      inputValue,  // 输入框的绑定值
-      handleInputKeydown, // 输入框的键盘事件处理
-    };
+    async readCharacteristic(characteristic) {
+      try {
+        const value = await characteristic.readValue();
+        this.processReceivedData(value);
+      } catch (error) {
+        console.error('读取特征值失败:', error);
+      }
+    },
+
+    handleCharacteristicValueChanged(event) {
+      const value = event.target.value;
+      this.processReceivedData(value);
+    },
+
+    processReceivedData(value) {
+      try {
+        // 将DataView转换为字节数组
+        const bytes = new Uint8Array(value.buffer);
+
+        // 转换为字符串 (重量秤通常发送ASCII数据)
+        let dataString = '';
+        for (let i = 0; i < bytes.length; i++) {
+          if (bytes[i] !== 0) { // 过滤掉空字节
+            dataString += String.fromCharCode(bytes[i]);
+          }
+        }
+
+        this.receivedData = dataString.trim();
+
+        // 尝试解析重量值 (假设数据格式为数字)
+        const weightMatch = this.receivedData.match(/(\d+(\.\d+)?)/);
+        if (weightMatch) {
+          this.weightValue = parseFloat(weightMatch[1]);
+        }
+
+        // 添加到历史记录
+        this.dataHistory.push({
+          timestamp: new Date().toLocaleTimeString(),
+          value: this.receivedData,
+          weight: this.weightValue
+        });
+
+        console.log(`接收到数据: ${this.receivedData}`, bytes);
+
+      } catch (error) {
+        console.error('处理接收数据失败:', error);
+        this.receivedData = `错误: ${error.message}`;
+      }
+    },
+
+    clearData() {
+      this.receivedData = '';
+      this.weightValue = null;
+      this.dataHistory = [];
+    },
+
+    clearDeviceList() {
+      this.availableDevices = [];
+      console.log('设备列表已清除');
+    },
+
+    // 扫描单个蓝牙设备(避免浏览器卡死)
+    async scanAllDevices() {
+      if (this.isScanning) return;
+
+      this.isScanning = true;
+      this.connectionStatus = '正在扫描设备...';
+
+      try {
+        console.log('开始扫描蓝牙设备...');
+
+        // 创建超时Promise,避免无限等待
+        const timeoutPromise = new Promise((_, reject) => {
+          setTimeout(() => reject(new Error('设备选择超时,请重试')), 30000);
+        });
+
+        // 请求设备选择(带超时保护)
+        const devicePromise = navigator.bluetooth.requestDevice({
+          acceptAllDevices: true,
+          optionalServices: [this.SERVICE_UUID]
+        });
+
+        const device = await Promise.race([devicePromise, timeoutPromise]);
+
+        // 处理发现的设备
+        this.addDeviceToList(device);
+        this.connectionStatus = `已发现设备: ${device.name || '未知设备'}`;
+
+        console.log('设备扫描成功:', device.name);
+
+      } catch (error) {
+        console.error('扫描设备失败:', error);
+
+        if (error.name === 'NotFoundError') {
+          // 用户取消了设备选择,这是正常行为
+          this.connectionStatus = '扫描已取消';
+          console.log('用户取消了设备选择');
+        } else if (error.message.includes('超时')) {
+          this.connectionStatus = '扫描超时';
+          alert('设备选择超时,请重试');
+        } else {
+          this.connectionStatus = '扫描失败';
+          alert(`扫描失败: ${error.message}`);
+        }
+      } finally {
+        this.isScanning = false;
+      }
+    },
+
+    // 添加设备到列表
+    addDeviceToList(device) {
+      const deviceInfo = {
+        id: device.id,
+        name: device.name || '未知设备',
+        rssi: device.rssi,
+        txPower: device.txPower,
+        adData: device.adData,
+        device: device, // 保存原始设备对象
+        timestamp: new Date().toLocaleString(),
+        discoveredAt: Date.now()
+      };
+
+      // 检查是否已存在
+      const existingIndex = this.availableDevices.findIndex(d => d.id === device.id);
+      if (existingIndex >= 0) {
+        // 更新现有设备信息
+        this.$set(this.availableDevices, existingIndex, { ...this.availableDevices[existingIndex], ...deviceInfo });
+        console.log('更新设备信息:', deviceInfo.name);
+      } else {
+        // 添加新设备
+        this.availableDevices.push(deviceInfo);
+        console.log('发现新设备:', deviceInfo.name);
+      }
+
+      // 按发现时间排序(最新的在前面)
+      this.availableDevices.sort((a, b) => b.discoveredAt - a.discoveredAt);
+    },
+
+    // 连接到特定设备
+    async connectToDevice(deviceInfo) {
+      if (this.isConnecting) return;
+
+      this.isConnecting = true;
+      this.connectionStatus = '正在连接到设备...';
+
+      try {
+        const device = deviceInfo.device;
+
+        console.log('正在连接到设备:', device.name);
+
+        // 如果设备已经连接,先断开
+        if (device.gatt && device.gatt.connected) {
+          await device.gatt.disconnect();
+        }
+
+        // 连接到GATT服务器
+        const server = await device.gatt.connect();
+        this.device = device;
+        this.deviceName = device.name || '未知设备';
+
+        // 监听断开连接事件
+        device.addEventListener('gattserverdisconnected', this.handleDisconnection);
+
+        this.connectionStatus = '正在查找服务...';
+
+        // 尝试获取重量秤服务,如果失败则查找所有可用服务
+        try {
+          const service = await server.getPrimaryService(this.SERVICE_UUID);
+          const characteristic = await service.getCharacteristic(this.CHARACTERISTIC_UUID);
+
+          // 设置通知
+          await characteristic.startNotifications();
+          characteristic.addEventListener('characteristicvaluechanged', this.handleCharacteristicValueChanged);
+
+          // 读取初始数据
+          await this.readCharacteristic(characteristic);
+
+          this.connectionStatus = '已连接 (重量秤模式)';
+          console.log('成功连接到重量秤设备');
+
+        } catch (serviceError) {
+          console.warn('未找到重量秤服务,尝试查找其他服务:', serviceError);
+
+          // 获取所有可用服务
+          const services = await server.getPrimaryServices();
+          console.log('发现的服务:', services.map(s => s.uuid));
+
+          // 查找包含我们特征的服务
+          let foundCharacteristic = null;
+          for (const service of services) {
+            try {
+              const characteristic = await service.getCharacteristic(this.CHARACTERISTIC_UUID);
+              foundCharacteristic = characteristic;
+              break;
+            } catch (e) {
+              // 继续查找下一个服务
+            }
+          }
+
+          if (foundCharacteristic) {
+            await foundCharacteristic.startNotifications();
+            foundCharacteristic.addEventListener('characteristicvaluechanged', this.handleCharacteristicValueChanged);
+            await this.readCharacteristic(foundCharacteristic);
+            this.connectionStatus = '已连接 (通用模式)';
+          } else {
+            this.connectionStatus = '已连接 (无可用特征)';
+            console.log('设备已连接但未找到可读特征');
+          }
+        }
+
+        this.isConnecting = false;
+
+      } catch (error) {
+        console.error('连接设备失败:', error);
+        this.connectionStatus = '连接失败';
+        this.isConnecting = false;
+        alert(`连接失败: ${error.message}`);
+      }
+    },
+
+    getStatusClass() {
+      switch (this.connectionStatus) {
+        case '未连接': return 'status-disconnected';
+        case '正在连接...':
+        case '正在连接到设备...':
+        case '正在配置服务...':
+        case '正在启动通知...':
+        case '正在重连...': return 'status-connecting';
+        case '已连接':
+        case '已重新连接': return 'status-connected';
+        case '连接失败':
+        case '连接已断开':
+        case '重连失败': return 'status-error';
+        default: return 'status-disconnected';
+      }
+    },
+
+    checkBrowserSupport() {
+      // 检查Web Bluetooth API支持
+      this.isBluetoothSupported = !!navigator.bluetooth;
+
+      // 检查是否在HTTPS环境下
+      this.isHttpsEnvironment = location.protocol === 'https:' ||
+                               location.hostname === 'localhost' ||
+                               location.hostname === '127.0.0.1';
+
+      if (this.isBluetoothSupported) {
+        console.log('✅ 浏览器支持Web Bluetooth API');
+      } else {
+        console.warn('❌ 浏览器不支持Web Bluetooth API');
+      }
+
+      if (this.isHttpsEnvironment) {
+        console.log('✅ 在HTTPS环境下运行');
+      } else {
+        console.warn('❌ 不在HTTPS环境下运行,Web Bluetooth API可能无法正常工作');
+      }
+    }
+  },
+
+  beforeUnmount() {
+    // 组件销毁前断开连接并清理
+    this.cleanup();
+  },
+
+  unmounted() {
+    // 确保清理完成
+    this.cleanup();
   },
+
+  cleanup() {
+    try {
+      if (this.device && this.device.gatt && this.device.gatt.connected) {
+        this.device.gatt.disconnect();
+        console.log('组件销毁时断开蓝牙连接');
+      }
+      this.resetConnection();
+    } catch (error) {
+      console.error('清理蓝牙连接失败:', error);
+    }
+  }
 };
 </script>
 
 <style scoped>
-/* 样式部分 */
+.warning {
+  margin: 20px 0;
+  padding: 15px;
+  background-color: #fff3cd;
+  border: 1px solid #ffeaa7;
+  border-radius: 8px;
+  color: #856404;
+}
+
+.warning p {
+  margin: 5px 0;
+  font-weight: bold;
+}
+
+.status {
+  margin: 20px 0;
+  padding: 15px;
+  background-color: #f5f5f5;
+  border-radius: 8px;
+}
+
+.status p {
+  margin: 5px 0;
+  font-size: 16px;
+}
+
+.connection-status {
+  font-weight: bold;
+}
+
+.connection-status.status-disconnected {
+  color: #666;
+}
+
+.connection-status.status-connecting {
+  color: #ffa500;
+}
+
+.connection-status.status-connected {
+  color: #28a745;
+}
+
+.connection-status.status-error {
+  color: #dc3545;
+}
+
+.controls {
+  margin: 20px 0;
+}
+
+button {
+  padding: 10px 20px;
+  margin: 0 10px 10px 0;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: background-color 0.3s;
+}
+
+.btn-connect {
+  background-color: #007bff;
+  color: white;
+}
+
+.btn-connect:hover:not(:disabled) {
+  background-color: #0056b3;
+}
+
+.btn-connect:disabled {
+  background-color: #6c757d;
+  cursor: not-allowed;
+}
+
+.btn-disconnect {
+  background-color: #dc3545;
+  color: white;
+}
+
+.btn-disconnect:hover:not(:disabled) {
+  background-color: #c82333;
+}
+
+.btn-disconnect:disabled {
+  background-color: #6c757d;
+  cursor: not-allowed;
+}
+
+.btn-clear {
+  background-color: #6c757d;
+  color: white;
+}
+
+.btn-clear:hover {
+  background-color: #545b62;
+}
+
+.btn-scan {
+  background-color: #17a2b8;
+  color: white;
+}
+
+.btn-scan:hover:not(:disabled) {
+  background-color: #138496;
+}
+
+.btn-scan:disabled {
+  background-color: #6c757d;
+  cursor: not-allowed;
+}
+
+.data-history {
+  margin-top: 30px;
+  padding: 15px;
+  background-color: #f8f9fa;
+  border-radius: 8px;
+}
+
+.data-history h3 {
+  margin-top: 0;
+  color: #333;
+}
+
+.data-history ul {
+  list-style-type: none;
+  padding: 0;
+}
+
+.data-history li {
+  padding: 8px 0;
+  border-bottom: 1px solid #dee2e6;
+  font-family: monospace;
+}
+
+.data-history li:last-child {
+  border-bottom: none;
+}
+
+/* 模式选择 */
+.mode-selection {
+  margin: 20px 0;
+  padding: 15px;
+  background-color: #e9ecef;
+  border-radius: 8px;
+  text-align: center;
+}
+
+/* 扫描信息提示 */
+.scan-info {
+  margin: 15px 0;
+  padding: 12px;
+  background-color: #d1ecf1;
+  border: 1px solid #bee5eb;
+  border-radius: 8px;
+  color: #0c5460;
+}
+
+.scan-info p {
+  margin: 5px 0;
+  font-size: 14px;
+}
+
+.mode-selection label {
+  margin: 0 15px;
+  font-size: 16px;
+  font-weight: bold;
+  cursor: pointer;
+}
+
+.mode-selection input[type="radio"] {
+  margin-right: 8px;
+  transform: scale(1.2);
+}
+
+/* 设备列表 */
+.device-list {
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #f8f9fa;
+  border-radius: 8px;
+}
+
+.device-list h3 {
+  margin-top: 0;
+  color: #333;
+  text-align: center;
+}
+
+.device-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 15px;
+  margin-top: 15px;
+}
+
+.device-card {
+  background-color: white;
+  border: 2px solid #dee2e6;
+  border-radius: 8px;
+  padding: 15px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  position: relative;
+}
+
+.device-card:hover {
+  border-color: #007bff;
+  box-shadow: 0 4px 8px rgba(0,123,255,0.1);
+}
+
+.device-card.connected {
+  border-color: #28a745;
+  background-color: #f8fff8;
+}
+
+.device-info h4 {
+  margin: 0 0 10px 0;
+  color: #333;
+  font-size: 16px;
+}
+
+.device-info p {
+  margin: 5px 0;
+  font-size: 14px;
+  color: #666;
+}
+
+.services {
+  margin-top: 10px;
+}
+
+.services ul {
+  margin: 5px 0 0 0;
+  padding-left: 20px;
+}
+
+.services li {
+  font-family: monospace;
+  font-size: 12px;
+  color: #666;
+}
+
+.btn-connect-device {
+  position: absolute;
+  bottom: 10px;
+  right: 10px;
+  padding: 6px 12px;
+  border: none;
+  border-radius: 4px;
+  background-color: #007bff;
+  color: white;
+  cursor: pointer;
+  font-size: 12px;
+  transition: background-color 0.3s;
+}
+
+.btn-connect-device:hover:not(:disabled) {
+  background-color: #0056b3;
+}
+
+.btn-connect-device:disabled {
+  background-color: #6c757d;
+  cursor: not-allowed;
+}
+
+.device-card.connected .btn-connect-device {
+  background-color: #28a745;
+}
+
+.timestamp {
+  font-size: 12px !important;
+  color: #999 !important;
+  margin-top: 8px !important;
+  font-style: italic;
+}
 </style>