Преглед изворни кода

手持-活动单蓝牙重量联调

zhaohuanhuan пре 2 месеци
родитељ
комит
d4fe0a7e4f

+ 337 - 22
src/views/outbound/check/activity/index.vue

@@ -6,9 +6,27 @@
         <van-icon name="arrow-left" size="25" />
         <div style="color: #fff">返回</div>
       </template>
-      <!--      <template #right>-->
-      <!--        <div class="nav-right" @click="onClickRight">提交任务</div>-->
-      <!--      </template>-->
+      <template #right>
+        <div
+          v-if="bluetoothConnected && connectedBluetoothDevice"
+          @click="handleDisconnectBluetooth"
+          class="nav-bluetooth-connected"
+        >
+          <div class="bluetooth-device-info">
+            <van-icon name="bluetooth" size="14px" />
+            <span class="device-name">{{ connectedBluetoothDevice.name }}</span>
+          </div>
+          <div class="bluetooth-disconnect-text">蓝牙断开</div>
+        </div>
+        <div
+          v-else
+          @click="handleOpenBluetoothScan"
+          class="nav-bluetooth-scan"
+        >
+          <van-icon name="bluetooth" size="16px" />
+          <span>蓝牙扫描</span>
+        </div>
+      </template>
     </van-nav-bar>
     <div class="activity">
       <div class="wave-title">
@@ -21,12 +39,33 @@
       <div class="scan-barcode">
         <van-field v-model.lazy="scanBarcode"  label-align="left" placeholder="请扫描商品条码/SKU" label="商品条码:"
                    class="input-barcode" autocomplete="off" @keydown.enter="_handlerScan(scanBarcode)"  />
-        <van-field  v-model.lazy="totalWeight" ref="weightRef" label-align="left" required placeholder="请输入商品重量KG" label="重&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;量:"
-                    autocomplete="off" type="number"  >
-          <template #button>
-            <div>KG</div>
-          </template>
-        </van-field>
+        <div class="weight-input-wrapper">
+          <van-field  v-model.lazy="totalWeight" ref="weightRef" label-align="left" required placeholder="请输入商品重量KG" label="重&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;量:"
+                      autocomplete="off" type="number" @input="handleWeightInput" @focus="handleWeightFocus" @keydown="handleWeightKeydown" 
+                      :class="{ 'weight-confirmed': weightConfirmed }">
+            <template #button>
+              <div>KG</div>
+            </template>
+          </van-field>
+          <van-button 
+            v-if="bluetoothConnected" 
+            type="warning" 
+            size="small" 
+            class="weight-reset-btn"
+            @click="resetWeight"
+          >
+            重置
+          </van-button>
+          <van-button 
+            v-if="bluetoothConnected" 
+            type="primary" 
+            size="small" 
+            class="weight-confirm-btn"
+            @click="confirmWeight"
+          >
+            确认
+          </van-button>
+        </div>
 <!--        :class="[totalWeight>0?'success-input-barcode':'error-input-barcode']"-->
       </div>
       <div class="order-detail">
@@ -88,17 +127,19 @@
            </tr>
            </thead>
            <tbody>
-           <tr v-for="(item, index) in orderList" :key="index" v-if="orderList.length>0"  :style="rowStyle(item)"  >
-             <td>{{ item.barcode }} <van-tag type="success" v-if="item.universalCode">万用</van-tag></td>
-             <td>{{item.qty}}/{{item.qtyOrdered}}</td>
-             <td>{{ item.qty }}</td>
-             <td>{{ statusMap[item.status] || '' }}</td>
-             <td v-if="isUniqueCode || isQualityCheck">
-               <van-tag v-if="item.uniqueRegExp" type="warning" >唯一码</van-tag>
-               <van-tag v-if="item.imeiRegExp" type="primary" >IMEI码</van-tag>
-               <van-tag v-if="item.qualityCheck" type="warning" >质检</van-tag>
-             </td>
-           </tr>
+           <template v-if="orderList.length>0">
+             <tr v-for="(item, index) in orderList" :key="index" :style="rowStyle(item)"  >
+               <td>{{ item.barcode }} <van-tag type="success" v-if="item.universalCode">万用</van-tag></td>
+               <td>{{item.qty}}/{{item.qtyOrdered}}</td>
+               <td>{{ item.qty }}</td>
+               <td>{{ statusMap[item.status] || '' }}</td>
+               <td v-if="isUniqueCode || isQualityCheck">
+                 <van-tag v-if="item.uniqueRegExp" type="warning" >唯一码</van-tag>
+                 <van-tag v-if="item.imeiRegExp" type="primary" >IMEI码</van-tag>
+                 <van-tag v-if="item.qualityCheck" type="warning" >质检</van-tag>
+               </td>
+             </tr>
+           </template>
            <tr v-else>
              <td colspan="4">
                <div>暂无数据</div>
@@ -121,6 +162,8 @@
     <related-materia ref="relatedMateriaRef" @cut-barcode="cutBarcode" />
     <!-- 复核组合商品-->
     <check-barcode-combine ref="checkBarcodeCombineRef"  @cutBarcode="cutBarcode" />
+    <!-- 蓝牙扫描-->
+    <bluetooth-scan :model-value="showBluetoothScan" @update:model-value="showBluetoothScan = $event" @connected="handleBluetoothConnected" @disconnected="handleBluetoothDisconnected" ref="bluetoothScanRef" />
   </div>
 </template>
 <script setup>
@@ -139,22 +182,192 @@ import orderListTable from '@/views/outbound/check/components/OrderListTable.vue
 import ReversePicking from '@/views/outbound/check/components/ReversePicking.vue'
 import RelatedMateria from '@/views/outbound/check/components/RelatedMateria.vue'
 import CheckBarcodeCombine from '@/views/outbound/check/components/CheckBarcodeCombine.vue'
+import BluetoothScan from '@/views/outbound/check/components/BluetoothScan.vue'
 const store = useStore()
 try {
   getHeader()
   androidFocus()
 } catch (error) {
 }
+// 打开蓝牙扫描(立即开始扫描)
+const handleOpenBluetoothScan = () => {
+  showBluetoothScan.value = true
+  // 延迟一下确保弹窗已打开,然后开始扫描
+  setTimeout(() => {
+    if (bluetoothScanRef.value) {
+      bluetoothScanRef.value.startScan()
+    }
+  }, 300)
+}
+
+// 蓝牙连接处理
+const handleBluetoothConnected = (device) => {
+  bluetoothConnected.value = true
+  connectedBluetoothDevice.value = device
+  weightConfirmed.value = false // 连接成功时重置确认状态,允许接收新的重量数据
+  // showNotify({ type: 'success', message: `已连接: ${device.name}` })
+}
+
+// 蓝牙断开处理
+const handleBluetoothDisconnected = () => {
+  bluetoothConnected.value = false
+  connectedBluetoothDevice.value = null
+  weightConfirmed.value = false // 断开时重置确认状态
+}
+
+// 停止接收蓝牙重量(用户输入或确认时调用)
+const stopReceivingBluetoothWeight = () => {
+  if (bluetoothConnected.value) {
+    weightConfirmed.value = true
+  }
+}
+
+// 处理重量输入(手动输入时立即停止接收蓝牙重量)
+const handleWeightInput = () => {
+  stopReceivingBluetoothWeight()
+}
+
+// 处理重量输入框获得焦点(用户开始输入时立即停止接收蓝牙重量)
+const handleWeightFocus = () => {
+  stopReceivingBluetoothWeight()
+}
+
+// 处理键盘输入(按下任何键时立即停止接收蓝牙重量)
+const handleWeightKeydown = () => {
+  stopReceivingBluetoothWeight()
+}
+
+// 重置重量(重置后继续接收蓝牙重量)
+const resetWeight = () => {
+  weightConfirmed.value = false
+  totalWeight.value = ''
+  showNotify({ type: 'success', duration: 2000, message: '已重置,继续接收蓝牙重量' })
+}
+
+// 确认重量(确认后不再接收蓝牙重量)
+const confirmWeight = () => {
+  if (!totalWeight.value || Number(totalWeight.value) <= 0) {
+    showNotify({ type: 'warning', duration: 3000, message: '请输入有效的重量值' })
+    return
+  }
+  stopReceivingBluetoothWeight()
+  showNotify({ type: 'success', duration: 2000, message: '重量已确认' })
+}
+
+// 蓝牙重量数据回调(仅在未输入且未确认时接收蓝牙重量)
+const handleBluetoothWeight = (weight, unit) => {
+  console.log(weight,unit,'handleBluetoothWeight')
+  // 只有在未确认重量(未输入且未点击确认)时才自动更新
+  if (!weightConfirmed.value && weight !== null && weight !== undefined) {
+    totalWeight.value = weight.toString()
+  }
+}
+
+// 蓝牙连接状态回调
+const handleBluetoothConnectionState = (isConnected) => {
+  bluetoothConnected.value = isConnected
+  if (bluetoothScanRef.value) {
+    bluetoothScanRef.value.handleConnectionState(isConnected)
+  }
+  if (!isConnected) {
+    // 断开连接时清除状态
+    handleBluetoothDisconnected()
+    // 确保组件内的状态也被清除
+    if (bluetoothScanRef.value) {
+      bluetoothScanRef.value.clearConnectedDevice()
+    }
+  } else {
+    // 连接成功时,从组件获取设备信息
+    const saved = localStorage.getItem('bluetooth-device')
+    if (saved) {
+      try {
+        const device = JSON.parse(saved)
+        handleBluetoothConnected(device)
+      } catch (error) {
+        console.error('解析保存的设备失败:', error)
+      }
+    }
+  }
+}
+
+// 蓝牙错误回调
+const handleBluetoothError = (errorMessage) => {
+  showNotify({ type: 'danger', message: `蓝牙错误: ${errorMessage}` })
+}
+
+// 初始化蓝牙回调
+const initBluetoothCallbacks = () => {
+  // 连接状态回调
+  window.onScaleConnectionState = handleBluetoothConnectionState
+
+  // 重量数据回调
+  window.onScaleWeight = handleBluetoothWeight
+
+  // 错误回调
+  window.onScaleError = handleBluetoothError
+}
+
+// 断开蓝牙连接
+const disconnectBluetooth = () => {
+  if (window.AndroidScale && bluetoothConnected.value) {
+    try {
+      window.AndroidScale.disconnect()
+      // 立即清除主页面状态
+      handleBluetoothDisconnected()
+      // 立即清除组件内的状态
+      if (bluetoothScanRef.value) {
+        bluetoothScanRef.value.clearConnectedDevice()
+      }
+    } catch (error) {
+      console.error('断开连接失败:', error)
+      showNotify({ type: 'danger', message: '断开连接失败' })
+      // 即使出错也清除状态
+      handleBluetoothDisconnected()
+      if (bluetoothScanRef.value) {
+        bluetoothScanRef.value.clearConnectedDevice()
+      }
+    }
+  } else {
+    // 如果没有 AndroidScale 或未连接,也清除状态
+    handleBluetoothDisconnected()
+    if (bluetoothScanRef.value) {
+      bluetoothScanRef.value.clearConnectedDevice()
+    }
+  }
+}
+
+// 处理断开连接(带确认)
+const handleDisconnectBluetooth = () => {
+  if (!bluetoothConnected.value) {
+    return
+  }
+  showConfirmDialog({
+    title: '温馨提示',
+    message: '确定要断开蓝牙连接吗?',
+  }).then(() => {
+    disconnectBluetooth()
+  }).catch(() => {
+    // 取消操作
+  })
+}
+
 // 页面初始化
 onMounted(() => {
   openListener()
   scanInit(_handlerScan)
+  initBluetoothCallbacks()
   loadData()
+  // 延迟加载已保存的蓝牙设备,确保组件已挂载
+  setTimeout(() => {
+    if (bluetoothScanRef.value) {
+      bluetoothScanRef.value.loadSavedDevice()
+    }
+  }, 100)
 })
 const warehouse = store.warehouse
 //收货容器号
 // JH-WH99-990
-const waveNo = ref('')
+const waveNo = ref('JH-WH99-990')
 const  isUniqueCode=ref(false)
 const isQualityCheck=ref(false)
 // 错误提示
@@ -163,6 +376,11 @@ const tips = ref('')
 const back = ref(true)
 const scanBarcode=ref('')
 const totalWeight=ref('')
+const showBluetoothScan=ref(false)
+const bluetoothConnected=ref(false)
+const bluetoothScanRef=ref(null)
+const connectedBluetoothDevice=ref(null)
+const weightConfirmed=ref(false) // 重量是否已确认
 const containerNoMap={
   'WH01':'FJ-WH01-20',
   'WH02':'FJ-WH02-20',
@@ -342,11 +560,13 @@ const reset=()=>{
     waveNo.value=''
     scanBarcode.value=''
     totalWeight.value=''
+    weightConfirmed.value = false // 重置确认状态
     orderDetail.value={}
     orderMap.value={}
     dataList.value=[]
     matchBarcodeList.value=[]
     tips.value='请扫描商品条码'
+    // 注意:不自动断开蓝牙连接,保持连接状态
     inputBarcodeRef.value?.show('', '请扫描波次/容器号', '')
   })
 }
@@ -410,7 +630,7 @@ const endCheck=()=>{
     return
   }
   const lastNumber=Object.keys(orderMap.value.dataGroup).length
-  if (totalWeight.value<=0 && lastNumber>0) {
+  if (Number(totalWeight.value)<=0 && lastNumber>0) {
     tips.value = '请输入重量';
     scanError()
     showNotify({ type: 'warning', duration: 3000, message: '请输入重量' });
@@ -564,6 +784,7 @@ const setBarcode = (code) => {
       matchBarcodeList.value=[]
       scanBarcode.value=''
       totalWeight.value=''
+      weightConfirmed.value = false // 切换波次时重置确认状态
       successNumber.value=0
     }
   }).catch(err => {
@@ -628,6 +849,11 @@ const loadData = () => {
 }
 onUnmounted(() => {
   closeListener()
+  // 清理蓝牙回调
+  disconnectBluetooth()
+  window.onScaleConnectionState = null
+  window.onScaleWeight = null
+  window.onScaleError = null
 })
 
 window.onRefresh = loadData
@@ -667,6 +893,31 @@ window.onRefresh = loadData
         font-size: 16px
         display: flex
         align-items: center
+
+      .weight-input-wrapper
+        display: flex
+        align-items: center
+        gap: 8px
+        width: 100%
+
+        ::v-deep(.van-field)
+          flex: 1
+
+        ::v-deep(.weight-confirmed)
+          .van-field__control
+            border-bottom: 2px solid #1ca600 !important
+            color: #1ca600
+
+        .weight-reset-btn
+          flex-shrink: 0
+          height: 36px
+          padding: 0 16px
+
+        .weight-confirm-btn
+          flex-shrink: 0
+          height: 36px
+          padding: 0 16px
+          margin-right: 15px
       .input-barcode
         ::v-deep(.van-field__control)
           border-bottom: 2px solid #0077ff
@@ -754,4 +1005,68 @@ window.onRefresh = loadData
         .task-table-bin tbody
           background: #cde7ff
 
+  .nav-bluetooth-scan
+    display: flex
+    align-items: center
+    gap: 4px
+    padding: 6px 12px
+    background: rgba(255, 255, 255, 0.2)
+    border: 1px solid rgba(255, 255, 255, 0.3)
+    border-radius: 16px
+    font-size: 13px
+    color: #fff
+    cursor: pointer
+    transition: all 0.3s ease
+    user-select: none
+    white-space: nowrap
+
+    &:active
+      background: rgba(255, 255, 255, 0.3)
+      transform: scale(0.95)
+
+    .van-icon
+      color: #4fc3f7
+
+  .nav-bluetooth-connected
+    display: flex
+    flex-direction: column
+    align-items: flex-end
+    gap: 2px
+    padding: 4px 10px
+    background: rgba(76, 175, 80, 0.2)
+    border: 1px solid rgba(76, 175, 80, 0.4)
+    border-radius: 12px
+    cursor: pointer
+    transition: all 0.3s ease
+    user-select: none
+    min-width: 80px
+
+    &:active
+      background: rgba(76, 175, 80, 0.3)
+      transform: scale(0.95)
+
+    .bluetooth-device-info
+      display: flex
+      align-items: center
+      gap: 4px
+      font-size: 12px
+      color: #fff
+      font-weight: 500
+      line-height: 1.2
+
+      .van-icon
+        color: #4caf50
+        flex-shrink: 0
+
+      .device-name
+        max-width: 100px
+        overflow: hidden
+        text-overflow: ellipsis
+        white-space: nowrap
+
+    .bluetooth-disconnect-text
+      font-size: 10px
+      color: rgba(255, 255, 255, 0.8)
+      line-height: 1.2
+
 </style>

+ 441 - 0
src/views/outbound/check/components/BluetoothScan.vue

@@ -0,0 +1,441 @@
+<template>
+  <van-popup
+    :show="show"
+    position="bottom"
+    :style="{ height: '70%' }"
+    round
+    closeable
+    close-icon-position="top-right"
+    @update:show="show = $event"
+    @close="handleClose"
+  >
+    <div class="bluetooth-scan">
+      <div class="scan-header">
+        <h3>蓝牙设备扫描</h3>
+        <div class="scan-status">
+          <van-loading v-if="isScanning" type="spinner" size="16px" />
+          <span v-if="isScanning" class="scan-text">正在扫描FAYA设备... (已发现 {{ devices.length }} 个)</span>
+          <span v-else-if="connectedDevice" class="scan-text">已连接设备: {{ connectedDevice.name }}</span>
+          <span v-else-if="devices.length > 0" class="scan-text">已发现 {{ devices.length }} 个FAYA设备,点击设备连接</span>
+          <span v-else class="scan-text">点击重新扫描开始搜索FAYA设备</span>
+        </div>
+      </div>
+
+      <div class="scan-actions">
+        <van-button
+          v-if="isScanning"
+          type="warning"
+          block
+          @click="handleStopScan"
+        >
+          停止扫描
+        </van-button>
+        <van-button
+          v-else-if="!connectedDevice"
+          type="primary"
+          block
+          @click="handleStartScan"
+        >
+          重新扫描
+        </van-button>
+      </div>
+
+      <div class="devices-list">
+        <div v-if="devices.length === 0 && !isScanning" class="empty-state">
+          <van-empty description="暂无设备,点击重新扫描" />
+        </div>
+        <div v-else class="device-items">
+          <div
+            v-for="device in devices"
+            :key="device.address"
+            class="device-item"
+            :class="{ 'device-connected': connectedDevice?.address === device.address }"
+            @click="handleConnect(device)"
+          >
+            <div class="device-info">
+              <div class="device-name">{{ device.name || '未知设备' }}</div>
+              <div class="device-address">{{ device.address }}</div>
+            </div>
+            <div class="device-action">
+              <van-tag v-if="connectedDevice?.address === device.address" type="success">
+                已连接
+              </van-tag>
+              <van-icon v-else name="arrow" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </van-popup>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+import { showToast, showNotify, closeToast } from 'vant'
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'connected', 'disconnected'])
+
+const show = ref(false)
+const isScanning = ref(false)
+const devices = ref([])
+const connectedDevice = ref(null)
+
+watch(() => props.modelValue, (val) => {
+  show.value = val
+})
+
+watch(show, (val) => {
+  emit('update:modelValue', val)
+  // 弹窗打开时,如果未连接设备,自动开始扫描
+  if (val && !connectedDevice.value && !isScanning.value) {
+    // 延迟一下确保弹窗已完全打开
+    setTimeout(() => {
+      handleStartScan()
+    }, 100)
+  } else if (!val) {
+    // 弹窗关闭时
+    closeToast() // 清除所有 toast
+    // 如果正在扫描,停止扫描
+    if (isScanning.value) {
+      handleStopScan()
+    }
+    // 如果正在连接,清除连接状态
+    if (connectingDevice.value) {
+      connectingDevice.value = null
+    }
+  }
+})
+
+// 开始扫描
+const handleStartScan = () => {
+  if (!window.AndroidScale) {
+    showNotify({ type: 'danger', message: '未找到 AndroidScale 接口' })
+    return
+  }
+  
+  // 检查缓存和设备状态,如果缓存中没有但组件状态有,清除组件状态
+  const saved = localStorage.getItem('bluetooth-device')
+  if (!saved && connectedDevice.value) {
+    console.log('检测到状态不一致,清除连接状态')
+    connectedDevice.value = null
+    connectingDevice.value = null
+  }
+  
+  // 如果组件状态显示已连接设备,不允许扫描
+  if (connectedDevice.value) {
+    showNotify({ type: 'warning', message: '请先断开当前连接' })
+    return
+  }
+  
+  isScanning.value = true
+  devices.value = []
+  
+  try {
+    window.AndroidScale.startScan()
+  } catch (error) {
+    console.error('开始扫描失败:', error)
+    showNotify({ type: 'danger', message: '扫描失败,请重试' })
+    isScanning.value = false
+  }
+}
+
+// 停止扫描
+const handleStopScan = () => {
+  if (!window.AndroidScale) {
+    showNotify({ type: 'danger', message: '未找到 AndroidScale 接口' })
+    return
+  }
+  
+  try {
+    // 如果支持停止扫描方法,调用它
+    if (window.AndroidScale.stopScan) {
+      window.AndroidScale.stopScan()
+    }
+    isScanning.value = false
+    closeToast() // 关闭扫描提示
+  } catch (error) {
+    console.error('停止扫描失败:', error)
+    isScanning.value = false
+    closeToast()
+    showNotify({ type: 'danger', message: '停止扫描失败' })
+  }
+}
+
+// 处理扫描结果回调
+const handleScanResult = (deviceStr) => {
+  try {
+    let deviceData
+    if (typeof deviceStr === 'string') {
+      deviceData = JSON.parse(deviceStr)
+    } else {
+      deviceData = deviceStr
+    }
+    
+    // 只处理名称为"FAYA"的设备
+    const deviceName = deviceData.name || ''
+    if (deviceName !== 'FAYA') {
+      return // 忽略非FAYA设备
+    }
+    
+    // 检查设备是否已存在
+    const exists = devices.value.some(d => d.address === deviceData.address)
+    if (!exists && deviceData.address) {
+      devices.value.push({
+        name: deviceData.name || '未知设备',
+        address: deviceData.address
+      })
+      
+      // 扫描到FAYA设备后自动停止扫描
+      if (isScanning.value) {
+        handleStopScan()
+      }
+    }
+  } catch (error) {
+    console.error('解析扫描结果失败:', error)
+  }
+}
+// 当前正在连接的设备
+const connectingDevice = ref(null)
+
+// 连接设备
+const handleConnect = async (device) => {
+  // 如果设备已连接,提示已连接
+  if (connectedDevice.value?.address === device.address) {
+    showToast({ type: 'success', message: '设备已连接' })
+    return
+  }
+  
+  if (!window.AndroidScale) {
+    showNotify({ type: 'danger', message: '未找到 AndroidScale 接口' })
+    return
+  }
+  
+  // 如果正在扫描,先停止扫描
+  if (isScanning.value) {
+    handleStopScan()
+  }
+  
+  try {
+    connectingDevice.value = device
+    window.AndroidScale.connect(device.address)
+  } catch (error) {
+    closeToast()
+    connectingDevice.value = null
+    console.error('连接失败:', error)
+    showNotify({ type: 'danger', message: '连接失败,请重试' })
+  }
+}
+
+// 处理连接状态变化
+const handleConnectionState = (isConnected) => {
+  closeToast()
+  console.log(isConnected,'isConnected')
+  
+  if (isConnected) {
+    // 连接成功
+    if (connectingDevice.value) {
+      connectedDevice.value = connectingDevice.value
+      // 保存到缓存
+      localStorage.setItem('bluetooth-device', JSON.stringify(connectedDevice.value))
+      emit('connected', connectedDevice.value)
+      connectingDevice.value = null
+      // 连接成功后自动关闭弹窗(不显示额外 toast)
+      setTimeout(() => {
+        closeToast() // 确保关闭所有 toast
+        show.value = false
+      }, 300)
+    }
+  } else {
+    // 断开连接
+    // 清除缓存
+    localStorage.removeItem('bluetooth-device')
+    connectedDevice.value = null
+    connectingDevice.value = null
+    emit('disconnected')
+    // 断开连接时也确保关闭 toast
+    closeToast()
+  }
+}
+
+// 关闭弹窗
+const handleClose = () => {
+  // 清除所有 toast
+  closeToast()
+  // 如果正在扫描,先停止扫描
+  if (isScanning.value) {
+    handleStopScan()
+  }
+  // 如果正在连接,清除连接状态
+  if (connectingDevice.value) {
+    connectingDevice.value = null
+  }
+  show.value = false
+}
+
+// 初始化回调
+const initCallbacks = () => {
+  // 扫描结果回调
+  window.onScaleScanResult = handleScanResult
+  
+  // 连接状态回调
+  window.onScaleConnectionState = handleConnectionState
+}
+
+// 加载已保存的设备
+const loadSavedDevice = () => {
+  try {
+    const saved = localStorage.getItem('bluetooth-device')
+    if (saved) {
+      const device = JSON.parse(saved)
+      connectedDevice.value = device
+      // 检查设备是否在列表中,如果不在则添加到列表
+      const exists = devices.value.some(d => d.address === device.address)
+      if (!exists) {
+        devices.value.push(device)
+      }
+      emit('connected', device)
+    }
+  } catch (error) {
+    console.error('加载保存的设备失败:', error)
+  }
+}
+
+// 断开连接
+const disconnect = () => {
+  // 立即清除连接状态(不等待回调)
+  const wasConnected = !!connectedDevice.value
+  connectedDevice.value = null
+  connectingDevice.value = null
+  localStorage.removeItem('bluetooth-device')
+  
+  if (wasConnected) {
+    emit('disconnected')
+  }
+  
+  if (window.AndroidScale) {
+    try {
+      window.AndroidScale.disconnect()
+      console.log('已调用 AndroidScale.disconnect()')
+    } catch (error) {
+      console.error('断开连接失败:', error)
+      showNotify({ type: 'danger', message: '断开连接失败' })
+    }
+  }
+}
+
+
+// 清除已连接设备状态(供外部调用)
+const clearConnectedDevice = () => {
+  connectedDevice.value = null
+  connectingDevice.value = null
+  // 同时清除缓存
+  localStorage.removeItem('bluetooth-device')
+  console.log('已清除连接设备状态')
+}
+
+// 暴露方法
+defineExpose({
+  handleConnectionState,
+  loadSavedDevice,
+  disconnect,
+  startScan: handleStartScan,
+  stopScan: handleStopScan,
+  clearConnectedDevice
+})
+
+// 初始化
+initCallbacks()
+loadSavedDevice()
+</script>
+
+<style scoped lang="sass">
+.bluetooth-scan
+  padding: 20px
+  height: 100%
+  display: flex
+  flex-direction: column
+  background: #fff
+
+  .scan-header
+    margin-bottom: 20px
+    text-align: center
+
+    h3
+      margin: 0 0 10px 0
+      font-size: 18px
+      font-weight: 600
+      color: #333
+
+    .scan-status
+      display: flex
+      align-items: center
+      justify-content: center
+      gap: 8px
+      font-size: 14px
+      color: #666
+
+      .scan-text
+        color: #999
+
+  .scan-actions
+    margin-bottom: 20px
+
+  .devices-list
+    flex: 1
+    overflow-y: auto
+
+    .empty-state
+      display: flex
+      align-items: center
+      justify-content: center
+      height: 100%
+      min-height: 200px
+
+    .device-items
+      display: flex
+      flex-direction: column
+      gap: 12px
+
+      .device-item
+        display: flex
+        align-items: center
+        justify-content: space-between
+        padding: 16px
+        background: #f7f8fa
+        border-radius: 8px
+        cursor: pointer
+        transition: all 0.3s ease
+
+        &:active
+          background: #ebedf0
+          transform: scale(0.98)
+
+        &.device-connected
+          background: #e8f5e9
+          border: 1px solid #4caf50
+
+        .device-info
+          flex: 1
+
+          .device-name
+            font-size: 16px
+            font-weight: 500
+            color: #333
+            margin-bottom: 4px
+
+          .device-address
+            font-size: 12px
+            color: #999
+            font-family: monospace
+
+        .device-action
+          display: flex
+          align-items: center
+</style>