From 05d46368d191b00f9e550d05fda14b16754d4cb4 Mon Sep 17 00:00:00 2001 From: ozh Date: Fri, 18 Sep 2026 17:36:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(device):=20=E8=AF=A6=E6=83=85=E9=A1=B5?= =?UTF-8?q?=E6=8E=A5=E4=B8=93=E7=94=A8=E6=8E=A5=E5=8F=A3=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=8D=95=E8=AE=BE=E5=A4=87=E8=AF=A6=E6=83=85=E8=BD=AE=E8=AF=A2?= =?UTF-8?q?=20+=20=E5=8E=86=E5=8F=B2=E5=BF=83=E7=8E=87=E6=B3=A2=E5=BD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - homeDevice.ts 新增 getHomeDeviceDetail(GET /ylHomeDevice/get/{installId},实测含 runTimeInfo) 与 getVitalRecords(web 端同源 /iot/propertyRecord/getPropertyRecords,按 deviceName 查近一天倒序) - 详情页轮询从「整列表再过滤」改为单设备详情接口,减小轮询载荷 - 恢复 VitalWave 心率波形 Canvas:历史 heartRate 序列按时间正序绘制贝塞尔平滑曲线 + 渐变填充 - 轮询生命周期 useDidShow 启停 / useDidHide 暂停,页面压栈后台不空跑 --- src/api/homeDevice.ts | 48 +++++++++++++++++++ src/pages/device/feature/detail.tsx | 74 +++++++++++++++++++---------- 2 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/api/homeDevice.ts b/src/api/homeDevice.ts index 415be33..0d38a19 100644 --- a/src/api/homeDevice.ts +++ b/src/api/homeDevice.ts @@ -53,6 +53,54 @@ export function getHomeDeviceList(params: HomeDeviceListReq): Promise) } +// 单设备详情(GET + 路径参数,含 runTimeInfo 实时快照) +export function getHomeDeviceDetail(installId: string): Promise { + return request.get(`/yanglao/app/ylHomeDevice/get/${installId}`) +} + +// —— 历史体征记录(web 端 IotPropertyRecord 同源接口,2026-09-18 实测)—— +export interface VitalRecord { + recordId: string + deviceName: string + name: string | null + time: string + heartRate?: number + respiratoryRate?: number + moveState?: number + bodyStatus?: number + bodyPosition?: number + bodyMovement?: number + productKey?: string +} + +interface PropertyRecordsReq { + pageIndex: number + pageSize: number + queryParams: { + searchType: 'deviceName' + deviceName: string + createTime?: string // '1' = 近一天(web 端约定) + sortColumn?: string + sortDirection?: string + } +} + +// 按 deviceName 查历史体征,倒序返回(最新的在前) +export function getVitalRecords(deviceName: string, pageSize = 50): Promise { + const params: PropertyRecordsReq = { + pageIndex: 1, + pageSize, + queryParams: { + searchType: 'deviceName', + deviceName, + createTime: '1', + sortColumn: 'createTime', + sortDirection: 'desc' + } + } + return request.post('/iot/propertyRecord/getPropertyRecords', params as unknown as Record) +} + export function addHomeDevice(params: HomeDeviceAddReq): Promise { return request.post('/yanglao/app/ylHomeDevice/add', params as unknown as Record) } diff --git a/src/pages/device/feature/detail.tsx b/src/pages/device/feature/detail.tsx index d57b20d..497b752 100644 --- a/src/pages/device/feature/detail.tsx +++ b/src/pages/device/feature/detail.tsx @@ -1,17 +1,20 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useRef, useCallback } from 'react' import { useRouter } from '@tarojs/taro' -import Taro 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 } from '@/utils/navigation' import { MOVE_STATE_MAP, BODY_STATUS_MAP, ONBED_STATUS_MAP } from '@/constants/enums' -import { getHomeDeviceList, type HomeDevice, type VitalSigns } from '@/api/homeDevice' +import { + getHomeDeviceDetail, getVitalRecords, type HomeDevice, type VitalSigns +} from '@/api/homeDevice' import type { DeviceModel } from '@/constants/enums' import styles from './detail.module.scss' -// 体征轮询间隔:与 web 端 VitalSignsDashboard 的 SWR refreshInterval 对齐 +// 轮询间隔:与 web 端 VitalSignsDashboard SWR refreshInterval 对齐 const POLL_INTERVAL = 3000 export default function Detail() { @@ -25,7 +28,9 @@ export default function Detail() { // 列表页 EventChannel 传入的设备对象:首屏零延迟 const [device, setDevice] = useState(null) const [online, setOnline] = useState(params.online === '1') - const homeIdRef = useRef(null) + const [wavePoints, setWavePoints] = useState([]) + const deviceNameRef = useRef(null) + const timerRef = useRef | null>(null) useEffect(() => { const inst = Taro.getCurrentInstance() @@ -33,33 +38,51 @@ export default function Detail() { page?.getOpenerEventChannel?.()?.on('device', (d) => { setDevice(d) setOnline(d.onlineStatus === 1) - homeIdRef.current = d.homeId + deviceNameRef.current = d.deviceName }) }, []) - // 轮询列表接口按 installId 定位目标设备,刷新体征快照(对齐 web 端 SWR 轮询模式) - useEffect(() => { - const poll = async () => { - if (!installId) return - const homeId = homeIdRef.current - if (!homeId) return - try { - const list = await getHomeDeviceList({ homeId }) - const next = (list ?? []).find((d) => d.installId === installId) - if (next) { - setDevice(next) - setOnline(next.onlineStatus === 1) - } - } catch { - // 单次轮询失败不中断定时器,下个周期重试 - } + // 单次刷新:设备详情(实时快照)+ 历史心率(波形) + 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 { + // 单次轮询失败不中断,下个周期重试 } - const timer = setInterval(poll, POLL_INTERVAL) - return () => clearInterval(timer) }, [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) @@ -81,6 +104,7 @@ export default function Detail() { 心率健康 + {signs?.lastTime && ( 更新于 {signs.lastTime} )}