Files
hms/apps/miniprogram/src/pages/doctor/alerts/detail/index.tsx
iven 10c79c5e39
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
feat(mp): 医护端告警列表/详情页 + DoctorHome 告警 banner 增强
- 新增告警列表页:按状态筛选、分页、严重程度/状态标签
- 新增告警详情页:完整信息展示 + 确认/忽略/恢复操作
- doctor.ts 新增 listAlerts/acknowledgeAlert/dismissAlert/resolveAlert API
- DoctorHome 告警 banner 跳转目标改为告警列表页
- 注册 alerts/index + alerts/detail/index 到 doctor subPackage
2026-04-28 20:05:55 +08:00

211 lines
6.9 KiB
TypeScript

import { useState, useEffect } from 'react';
import { View, Text, ScrollView, Button } from '@tarojs/components';
import Taro from '@tarojs/taro';
import * as doctorApi from '@/services/doctor';
import Loading from '@/components/Loading';
import './index.scss';
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
info: { label: '提示', className: 'detail-severity--info' },
warning: { label: '警告', className: 'detail-severity--warning' },
critical: { label: '严重', className: 'detail-severity--critical' },
urgent: { label: '紧急', className: 'detail-severity--urgent' },
};
const STATUS_MAP: Record<string, { label: string; className: string }> = {
pending: { label: '待处理', className: 'detail-status--pending' },
acknowledged: { label: '已确认', className: 'detail-status--acknowledged' },
resolved: { label: '已恢复', className: 'detail-status--resolved' },
dismissed: { label: '已忽略', className: 'detail-status--dismissed' },
};
export default function AlertDetail() {
const [alert, setAlert] = useState<doctorApi.Alert | null>(null);
const [loading, setLoading] = useState(true);
const [actionLoading, setActionLoading] = useState(false);
useEffect(() => {
const params = Taro.getCurrentInstance().router?.params;
if (params?.id) {
loadAlert(params.id);
}
}, []);
const loadAlert = async (id: string) => {
try {
// 告警列表 API 支持按 ID 查询,此处用列表加载后过滤
const res = await doctorApi.listAlerts({ page: 1, page_size: 100 });
const found = (res.data || []).find((a) => a.id === id);
if (found) {
setAlert(found);
} else {
Taro.showToast({ title: '告警不存在', icon: 'none' });
}
} catch {
Taro.showToast({ title: '加载失败', icon: 'none' });
} finally {
setLoading(false);
}
};
const handleAcknowledge = async () => {
if (!alert) return;
setActionLoading(true);
try {
const updated = await doctorApi.acknowledgeAlert(alert.id, alert.version);
setAlert(updated);
Taro.showToast({ title: '已确认', icon: 'success' });
} catch {
Taro.showToast({ title: '操作失败', icon: 'none' });
} finally {
setActionLoading(false);
}
};
const handleDismiss = async () => {
if (!alert) return;
setActionLoading(true);
try {
const updated = await doctorApi.dismissAlert(alert.id, alert.version);
setAlert(updated);
Taro.showToast({ title: '已忽略', icon: 'success' });
} catch {
Taro.showToast({ title: '操作失败', icon: 'none' });
} finally {
setActionLoading(false);
}
};
const handleResolve = async () => {
if (!alert) return;
setActionLoading(true);
try {
const updated = await doctorApi.resolveAlert(alert.id, alert.version);
setAlert(updated);
Taro.showToast({ title: '已恢复', icon: 'success' });
} catch {
Taro.showToast({ title: '操作失败', icon: 'none' });
} finally {
setActionLoading(false);
}
};
if (loading) return <Loading />;
if (!alert) {
return (
<View className='alert-detail-page'>
<Text></Text>
</View>
);
}
const severity = SEVERITY_MAP[alert.severity] ?? SEVERITY_MAP.info;
const status = STATUS_MAP[alert.status] ?? STATUS_MAP.pending;
const isPending = alert.status === 'pending';
const isAcknowledged = alert.status === 'acknowledged';
return (
<ScrollView scrollY className='alert-detail-page'>
{/* 顶部状态 */}
<View className='alert-detail-header'>
<View className='alert-detail-header__tags'>
<Text className={`detail-severity ${severity.className}`}>
{severity.label}
</Text>
<Text className={`detail-status ${status.className}`}>
{status.label}
</Text>
</View>
<Text className='alert-detail-header__time'>
{new Date(alert.created_at).toLocaleString('zh-CN')}
</Text>
</View>
{/* 告警信息 */}
<View className='alert-detail-card'>
<Text className='alert-detail-card__label'></Text>
<Text className='alert-detail-card__value'>{alert.title}</Text>
</View>
<View className='alert-detail-card'>
<Text className='alert-detail-card__label'> ID</Text>
<Text className='alert-detail-card__value alert-detail-card__value--id'>
{alert.patient_id.slice(0, 8)}...
</Text>
</View>
<View className='alert-detail-card'>
<Text className='alert-detail-card__label'></Text>
<Text className='alert-detail-card__value'>{severity.label}</Text>
</View>
{alert.detail && (
<View className='alert-detail-card'>
<Text className='alert-detail-card__label'></Text>
<Text className='alert-detail-card__value alert-detail-card__value--detail'>
{JSON.stringify(alert.detail, null, 2)}
</Text>
</View>
)}
{alert.acknowledged_by && (
<View className='alert-detail-card'>
<Text className='alert-detail-card__label'></Text>
<Text className='alert-detail-card__value'>{alert.acknowledged_by}</Text>
</View>
)}
{alert.acknowledged_at && (
<View className='alert-detail-card'>
<Text className='alert-detail-card__label'></Text>
<Text className='alert-detail-card__value'>
{new Date(alert.acknowledged_at).toLocaleString('zh-CN')}
</Text>
</View>
)}
{alert.resolved_at && (
<View className='alert-detail-card'>
<Text className='alert-detail-card__label'></Text>
<Text className='alert-detail-card__value'>
{new Date(alert.resolved_at).toLocaleString('zh-CN')}
</Text>
</View>
)}
{/* 操作按钮 */}
{(isPending || isAcknowledged) && (
<View className='alert-detail-actions'>
{isPending && (
<>
<Button
className='alert-action-btn alert-action-btn--primary'
onClick={handleAcknowledge}
disabled={actionLoading}
>
</Button>
<Button
className='alert-action-btn alert-action-btn--default'
onClick={handleDismiss}
disabled={actionLoading}
>
</Button>
</>
)}
{(isPending || isAcknowledged) && (
<Button
className='alert-action-btn alert-action-btn--resolve'
onClick={handleResolve}
disabled={actionLoading}
>
</Button>
)}
</View>
)}
</ScrollView>
);
}