feat(mp): 患者端健康告警页面 + 首页入口
P1-8: 小程序患者告警推送 - 新增 alert service:listPatientAlerts 按患者 ID 查询告警 - 新增 pkg-health/alerts 告警列表页:严重程度标签 + 状态过滤 + 分页 - 首页快捷服务新增"健康告警"入口 - app.config.ts 注册 alerts/index 页面路由
This commit is contained in:
@@ -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' },
|
||||
];
|
||||
|
||||
134
apps/miniprogram/src/pages/pkg-health/alerts/index.scss
Normal file
134
apps/miniprogram/src/pages/pkg-health/alerts/index.scss
Normal file
@@ -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;
|
||||
}
|
||||
133
apps/miniprogram/src/pages/pkg-health/alerts/index.tsx
Normal file
133
apps/miniprogram/src/pages/pkg-health/alerts/index.tsx
Normal file
@@ -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<string, { label: string; className: string }> = {
|
||||
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<Alert[]>([]);
|
||||
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 (
|
||||
<View className='alerts-page'>
|
||||
<View className='alerts-empty'>
|
||||
<Text className='alerts-empty-text'>请先完善个人档案</Text>
|
||||
<View className='alerts-empty-action' onClick={() => Taro.navigateTo({ url: '/pages/pkg-profile/family-add/index' })}>
|
||||
<Text className='alerts-empty-action-text'>去建档</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='alerts-page'>
|
||||
<View className='alerts-tabs'>
|
||||
{STATUS_TABS.map((tab) => (
|
||||
<View
|
||||
key={tab.key}
|
||||
className={`alerts-tab ${status === tab.key ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange(tab.key)}
|
||||
>
|
||||
<Text className={`alerts-tab-text ${status === tab.key ? 'active' : ''}`}>{tab.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{alerts.length === 0 && !loading ? (
|
||||
<View className='alerts-empty'>
|
||||
<Text className='alerts-empty-text'>暂无告警记录</Text>
|
||||
<Text className='alerts-empty-hint'>您的各项指标正常</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='alerts-list'>
|
||||
{alerts.map((item) => {
|
||||
const sev = SEVERITY_MAP[item.severity] || SEVERITY_MAP.warning;
|
||||
return (
|
||||
<View className='alert-card' key={item.id}>
|
||||
<View className='alert-header'>
|
||||
<View className={`alert-badge ${sev.className}`}>
|
||||
<Text className='alert-badge-text'>{sev.label}</Text>
|
||||
</View>
|
||||
<Text className='alert-time'>
|
||||
{new Date(item.created_at).toLocaleDateString()}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className='alert-title'>{item.title}</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
{loading && <Loading />}
|
||||
{!loading && alerts.length >= total && total > 0 && (
|
||||
<Loading text='没有更多了' />
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user