|
|
@@ -2,10 +2,26 @@
|
|
|
<div>
|
|
|
<h1>蓝牙重量秤连接</h1>
|
|
|
|
|
|
+ <!-- 平台信息显示 -->
|
|
|
+ <div class="platform-info">
|
|
|
+ <p><strong>📱 检测到平台:</strong> {{ platformInfo }}</p>
|
|
|
+ <p><strong>🔧 蓝牙支持:</strong>
|
|
|
+ <span :class="isBluetoothSupported ? 'success' : 'error'">
|
|
|
+ {{ isBluetoothSupported ? '✅ 支持' : '❌ 不支持' }}
|
|
|
+ </span>
|
|
|
+ </p>
|
|
|
+ <p><strong>🌐 HTTPS环境:</strong>
|
|
|
+ <span :class="isHttpsEnvironment ? 'success' : 'error'">
|
|
|
+ {{ isHttpsEnvironment ? '✅ 是' : '❌ 否' }}
|
|
|
+ </span>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 浏览器支持检查 -->
|
|
|
<div v-if="!isBluetoothSupported" class="warning">
|
|
|
<p>⚠️ 您的浏览器不支持Web Bluetooth API</p>
|
|
|
<p>请使用Chrome、Edge或Opera浏览器,并确保在HTTPS环境下运行</p>
|
|
|
+ <p v-if="isMobileDevice">📱 在移动端,请确保使用系统浏览器而非微信等内置浏览器</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- 环境检查 -->
|
|
|
@@ -14,6 +30,17 @@
|
|
|
<p>请部署到HTTPS服务器或使用localhost进行开发</p>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- 移动端特殊提示 -->
|
|
|
+ <div v-if="isMobileDevice && isBluetoothSupported && isHttpsEnvironment" class="info">
|
|
|
+ <p>📱 <strong>移动端提示:</strong></p>
|
|
|
+ <ul>
|
|
|
+ <li>确保设备蓝牙已开启</li>
|
|
|
+ <li>蓝牙设备需要在附近(5米内)</li>
|
|
|
+ <li>首次使用需要授予蓝牙权限</li>
|
|
|
+ <li>如果扫描不到设备,请重启设备蓝牙</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div class="status">
|
|
|
<p>连接状态: <span :class="['connection-status', getStatusClass()]">{{ connectionStatus }}</span></p>
|
|
|
<p v-if="deviceName">已连接设备: {{ deviceName }}</p>
|
|
|
@@ -41,23 +68,15 @@
|
|
|
|
|
|
<!-- 扫描模式按钮组 -->
|
|
|
<div v-else class="scan-controls">
|
|
|
- <button @click="scanAllDevices" :disabled="isScanning" class="btn-scan">
|
|
|
- {{ isScanning ? '扫描中...' : '扫描设备' }}
|
|
|
+ <button @click="scanSingleDevice" :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 class="scan-info-compact">
|
|
|
+ <p><strong>💡 提示:</strong> 每次点击只能发现一个设备,重复点击可发现更多设备</p>
|
|
|
+ <p v-if="isMobileDevice" class="mobile-warning">
|
|
|
+ 📱 <strong>移动端注意:</strong> 确保设备蓝牙已开启且在附近
|
|
|
+ </p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -78,7 +97,7 @@
|
|
|
<div class="scan-guide">
|
|
|
<div class="guide-step">
|
|
|
<span class="step-number">1</span>
|
|
|
- <span>点击"扫描所有设备"按钮</span>
|
|
|
+ <span>点击"扫描单个设备"按钮</span>
|
|
|
</div>
|
|
|
<div class="guide-step">
|
|
|
<span class="step-number">2</span>
|
|
|
@@ -93,6 +112,16 @@
|
|
|
<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 v-if="isMobileDevice" class="mobile-scan-tips">
|
|
|
+ <p><strong>📱 移动端特殊提示:</strong></p>
|
|
|
+ <ul>
|
|
|
+ <li>确保App有蓝牙权限</li>
|
|
|
+ <li>设备需要开启蓝牙广播模式</li>
|
|
|
+ <li>尝试重启设备蓝牙功能</li>
|
|
|
+ <li>检查是否在微信等内置浏览器中</li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -162,9 +191,9 @@ export default {
|
|
|
availableDevices: [],
|
|
|
isScanning: false,
|
|
|
scanMode: false, // true: 扫描所有设备, false: 连接指定设备
|
|
|
- batchScanMode: false, // 是否启用批量扫描模式
|
|
|
- batchScanCount: 0, // 批量扫描计数
|
|
|
deviceSearchQuery: '', // 设备搜索查询
|
|
|
+ isMobileDevice: false, // 是否为移动设备
|
|
|
+ platformInfo: '', // 平台信息
|
|
|
// 重量秤设备UUID (HM-10模块)
|
|
|
SERVICE_UUID: '0000ffe0-0000-1000-8000-00805f9b34fb',
|
|
|
CHARACTERISTIC_UUID: '0000ffe1-0000-1000-8000-00805f9b34fb',
|
|
|
@@ -174,6 +203,7 @@ export default {
|
|
|
|
|
|
mounted() {
|
|
|
this.checkBrowserSupport();
|
|
|
+ this.detectPlatform();
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
@@ -423,73 +453,28 @@ 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() {
|
|
|
+ // 扫描单个蓝牙设备(简化版,避免卡死)
|
|
|
+ async scanSingleDevice() {
|
|
|
if (this.isScanning) return;
|
|
|
|
|
|
this.isScanning = true;
|
|
|
- const scanMessage = this.batchScanMode ? `正在扫描设备 (${this.batchScanCount})...` : '正在扫描设备...';
|
|
|
- this.connectionStatus = scanMessage;
|
|
|
+ this.connectionStatus = '正在扫描设备...';
|
|
|
|
|
|
try {
|
|
|
console.log('开始扫描蓝牙设备...');
|
|
|
|
|
|
- // 在批量扫描模式下给出不同的提示
|
|
|
- if (this.batchScanMode) {
|
|
|
- alert(`请在设备列表中选择第 ${this.batchScanCount} 个设备。\n\n如果没有更多设备可以选择,请点击"取消"或关闭对话框来停止批量扫描。`);
|
|
|
+ // 移动端特殊提示
|
|
|
+ if (this.isMobileDevice) {
|
|
|
+ console.log('📱 移动端扫描模式');
|
|
|
+ // 在移动端给出更简洁的提示,避免弹窗干扰
|
|
|
+ this.connectionStatus = '请在设备列表中选择蓝牙设备...';
|
|
|
}
|
|
|
|
|
|
- // 创建超时Promise,避免无限等待
|
|
|
+ // 创建超时Promise(根据平台调整超时时间)
|
|
|
+ const timeoutMs = this.isMobileDevice ? 45000 : 30000; // 移动端给更长时间
|
|
|
const timeoutPromise = new Promise((_, reject) => {
|
|
|
- setTimeout(() => reject(new Error('设备选择超时,请重试')), 30000);
|
|
|
+ setTimeout(() => reject(new Error('设备选择超时,请重试')), timeoutMs);
|
|
|
});
|
|
|
|
|
|
// 请求设备选择(带超时保护)
|
|
|
@@ -498,45 +483,70 @@ export default {
|
|
|
optionalServices: [this.SERVICE_UUID]
|
|
|
});
|
|
|
|
|
|
+ console.log('等待用户选择设备...');
|
|
|
const device = await Promise.race([devicePromise, timeoutPromise]);
|
|
|
|
|
|
// 处理发现的设备
|
|
|
this.addDeviceToList(device);
|
|
|
const deviceName = device.name || '未知设备';
|
|
|
- this.connectionStatus = `已发现设备: ${deviceName}`;
|
|
|
+ this.connectionStatus = `✅ 已发现设备: ${deviceName}`;
|
|
|
|
|
|
- if (this.batchScanMode) {
|
|
|
- this.logBatchScan(`成功发现设备: ${deviceName}`);
|
|
|
- }
|
|
|
+ console.log('设备扫描成功:', deviceName, device.id);
|
|
|
|
|
|
- console.log('设备扫描成功:', deviceName);
|
|
|
+ // 成功提示(仅在PC端显示,避免移动端弹窗干扰)
|
|
|
+ if (!this.isMobileDevice) {
|
|
|
+ setTimeout(() => {
|
|
|
+ alert(`设备 "${deviceName}" 已添加到列表!\n\n如需发现更多设备,请再次点击"扫描单个设备"按钮。`);
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('扫描设备失败:', error);
|
|
|
|
|
|
+ // 处理不同类型的错误
|
|
|
if (error.name === 'NotFoundError') {
|
|
|
- // 用户取消了设备选择
|
|
|
- if (this.batchScanMode) {
|
|
|
- this.connectionStatus = '批量扫描已停止';
|
|
|
- this.batchScanMode = false;
|
|
|
- this.logBatchScan('用户取消扫描,批量扫描已停止');
|
|
|
- } else {
|
|
|
- this.connectionStatus = '扫描已取消';
|
|
|
- }
|
|
|
+ // 用户主动取消选择
|
|
|
+ this.connectionStatus = '扫描已取消';
|
|
|
console.log('用户取消了设备选择');
|
|
|
+
|
|
|
+ if (!this.isMobileDevice) {
|
|
|
+ setTimeout(() => {
|
|
|
+ alert('扫描已取消。如需扫描设备,请再次点击"扫描单个设备"按钮。');
|
|
|
+ }, 300);
|
|
|
+ }
|
|
|
+
|
|
|
} else if (error.message.includes('超时')) {
|
|
|
- this.connectionStatus = '扫描超时';
|
|
|
- if (!this.batchScanMode) {
|
|
|
- alert('设备选择超时,请重试');
|
|
|
+ this.connectionStatus = '扫描超时,请重试';
|
|
|
+ console.log('设备选择超时');
|
|
|
+
|
|
|
+ if (!this.isMobileDevice) {
|
|
|
+ alert('设备选择超时,请重试。\n\n可能的原因:\n• 设备选择对话框响应慢\n• 用户长时间未选择设备\n• 浏览器权限问题');
|
|
|
}
|
|
|
+
|
|
|
+ } else if (error.name === 'NotAllowedError') {
|
|
|
+ this.connectionStatus = '权限被拒绝';
|
|
|
+ console.log('蓝牙权限被拒绝');
|
|
|
+
|
|
|
+ alert('蓝牙权限被拒绝!\n\n请检查:\n• 浏览器是否支持Web Bluetooth\n• 是否在HTTPS环境下\n• 是否授予了蓝牙访问权限');
|
|
|
+
|
|
|
+ } else if (error.name === 'NotSupportedError') {
|
|
|
+ this.connectionStatus = '不支持此功能';
|
|
|
+ console.log('Web Bluetooth不支持');
|
|
|
+
|
|
|
+ alert('您的浏览器不支持Web Bluetooth API\n\n请使用:\n• Chrome (56+)\n• Edge (79+)\n• Opera (43+)');
|
|
|
+
|
|
|
} else {
|
|
|
this.connectionStatus = '扫描失败';
|
|
|
- if (!this.batchScanMode) {
|
|
|
+ console.error('未知扫描错误:', error);
|
|
|
+
|
|
|
+ if (!this.isMobileDevice) {
|
|
|
alert(`扫描失败: ${error.message}`);
|
|
|
}
|
|
|
}
|
|
|
} finally {
|
|
|
+ // 确保扫描状态被重置
|
|
|
this.isScanning = false;
|
|
|
+ console.log('扫描过程结束');
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -688,6 +698,45 @@ export default {
|
|
|
} else {
|
|
|
console.warn('❌ 不在HTTPS环境下运行,Web Bluetooth API可能无法正常工作');
|
|
|
}
|
|
|
+ },
|
|
|
+
|
|
|
+ detectPlatform() {
|
|
|
+ // 检测设备类型
|
|
|
+ const userAgent = navigator.userAgent;
|
|
|
+ const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
|
|
+ const isAndroid = /Android/i.test(userAgent);
|
|
|
+ const isIOS = /iPad|iPhone|iPod/.test(userAgent);
|
|
|
+ const isWeChat = /MicroMessenger/i.test(userAgent);
|
|
|
+ const isEmbedded = window.parent !== window || window.top !== window;
|
|
|
+
|
|
|
+ this.isMobileDevice = isMobile;
|
|
|
+
|
|
|
+ // 构建平台信息
|
|
|
+ let platform = [];
|
|
|
+ if (isAndroid) platform.push('Android');
|
|
|
+ if (isIOS) platform.push('iOS');
|
|
|
+ if (!isMobile) platform.push('PC桌面端');
|
|
|
+ if (isWeChat) platform.push('微信内置浏览器');
|
|
|
+ if (isEmbedded) platform.push('嵌入式H5');
|
|
|
+
|
|
|
+ this.platformInfo = platform.join(' + ');
|
|
|
+
|
|
|
+ console.log('📱 检测到平台:', this.platformInfo);
|
|
|
+ console.log('📊 设备类型:', this.isMobileDevice ? '移动设备' : '桌面设备');
|
|
|
+ console.log('🌐 是否嵌入式:', isEmbedded ? '是' : '否');
|
|
|
+
|
|
|
+ // 安卓嵌入式H5的特殊处理
|
|
|
+ if (isAndroid && isEmbedded) {
|
|
|
+ console.warn('⚠️ 检测到安卓嵌入式H5环境,蓝牙功能可能受限');
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ isMobile,
|
|
|
+ isAndroid,
|
|
|
+ isIOS,
|
|
|
+ isWeChat,
|
|
|
+ isEmbedded
|
|
|
+ };
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -863,6 +912,42 @@ button {
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
+/* 平台信息 */
|
|
|
+.platform-info {
|
|
|
+ margin: 15px 0;
|
|
|
+ padding: 12px;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ border: 1px solid #dee2e6;
|
|
|
+ border-radius: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.platform-info p {
|
|
|
+ margin: 3px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.success { color: #28a745; font-weight: bold; }
|
|
|
+.error { color: #dc3545; font-weight: bold; }
|
|
|
+
|
|
|
+/* 信息提示 */
|
|
|
+.info {
|
|
|
+ margin: 15px 0;
|
|
|
+ padding: 12px;
|
|
|
+ background-color: #d1ecf1;
|
|
|
+ border: 1px solid #bee5eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ color: #0c5460;
|
|
|
+}
|
|
|
+
|
|
|
+.info ul {
|
|
|
+ margin: 8px 0 0 0;
|
|
|
+ padding-left: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.info li {
|
|
|
+ margin: 3px 0;
|
|
|
+}
|
|
|
+
|
|
|
/* 扫描信息提示 */
|
|
|
.scan-info {
|
|
|
margin: 15px 0;
|
|
|
@@ -925,57 +1010,20 @@ button {
|
|
|
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;
|
|
|
+.scan-info-compact {
|
|
|
font-size: 14px;
|
|
|
+ color: #666;
|
|
|
}
|
|
|
|
|
|
-.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;
|
|
|
+.mobile-warning {
|
|
|
+ color: #856404 !important;
|
|
|
+ background-color: #fff3cd;
|
|
|
+ padding: 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ margin-top: 8px !important;
|
|
|
+ border: 1px solid #ffeaa7;
|
|
|
}
|
|
|
|
|
|
-.btn-stop-batch:hover {
|
|
|
- background-color: #c82333;
|
|
|
-}
|
|
|
|
|
|
.mode-selection label {
|
|
|
margin: 0 15px;
|