fix: patient report shows interpretation, medication daily tracking, followup bugs, home overview restored, doctor renamed

This commit is contained in:
MingNian
2026-05-21 16:32:20 +08:00
parent 0df75c35e9
commit 4c85cd50be
7 changed files with 257 additions and 146 deletions

View File

@@ -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>

View File

@@ -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) }}>

View File

@@ -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>
);
}