[AI Generated]: feat(*): 修复连接设备页及配网页交互逻辑、蓝牙状态显示与设备名获取

This commit is contained in:
17792275749
2026-05-14 17:18:10 +08:00
parent 29680cb7a4
commit 00a1529256
14 changed files with 402 additions and 155 deletions
@@ -4,6 +4,7 @@
height: 100%;
padding: 48rpx 30rpx 0;
box-sizing: border-box;
background-color: #ffffff;
/* 三层同心圆波纹动画 */
.ripple {
@@ -1,103 +1,189 @@
import { lefuService } from '../../lefu/index'
import type { ScannedDevice } from '../../lefu/index'
/** 展示用设备项 */
/** 展示用设备项(仅存入 setData,不含复杂对象) */
interface DeviceItem {
/** 设备名称 */
name: string
/** 是否已连接 */
connected: boolean
/** 原始设备对象(连接时传回给 SDK) */
_raw: ScannedDevice
}
/** 页面状态 */
type DeviceStatus = 'idle' | 'searching' | 'found' | 'empty'
/** 页面级原始设备缓存,不进入 setData 避免序列化异常 */
let _rawDevices: ScannedDevice[] = []
const TAG = '[connectedDevice]'
Page({
data: {
/**
* 页面状态
* - idle: 未搜索(进入页面默认态)
* - searching: 扫描中
* - found: 扫描完成且有设备
* - empty: 扫描完成但无设备
*/
status: 'idle' as DeviceStatus,
/** 扫描到的设备列表 */
deviceList: [] as DeviceItem[],
/** 防止权限弹框重入 */
_modalLock: false,
},
onShow() {
// 注册回调后开始扫描
lefuService.onDevicesList((devices) => {
if (!devices.length) {
this.setData({ status: 'empty', deviceList: [] })
return
}
const list: DeviceItem[] = devices.map(d => ({
name: d.name,
connected: false,
_raw: d,
}))
this.setData({ status: 'found', deviceList: list })
})
lefuService.onConnectState((state) => {
const plugin = lefuService['_plugin']
if (!plugin) return
if (state === plugin.BLUE_STATE.CONNECTSUCCESS) {
// 蓝牙连接成功,存储设备信息后跳配网页
const connected = this.data.deviceList.find(d => d.connected)
if (connected) {
wx.setStorageSync('connectDeviceInfo', { name: connected.name })
}
wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' })
} else if (state === plugin.BLUE_STATE.WIFISUCCESS) {
// 设备已配网,直接进首页
wx.reLaunch({ url: '/pages/home/home' })
}
})
this.onStartSearch()
console.log(TAG, 'onShow')
},
onHide() {
// 页面隐藏时停止扫描,释放 bus 订阅
console.log(TAG, 'onHide, 停止扫描')
lefuService.stopScan()
},
onUnload() {
console.log(TAG, 'onUnload, 停止扫描')
lefuService.stopScan()
},
/** 开始 / 重新搜索 */
onStartSearch() {
/** 点击「开始搜索 / 重新搜索」按钮:先验权限,再扫描 */
async onStartSearch() {
console.log(TAG, 'onStartSearch, _modalLock:', this.data._modalLock)
if (this.data._modalLock) return
_rawDevices = []
this.setData({ status: 'searching', deviceList: [] })
await this._checkPermissionAndScan()
},
/** 检查蓝牙 + 位置权限,通过后开始扫描 */
async _checkPermissionAndScan() {
console.log(TAG, '_checkPermissionAndScan 开始')
try {
const { authSetting } = await wx.getSetting()
const setting = authSetting as Record<string, boolean | undefined>
const hasBle = setting['scope.bluetooth']
const hasLoc = setting['scope.userLocation']
console.log(TAG, '权限状态 → 蓝牙:', hasBle, '位置:', hasLoc)
if (hasBle && hasLoc) {
await this._openAdapterAndScan()
return
}
// 逐个申请缺失权限
const missing: Array<{ scope: string; label: string }> = []
if (!hasBle) missing.push({ scope: 'scope.bluetooth', label: '蓝牙' })
if (!hasLoc) missing.push({ scope: 'scope.userLocation', label: '位置' })
console.log(TAG, '缺失权限:', missing.map(m => m.label))
try {
for (const item of missing) {
console.log(TAG, '申请权限:', item.scope)
await wx.authorize({ scope: item.scope })
console.log(TAG, '权限申请成功:', item.scope)
}
await this._openAdapterAndScan()
} catch (err) {
console.warn(TAG, '权限申请被拒绝:', err)
this._showPermissionModal(missing.map(m => m.label).join('和'))
}
} catch (err) {
console.error(TAG, 'getSetting 失败:', err)
wx.showToast({ title: '获取权限设置失败', icon: 'none' })
this.setData({ status: 'idle' })
}
},
/** 引导用户去设置页开启权限 */
_showPermissionModal(permissionNames: string) {
console.log(TAG, '弹出权限引导框, 缺失:', permissionNames)
this.setData({ _modalLock: true })
wx.showModal({
title: '需要授权',
content: `小程序需要您的${permissionNames}权限才能搜索蓝牙设备,请前往手机设置开启。`,
confirmText: '去开启',
cancelText: '取消',
success: (res) => {
console.log(TAG, '权限弹框结果:', res.confirm ? '去开启' : '取消')
this.setData({ _modalLock: false, status: 'idle' })
if (res.confirm) {
wx.openSetting({
success: () => this.onStartSearch(),
})
}
},
fail: () => {
this.setData({ _modalLock: false, status: 'idle' })
},
})
},
/** 打开蓝牙适配器,成功后启动扫描 */
async _openAdapterAndScan() {
console.log(TAG, 'wx.openBluetoothAdapter() 开始')
try {
await wx.openBluetoothAdapter()
console.log(TAG, 'wx.openBluetoothAdapter() 成功')
this._startScan()
} catch (err: any) {
console.error(TAG, 'wx.openBluetoothAdapter() 失败, errCode:', err?.errCode, err?.errMsg)
if (err?.errCode === 10001) {
wx.showModal({
title: '蓝牙未开启',
content: '请先开启手机蓝牙后重试',
showCancel: false,
confirmText: '知道了',
success: () => this.setData({ status: 'idle' }),
})
} else {
wx.showToast({ title: '蓝牙初始化失败', icon: 'none' })
this.setData({ status: 'idle' })
}
}
},
/** 权限已就绪,注册设备列表回调并启动扫描 */
_startScan() {
console.log(TAG, '_startScan 启动')
lefuService.onDevicesList((devices) => {
console.log(TAG, 'onDevicesList 回调, 设备数:', devices.length, devices.map(d => d.name))
if (!devices.length) {
_rawDevices = []
this.setData({ status: 'empty', deviceList: [] })
return
}
_rawDevices = devices
// 保留已连接设备的状态,避免扫描刷新时把 connected 重置为 false
const connectedName = this.data.deviceList.find(d => d.connected)?.name
const list: DeviceItem[] = devices.map(d => ({
name: d.name,
connected: d.name === connectedName,
}))
this.setData({ status: 'found', deviceList: list })
})
lefuService.startScan()
console.log(TAG, 'lefuService.startScan() 已调用')
},
/** 连接指定设备 */
onConnect(e: WechatMiniprogram.TouchEvent) {
const index = e.currentTarget.dataset.index as number
const target = this.data.deviceList[index]
if (!target) return
const raw = _rawDevices[index]
console.log(TAG, 'onConnect, index:', index, 'device:', raw)
if (!raw) {
console.warn(TAG, 'onConnect: 找不到对应 raw device, index:', index)
return
}
// 更新 UI 连接状态
const list = this.data.deviceList.map((item, i) => ({
...item,
connected: i === index,
}))
this.setData({ deviceList: list })
// 调用 SDK 连接(BleAdv 类型已在 startScan 内自动处理,BleConnect 类型需此处触发)
lefuService.connect(target._raw)
wx.showLoading({ title: '连接中...', mask: true })
lefuService.connect(raw)
lefuService.onDeviceConnect(() => {
const list = this.data.deviceList.map((item, i) => ({
...item,
connected: i === index,
}))
this.setData({ deviceList: list })
wx.hideLoading()
console.log(TAG, '设备连接就绪,跳转配网页')
wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' })
})
},
/** 断开当前连接 */
onDisconnect() {
console.log(TAG, 'onDisconnect')
const list = this.data.deviceList.map(item => ({ ...item, connected: false }))
this.setData({ deviceList: list })
lefuService.disconnect()
@@ -54,7 +54,7 @@
<!-- 找到设备:统一循环,内部按 connected 切换 UI -->
<block wx:if="{{ status === 'found' }}">
<view class="device-list">
<block wx:for="{{ deviceList }}" wx:key="id">
<block wx:for="{{ deviceList }}" wx:key="name">
<view class="device-card">
<view class="card-icon"></view>
<view class="card-info">
@@ -167,26 +167,33 @@
display: flex;
flex-wrap: nowrap;
align-items: center;
>view {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
margin-right: 14rpx;
background-color: #2AC79F;
}
>text {
color: #2AC79F;
height: 28rpx;
font-size: 28rpx;
font-weight: 500;
line-height: 28rpx;
}
}
}
}
.status-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
margin-right: 14rpx;
flex-shrink: 0;
&--green { background-color: #2AC79F; }
&--red { background-color: #F24439; }
&--gray { background-color: #999999; }
}
.status-text {
height: 28rpx;
font-size: 28rpx;
font-weight: 500;
line-height: 28rpx;
&--green { color: #2AC79F; }
&--red { color: #F24439; }
&--gray { color: #999999; }
}
/* idle / searching 两态:居中文案 */
.wifiIdle,
.wifiSearching {
@@ -246,7 +253,7 @@
width: 100%;
flex: 1;
min-height: 0;
padding: 0 12rpx;
padding-left: 12rpx;
box-sizing: border-box;
}
@@ -259,15 +266,20 @@
align-items: center;
margin-bottom: 32rpx;
/* 左侧灰底圆形图标占位 */
/* 左侧 wifi 图标容器 */
>view:nth-of-type(1) {
width: 80rpx;
height: 80rpx;
overflow: hidden;
margin-right: 24rpx;
border-radius: 50%;
background-color: #F7F7F7;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
height: 100%;
}
}
/* WiFi 名称 */
@@ -284,17 +296,11 @@
>view:nth-of-type(2) {
width: 10rpx;
height: 18rpx;
background-color: #B6B6B6;
flex-shrink: 0;
}
}
/* 列表项:已连接态 */
.wifiItem-connected {
/* 左侧图标不加底色圆,保留图标自身颜色 */
>view:nth-of-type(1) {
background-color: transparent;
}
>text {
color: #1385FA;
@@ -364,6 +370,7 @@
>view {
width: 566rpx;
height: 499rpx;
margin-bottom: 40rpx;
border-radius: 16rpx;
background-color: #F5F8FF;
}
@@ -38,12 +38,21 @@ Page({
*/
status: 'idle' as WifiStatus,
/** 顶部固定卡片(从上一页状态中读取,当前先用占位) */
/** 顶部固定卡片 */
currentDevice: {
id: '',
name: '智能体重秤',
} as CurrentDevice,
/** 蓝牙连接状态常量(从插件获取,供 wxml 做条件判断) */
BLUE_STATE: {} as Record<string, string>,
/** 当前蓝牙连接状态(实时更新) */
connectState: '',
/** 顶部卡片蓝牙状态:connected / failed / connecting */
bleStatus: 'connected' as 'connected' | 'failed' | 'connecting',
/** WiFi 列表 */
wifiList: [] as WifiItem[],
@@ -60,21 +69,49 @@ Page({
type: true,
},
_toBleStatus(state: string): 'connected' | 'failed' | 'connecting' {
const bs = lefuService.BLUE_STATE
if (state === bs.CONNECTSUCCESS || state === bs.WIFISUCCESS) return 'connected'
if (state === bs.CONNECTFAILED) return 'failed'
return 'connecting'
},
onLoad() {
// idle 起步,通过统一入口触发搜索
this.setData({
bleStatus: this._toBleStatus(lefuService.connectState),
currentDevice: { id: '', name: lefuService.deviceInfo?.modelNumber ?? '智能体重秤' },
})
this.onStartSearch()
},
onShow() {
lefuService.onConnectState((state) => {
const bs = lefuService.BLUE_STATE
// 只处理明确的连接/断开状态,忽略蓝牙初始化过程中的中间状态
if (state === bs.CONNECTSUCCESS || state === bs.WIFISUCCESS || state === bs.CONNECTFAILED) {
this.setData({ bleStatus: this._toBleStatus(state) })
}
})
lefuService.onDeviceInfo((info) => {
this.setData({ currentDevice: { id: '', name: info.modelNumber } })
})
},
onUnload() {
// 页面卸载时不调用 stopScan,蓝牙连接由 home 页维持
wx.hideLoading()
if (this.data.status === 'configuring') {
lefuService.disconnect()
}
},
/** 从设备获取 WiFi 列表 */
onStartSearch() {
this.setData({ status: 'searching', wifiList: [] })
wx.showLoading({ title: '正在获取 WiFi 列表...', mask: true })
lefuService.getWifiList()
.then((items: LeFuWifiItem[]) => {
wx.hideLoading()
if (!items.length) {
this.setData({ status: 'empty' })
return
@@ -86,6 +123,7 @@ Page({
this.setData({ status: 'list', wifiList: list })
})
.catch(() => {
wx.hideLoading()
this.setData({ status: 'empty' })
})
},
@@ -139,7 +177,6 @@ Page({
lefuService.configWifi(selectedWifi.ssid, selectWifiPWD, this._wifiVersion)
.then(() => {
wx.hideLoading()
// 配网成功:标记该 WiFi,回列表态
const next = this.data.wifiList.map(item => ({
...item,
connected: item.ssid === selectedWifi.ssid,
@@ -5,9 +5,9 @@
<view class="setWifi-form">
<view>
<view>
<image src="/images/configureDevice/wifiPwd.png"/>
<image src="/images/connectedWifi/wifiPwd.png"/>
</view>
<view>{{selectedWifi.name}}</view>
<view>{{selectedWifi.ssid}}</view>
</view>
<view>
<view>密码</view>
@@ -30,7 +30,7 @@
</block>
</view>
<view bind:tap="inputTypeChange">
<image src="/images/configureDevice/{{type ? 'open' : 'close'}}.png"/>
<image src="/images/connectedWifi/{{type ? 'open' : 'close'}}.png"/>
</view>
</view>
</view>
@@ -48,8 +48,18 @@
<view>
<view>{{currentDevice.name}}</view>
<view>
<view></view>
<text>已连接</text>
<block wx:if="{{bleStatus === 'connected'}}">
<view class="status-dot status-dot--green"></view>
<text class="status-text status-text--green">已连接</text>
</block>
<block wx:elif="{{bleStatus === 'failed'}}">
<view class="status-dot status-dot--red"></view>
<text class="status-text status-text--red">连接断开</text>
</block>
<block wx:else>
<view class="status-dot status-dot--gray"></view>
<text class="status-text status-text--gray">连接中...</text>
</block>
</view>
</view>
</view>
@@ -74,11 +84,13 @@
<view>网络列表(请选择一个网络进行连接)</view>
</view>
<scroll-view class="wifiList" scroll-y>
<block wx:for="{{wifiList}}" wx:key="id">
<block wx:for="{{wifiList}}" wx:key="ssid">
<block wx:if="{{item.connected}}">
<view class="wifiItem wifiItem-connected" data-id="{{item.id}}" bind:tap="onTapWifi">
<view></view>
<text>{{item.name}}</text>
<view class="wifiItem wifiItem-connected" data-ssid="{{item.ssid}}" bind:tap="onTapWifi">
<view>
<image src="/images/connectedWifi/wifi.png" mode="aspectFit"/>
</view>
<text>{{item.ssid}}</text>
<view>
<text>已连接</text>
<view></view>
@@ -86,10 +98,12 @@
</view>
</block>
<block wx:else>
<view class="wifiItem" data-id="{{item.id}}" bind:tap="onTapWifi">
<view></view>
<text>{{item.name}}</text>
<view></view>
<view class="wifiItem" data-ssid="{{item.ssid}}" bind:tap="onTapWifi">
<view>
<image src="/images/connectedWifi/wifi.png" mode="aspectFit"/>
</view>
<text>{{item.ssid}}</text>
<view class="arrow"></view>
</view>
</block>
</block>
@@ -108,7 +122,9 @@
<view>网络列表(请选择一个网络进行连接)</view>
</view>
<view class="wifiEmpty">
<view></view>
<view>
<image src="/images/connectedWifi/noData.png" mode="aspectFit"/>
</view>
<text>未查找到可用wifi,请重试</text>
</view>
</block>