diff --git a/apps/miniprogram/src/app.config.ts b/apps/miniprogram/src/app.config.ts index ee44ed9..f6e80c4 100644 --- a/apps/miniprogram/src/app.config.ts +++ b/apps/miniprogram/src/app.config.ts @@ -16,7 +16,7 @@ export default defineAppConfig({ subPackages: [ { root: 'pages/pkg-health', - pages: ['trend/index', 'input/index', 'daily-monitoring/index'], + pages: ['trend/index', 'input/index', 'daily-monitoring/index', 'alerts/index'], }, { root: 'pages/doctor', diff --git a/apps/miniprogram/src/pages/index/index.tsx b/apps/miniprogram/src/pages/index/index.tsx index ab01e0b..8944e85 100644 --- a/apps/miniprogram/src/pages/index/index.tsx +++ b/apps/miniprogram/src/pages/index/index.tsx @@ -14,6 +14,7 @@ const QUICK_SERVICES = [ { label: '预约挂号', char: '约', path: '/pages/appointment/create/index' }, { label: '健康录入', char: '录', path: '/pages/pkg-health/input/index' }, { label: '健康趋势', char: '势', path: '/pages/pkg-health/trend/index' }, + { label: '健康告警', char: '警', path: '/pages/pkg-health/alerts/index' }, { label: '资讯文章', char: '文', path: '/pages/article/index' }, { label: 'AI 报告', char: 'AI', path: '/pages/ai-report/list/index' }, ]; diff --git a/apps/miniprogram/src/pages/pkg-health/alerts/index.scss b/apps/miniprogram/src/pages/pkg-health/alerts/index.scss new file mode 100644 index 0000000..fcb84ec --- /dev/null +++ b/apps/miniprogram/src/pages/pkg-health/alerts/index.scss @@ -0,0 +1,134 @@ +.alerts-page { + min-height: 100vh; + background: #f5f5f5; + padding-bottom: env(safe-area-inset-bottom); +} + +.alerts-tabs { + display: flex; + background: #fff; + padding: 20px 16px; + gap: 16px; + border-bottom: 1px solid #eee; +} + +.alerts-tab { + padding: 8px 20px; + border-radius: 20px; + background: #f0f0f0; +} + +.alerts-tab.active { + background: #C4623A; +} + +.alerts-tab-text { + font-size: 26px; + color: #666; +} + +.alerts-tab-text.active { + color: #fff; +} + +.alerts-list { + padding: 16px; +} + +.alert-card { + background: #fff; + border-radius: 16px; + padding: 24px; + margin-bottom: 16px; +} + +.alert-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.alert-badge { + padding: 4px 16px; + border-radius: 8px; +} + +.alert-badge.sev-info { + background: #e6f7ff; +} + +.alert-badge.sev-warning { + background: #fff7e6; +} + +.alert-badge.sev-critical { + background: #fff1f0; +} + +.alert-badge.sev-urgent { + background: #ff4d4f; +} + +.alert-badge-text { + font-size: 22px; + font-weight: 600; +} + +.alert-badge.sev-info .alert-badge-text { + color: #1890ff; +} + +.alert-badge.sev-warning .alert-badge-text { + color: #fa8c16; +} + +.alert-badge.sev-critical .alert-badge-text { + color: #f5222d; +} + +.alert-badge.sev-urgent .alert-badge-text { + color: #fff; +} + +.alert-time { + font-size: 24px; + color: #999; +} + +.alert-title { + font-size: 28px; + color: #333; + line-height: 1.5; +} + +.alerts-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 120px 0; +} + +.alerts-empty-text { + font-size: 30px; + color: #999; + margin-bottom: 16px; +} + +.alerts-empty-hint { + font-size: 26px; + color: #bbb; +} + +.alerts-empty-action { + margin-top: 24px; + padding: 16px 48px; + background: #C4623A; + border-radius: 32px; +} + +.alerts-empty-action-text { + color: #fff; + font-size: 28px; +} diff --git a/apps/miniprogram/src/pages/pkg-health/alerts/index.tsx b/apps/miniprogram/src/pages/pkg-health/alerts/index.tsx new file mode 100644 index 0000000..2651ecd --- /dev/null +++ b/apps/miniprogram/src/pages/pkg-health/alerts/index.tsx @@ -0,0 +1,133 @@ +import React, { useState, useCallback, useRef } from 'react'; +import { View, Text } from '@tarojs/components'; +import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro'; +import { listPatientAlerts, type Alert } from '@/services/alert'; +import { useAuthStore } from '@/stores/auth'; +import Loading from '@/components/Loading'; +import './index.scss'; + +const SEVERITY_MAP: Record = { + info: { label: '提示', className: 'sev-info' }, + warning: { label: '警告', className: 'sev-warning' }, + critical: { label: '严重', className: 'sev-critical' }, + urgent: { label: '紧急', className: 'sev-urgent' }, +}; + +const STATUS_TABS = [ + { key: '', label: '全部' }, + { key: 'pending', label: '待处理' }, + { key: 'acknowledged', label: '已确认' }, + { key: 'resolved', label: '已恢复' }, +]; + +export default function PatientAlerts() { + const { currentPatient } = useAuthStore(); + const [alerts, setAlerts] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [status, setStatus] = useState(''); + const [loading, setLoading] = useState(false); + const loadingRef = useRef(false); + + const fetchAlerts = useCallback( + async (pageNum: number, s: string, isRefresh = false) => { + if (!currentPatient || loadingRef.current) return; + loadingRef.current = true; + setLoading(true); + try { + const res = await listPatientAlerts(currentPatient.id, { + page: pageNum, + page_size: 20, + status: s || undefined, + }); + const list = res.data || []; + if (isRefresh) { + setAlerts(list); + } else { + setAlerts((prev) => [...prev, ...list]); + } + setTotal(res.total); + setPage(pageNum); + } catch { + Taro.showToast({ title: '加载失败', icon: 'none' }); + } finally { + loadingRef.current = false; + setLoading(false); + } + }, + [currentPatient], + ); + + useDidShow(() => { + Taro.setNavigationBarTitle({ title: '健康告警' }); + fetchAlerts(1, status, true); + }); + + usePullDownRefresh(() => { + fetchAlerts(1, status, true).finally(() => Taro.stopPullDownRefresh()); + }); + + const handleTabChange = (key: string) => { + setStatus(key); + fetchAlerts(1, key, true); + }; + + if (!currentPatient) { + return ( + + + 请先完善个人档案 + Taro.navigateTo({ url: '/pages/pkg-profile/family-add/index' })}> + 去建档 + + + + ); + } + + return ( + + + {STATUS_TABS.map((tab) => ( + handleTabChange(tab.key)} + > + {tab.label} + + ))} + + + {alerts.length === 0 && !loading ? ( + + 暂无告警记录 + 您的各项指标正常 + + ) : ( + + {alerts.map((item) => { + const sev = SEVERITY_MAP[item.severity] || SEVERITY_MAP.warning; + return ( + + + + {sev.label} + + + {new Date(item.created_at).toLocaleDateString()} + + + {item.title} + + ); + })} + {loading && } + {!loading && alerts.length >= total && total > 0 && ( + + )} + + )} + + ); +} diff --git a/apps/miniprogram/src/services/alert.ts b/apps/miniprogram/src/services/alert.ts new file mode 100644 index 0000000..4565bf1 --- /dev/null +++ b/apps/miniprogram/src/services/alert.ts @@ -0,0 +1,21 @@ +import { api } from './request'; + +export interface Alert { + id: string; + severity: string; + title: string; + status: string; + detail?: Record; + created_at: string; + acknowledged_at?: string; + resolved_at?: string; +} + +export async function listPatientAlerts(patientId: string, params?: { status?: string; page?: number; page_size?: number }) { + return api.get<{ data: Alert[]; total: number }>('/health/alerts', { + patient_id: patientId, + page: params?.page ?? 1, + page_size: params?.page_size ?? 20, + status: params?.status, + }); +}