diff --git a/src/api/homeDevice.ts b/src/api/homeDevice.ts index 0d38a19..ca5fba7 100644 --- a/src/api/homeDevice.ts +++ b/src/api/homeDevice.ts @@ -30,6 +30,13 @@ export interface VitalSigns { bodyMovement?: number } +// ED719 跌倒检测快照(2026-09-21 实测:status 枚举见 FALL_STATUS_MAP;version 为固件版本) +export interface FallDetectSigns { + lastTime?: string + status?: number + version?: string +} + // 响应字段按 2026-09-17 联调实测 export interface HomeDevice { installId: string @@ -46,7 +53,7 @@ export interface HomeDevice { status: number onlineStatus: number // 1-在线 0-离线 lastOnlineTime: string | null - runTimeInfo?: { properties?: VitalSigns } | null + runTimeInfo?: { properties?: VitalSigns & FallDetectSigns } | null } export function getHomeDeviceList(params: HomeDeviceListReq): Promise { diff --git a/src/constants/enums.ts b/src/constants/enums.ts index 4771c9a..a8fd0ac 100644 --- a/src/constants/enums.ts +++ b/src/constants/enums.ts @@ -31,3 +31,11 @@ export const ONBED_STATUS_MAP: Record = { 0: '不在床', 1: '在床' } + +// ED719 跌倒状态枚举(2026-09-21 用户提供:runTimeInfo.properties.status 实时值) +export const FALL_STATUS_MAP: Record = { + 3101: '摔倒报警', + 3102: '疑似摔倒', + 3103: '解除摔倒', + 3104: '驻留超时' +} diff --git a/src/pages/device/feature/detail.module.scss b/src/pages/device/feature/detail.module.scss index 0489e0f..f143534 100644 --- a/src/pages/device/feature/detail.module.scss +++ b/src/pages/device/feature/detail.module.scss @@ -53,6 +53,9 @@ line-height: 1.1; } +// 心率越阈(<60 或 >100 BPM):大数字转红 +.heartValueDanger { color: $color-danger; } + .heartUnit { margin-top: 8px; font-size: 30px; color: $color-text-secondary; } .badge { @@ -143,6 +146,19 @@ color: $color-text; } +// 状态告警色:warning 橙(注意态)、danger 红(越阈值) +.metricValueWarning { + font-size: 44px; + font-weight: 700; + color: $color-warning; +} + +.metricValueDanger { + font-size: 44px; + font-weight: 700; + color: $color-danger; +} + .metricBlue { color: $color-primary-deep; } .metricOrange { color: #fa8c16; } .metricGreen { color: $color-success; } diff --git a/src/pages/device/feature/detail.tsx b/src/pages/device/feature/detail.tsx index e55898e..f5290fa 100644 --- a/src/pages/device/feature/detail.tsx +++ b/src/pages/device/feature/detail.tsx @@ -8,16 +8,33 @@ 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 { MOVE_STATE_MAP, BODY_STATUS_MAP, ONBED_STATUS_MAP, FALL_STATUS_MAP } from '@/constants/enums' import { getHomeDeviceDetail, getVitalRecords, unbindHomeDevice, type HomeDevice, type VitalSigns } from '@/api/homeDevice' +import { FallStatusCard } from './fall-status-card' import type { DeviceModel } from '@/constants/enums' import styles from './detail.module.scss' // 轮询间隔:与 web 端 VitalSignsDashboard SWR refreshInterval 对齐 const POLL_INTERVAL = 3000 +// 数值告警阈值(医学常规):心率 60-100 BPM、呼吸率 12-24 /min +const HR_NORMAL: [number, number] = [60, 100] +const RR_NORMAL: [number, number] = [12, 24] + +// 数值越阈 → 红;否则保持主文本色 +const numericLevel = (v: number | undefined, range: [number, number]): 'normal' | 'danger' => + v === undefined || v === null ? 'normal' : (v < range[0] || v > range[1] ? 'danger' : 'normal') + +// 枚举状态 → 语义色:异常态橙红、正常态主文本色 +const MOVE_STATE_LEVEL: Record = { 2: 'warning' } // 频繁活动 +const BODY_STATUS_LEVEL: Record = { 0: 'warning', 1: 'warning' } // 无人/运动 +const ONBED_LEVEL: Record = { 0: 'warning' } // 不在床 + +const levelCls = (level: 'normal' | 'warning' | 'danger'): string => + level === 'normal' ? styles.metricValue : level === 'warning' ? styles.metricValueWarning : styles.metricValueDanger + export default function Detail() { const router = useRouter() const params = router.params as { id?: string; name?: string; model?: DeviceModel; online?: string } @@ -44,7 +61,7 @@ export default function Detail() { }) }, []) - // 单次刷新:设备详情(实时快照)+ 历史心率(波形) + // 单次刷新:设备详情(实时快照)+ 历史心率(波形,仅 ED713) const fetchAll = useCallback(async () => { if (!installId) return try { @@ -52,6 +69,7 @@ export default function Detail() { setDevice(d) setOnline(d.onlineStatus === 1) deviceNameRef.current = d.deviceName + if (d.subDeviceType === 'ED719') return const records = await getVitalRecords(d.deviceName) // 倒序返回(最新在前),波形需按时间正序绘制 const pts = (records ?? []).map((r) => r.heartRate).filter((v): v is number => typeof v === 'number') @@ -86,6 +104,8 @@ export default function Detail() { const signs: VitalSigns | undefined = device?.runTimeInfo?.properties const fmt = (v: number | undefined): string => (v === undefined || v === null ? '--' : String(v)) + // ED719 跌倒检测器走独立卡片,不渲染体征面板 + const isFallDetector = deviceModel === 'ED719' || device?.subDeviceType === 'ED719' const goBleConfig = () => navigateTo(ROUTES.DEVICE_BLE_CONFIG) void goBleConfig @@ -125,67 +145,84 @@ export default function Detail() { - - - - ❤ + {isFallDetector ? ( + + ) : ( + <> + + + + ❤ + + {fmt(signs?.heartRate)} + BPM + + + {online ? '在线' : '离线'} + + + + 心率健康 + + {signs?.lastTime && ( + 更新于 {signs.lastTime} + )} + - {fmt(signs?.heartRate)} - BPM - - - {online ? '在线' : '离线'} - - - - 心率健康 - - {signs?.lastTime && ( - 更新于 {signs.lastTime} - )} - - - - - 呼吸率 - - {fmt(signs?.respiratoryRate)} - /min + + + 呼吸率 + + {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)} + 分米 + + - - - 体动 - - {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/feature/fall-status-card.module.scss b/src/pages/device/feature/fall-status-card.module.scss new file mode 100644 index 0000000..07b8606 --- /dev/null +++ b/src/pages/device/feature/fall-status-card.module.scss @@ -0,0 +1,97 @@ +// ED719 跌倒检测状态卡:状态色随语义变化(normal 绿 / warning 橙 / danger 红 / success 绿 / offline 灰) +// variables 由构建配置全局注入(config/index.ts sass.resource) + +.fallCard { + margin: 24px; + padding: 32px; + border-radius: $radius-lg; + background: $color-bg-white; + box-shadow: $shadow-card; + display: flex; + flex-direction: column; + align-items: center; +} + +.fallCardDanger { background: linear-gradient(160deg, #fff5f4 0%, #ffffff 60%); } +.fallCardWarning { background: linear-gradient(160deg, #fffaf0 0%, #ffffff 60%); } +.fallCardSuccess { background: linear-gradient(160deg, #f4fff0 0%, #ffffff 60%); } + +.fallHead { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.fallTitle { + font-size: 30px; + font-weight: 600; + color: $color-text; +} + +.fallBadge { + display: flex; + align-items: center; +} + +.fallBadgeDot { + width: 14px; + height: 14px; + border-radius: $radius-circle; + margin-right: 10px; +} + +.fallBadgeDotOn { background: $color-success; } +.fallBadgeDotOff { background: $color-text-secondary; } + +.fallBadgeTextOn { font-size: 24px; color: $color-success; } +.fallBadgeTextOff { font-size: 24px; color: $color-text-secondary; } + +// 图标圆底:色随状态 +.fallIconWrap { + width: 160px; + height: 160px; + margin-top: 40px; + border-radius: $radius-circle; + display: flex; + align-items: center; + justify-content: center; + background: $color-primary-light-bg; +} + +.fallIconWrapNormal { background: $color-primary-light-bg; } +.fallIconWrapOffline { background: $color-bg-disabled; } +.fallIconWrapDanger { background: $color-danger-light-bg; } +.fallIconWrapWarning { background: #fff7e8; } +.fallIconWrapSuccess { background: $color-success-light-bg; } + +.fallIcon { + font-size: 72px; + line-height: 1; + color: $color-primary; +} + +.fallStatusText { + margin-top: 32px; + font-size: 44px; + font-weight: 700; + color: $color-text; +} + +.fallStatusTextNormal { color: $color-primary-deep; } +.fallStatusTextOffline { color: $color-text-secondary; } +.fallStatusTextDanger { color: $color-danger; } +.fallStatusTextWarning { color: $color-warning; } +.fallStatusTextSuccess { color: $color-success; } + +.fallTime { + margin-top: 16px; + font-size: 24px; + color: $color-text-tertiary; +} + +.fallVersion { + margin-top: 8px; + font-size: 24px; + color: $color-text-tertiary; +} diff --git a/src/pages/device/feature/fall-status-card.tsx b/src/pages/device/feature/fall-status-card.tsx new file mode 100644 index 0000000..bff6f74 --- /dev/null +++ b/src/pages/device/feature/fall-status-card.tsx @@ -0,0 +1,47 @@ +import { View, Text } from '@tarojs/components' +import { FALL_STATUS_MAP } from '@/constants/enums' +import styles from './fall-status-card.module.scss' + +interface Props { + status?: number + lastTime?: string + version?: string + online: boolean +} + +// 状态 → 语义色(正常无编码/离线灰、疑似黄、摔倒红、解除绿、驻留橙) +const STATUS_CLS: Record = { + 3101: 'danger', + 3102: 'warning', + 3103: 'success', + 3104: 'warning' +} + +// ED719 跌倒检测状态卡:实时状态大卡(参考 web 端 DeviceScgDashboard DeviceItem 布局) +export function FallStatusCard({ status, lastTime, version, online }: Props) { + const label = FALL_STATUS_MAP[status ?? -1] ?? (online ? '正常' : '离线') + const level = STATUS_CLS[status ?? -1] ?? (online ? 'normal' : 'offline') + + return ( + + + 检测状态 + + + {online ? '在线' : '离线'} + + + + + ⌁ + + {label} + {lastTime && 更新于 {lastTime}} + {version && 固件版本 {version}} + + ) +} + +// CSS Modules 连续大写坑:camelCaseOnly 会把 TL 转 Tl(见记忆 css-modules-camelcase-trap), +// 动态拼 key 需首字母大写驼峰 +const cap = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)