import { useState, useEffect, useRef, useCallback } from 'react' import { useRouter } from '@tarojs/taro' import Taro, { useDidShow, useDidHide } from '@tarojs/taro' import { View, Text } from '@tarojs/components' import { Navbar } from '@/components/navbar' import { ListRow, ListRowGroup } from '@/components/list-row' import { VitalWave } from '@/components/vital-wave' import { ROUTES } from '@/constants/routes' import { navigateTo, navigateBack } from '@/utils/navigation' import { showToast } from '@/utils/toast' import { MOVE_STATE_MAP, BODY_STATUS_MAP, ONBED_STATUS_MAP } from '@/constants/enums' import { getHomeDeviceDetail, getVitalRecords, unbindHomeDevice, type HomeDevice, type VitalSigns } from '@/api/homeDevice' import type { DeviceModel } from '@/constants/enums' import styles from './detail.module.scss' // 轮询间隔:与 web 端 VitalSignsDashboard SWR refreshInterval 对齐 const POLL_INTERVAL = 3000 export default function Detail() { const router = useRouter() const params = router.params as { id?: string; name?: string; model?: DeviceModel; online?: string } const installId = params.id ?? '' const deviceName = params.name ? decodeURIComponent(params.name) : '设备详情' const deviceModel = params.model ?? '' // 列表页 EventChannel 传入的设备对象:首屏零延迟 const [device, setDevice] = useState(null) const [online, setOnline] = useState(params.online === '1') const [wavePoints, setWavePoints] = useState([]) const [unbinding, setUnbinding] = useState(false) const deviceNameRef = useRef(null) const timerRef = useRef | null>(null) useEffect(() => { const inst = Taro.getCurrentInstance() const page = inst.page as unknown as { getOpenerEventChannel?: () => { on: (e: string, cb: (d: HomeDevice) => void) => void } } | undefined page?.getOpenerEventChannel?.()?.on('device', (d) => { setDevice(d) setOnline(d.onlineStatus === 1) deviceNameRef.current = d.deviceName }) }, []) // 单次刷新:设备详情(实时快照)+ 历史心率(波形) const fetchAll = useCallback(async () => { if (!installId) return try { const d = await getHomeDeviceDetail(installId) setDevice(d) setOnline(d.onlineStatus === 1) deviceNameRef.current = d.deviceName const records = await getVitalRecords(d.deviceName) // 倒序返回(最新在前),波形需按时间正序绘制 const pts = (records ?? []).map((r) => r.heartRate).filter((v): v is number => typeof v === 'number') setWavePoints(pts.reverse()) } catch { // 单次轮询失败不中断,下个周期重试 } }, [installId]) // 首拉 + useDidShow/useDidHide 控制轮询启停:页面压栈后台不空跑 useEffect(() => { fetchAll() }, [fetchAll]) useDidShow(() => { if (timerRef.current) clearInterval(timerRef.current) timerRef.current = setInterval(() => { fetchAll() }, POLL_INTERVAL) }) useDidHide(() => { if (timerRef.current) { clearInterval(timerRef.current) timerRef.current = null } }) useEffect(() => { return () => { if (timerRef.current) clearInterval(timerRef.current) } }, []) const signs: VitalSigns | undefined = device?.runTimeInfo?.properties const fmt = (v: number | undefined): string => (v === undefined || v === null ? '--' : String(v)) const goBleConfig = () => navigateTo(ROUTES.DEVICE_BLE_CONFIG) void goBleConfig // 解绑确认:防误触,成功后停轮询返回列表页 const confirmUnbind = () => { if (unbinding || !installId) return Taro.showModal({ title: '解绑设备', content: `确定解绑「${deviceName}」吗?解绑后需重新添加。`, confirmText: '解绑', confirmColor: '#EE4B4B', success: (res) => { if (!res.confirm) return doUnbind() } }) } const doUnbind = async () => { setUnbinding(true) try { await unbindHomeDevice({ installId }) showToast('已解绑') if (timerRef.current) { clearInterval(timerRef.current) timerRef.current = null } navigateBack() } catch (e) { showToast(e instanceof Error ? e.message : '解绑失败') setUnbinding(false) } } return ( ❤ {fmt(signs?.heartRate)} BPM {online ? '在线' : '离线'} 心率健康 {signs?.lastTime && ( 更新于 {signs.lastTime} )} 呼吸率 {fmt(signs?.respiratoryRate)} /min 体动 {fmt(signs?.bodyMovement)} 运动状态 {MOVE_STATE_MAP[signs?.moveState ?? -1] ?? '--'} 在床状态 {ONBED_STATUS_MAP[signs?.onbedStatus ?? -1] ?? '--'} 人体状态 {BODY_STATUS_MAP[signs?.bodyStatus ?? -1] ?? '--'} 人体位置 {fmt(signs?.bodyPosition)} 分米 设备信息 {/* BLE 配置流程联调未完成,暂隐藏两个配置入口 */} ) }