diff --git a/miniprogram/app.json b/miniprogram/app.json index f54cf00..3a02d88 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -29,6 +29,20 @@ "componentFramework": "glass-easel", "sitemapLocation": "sitemap.json", "lazyCodeLoading": "requiredComponents", + "plugins": { + "ppScale-plugin": { + "version": "0.0.24", + "provider": "wx0826af4e4908f524" + } + }, + "permission": { + "scope.userLocation": { + "desc": "您的位置信息将用于发现附近的蓝牙设备,以便连接蓝牙秤。" + } + }, + "requiredPrivateInfos": [ + "getLocation" + ], "tabBar": { "color": "#999999", "selectedColor": "#000000", diff --git a/miniprogram/app.ts b/miniprogram/app.ts index d03b92f..28d4986 100644 --- a/miniprogram/app.ts +++ b/miniprogram/app.ts @@ -11,13 +11,13 @@ App({ lefuService.init() // 已登录则跳过登录页 - const userInfo = wx.getStorageSync('userInfo') - if (userInfo) { - const connectDeviceInfo = wx.getStorageSync('connectDeviceInfo') - const hasDevice = connectDeviceInfo && Object.keys(connectDeviceInfo).length > 0 - wx.reLaunch({ - url: hasDevice ? '/pages/home/home' : '/pages/connectedDevice/connectedDevice' - }) - } + // const userInfo = wx.getStorageSync('userInfo') + // if (userInfo) { + // const connectDeviceInfo = wx.getStorageSync('connectDeviceInfo') + // const hasDevice = connectDeviceInfo && Object.keys(connectDeviceInfo).length > 0 + // wx.reLaunch({ + // url: hasDevice ? '/pages/home/home' : '/pages/connectedDevice/connectedDevice' + // }) + // } }, }) diff --git a/miniprogram/images/connectedWifi/close.png b/miniprogram/images/connectedWifi/close.png new file mode 100644 index 0000000..6e32387 Binary files /dev/null and b/miniprogram/images/connectedWifi/close.png differ diff --git a/miniprogram/images/connectedWifi/noData.png b/miniprogram/images/connectedWifi/noData.png new file mode 100644 index 0000000..9a7fab2 Binary files /dev/null and b/miniprogram/images/connectedWifi/noData.png differ diff --git a/miniprogram/images/connectedWifi/open.png b/miniprogram/images/connectedWifi/open.png new file mode 100644 index 0000000..2b2c650 Binary files /dev/null and b/miniprogram/images/connectedWifi/open.png differ diff --git a/miniprogram/images/connectedWifi/wifi.png b/miniprogram/images/connectedWifi/wifi.png new file mode 100644 index 0000000..a0882a8 Binary files /dev/null and b/miniprogram/images/connectedWifi/wifi.png differ diff --git a/miniprogram/images/connectedWifi/wifiPwd.png b/miniprogram/images/connectedWifi/wifiPwd.png new file mode 100644 index 0000000..2999896 Binary files /dev/null and b/miniprogram/images/connectedWifi/wifiPwd.png differ diff --git a/miniprogram/lefu/service.ts b/miniprogram/lefu/service.ts index e1d7bf2..2fabe85 100644 --- a/miniprogram/lefu/service.ts +++ b/miniprogram/lefu/service.ts @@ -68,6 +68,8 @@ const DEVICE_SETTINGS: DeviceSetting[] = [ }, ] +const TAG = '[LeFuService]' + class LeFuService { private _plugin: LeFuPlugin | null = null /** 设备协议对象,devicesModel 事件返回,用于 WiFi 配网等高级操作 */ @@ -84,16 +86,23 @@ class LeFuService { /** 连接后从 deviceInfo 事件获取的固件信息 */ private _deviceInfo: DeviceInfo | null = null + /** bus 事件是否已订阅(防止多次 startScan 重复 subscribe 累积) */ + private _busSubscribed = false + /** 回调注册(页面通过 on* 方法注册,stopScan 时清空) */ private _onDevicesListCb: ((devices: ScannedDevice[]) => void) | null = null private _onConnectStateCb: ((state: string) => void) | null = null + private _onDeviceConnectCb: (() => void) | null = null private _onProgressCb: ((data: ProgressData) => void) | null = null private _onLockedCb: ((data: LockData) => void) | null = null private _onDisconnectedCb: (() => void) | null = null + private _onDeviceInfoCb: ((info: DeviceInfo) => void) | null = null /** 在 app.ts onLaunch 中调用,加载 ppScale-plugin */ init(): void { + console.log(TAG, 'init() 开始加载插件') this._plugin = requirePlugin('ppScale-plugin') as LeFuPlugin + console.log(TAG, 'init() 插件加载完成:', !!this._plugin) } /** 获取插件(未初始化时抛出) */ @@ -111,14 +120,45 @@ class LeFuService { /** 注册设备列表回调(扫描到设备时触发) */ onDevicesList(cb: (devices: ScannedDevice[]) => void): void { + console.log(TAG, 'onDevicesList 回调已注册') this._onDevicesListCb = cb } /** 注册蓝牙连接状态回调 */ onConnectState(cb: (state: string) => void): void { + console.log(TAG, 'onConnectState 回调已注册') this._onConnectStateCb = cb } + /** 注册设备连接就绪回调(activeProtocol 可用时触发) */ + onDeviceConnect(cb: () => void): void { + console.log(TAG, 'onDeviceConnect 回调已注册') + this._onDeviceConnectCb = cb + } + + /** 当前蓝牙连接状态(connectState bus 事件实时更新) */ + private _connectState = '' + + /** 设备是否已连接(activeProtocol 就绪) */ + get isConnected(): boolean { + return !!this._activeProtocol + } + + /** 当前蓝牙连接状态字符串 */ + get connectState(): string { + return this._connectState + } + + /** 插件 BLUE_STATE 常量对象(供页面 wxml 条件判断用) */ + get BLUE_STATE(): Record { + return this._plugin?.BLUE_STATE ?? {} + } + + /** 连接后从 deviceInfo 事件获取的设备信息(含 modelNumber / serialNumber 等) */ + get deviceInfo(): DeviceInfo | null { + return this._deviceInfo + } + /** 注册实时测量回调(测量过程中多次触发) */ onMeasuring(cb: (data: ProgressData) => void): void { this._onProgressCb = cb @@ -134,57 +174,84 @@ class LeFuService { this._onDisconnectedCb = cb } + /** 注册 deviceInfo 就绪回调(设备名/固件信息可用时触发) */ + onDeviceInfo(cb: (info: DeviceInfo) => void): void { + this._onDeviceInfoCb = cb + } + // ─── 扫描与连接 ─────────────────────────────── /** * 开始蓝牙扫描 - * 先注册所有 bus 事件再调用 plugin.Blue.start() - * 页面 onShow 时调用 + * 对齐 master:先 setDeviceSetting → start,再 subscribe(master searchDevice.js 顺序) */ startScan(): void { + console.log(TAG, 'startScan() 开始,busSubscribed:', this._busSubscribed) const plugin = this._p - // 扫描到设备列表 - plugin.bus.subscribe('devicesList', (devList: RawDevice[]) => { - // 广播秤(BleAdv)自动调用 setbroadcastDev,无需用户手动点击连接 - const advDev = devList.find((item: RawDevice) => { - const model = plugin.Blue.getDeviceModel(item) - return model?.deviceConnectType === plugin.PPBluetoothDefine.PPDeviceConnectType.PPDeviceConnectTypeBleAdv - }) - if (advDev) { - plugin.Blue.setbroadcastDev(advDev) - } + // ① 先设置设备配置并启动扫描(必须在 subscribe 之前,与 master 一致) + plugin.Blue.setDeviceSetting(DEVICE_SETTINGS) + const deviceNames = DEVICE_SETTINGS.map(s => s.deviceName) + console.log(TAG, 'plugin.Blue.start() 调用,设备名称:', deviceNames) + plugin.Blue.start(deviceNames, false) - // 转换为展示层数据 + // bus 订阅只注册一次,stopScan 时重置,避免多次搜索导致订阅累积 + if (this._busSubscribed) { + console.log(TAG, 'bus 已订阅,跳过重复注册') + return + } + this._busSubscribed = true + + // ② 启动后再订阅设备列表(master 也是 start 后才 subscribe) + plugin.bus.subscribe('devicesList', (devList: RawDevice[]) => { + console.log(TAG, 'bus[devicesList] 触发,原始设备数:', devList?.length, devList?.map((d: RawDevice) => d.name || d.deviceName)) + if (!devList || !devList.length) { + this._onDevicesListCb?.([]) + return + } + // 直接用原始列表转换,不调用 getDeviceModel(master 也是直接传 res) const devices: ScannedDevice[] = devList.map((item: RawDevice) => ({ name: item.name || item.deviceName || '未知设备', raw: item, - model: plugin.Blue.getDeviceModel(item) || {}, + model: {}, })) + console.log(TAG, '转换后设备列表:', devices.map(d => d.name)) this._onDevicesListCb?.(devices) }) - // 协议对象(持有 activeProtocol,WiFi 配网等高级操作依赖它) + // 设备模型信息(含 mac 等,仅用于日志/调试) plugin.bus.subscribe('devicesModel', (res: any) => { - this._activeProtocol = res + console.log(TAG, 'bus[devicesModel] 触发,deviceMac:', res?.deviceMac) }) // 连接成功后获取固件信息(用于后续 OTA 版本比对) plugin.bus.subscribe('deviceInfo', (res: DeviceInfo) => { + console.log(TAG, 'bus[deviceInfo] 触发:',res, res?.firmwareRevision, res?.modelNumber) this._deviceInfo = res + this._onDeviceInfoCb?.(res) }) - // 连接成功 + // 连接建立:从 ScaleAction 获取 activeProtocol,启动数据进度(对齐 master configureDevice.js) plugin.bus.subscribe('deviceConnect', () => { + console.log(TAG, 'bus[deviceConnect] 触发,获取 activeProtocol,启动数据进度') this._reconnectCount = 0 - plugin.ScaleAction.startDataProgress() - this._startKeepAlive() + plugin.ScaleAction.startDataProgress(true) + this._activeProtocol = plugin.ScaleAction.getActiveProtocol() + this._connectState = plugin.BLUE_STATE.CONNECTSUCCESS + this._onDeviceConnectCb?.() }) - // 连接状态变化 + // 连接状态变化:CONNECTSUCCESS 时启动保活,CONNECTFAILED 时重连 plugin.bus.subscribe('connectState', (res: string) => { + console.log(TAG, 'bus[connectState] 触发:', res) + this._connectState = res this._onConnectStateCb?.(res) + if (res === plugin.BLUE_STATE.CONNECTSUCCESS) { + console.log(TAG, '连接成功,启动保活心跳') + this._startKeepAlive() + } if (res === plugin.BLUE_STATE.CONNECTFAILED) { + console.warn(TAG, '连接失败,触发重连逻辑') this._stopKeepAlive() this._doReconnect() } @@ -192,58 +259,51 @@ class LeFuService { // 实时测量数据 plugin.bus.subscribe('progressData', (res: ProgressData) => { + console.log(TAG, 'bus[progressData] 体重(g):', res?.weight) this._onProgressCb?.(res) }) // 测量锁定(完成) plugin.bus.subscribe('lockData', (res: LockData) => { + console.log(TAG, 'bus[lockData] 测量完成,体重(g):', res?.weight, '阻抗:', res?.resistance) this._onLockedCb?.(res) }) // 设备自动断开 plugin.bus.subscribe('deviceWillDisconnect', () => { + console.warn(TAG, 'bus[deviceWillDisconnect] 设备断开,触发重连') this._stopKeepAlive() this._onDisconnectedCb?.() this._doReconnect() }) - - // 开始扫描,传入设备配置过滤列表 - plugin.Blue.start(DEVICE_SETTINGS) } /** - * 主动连接指定设备 - * BleAdv 类型已在 devicesList 中自动处理,无需调用此方法 - * BleConnect 类型需要用户点击后调用 + * 主动连接指定设备(对齐 master:直接调 createBLEConnection,不判断 deviceConnectType) */ connect(device: ScannedDevice): void { - const connectType = this._p.PPBluetoothDefine.PPDeviceConnectType - if (device.model?.deviceConnectType === connectType.PPDeviceConnectTypeBleConnect) { - this._p.Blue.createBLEConnection(device.raw) - } + console.log(TAG, 'connect() 设备:', device.name) + this._p.Blue.createBLEConnection(device.raw) } /** 主动断开当前连接 */ disconnect(): void { + console.log(TAG, 'disconnect() 主动断开') this._stopKeepAlive() this._p.Blue.disconnect() } /** - * 停止扫描,清理所有 bus 订阅与回调 - * 页面 onUnload / onHide 时调用 + * 停止扫描(仅停止设备发现,不清连接态) + * connectedDevice onHide / onUnload 时调用 */ stopScan(): void { - this._stopKeepAlive() + console.log(TAG, 'stopScan() 停止扫描') this._stopReconnectTimer() - this._p.Blue.stop() - this._activeProtocol = null - this._deviceInfo = null + this._busSubscribed = false + wx.stopBluetoothDevicesDiscovery() this._onDevicesListCb = null - this._onConnectStateCb = null - this._onProgressCb = null - this._onLockedCb = null - this._onDisconnectedCb = null + this._onDeviceConnectCb = null } // ─── WiFi 配网 ──────────────────────────────── @@ -253,6 +313,7 @@ class LeFuService { * 内部延迟 2 秒后调用,15 秒超时 */ getWifiList(): Promise { + console.log(TAG, 'getWifiList() 开始') return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接,无法获取 WiFi 列表')) @@ -267,6 +328,7 @@ class LeFuService { setTimeout(() => { this._activeProtocol.dataFindSurroundDevice((res: any[]) => { clearTimeout(timer) + console.log(TAG, 'getWifiList 原始结果:', res) if (!res || res.length === 0) { this._activeProtocol.dataExitWifiConfig(() => {}) resolve([]) @@ -276,6 +338,7 @@ class LeFuService { ssid: item.ssid || item.name || '', signal: item.signal, })) + console.log(TAG, 'getWifiList 解析结果:', list.map(w => w.ssid)) resolve(list) }) }, 2000) @@ -290,6 +353,7 @@ class LeFuService { * 返回码 23 = 配网成功 */ configWifi(ssid: string, password: string, version: WifiVersion): Promise { + console.log(TAG, 'configWifi() ssid:', ssid, 'version:', version) return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接,无法配网')) @@ -297,9 +361,12 @@ class LeFuService { } const callback = (res: number) => { + console.log(TAG, 'configWifi 回调 res:', res) if (res === 23) { + console.log(TAG, 'configWifi 成功') resolve() } else { + console.error(TAG, 'configWifi 失败,错误码:', res) reject(new Error(`[LeFu] 配网失败,错误码:${res}`)) } } @@ -329,12 +396,14 @@ class LeFuService { * 返回 true = 已配网(0x01),false = 未配网 */ checkWifiConfig(): Promise { + console.log(TAG, 'checkWifiConfig() 查询配网状态') return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接')) return } this._activeProtocol.codeFetchWifiConfig((res: number | undefined | null) => { + console.log(TAG, 'checkWifiConfig 回调 res:', res, '已配网:', res === 0x01) resolve(res === 0x01) }) }) @@ -348,6 +417,7 @@ class LeFuService { * 调用时机由业务层决定,不自动触发 */ checkAndUpgrade(): Promise { + console.log(TAG, 'checkAndUpgrade() 开始检查固件版本') return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接')) @@ -359,6 +429,7 @@ class LeFuService { } const { firmwareRevision, modelNumber } = this._deviceInfo + console.log(TAG, '当前固件:', firmwareRevision, '型号:', modelNumber) const [mcuVer, bleVer, wifiVer, resVer] = (firmwareRevision || '').split('.') // 设备单段版本(如 "006")vs 服务端 x.y.z 格式(如 "0.0.6")比对 @@ -371,12 +442,14 @@ class LeFuService { get('weighingScale/v2/firmware/version', { type: modelNumber }, { loading: false }) .then(res => { const { mcuVersion, bleVersion, wifiVersion, resVersion } = res.result + console.log(TAG, '服务端固件版本:', { mcuVersion, bleVersion, wifiVersion, resVersion }) const needUpdate = isServerNewer(mcuVer, mcuVersion) || isServerNewer(bleVer, bleVersion) || isServerNewer(wifiVer, wifiVersion) || isServerNewer(resVer, resVersion) + console.log(TAG, '需要 OTA 升级:', needUpdate) if (!needUpdate) { resolve(false) return @@ -384,6 +457,7 @@ class LeFuService { // status: false = 成功,true = 失败 this._activeProtocol.codeOtaUpdate((status: boolean) => { + console.log(TAG, 'OTA 升级回调 status:', status) if (!status) { resolve(true) } else { @@ -399,6 +473,7 @@ class LeFuService { private _startKeepAlive(): void { this._stopKeepAlive() + console.log(TAG, '_startKeepAlive 保活心跳启动') this._keepAliveTimer = setInterval(() => { this._activeProtocol?.sendKeepAliveCode?.() }, 15000) @@ -408,6 +483,7 @@ class LeFuService { if (this._keepAliveTimer !== null) { clearInterval(this._keepAliveTimer) this._keepAliveTimer = null + console.log(TAG, '_stopKeepAlive 保活心跳停止') } } @@ -421,11 +497,16 @@ class LeFuService { /** 自动重连,最多 _maxReconnect 次,超限后等待用户手动重试 */ private _doReconnect(): void { - if (this._reconnectCount >= this._maxReconnect) return + if (this._reconnectCount >= this._maxReconnect) { + console.warn(TAG, '重连次数已达上限:', this._maxReconnect) + return + } this._reconnectCount++ + console.log(TAG, `_doReconnect 第 ${this._reconnectCount} 次重连`) this._stopReconnectTimer() this._reconnectTimer = setTimeout(() => { this._plugin?.Blue?.disconnect((res: any) => { + console.log(TAG, '重连 disconnect 回调:', res?.errCode) if (res?.errCode === 0) { this._plugin?.Blue?.startBluetoothDevicesDiscovery() } @@ -442,6 +523,7 @@ class LeFuService { * @param members 成员列表,必须包含且仅包含一个 isSelf=true 的主用户 */ syncMembersToDevice(members: DeviceMember[]): Promise { + console.log(TAG, 'syncMembersToDevice() 成员数:', members.length) return new Promise((resolve, reject) => { if (!this._activeProtocol) { reject(new Error('[LeFu] 设备未连接,无法同步成员')) @@ -461,6 +543,7 @@ class LeFuService { this._activeProtocol.codeClearDeviceData('01', (clearRes: number) => { clearTimeout(clearTimer) + console.log(TAG, 'codeClearDeviceData 回调:', clearRes) if (clearRes !== 0x00) { reject(new Error('[LeFu] 清除设备成员失败')) return @@ -480,11 +563,13 @@ class LeFuService { const syncNext = () => { if (index >= members.length) { if (syncTimer !== null) clearTimeout(syncTimer) + console.log(TAG, 'syncMembersToDevice 全部同步完成') resolve() return } const member = members[index] + console.log(TAG, `下发第 ${index + 1}/${members.length} 个成员:`, member.name) resetTimer() this._activeProtocol.dataSyncUserInfo( @@ -504,6 +589,7 @@ class LeFuService { recentData: [], }, (res: number) => { + console.log(TAG, `第 ${index + 1} 个成员同步回调:`, res) if (res === 0) { index++ } diff --git a/miniprogram/pages/connectedDevice/connectedDevice.less b/miniprogram/pages/connectedDevice/connectedDevice.less index b1365a7..7a76b77 100644 --- a/miniprogram/pages/connectedDevice/connectedDevice.less +++ b/miniprogram/pages/connectedDevice/connectedDevice.less @@ -4,6 +4,7 @@ height: 100%; padding: 48rpx 30rpx 0; box-sizing: border-box; + background-color: #ffffff; /* 三层同心圆波纹动画 */ .ripple { diff --git a/miniprogram/pages/connectedDevice/connectedDevice.ts b/miniprogram/pages/connectedDevice/connectedDevice.ts index 15a2966..cc273b2 100644 --- a/miniprogram/pages/connectedDevice/connectedDevice.ts +++ b/miniprogram/pages/connectedDevice/connectedDevice.ts @@ -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 + 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() diff --git a/miniprogram/pages/connectedDevice/connectedDevice.wxml b/miniprogram/pages/connectedDevice/connectedDevice.wxml index c0f6322..94462f8 100644 --- a/miniprogram/pages/connectedDevice/connectedDevice.wxml +++ b/miniprogram/pages/connectedDevice/connectedDevice.wxml @@ -54,7 +54,7 @@ - + diff --git a/miniprogram/pages/connectedWifi/connectedWifi.less b/miniprogram/pages/connectedWifi/connectedWifi.less index 82cfb1c..8998aa9 100644 --- a/miniprogram/pages/connectedWifi/connectedWifi.less +++ b/miniprogram/pages/connectedWifi/connectedWifi.less @@ -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; } diff --git a/miniprogram/pages/connectedWifi/connectedWifi.ts b/miniprogram/pages/connectedWifi/connectedWifi.ts index a5cdd89..2b20495 100644 --- a/miniprogram/pages/connectedWifi/connectedWifi.ts +++ b/miniprogram/pages/connectedWifi/connectedWifi.ts @@ -38,12 +38,21 @@ Page({ */ status: 'idle' as WifiStatus, - /** 顶部固定卡片(从上一页状态中读取,当前先用占位) */ + /** 顶部固定卡片 */ currentDevice: { id: '', name: '智能体重秤', } as CurrentDevice, + /** 蓝牙连接状态常量(从插件获取,供 wxml 做条件判断) */ + BLUE_STATE: {} as Record, + + /** 当前蓝牙连接状态(实时更新) */ + 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, diff --git a/miniprogram/pages/connectedWifi/connectedWifi.wxml b/miniprogram/pages/connectedWifi/connectedWifi.wxml index 94434d8..d901ab7 100644 --- a/miniprogram/pages/connectedWifi/connectedWifi.wxml +++ b/miniprogram/pages/connectedWifi/connectedWifi.wxml @@ -5,9 +5,9 @@ - + - {{selectedWifi.name}} + {{selectedWifi.ssid}} 密码 @@ -30,7 +30,7 @@ - + @@ -48,8 +48,18 @@ {{currentDevice.name}} - - 已连接 + + + 已连接 + + + + 连接断开 + + + + 连接中... + @@ -74,11 +84,13 @@ 网络列表(请选择一个网络进行连接) - + - - - {{item.name}} + + + + + {{item.ssid}} 已连接 @@ -86,10 +98,12 @@ - - - {{item.name}} - + + + + + {{item.ssid}} + @@ -108,7 +122,9 @@ 网络列表(请选择一个网络进行连接) - + + + 未查找到可用wifi,请重试