SEC_Warehouse/src/pages/device/feature/ble-config.tsx

145 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useState } from 'react'
import Taro from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { Navbar } from '@/components/navbar'
import { useBle } from '@/ble/hooks/useBle'
import { navigateTo } from '@/utils/navigation'
import { ROUTES } from '@/constants/routes'
import { getErrorMessage } from '@/utils/error'
import { DEVICE_ACTION_KEYS, ACTION_REGISTRY, isEp832Device } from '@/constants/deviceActions'
import type { BleDevice } from '@/ble/hooks/useBle'
import styles from './ble-config.module.scss'
export default function DeviceBleConfig() {
const { state, discovery } = useBle()
const [preparingId, setPreparingId] = useState('')
// 页面隐藏/卸载时清理搜索(对齐旧版 onHide/onUnload),连接保持供配网页使用
useEffect(() => {
return () => {
if (discovery.getState().isSearching) {
discovery.stopSearch(false)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const startScan = async () => {
if (state.searching) {
await discovery.stopSearch()
return
}
// startSearch 内部自动完成:Android 定位权限检查 + 适配器初始化 +
// 服务 UUID 过滤扫描(00F4/F400/FFF4)+ 4s 无结果兜底全扫 + 30s 自动停止
try {
await discovery.startSearch()
} catch {
// startSearch 内部已 toast 具体错误码,此处不再重复提示
}
}
// 点击"网络配置":连接 → 发现服务特征 → 订阅 F301(厂家确认无需鉴权)→ 打开配网页
const openWifiPanel = async (deviceId: string) => {
if (preparingId) return
setPreparingId(deviceId)
Taro.showLoading({ title: '连接设备中', mask: true })
let prepError: unknown = null
try {
await discovery.prepareWifiConfig(deviceId)
navigateTo(ROUTES.DEVICE_CONFIG)
} catch (error) {
prepError = error
} finally {
setPreparingId('')
// showLoading 与 showToast 共用同一遮罩层,必须先 hideLoading 再弹提示
Taro.hideLoading()
}
if (prepError) {
// 连接/订阅失败用模态弹窗,需用户主动确认,确保错误诊断信息可见可读
Taro.showModal({
title: '设备连接失败',
content: getErrorMessage(prepError),
showCancel: false,
confirmText: '知道了'
})
}
}
const invokeAction = (key: string, deviceId: string) => {
if (key === 'WIFI') openWifiPanel(deviceId)
// PARAMS/SERVER 仅占位,后续接入
}
const isActionBusy = (key: string, deviceId: string) =>
key === 'WIFI' && preparingId === deviceId
const renderDevice = (device: BleDevice) => {
const actions = (DEVICE_ACTION_KEYS[isEp832Device(device) ? 'EP832' : 'DEFAULT'] ?? DEVICE_ACTION_KEYS.DEFAULT)
.map((key) => ({ key, ...ACTION_REGISTRY[key] }))
return (
<View key={device.deviceId} className={styles.deviceCard}>
<View className={styles.deviceIcon} />
<View className={styles.deviceMain}>
<View className={styles.deviceNameRow}>
<Text className={styles.deviceName}>{String(device.showName ?? device.deviceId)}</Text>
{device.hasTargetService ? (
<Text className={styles.deviceTag}>00F4</Text>
) : device.matchedByName || device.matchedByPayload ? (
<Text className={`${styles.deviceTag} ${styles.nameTag}`}>名称匹配</Text>
) : null}
</View>
<Text className={styles.deviceIdText}>{String(device.deviceId)}</Text>
<View className={styles.deviceActions}>
{actions.map((action) => {
const busy = isActionBusy(action.key, device.deviceId)
return (
<View
key={action.key}
className={`${styles.actionBtn} ${busy ? styles.disabled : ''}`}
onClick={() => !busy && invokeAction(action.key, device.deviceId)}
>
<Text>{busy ? action.busyLabel : action.label}</Text>
</View>
)
})}
</View>
</View>
</View>
)
}
return (
<View className={styles.page}>
<Navbar title="设备配置" variant="hero" />
<View className={styles.searchWrap}>
<View
className={`${styles.searchBtn} ${state.searching ? styles.isSearching : ''}`}
onClick={startScan}
>
<Text>{state.searching ? '搜索中' : '搜索'}</Text>
</View>
</View>
<View className={styles.tipRow}>
<Text>*手机需开启蓝牙功能,Android手机还需开启定位功能</Text>
</View>
<View className={styles.listHeader}>
<Text className={styles.listTitle}>搜索到的设备列表</Text>
</View>
<View className={styles.listBody}>
{state.deviceList.length === 0 ? (
<View className={styles.emptyRow}><Text>暂无搜索结果</Text></View>
) : (
state.deviceList.map(renderDevice)
)}
</View>
</View>
)
}