From d7bd20eb79642e64cee2fb349af9f89a6285de41 Mon Sep 17 00:00:00 2001 From: ozh Date: Fri, 18 Sep 2026 17:24:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(device):=20=E8=AE=BE=E5=A4=87=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E6=8E=A5=E5=85=A5=E5=AE=9E=E6=97=B6=E4=BD=93=E5=BE=81?= =?UTF-8?q?=E2=80=94=E2=80=94EventChannel=20=E4=BC=A0=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=20+=203s=20=E8=BD=AE=E8=AF=A2=E5=88=B7?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HomeDevice 补 runTimeInfo.properties 体征字段类型(2026-09-18 联调实测) - 列表页跳详情改用 Taro.navigateTo 原生调用,EventChannel emit 整个 device 对象 - 详情页 getOpenerEventChannel 接收首屏零延迟渲染;setInterval 3s 轮询列表接口 按 installId 定位刷新(对齐 web 端 VitalSignsDashboard SWR refreshInterval=3000) - 体征六指标卡:呼吸率/体动/运动状态/在床状态/人体状态/人体位置, 枚举文案复用 MOVE_STATE_MAP/BODY_STATUS_MAP/ONBED_STATUS_MAP - 详情信息行新增安装房间 --- src/api/homeDevice.ts | 13 +++ src/pages/device/feature/detail.module.scss | 10 ++ src/pages/device/feature/detail.tsx | 103 ++++++++++++++++++-- src/pages/device/index.tsx | 8 +- 4 files changed, 124 insertions(+), 10 deletions(-) diff --git a/src/api/homeDevice.ts b/src/api/homeDevice.ts index 89f96c0..415be33 100644 --- a/src/api/homeDevice.ts +++ b/src/api/homeDevice.ts @@ -18,6 +18,18 @@ export interface HomeDeviceUnbindReq { installId: string // 设备安装 ID } +// 体征实时快照(runTimeInfo.properties,2026-09-18 联调实测;字段与 web 端 VitalSignsDashboard 一致) +export interface VitalSigns { + lastTime?: string + heartRate?: number + respiratoryRate?: number + moveState?: number + bodyStatus?: number + onbedStatus?: number + bodyPosition?: number + bodyMovement?: number +} + // 响应字段按 2026-09-17 联调实测 export interface HomeDevice { installId: string @@ -34,6 +46,7 @@ export interface HomeDevice { status: number onlineStatus: number // 1-在线 0-离线 lastOnlineTime: string | null + runTimeInfo?: { properties?: VitalSigns } | null } export function getHomeDeviceList(params: HomeDeviceListReq): Promise { diff --git a/src/pages/device/feature/detail.module.scss b/src/pages/device/feature/detail.module.scss index cb0e70c..5c126e8 100644 --- a/src/pages/device/feature/detail.module.scss +++ b/src/pages/device/feature/detail.module.scss @@ -71,6 +71,11 @@ display: block; } +.heartTime { + font-size: 24px; + color: $color-text-tertiary; +} + .metricGrid { display: flex; flex-wrap: wrap; @@ -98,6 +103,11 @@ .metricValue { font-size: 44px; font-weight: 700; } +.metricBlue { color: $color-primary-deep; } +.metricOrange { color: #fa8c16; } +.metricGreen { color: $color-success; } +.metricRed { color: $color-danger; } + .metricUnit { margin-left: 8px; font-size: 24px; diff --git a/src/pages/device/feature/detail.tsx b/src/pages/device/feature/detail.tsx index 204075e..d57b20d 100644 --- a/src/pages/device/feature/detail.tsx +++ b/src/pages/device/feature/detail.tsx @@ -1,22 +1,66 @@ +import { useState, useEffect, useRef } from 'react' import { useRouter } from '@tarojs/taro' +import Taro from '@tarojs/taro' import { View, Text } from '@tarojs/components' import { Navbar } from '@/components/navbar' import { ListRow, ListRowGroup } from '@/components/list-row' 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 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 deviceId = params.id ?? '' + const installId = params.id ?? '' const deviceName = params.name ? decodeURIComponent(params.name) : '设备详情' const deviceModel = params.model ?? '' - const online = params.online === '1' + + // 列表页 EventChannel 传入的设备对象:首屏零延迟 + const [device, setDevice] = useState(null) + const [online, setOnline] = useState(params.online === '1') + const homeIdRef = useRef(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) + homeIdRef.current = d.homeId + }) + }, []) + + // 轮询列表接口按 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 timer = setInterval(poll, POLL_INTERVAL) + return () => clearInterval(timer) + }, [installId]) + + 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) const goWifiConfig = () => navigateTo(ROUTES.DEVICE_BLE_CONFIG) @@ -28,7 +72,7 @@ export default function Detail() { ❤ - -- + {fmt(signs?.heartRate)} BPM @@ -36,14 +80,59 @@ export default function Detail() { - 体征数据接入中 + 心率健康 + {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)} + 分米 + - + + diff --git a/src/pages/device/index.tsx b/src/pages/device/index.tsx index 72d5f16..bef8ded 100644 --- a/src/pages/device/index.tsx +++ b/src/pages/device/index.tsx @@ -100,11 +100,13 @@ export default function DeviceList() { loadRooms() }) + // EventChannel 把整个设备对象传给详情页:首屏零延迟渲染体征快照 const goDetail = (device: HomeDevice) => { const name = encodeURIComponent(deviceLabel(device)) - navigateTo( - `${ROUTES.DEVICE_DETAIL}?id=${device.installId}&name=${name}&model=${device.subDeviceType ?? ''}&online=${device.onlineStatus ? 1 : 0}` - ) + Taro.navigateTo({ + url: `${ROUTES.DEVICE_DETAIL}?id=${device.installId}&name=${name}&model=${device.subDeviceType ?? ''}&online=${device.onlineStatus ? 1 : 0}`, + success: (res) => res.eventChannel.emit('device', device) + }) } const goAdd = () => navigateTo(ROUTES.DEVICE_ADD) const goRoom = () => navigateTo(ROUTES.SCENE_SELECT)