Files
app_03_pregnancy/frontend/src/app/pages/DoctorQuestionsPage.tsx
Karlcarlo 409dabafb5 feat: 初始化孕育无忧全栈项目基础代码结构
完成项目初始搭建,包含后端FastAPI+MongoDB服务、前端React+Vite项目的完整基础结构,添加各类配置文件与依赖清单,涵盖核心业务初始框架。
2026-05-21 00:46:48 +08:00

284 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router";
import { motion } from "motion/react";
import {
ChevronLeft,
Copy,
Check,
MessageCircle,
Clipboard,
Share2,
Download,
Loader2,
} from "lucide-react";
import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
import { Badge } from "../components/ui/badge";
import { getDoctorQuestions } from "../services/api";
interface Question {
id: string;
category: string;
question: string;
reason: string;
priority: "high" | "medium" | "low";
}
export function DoctorQuestionsPage() {
const navigate = useNavigate();
const { reportId } = useParams();
const [copiedId, setCopiedId] = useState(null);
const [questions, setQuestions] = useState<Question[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!reportId) return;
getDoctorQuestions(reportId)
.then((data) => {
setQuestions(
data.questions.map((q, i) => ({
id: String(i),
category: q.category,
question: q.question,
reason: q.reason,
priority: (i < 2 ? "high" : i < 4 ? "medium" : "low") as Question["priority"],
}))
);
})
.catch(() => {/* keep empty */})
.finally(() => setLoading(false));
}, [reportId]);
const handleCopy = (text: string, id: string) => {
navigator.clipboard.writeText(text);
setCopiedId(id);
setTimeout(() => setCopiedId(null), 2000);
};
const handleCopyAll = () => {
const allQuestions = questions
.map((q, index) => `${index + 1}. ${q.question}`)
.join("\n\n");
navigator.clipboard.writeText(allQuestions);
setCopiedId("all");
setTimeout(() => setCopiedId(null), 2000);
};
const priorityLabels = {
high: { text: "重要", color: "bg-red-100 text-red-700" },
medium: { text: "建议", color: "bg-morandi-rose-100 text-morandi-rose-400" },
low: { text: "了解", color: "bg-morandi-50 text-morandi-500" },
};
if (loading) {
return (
<div className="min-h-dvh bg-morandi-50 flex items-center justify-center">
<div className="text-center">
<Loader2 className="w-8 h-8 text-morandi-400 animate-spin mx-auto mb-3" />
<p className="text-sm text-gray-500">正在生成问诊清单...</p>
</div>
</div>
);
}
return (
<div className="min-h-dvh bg-morandi-50">
{/* 顶部导航 */}
<div className="bg-white border-b border-gray-100 sticky top-0 z-10">
<div className="mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<button
onClick={() => navigate(-1)}
className="w-10 h-10 rounded-full hover:bg-gray-100 flex items-center justify-center"
>
<ChevronLeft className="w-6 h-6 text-gray-600" />
</button>
<div className="flex items-center gap-2">
<div className="w-10 h-10 bg-morandi-400 rounded-full flex items-center justify-center">
<MessageCircle className="w-5 h-5 text-white" />
</div>
<div>
<h1 className="font-semibold text-gray-800">问诊清单</h1>
<p className="text-xs text-gray-500">
智能生成{questions.length}个问题
</p>
</div>
</div>
</div>
<div className="flex gap-2">
<Button
onClick={handleCopyAll}
variant="outline"
size="sm"
className="rounded-full"
>
{copiedId === "all" ? (
<>
<Check className="w-4 h-4 mr-2" />
已复制
</>
) : (
<>
<Clipboard className="w-4 h-4 mr-2" />
全部复制
</>
)}
</Button>
</div>
</div>
</div>
<div className="mx-auto p-4 space-y-6">
{/* 使用说明 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
<Card className="bg-gradient-to-br from-morandi-400 to-morandi-500 border-0 text-white p-6 rounded-3xl shadow-sm">
<h2 className="text-xl font-semibold mb-3">💬 如何使用问诊清单</h2>
<div className="space-y-2 text-sm text-white/90">
<p>✓ 基于您的报告异常项,AI智能生成针对性问题</p>
<p>✓ 点击单个问题可复制,方便向医生提问</p>
<p>✓ 可一键复制全部问题,就诊时查看</p>
<p>✓ 按优先级排序,重要问题优先询问</p>
</div>
</Card>
</motion.div>
{/* 问题列表 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<h2 className="text-lg font-semibold text-gray-800 mb-4">
📋 建议咨询的问题
</h2>
<div className="space-y-4">
{questions.map((question, index) => (
<motion.div
key={question.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.2 + index * 0.05 }}
>
<Card className="bg-white rounded-2xl p-5 shadow-sm border-0 hover:shadow-md transition-shadow">
<div className="flex items-start gap-4">
<div className="flex-shrink-0 w-8 h-8 bg-morandi-100 text-morandi-500 rounded-full flex items-center justify-center font-semibold text-sm">
{index + 1}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-3">
<Badge
className={`${
priorityLabels[question.priority].color
} hover:bg-current`}
>
{priorityLabels[question.priority].text}
</Badge>
<span className="text-xs text-gray-500">
{question.category}
</span>
</div>
<p className="text-gray-800 leading-relaxed mb-3 font-medium">
{question.question}
</p>
<div className="bg-morandi-slate-50 rounded-xl p-3 mb-3">
<p className="text-sm text-morandi-500">
💡 <strong>为什么问:</strong>
{question.reason}
</p>
</div>
<Button
onClick={() =>
handleCopy(question.question, question.id)
}
size="sm"
variant="outline"
className="w-full"
>
{copiedId === question.id ? (
<>
<Check className="w-4 h-4 mr-2" />
已复制
</>
) : (
<>
<Copy className="w-4 h-4 mr-2" />
复制此问题
</>
)}
</Button>
</div>
</div>
</Card>
</motion.div>
))}
</div>
</motion.div>
{/* 其他建议 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5 }}
>
<Card className="bg-white rounded-2xl p-5 shadow-sm border-0">
<h3 className="font-semibold text-gray-800 mb-3 flex items-center gap-2">
<MessageCircle className="w-5 h-5 text-morandi-400" />
💡 就诊小贴士
</h3>
<ul className="space-y-2 text-sm text-gray-600">
<li className="flex items-start gap-2">
<span className="flex-shrink-0 w-1.5 h-1.5 bg-morandi-400 rounded-full mt-2"></span>
<span>
问诊时可以拿出手机查看问题清单,避免紧张时忘记要问的内容
</span>
</li>
<li className="flex items-start gap-2">
<span className="flex-shrink-0 w-1.5 h-1.5 bg-morandi-400 rounded-full mt-2"></span>
<span>听从医生建议,记录重要的用药和注意事项</span>
</li>
<li className="flex items-start gap-2">
<span className="flex-shrink-0 w-1.5 h-1.5 bg-morandi-400 rounded-full mt-2"></span>
<span>如医生时间有限,优先询问标记为"重要"的问题</span>
</li>
<li className="flex items-start gap-2">
<span className="flex-shrink-0 w-1.5 h-1.5 bg-morandi-400 rounded-full mt-2"></span>
<span>
就诊后可返回APP查看AI建议,结合医生意见调整生活方式
</span>
</li>
</ul>
</Card>
</motion.div>
{/* 底部操作 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6 }}
>
<div className="grid grid-cols-2 gap-3">
<Button
variant="outline"
className="h-12"
onClick={() => navigate(`/share/${reportId}`)}
>
<Share2 className="w-4 h-4 mr-2" />
分享清单
</Button>
<Button className="h-12 bg-morandi-400">
<Download className="w-4 h-4 mr-2" />
导出PDF
</Button>
</div>
</motion.div>
</div>
</div>
);
}