import { useCallback, useState, useMemo } from 'react'; import { Table, Button, Modal, Form, InputNumber, DatePicker, Input, message, Typography, Tooltip, Popconfirm, Space, Card } from 'antd'; import { PlusOutlined, InfoCircleOutlined, EditOutlined, DeleteOutlined, ThunderboltOutlined } from '@ant-design/icons'; import type { Dayjs } from 'dayjs'; import { dayjs } from '../../../utils/dayjs'; import { healthDataApi } from '../../../api/health/healthData'; import type { VitalSigns } from '../../../api/health/healthData'; import { VitalSignsChart } from './VitalSignsChart'; import { usePaginatedData } from '../../../hooks/usePaginatedData'; import { AuthButton } from '../../../components/AuthButton'; import { handleApiError } from '../../../api/client'; import { startAnalysis } from '../../../api/ai/analysisSse'; const { Text } = Typography; interface Props { patientId: string; } export function VitalSignsTab({ patientId }: Props) { const [modalOpen, setModalOpen] = useState(false); const [editingRecord, setEditingRecord] = useState(null); const [chartRefreshKey, setChartRefreshKey] = useState(0); const [analyzingTrend, setAnalyzingTrend] = useState(false); const [trendContent, setTrendContent] = useState(''); const [form] = Form.useForm(); const [submitting, setSubmitting] = useState(false); const fetcher = useCallback( async (page: number, pageSize: number) => { return healthDataApi.listVitalSigns(patientId, { page, page_size: pageSize }); }, [patientId], ); const { data, total, page, loading, refresh } = usePaginatedData(fetcher, 10); const handleTrendAnalysis = async () => { setAnalyzingTrend(true); setTrendContent(''); await startAnalysis('trends', { patient_id: patientId }, { onChunk: (content) => setTrendContent(prev => prev + content), onError: (msg) => { message.error(msg); setAnalyzingTrend(false); }, onDone: () => { message.success('AI 趋势分析完成'); setAnalyzingTrend(false); }, }); }; const handleOpenCreate = () => { setEditingRecord(null); form.resetFields(); setModalOpen(true); }; const handleOpenEdit = (record: VitalSigns) => { setEditingRecord(record); form.setFieldsValue({ record_date: dayjs(record.record_date), systolic_bp_morning: record.systolic_bp_morning, diastolic_bp_morning: record.diastolic_bp_morning, heart_rate: record.heart_rate, weight: record.weight, blood_sugar: record.blood_sugar, water_intake_ml: record.water_intake_ml, urine_output_ml: record.urine_output_ml, notes: record.notes, }); setModalOpen(true); }; const handleDelete = async (record: VitalSigns) => { try { await healthDataApi.deleteVitalSigns(patientId, record.id); message.success('删除成功'); refresh(); setChartRefreshKey((k) => k + 1); } catch (err) { handleApiError(err, '删除失败'); } }; const handleSubmit = async (values: { record_date: Dayjs; systolic_bp_morning?: number; diastolic_bp_morning?: number; heart_rate?: number; weight?: number; blood_sugar?: number; water_intake_ml?: number; urine_output_ml?: number; notes?: string; }) => { setSubmitting(true); try { const payload = { record_date: values.record_date.format('YYYY-MM-DD'), systolic_bp_morning: values.systolic_bp_morning, diastolic_bp_morning: values.diastolic_bp_morning, heart_rate: values.heart_rate, weight: values.weight, blood_sugar: values.blood_sugar, water_intake_ml: values.water_intake_ml, urine_output_ml: values.urine_output_ml, notes: values.notes, }; if (editingRecord) { await healthDataApi.updateVitalSigns(patientId, editingRecord.id, { ...payload, version: editingRecord.version, }); message.success('体征数据更新成功'); } else { await healthDataApi.createVitalSigns(patientId, payload); message.success('体征数据录入成功'); } setModalOpen(false); form.resetFields(); setEditingRecord(null); refresh(); setChartRefreshKey((k) => k + 1); } catch (err) { handleApiError(err, editingRecord ? '更新失败' : '录入失败'); } finally { setSubmitting(false); } }; const columns = useMemo( () => [ { title: '记录日期', dataIndex: 'record_date', key: 'record_date', width: 110, fixed: 'left' as const, }, { title: () => ( 收缩压(晨) ), dataIndex: 'systolic_bp_morning', key: 'systolic_bp_morning', width: 110, render: (v?: number) => (v != null ? `${v} mmHg` : '-'), }, { title: () => ( 舒张压(晨) ), dataIndex: 'diastolic_bp_morning', key: 'diastolic_bp_morning', width: 110, render: (v?: number) => (v != null ? `${v} mmHg` : '-'), }, { title: '心率', dataIndex: 'heart_rate', key: 'heart_rate', width: 80, render: (v?: number) => (v != null ? `${v} bpm` : '-'), }, { title: '体重', dataIndex: 'weight', key: 'weight', width: 80, render: (v?: number) => (v != null ? `${v} kg` : '-'), }, { title: '血糖', dataIndex: 'blood_sugar', key: 'blood_sugar', width: 90, render: (v?: number) => (v != null ? `${v} mmol/L` : '-'), }, { title: '操作', key: 'action', width: 120, fixed: 'right' as const, render: (_: unknown, record: VitalSigns) => ( {/* AI 趋势分析结果 */} {trendContent && ( AI 趋势分析结果} size="small" style={{ marginBottom: 12 }} extra={ } >
{trendContent}
)} {/* 最新记录摘要条 */} {latest && (
最新记录({latest.record_date}) {latest.systolic_bp_morning != null && ( 收缩压 {latest.systolic_bp_morning} mmHg )} {latest.diastolic_bp_morning != null && ( 舒张压 {latest.diastolic_bp_morning} mmHg )} {latest.heart_rate != null && ( 心率 {latest.heart_rate} bpm )} {latest.weight != null && ( 体重 {latest.weight} kg )} {latest.blood_sugar != null && ( 血糖 {latest.blood_sugar} mmol/L )}
)} {/* 操作栏 + 表格 */}
历史记录(共 {total} 条)
refresh(p), showTotal: (t) => `共 ${t} 条`, size: 'small', style: { margin: 0 }, }} scroll={{ x: 700 }} /> { setModalOpen(false); setEditingRecord(null); }} onOk={() => form.submit()} confirmLoading={submitting} destroyOnHidden width={600} >
); }