fix: patient report shows interpretation, medication daily tracking, followup bugs, home overview restored, doctor renamed
This commit is contained in:
@@ -11,21 +11,27 @@ export function FollowUpEditPage() {
|
||||
const navigate = useNavigate();
|
||||
const [title, setTitle] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [doctorName, setDoctorName] = useState('');
|
||||
const [notes, setNotes] = useState('');
|
||||
const [scheduledAt, setScheduledAt] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!title) { toast('请填写标题', 'error'); return; }
|
||||
setLoading(true);
|
||||
await followupService.addFollowUp({
|
||||
title, description,
|
||||
scheduledAt: scheduledAt || new Date().toISOString(),
|
||||
status: 'upcoming',
|
||||
reminderEnabled: true,
|
||||
});
|
||||
toast('添加成功');
|
||||
navigate(-1);
|
||||
try {
|
||||
await followupService.addFollowUp({
|
||||
title, description, notes,
|
||||
scheduledAt: scheduledAt || new Date().toISOString(),
|
||||
status: 'upcoming',
|
||||
reminderEnabled: true,
|
||||
});
|
||||
toast('添加成功');
|
||||
navigate(-1);
|
||||
} catch {
|
||||
toast('添加失败', 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -33,12 +39,11 @@ export function FollowUpEditPage() {
|
||||
<PageHeader title="新增复查" />
|
||||
<div className={styles.form}>
|
||||
<Input label="复查标题" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="如:PCI术后3个月复查" />
|
||||
<Input label="医生" value={doctorName} onChange={(e) => setDoctorName(e.target.value)} placeholder="医生姓名" />
|
||||
<Input label="描述" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="复查描述" />
|
||||
<Input label="复查描述" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="简单描述复查目的" />
|
||||
<Input label="复查时间" value={scheduledAt} onChange={(e) => setScheduledAt(e.target.value)} type="datetime-local" />
|
||||
<div className={styles.textareaWrap}>
|
||||
<label className={styles.label}>备注</label>
|
||||
<textarea className={styles.textarea} value={description} onChange={(e) => setDescription(e.target.value)} placeholder="复查说明..." rows={3} />
|
||||
<textarea className={styles.textarea} value={notes} onChange={(e) => setNotes(e.target.value)} placeholder="其他补充说明..." rows={3} />
|
||||
</div>
|
||||
<Button variant="primary" size="lg" fullWidth loading={loading} onClick={handleSubmit}>保存</Button>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function FollowUpListPage() {
|
||||
<Empty icon="🏥" message="暂无复查计划" />
|
||||
) : (
|
||||
filtered.map((f) => (
|
||||
<Card key={f.id} className={styles.card} onClick={() => navigate(`/health/medications`)}>
|
||||
<Card key={f.id} className={styles.card}>
|
||||
<div className={styles.cardHeader}>
|
||||
<span className={styles.title}>{f.title}</span>
|
||||
<span className={styles.status} style={{ color: statusColor(f.status) }}>
|
||||
|
||||
@@ -2,63 +2,99 @@ import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { PageHeader } from '@/components/layout/PageHeader';
|
||||
import { Card } from '@/components/common/Card';
|
||||
import { Button } from '@/components/common/Button';
|
||||
import { ToastContainer, toast } from '@/components/common/Toast';
|
||||
import * as reportService from '@/services/report.service';
|
||||
import type { Report } from '@/types';
|
||||
import { formatDate } from '@/utils/format';
|
||||
import * as reportService from '@/services/report.service';
|
||||
import styles from './ReportDetailPage.module.css';
|
||||
|
||||
interface ReportData {
|
||||
id: string; title: string; category: string; status: string;
|
||||
imageUrls: string[]; riskLevel?: string; summary?: string; suggestions?: string;
|
||||
createdAt: string; completedAt?: string; patientName?: string; doctorName?: string;
|
||||
items?: { id: string; itemName: string; resultValue: string; unit?: string; referenceRange?: string; isAbnormal: boolean }[];
|
||||
}
|
||||
|
||||
export function ReportDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [report, setReport] = useState<Report | null>(null);
|
||||
const [report, setReport] = useState<ReportData | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) reportService.getReport(id).then((r) => setReport(r || null));
|
||||
if (id) {
|
||||
import('@/services/api-client').then(({ api }) => {
|
||||
api.get<ReportData>(`/api/reports/${id}`).then((r) => setReport(r.data));
|
||||
});
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
if (!report) {
|
||||
return <div className="page--no-tab"><PageHeader title="报告详情" /><div style={{ padding: 40, textAlign: 'center', color: '#9CA3AF' }}>报告不存在</div></div>;
|
||||
}
|
||||
if (!report) return <div className="page--no-tab"><PageHeader title="报告详情" /><div style={{ padding: 40, textAlign: 'center', color: '#9CA3AF' }}>加载中...</div></div>;
|
||||
|
||||
const riskLabels = { normal: '正常', attention: '需关注', abnormal: '异常' };
|
||||
const riskColors = { normal: '#10B981', attention: '#F59E0B', abnormal: '#EF4444' };
|
||||
const riskMap: Record<string, { label: string; color: string }> = {
|
||||
normal: { label: '正常', color: '#10B981' },
|
||||
attention: { label: '需关注', color: '#F59E0B' },
|
||||
abnormal: { label: '异常', color: '#EF4444' },
|
||||
};
|
||||
|
||||
const isCompleted = report.status === 'completed';
|
||||
|
||||
return (
|
||||
<div className="page--no-tab">
|
||||
<PageHeader title={report.title} />
|
||||
<Card className={styles.card}>
|
||||
<div className={styles.infoRow}><span>类型</span><span>{report.category}</span></div>
|
||||
<div className={styles.infoRow}><span>上传时间</span><span>{formatDate(report.uploadAt)}</span></div>
|
||||
<div className={styles.infoRow}><span>状态</span><span>{report.status === 'completed' ? '✅ 已解读' : report.status === 'interpreting' ? '⏳ 解读中' : '📤 待解读'}</span></div>
|
||||
{report.result && (
|
||||
<div className={styles.result}>
|
||||
<div className={styles.riskBadge} style={{ background: riskColors[report.result.riskLevel] }}>
|
||||
{riskLabels[report.result.riskLevel]}
|
||||
</div>
|
||||
<p className={styles.summary}>{report.result.summary}</p>
|
||||
<div className={styles.infoRow}><span>上传时间</span><span>{formatDate(report.createdAt)}</span></div>
|
||||
<div className={styles.infoRow}>
|
||||
<span>状态</span>
|
||||
<span style={{ color: isCompleted ? '#10B981' : '#F59E0B' }}>
|
||||
{isCompleted ? '已完成解读' : '待解读'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.findingsTitle}>检查结果</div>
|
||||
{report.result.findings.map((f, i) => (
|
||||
<div key={i} className={styles.finding}>
|
||||
<div className={styles.findingHeader}>
|
||||
<span className={styles.findingItem}>{f.item}</span>
|
||||
<span className={`${styles.findingValue} ${f.assessment === 'abnormal' ? styles.abnormal : ''}`}>{f.value}</span>
|
||||
</div>
|
||||
<div className={styles.findingRef}>参考范围:{f.referenceRange}</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className={styles.findingsTitle}>建议</div>
|
||||
<ul className={styles.suggestions}>
|
||||
{report.result.suggestions.map((s, i) => (
|
||||
<li key={i}>{s}</li>
|
||||
{report.imageUrls && report.imageUrls.length > 0 && (
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 8 }}>报告图片</div>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{report.imageUrls.map((url, i) => (
|
||||
<img key={i} src={`http://localhost:5000${url}`} alt="report"
|
||||
style={{ width: 80, height: 80, borderRadius: 8, objectFit: 'cover', border: '1px solid #eee' }} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isCompleted && (
|
||||
<div className={styles.result}>
|
||||
{report.riskLevel && (
|
||||
<div className={styles.riskBadge} style={{ background: riskMap[report.riskLevel]?.color || '#888' }}>
|
||||
{riskMap[report.riskLevel]?.label || report.riskLevel}
|
||||
</div>
|
||||
)}
|
||||
<p className={styles.summary}>{report.summary}</p>
|
||||
|
||||
{report.items && report.items.length > 0 && (
|
||||
<>
|
||||
<div className={styles.findingsTitle}>检查项目</div>
|
||||
{report.items.map((item) => (
|
||||
<div key={item.id} className={styles.finding}>
|
||||
<div className={styles.findingHeader}>
|
||||
<span className={styles.findingItem}>{item.itemName}</span>
|
||||
<span className={styles.findingValue} style={{ color: item.isAbnormal ? '#EF4444' : '#10B981' }}>
|
||||
{item.resultValue} {item.unit || ''}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.findingRef}>参考:{item.referenceRange || '-'}</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{report.suggestions && (
|
||||
<>
|
||||
<div className={styles.findingsTitle}>医生建议</div>
|
||||
<p style={{ fontSize: 13, color: '#555', lineHeight: 1.6 }}>{report.suggestions}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
<ToastContainer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user