import { useEffect, useState, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { startAnalysis, type AnalysisType } from '../../api/ai/analysisSse'; import { Card, Descriptions, Tabs, Button, Space, Modal, Form, Input, Select, DatePicker, message, Spin, Tooltip, } from 'antd'; import { ArrowLeftOutlined, EditOutlined, InfoCircleOutlined } from '@ant-design/icons'; import { useAuthStore } from '../../stores/auth'; import { patientApi } from '../../api/health/patients'; import { AuthButton } from '../../components/AuthButton'; import { CopilotBadge } from '../../components/Copilot'; import type { PatientDetail as PatientDetailType, UpdatePatientReq, } from '../../api/health/patients'; import { StatusTag } from './components/StatusTag'; import { VitalSignsTab } from './components/VitalSignsTab'; import { LabReportsTab } from './components/LabReportsTab'; import { HealthRecordsTab } from './components/HealthRecordsTab'; import { FollowUpTab } from './components/FollowUpTab'; import { DeviceReadingsTab } from './components/DeviceReadingsTab'; import { PointsAccountTab } from './components/PointsAccountTab'; import { AiSuggestionTab } from './components/AiSuggestionTab'; import { FamilyMembersTab } from './components/FamilyMembersTab'; import { DailyMonitoringTab } from './components/DailyMonitoringTab'; import { GENDER_OPTIONS, BLOOD_TYPE_OPTIONS, GENDER_LABEL } from '../../constants/health'; import { useThemeMode } from '../../hooks/useThemeMode'; export default function PatientDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [patient, setPatient] = useState(null); const [loading, setLoading] = useState(false); const [analysisResult, setAnalysisResult] = useState(''); const [analyzing, setAnalyzing] = useState(false); const triggerAnalysis = async (type: AnalysisType) => { if (!id) return; setAnalyzing(true); setAnalysisResult(''); await startAnalysis(type, { patient_id: id }, { onChunk: (content) => setAnalysisResult(prev => prev + content), onError: (msg) => { message.error(msg); setAnalyzing(false); }, onDone: () => { message.success('分析完成'); setAnalyzing(false); }, }); }; const [editModalOpen, setEditModalOpen] = useState(false); const [form] = Form.useForm(); const isDark = useThemeMode(); const permissions = useAuthStore((s) => s.permissions); /** Tab 权限映射:无权限码的 tab 始终可见 */ const TAB_PERMISSIONS: Record = { info: undefined, family: 'health.patient.manage', health: 'health.health-data.list', followup: 'health.follow-up.list', points: 'health.points.list', ai: 'ai.analysis.list', }; const hasPermission = (code: string | undefined): boolean => { if (!code) return true; return permissions.includes(code); }; // --- 加载患者基本信息 --- const fetchPatient = useCallback(async () => { if (!id) return; setLoading(true); try { const data = await patientApi.get(id); setPatient(data); } catch { message.error('加载患者信息失败'); } setLoading(false); }, [id]); useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect fetchPatient(); }, [fetchPatient]); // --- 编辑患者 --- const handleEdit = async (values: { name?: string; gender?: string; birth_date?: unknown; blood_type?: string; id_number?: string; allergy_history?: string; medical_history_summary?: string; emergency_contact_name?: string; emergency_contact_phone?: string; notes?: string; }) => { if (!patient) return; const formatted = { ...values, birth_date: values.birth_date && typeof values.birth_date === 'object' && 'format' in (values.birth_date as object) ? (values.birth_date as { format: (f: string) => string }).format('YYYY-MM-DD') : (values.birth_date as string | undefined), }; try { const req: UpdatePatientReq & { version: number } = { ...formatted, version: patient.version, }; await patientApi.update(patient.id, req); message.success('患者信息更新成功'); setEditModalOpen(false); fetchPatient(); } catch (err: unknown) { const errorMsg = (err as { response?: { data?: { message?: string } } })?.response?.data ?.message || '更新失败'; message.error(errorMsg); } }; const openEditModal = () => { if (!patient) return; form.setFieldsValue({ name: patient.name, gender: patient.gender, birth_date: patient.birth_date, blood_type: patient.blood_type, id_number: patient.id_number, allergy_history: patient.allergy_history, medical_history_summary: patient.medical_history_summary, emergency_contact_name: patient.emergency_contact_name, emergency_contact_phone: patient.emergency_contact_phone, notes: patient.notes, }); setEditModalOpen(true); }; // --- 加载状态 --- if (loading) { return (
); } if (!patient) { return (

未找到患者信息

); } // --- 主题卡片样式 --- const cardStyle = { borderRadius: 12, background: isDark ? '#111827' : '#FFFFFF', border: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`, }; return (
{/* 顶部导航 */}
{/* 患者基本信息卡片 */}
{(patient.name?.[0] || 'P').toUpperCase()}
{patient.name}
{GENDER_LABEL[patient.gender || ''] || patient.gender || '-'} {patient.birth_date || '-'} {patient.blood_type || '-'} {patient.id_number || '-'} {patient.source || '-'} {patient.created_at ? new Date(patient.created_at).toLocaleString('zh-CN') : '-'}
{/* 快捷导航 */} 快捷跳转: {patient.name} {GENDER_LABEL[patient.gender || ''] || patient.gender || '-'} {patient.birth_date || '-'} {patient.blood_type || '-'} {patient.id_number || '-'} 状态 }> 认证状态 }> {patient.source || '-'} {patient.allergy_history || '-'} {patient.medical_history_summary || '-'} {patient.emergency_contact_name || '-'} {patient.emergency_contact_phone || '-'} {patient.notes || '-'} ), }, { key: 'family', label: '家属管理', children: id ? : null, }, { key: 'health', label: '健康数据', children: id ? ( }, { key: 'device', label: '设备数据', children: }, { key: 'lab', label: '化验报告', children: }, { key: 'records', label: '健康档案', children: }, { key: 'daily', label: '日常监测', children: }, ]} /> ) : null, }, { key: 'followup', label: '随访记录', children: id ? : null, }, { key: 'points', label: '积分账户', children: id ? : null, }, { key: 'ai', label: 'AI 建议', children: id ? ( {analysisResult && (
{analysisResult}
)}
) : null, }, ].filter((tab) => hasPermission(TAB_PERMISSIONS[tab.key]))} />
{/* 编辑弹窗 */} setEditModalOpen(false)} onOk={() => form.submit()} width={600} >
); }