727 lines
31 KiB
TypeScript
727 lines
31 KiB
TypeScript
import { useState, useEffect } from "react";
|
||
import { useNavigate, useParams } from "react-router";
|
||
import { motion, AnimatePresence } from "motion/react";
|
||
import bridge from "../services/bridge";
|
||
import {
|
||
ChevronLeft,
|
||
Heart,
|
||
TrendingUp,
|
||
AlertCircle,
|
||
CheckCircle2,
|
||
Info,
|
||
Calendar,
|
||
Share2,
|
||
Download,
|
||
Sparkles,
|
||
Activity,
|
||
Baby,
|
||
MessageCircle,
|
||
FileText,
|
||
X,
|
||
Link2,
|
||
Check,
|
||
AlertTriangle,
|
||
ShieldAlert,
|
||
Loader2,
|
||
Image as ImageIcon,
|
||
} from "lucide-react";
|
||
import { Button } from "../components/ui/button";
|
||
import { Card } from "../components/ui/card";
|
||
import { Badge } from "../components/ui/badge";
|
||
import { generateAnalysis, type AnalysisResponse } from "../services/api";
|
||
|
||
export function AIAnalysisPage() {
|
||
const navigate = useNavigate();
|
||
const { reportId } = useParams();
|
||
const [showShareSheet, setShowShareSheet] = useState(false);
|
||
const [linkCopied, setLinkCopied] = useState(false);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [analysisData, setAnalysisData] = useState<AnalysisResponse | null>(null);
|
||
|
||
useEffect(() => {
|
||
if (!reportId) return;
|
||
setLoading(true);
|
||
generateAnalysis(reportId)
|
||
.then((data) => setAnalysisData(data))
|
||
.catch((err) => setError(err instanceof Error ? err.message : "加载失败"))
|
||
.finally(() => setLoading(false));
|
||
}, [reportId]);
|
||
|
||
const handleCopyLink = async () => {
|
||
const shareUrl = `${window.location.origin}/share/${reportId}`;
|
||
// 微信 web-view 中 navigator.clipboard 不可用,用 execCommand 替代
|
||
let copied = false;
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = shareUrl;
|
||
textArea.style.position = 'fixed';
|
||
textArea.style.left = '-999999px';
|
||
textArea.style.top = '-999999px';
|
||
textArea.style.opacity = '0';
|
||
document.body.appendChild(textArea);
|
||
textArea.focus();
|
||
textArea.select();
|
||
try {
|
||
copied = document.execCommand('copy');
|
||
} catch (e) {
|
||
console.warn('[copy] execCommand failed:', e);
|
||
}
|
||
document.body.removeChild(textArea);
|
||
|
||
if (copied) {
|
||
setLinkCopied(true);
|
||
setTimeout(() => setLinkCopied(false), 2000);
|
||
} else {
|
||
// 兜底:通过小程序桥接(postMessage 在页面离开时触发)
|
||
bridge.copyLink(shareUrl);
|
||
setLinkCopied(true);
|
||
setTimeout(() => setLinkCopied(false), 2000);
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="min-h-dvh bg-morandi-50 flex items-center justify-center">
|
||
<div className="text-center">
|
||
<Loader2 className="w-12 h-12 text-morandi-400 animate-spin mx-auto mb-4" />
|
||
<p className="text-gray-600 font-medium">AI 正在解读您的报告...</p>
|
||
<p className="text-gray-400 text-sm mt-2">这可能需要几秒钟</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error || !analysisData) {
|
||
return (
|
||
<div className="min-h-dvh bg-morandi-50 flex items-center justify-center">
|
||
<div className="text-center px-8">
|
||
<AlertCircle className="w-12 h-12 text-red-400 mx-auto mb-4" />
|
||
<p className="text-gray-800 font-medium mb-2">解读失败</p>
|
||
<p className="text-gray-500 text-sm mb-4">{error || "未知错误"}</p>
|
||
<Button onClick={() => navigate("/home")} variant="outline">返回首页</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const getStatusColor = (status: string) => {
|
||
switch (status) {
|
||
case "normal":
|
||
return {
|
||
bg: "bg-morandi-sage-100",
|
||
border: "border-morandi-sage-200",
|
||
text: "text-morandi-sage-600",
|
||
badge: "bg-morandi-sage-100 text-morandi-sage-600",
|
||
icon: "text-morandi-sage-400",
|
||
};
|
||
case "attention":
|
||
return {
|
||
bg: "bg-morandi-rose-100",
|
||
border: "border-morandi-200",
|
||
text: "text-morandi-600",
|
||
badge: "bg-morandi-rose-100 text-morandi-600",
|
||
icon: "text-morandi-rose-400",
|
||
};
|
||
case "high":
|
||
return {
|
||
bg: "bg-red-50",
|
||
border: "border-red-200",
|
||
text: "text-red-700",
|
||
badge: "bg-red-100 text-red-700",
|
||
icon: "text-red-500",
|
||
};
|
||
default:
|
||
return {
|
||
bg: "bg-gray-50",
|
||
border: "border-gray-200",
|
||
text: "text-gray-700",
|
||
badge: "bg-gray-100 text-gray-700",
|
||
icon: "text-gray-500",
|
||
};
|
||
}
|
||
};
|
||
|
||
const statusColors = getStatusColor(analysisData.overall_status);
|
||
|
||
// 指标状态颜色(紫色系色温区分:冷调sage→中性purple→暖调rose)
|
||
const getIndicatorColor = (indicatorStatus: string) => {
|
||
switch (indicatorStatus) {
|
||
case "normal":
|
||
return {
|
||
bg: "bg-morandi-sage-100",
|
||
border: "border-morandi-sage-200",
|
||
text: "text-morandi-sage-600",
|
||
badge: "bg-morandi-sage-100 text-morandi-sage-600",
|
||
barBg: "bg-morandi-sage-200",
|
||
barFill: "bg-morandi-sage-400",
|
||
};
|
||
case "borderline":
|
||
return {
|
||
bg: "bg-morandi-100",
|
||
border: "border-morandi-200",
|
||
text: "text-morandi-600",
|
||
badge: "bg-morandi-200 text-morandi-700",
|
||
barBg: "bg-morandi-200",
|
||
barFill: "bg-morandi-400",
|
||
};
|
||
case "abnormal":
|
||
return {
|
||
bg: "bg-morandi-rose-100",
|
||
border: "border-morandi-rose-300",
|
||
text: "text-morandi-700",
|
||
badge: "bg-morandi-rose-100 text-morandi-rose-400",
|
||
barBg: "bg-morandi-rose-100",
|
||
barFill: "bg-morandi-rose-400",
|
||
};
|
||
default:
|
||
return {
|
||
bg: "bg-gray-50",
|
||
border: "border-gray-200",
|
||
text: "text-gray-700",
|
||
badge: "bg-gray-100 text-gray-700",
|
||
barBg: "bg-gray-100",
|
||
barFill: "bg-gray-400",
|
||
};
|
||
}
|
||
};
|
||
|
||
const getRiskLabel = (riskLevel: string) => {
|
||
switch (riskLevel) {
|
||
case "low":
|
||
return { label: "低风险", color: "bg-morandi-100 text-morandi-600" };
|
||
case "medium":
|
||
return { label: "中风险", color: "bg-morandi-rose-100 text-morandi-rose-400" };
|
||
case "high":
|
||
return { label: "高风险", color: "bg-morandi-rose-100 text-morandi-700" };
|
||
default:
|
||
return { label: "", color: "" };
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="min-h-dvh bg-morandi-50">
|
||
{/* 顶部导航 */}
|
||
<div className="bg-white border-b border-gray-100 sticky top-0 z-20">
|
||
<div className="mx-auto px-4 py-4 flex items-center justify-between">
|
||
<button
|
||
onClick={() => navigate(-1)}
|
||
className="flex items-center text-gray-600 hover:text-gray-900"
|
||
>
|
||
<ChevronLeft className="w-5 h-5 mr-1" />
|
||
返回
|
||
</button>
|
||
<h1 className="font-semibold text-gray-800">AI 智能解读</h1>
|
||
<button
|
||
onClick={() => setShowShareSheet(true)}
|
||
className="p-2 hover:bg-gray-100 rounded-lg"
|
||
>
|
||
<Share2 className="w-5 h-5 text-gray-600" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mx-auto p-4 space-y-6 pb-20">
|
||
{/* AI 解读标识 */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
className="flex items-center justify-center gap-2 text-morandi-500"
|
||
>
|
||
<Sparkles className="w-5 h-5" />
|
||
<span className="text-sm font-medium">AI 深度解读 · 个性化定制</span>
|
||
</motion.div>
|
||
|
||
{/* 整体评估卡片 */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.1 }}
|
||
>
|
||
<Card
|
||
className={`${statusColors.bg} border-2 ${statusColors.border} rounded-3xl p-6 shadow-sm`}
|
||
>
|
||
<div className="flex items-start gap-4 mb-4">
|
||
<div
|
||
className={`w-16 h-16 ${statusColors.badge} rounded-2xl flex items-center justify-center flex-shrink-0`}
|
||
>
|
||
<Baby className={`w-8 h-8 ${statusColors.icon}`} />
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<h2 className="text-xl font-semibold text-gray-800">
|
||
整体评估
|
||
</h2>
|
||
<Badge className={statusColors.badge}>
|
||
{analysisData.overall_status === "normal"
|
||
? "一切正常"
|
||
: analysisData.overall_status === "attention"
|
||
? "需要关注"
|
||
: "需要重视"}
|
||
</Badge>
|
||
</div>
|
||
<p className={`text-sm ${statusColors.text} mb-3 leading-relaxed`}>
|
||
{analysisData.emotional_summary || analysisData.overall_summary}
|
||
</p>
|
||
<div className="flex flex-wrap gap-3 text-xs text-gray-600">
|
||
<div className="flex items-center gap-1">
|
||
<Calendar className="w-3 h-3" />
|
||
<span>{new Date(analysisData.created_at).toLocaleDateString("zh-CN")}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 风险等级指示器 */}
|
||
<div className="bg-white/50 backdrop-blur-sm rounded-2xl p-4">
|
||
<div className="flex items-center justify-between mb-2">
|
||
<span className="text-sm font-medium text-gray-700">
|
||
综合风险评级
|
||
</span>
|
||
<span
|
||
className={`text-sm font-semibold ${
|
||
analysisData.risk_level === "low"
|
||
? "text-morandi-sage-500"
|
||
: analysisData.risk_level === "medium"
|
||
? "text-morandi-600"
|
||
: "text-red-600"
|
||
}`}
|
||
>
|
||
{analysisData.risk_level === "low"
|
||
? "低风险"
|
||
: analysisData.risk_level === "medium"
|
||
? "中风险"
|
||
: "高风险"}
|
||
</span>
|
||
</div>
|
||
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
|
||
<motion.div
|
||
initial={{ width: 0 }}
|
||
animate={{
|
||
width:
|
||
analysisData.risk_level === "low"
|
||
? "25%"
|
||
: analysisData.risk_level === "medium"
|
||
? "60%"
|
||
: "90%",
|
||
}}
|
||
transition={{ delay: 0.5, duration: 0.8 }}
|
||
className={`h-full rounded-full ${
|
||
analysisData.risk_level === "low"
|
||
? "bg-morandi-sage-400"
|
||
: analysisData.risk_level === "medium"
|
||
? "bg-morandi-rose-400"
|
||
: "bg-red-500"
|
||
}`}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</motion.div>
|
||
|
||
{/* 分类详情 */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.2 }}
|
||
>
|
||
<h2 className="text-lg font-semibold text-gray-800 mb-4">详细解读</h2>
|
||
|
||
<div className="space-y-4">
|
||
{analysisData.categories.map((category, index) => {
|
||
const colors = getStatusColor(category.status);
|
||
return (
|
||
<motion.div
|
||
key={category.category}
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.3 + index * 0.1 }}
|
||
>
|
||
<Card className="bg-white rounded-3xl shadow-sm overflow-hidden border-0">
|
||
{/* 分类头部 */}
|
||
<div className={`${colors.bg} border-b-2 ${colors.border} p-5`}>
|
||
<div className="flex items-center gap-3 mb-2">
|
||
<div
|
||
className={`w-10 h-10 ${colors.badge} rounded-xl flex items-center justify-center`}
|
||
>
|
||
<Activity className={`w-5 h-5 ${colors.icon}`} />
|
||
</div>
|
||
<div className="flex-1">
|
||
<h3 className="font-semibold text-gray-800">
|
||
{category.category}
|
||
</h3>
|
||
</div>
|
||
<Badge className={colors.badge}>
|
||
{category.status === "normal"
|
||
? "正常"
|
||
: category.status === "attention"
|
||
? "需关注"
|
||
: "异常"}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 指标详情 */}
|
||
<div className="p-5 space-y-4">
|
||
{category.indicators.map((item, itemIdx) => {
|
||
const indicatorStatus = item.status === "normal" ? "normal" : item.status === "low" || item.status === "high" ? "abnormal" : "borderline";
|
||
const indColors = getIndicatorColor(indicatorStatus);
|
||
return (
|
||
<div
|
||
key={itemIdx}
|
||
className={`${indColors.bg} border-2 ${indColors.border} rounded-2xl p-4`}
|
||
>
|
||
{/* 指标名称和数值 */}
|
||
<div className="flex items-start justify-between mb-3">
|
||
<div className="flex-1">
|
||
<h4 className="font-semibold text-gray-800 mb-1">
|
||
{item.name}
|
||
</h4>
|
||
{item.nickname && (
|
||
<p className="text-xs text-gray-500 mb-2">
|
||
{item.nickname}
|
||
</p>
|
||
)}
|
||
<div className="flex items-baseline gap-2">
|
||
<span
|
||
className={`text-2xl font-semibold ${indColors.text}`}
|
||
>
|
||
{item.value}
|
||
</span>
|
||
<span className="text-sm text-gray-500">
|
||
{item.unit}
|
||
</span>
|
||
</div>
|
||
{item.reference_range && (
|
||
<p className="text-xs text-gray-400 mt-1">
|
||
参考范围:{item.reference_range}
|
||
</p>
|
||
)}
|
||
</div>
|
||
<div className="flex flex-col items-end gap-1.5">
|
||
<div
|
||
className={`px-3 py-1 ${indColors.badge} rounded-full flex items-center gap-1`}
|
||
>
|
||
{indicatorStatus === "normal" ? (
|
||
<CheckCircle2 className="w-4 h-4" />
|
||
) : (
|
||
<AlertCircle className="w-4 h-4" />
|
||
)}
|
||
<span className="text-xs font-medium">
|
||
{item.status === "normal" ? "正常" : item.status === "high" ? "偏高" : "偏低"}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 状态指示条 */}
|
||
<div className="mb-3">
|
||
<div className={`h-1.5 ${indColors.barBg} rounded-full overflow-hidden`}>
|
||
<div
|
||
className={`h-full ${indColors.barFill} rounded-full transition-all`}
|
||
style={{
|
||
width: indicatorStatus === "normal" ? "100%" : indicatorStatus === "borderline" ? "60%" : "30%",
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 白话解释 */}
|
||
<div className="space-y-3">
|
||
{item.what_is_it && (
|
||
<div className="bg-white/70 rounded-xl p-3">
|
||
<div className="flex items-start gap-2">
|
||
<Info className="w-4 h-4 text-morandi-400 flex-shrink-0 mt-0.5" />
|
||
<div className="flex-1">
|
||
<p className="text-xs font-medium text-gray-700 mb-1">这是什么?</p>
|
||
<p className="text-xs text-gray-600 leading-relaxed">{item.what_is_it}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{item.baby_impact && (
|
||
<div className="bg-white/70 rounded-xl p-3">
|
||
<div className="flex items-start gap-2">
|
||
<Baby className="w-4 h-4 text-morandi-400 flex-shrink-0 mt-0.5" />
|
||
<div className="flex-1">
|
||
<p className="text-xs font-medium text-gray-700 mb-1">对宝宝的影响</p>
|
||
<p className="text-xs text-gray-600 leading-relaxed">{item.baby_impact}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{item.suggestion && (
|
||
<div className="bg-morandi-100/50 rounded-xl p-3">
|
||
<div className="flex items-start gap-2">
|
||
<Heart className="w-4 h-4 text-morandi-400 flex-shrink-0 mt-0.5" />
|
||
<div className="flex-1">
|
||
<p className="text-xs font-medium text-gray-700 mb-1">建议措施</p>
|
||
<p className="text-xs text-gray-600 leading-relaxed">{item.suggestion}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{item.follow_up && (
|
||
<div className="flex items-center gap-2 text-xs text-morandi-500">
|
||
<Calendar className="w-3 h-3" />
|
||
<span>{item.follow_up}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</Card>
|
||
</motion.div>
|
||
);
|
||
})}
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* 行动建议 */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.5 }}
|
||
>
|
||
<h2 className="text-lg font-semibold text-gray-800 mb-4">
|
||
接下来该做什么?
|
||
</h2>
|
||
|
||
<div className="space-y-3">
|
||
{analysisData.action_items.map((rec, index) => (
|
||
<motion.div
|
||
key={index}
|
||
initial={{ opacity: 0, x: -20 }}
|
||
animate={{ opacity: 1, x: 0 }}
|
||
transition={{ delay: 0.6 + index * 0.1 }}
|
||
>
|
||
<Card
|
||
className={`rounded-2xl p-4 border-2 ${
|
||
rec.priority === "high"
|
||
? "bg-morandi-rose-100 border-morandi-200"
|
||
: rec.priority === "medium"
|
||
? "bg-morandi-50 border-morandi-200"
|
||
: "bg-gray-50 border-gray-200"
|
||
}`}
|
||
>
|
||
<div className="flex items-start gap-3">
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-2 mb-1">
|
||
<h4 className="font-semibold text-gray-800">
|
||
{rec.title}
|
||
</h4>
|
||
{rec.priority === "high" && (
|
||
<Badge className="bg-morandi-rose-100 text-morandi-600 text-xs">
|
||
重要
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
<p className="text-sm text-gray-600 mb-2">
|
||
{rec.description}
|
||
</p>
|
||
{rec.timeframe && (
|
||
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||
<Calendar className="w-3 h-3" />
|
||
<span>{rec.timeframe}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* 深度服务 - 新增 */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.7 }}
|
||
>
|
||
<h2 className="text-lg font-semibold text-gray-800 mb-4">
|
||
🎯 获取更多帮助
|
||
</h2>
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<Card
|
||
onClick={() => navigate(`/chat/${reportId}`)}
|
||
className="bg-morandi-100 rounded-2xl p-4 border-0 cursor-pointer hover:scale-105 transition-transform"
|
||
>
|
||
<div className="w-12 h-12 bg-morandi-400 rounded-xl flex items-center justify-center mb-3">
|
||
<MessageCircle className="w-6 h-6 text-white" />
|
||
</div>
|
||
<h3 className="font-semibold text-gray-800 mb-1">AI对话追问</h3>
|
||
<p className="text-xs text-gray-600">针对报告提问</p>
|
||
</Card>
|
||
|
||
<Card
|
||
onClick={() => navigate(`/doctor-questions/${reportId}`)}
|
||
className="bg-gradient-to-br from-morandi-slate-100 to-morandi-slate-50 rounded-2xl p-4 border-0 cursor-pointer hover:scale-105 transition-transform"
|
||
>
|
||
<div className="w-12 h-12 bg-morandi-slate-500 rounded-xl flex items-center justify-center mb-3">
|
||
<FileText className="w-6 h-6 text-white" />
|
||
</div>
|
||
<h3 className="font-semibold text-gray-800 mb-1">问诊清单</h3>
|
||
<p className="text-xs text-gray-600">就医沟通话术</p>
|
||
</Card>
|
||
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* 温馨提示 */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.9 }}
|
||
>
|
||
<Card className="bg-morandi-50 border-morandi-200 rounded-2xl p-4">
|
||
<div className="flex items-start gap-3">
|
||
<Heart className="w-5 h-5 text-morandi-400 flex-shrink-0 mt-0.5" />
|
||
<div className="flex-1">
|
||
<h4 className="font-semibold text-gray-800 mb-1">
|
||
温馨提示
|
||
</h4>
|
||
<p className="text-sm text-gray-600 leading-relaxed">
|
||
以上解读仅供参考,不能替代医生的专业诊断。如有任何疑问或不适,请及时就医咨询专业医生。保持好心情,宝宝能感受到妈妈的情绪哦 💕
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</motion.div>
|
||
</div>
|
||
|
||
{/* 底部操作栏 */}
|
||
<div className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 p-4 pb-safe z-10">
|
||
<div className="max-w-md mx-auto flex gap-3">
|
||
<Button
|
||
onClick={() => navigate("/home")}
|
||
variant="outline"
|
||
className="flex-1 h-12 rounded-xl border-2 border-gray-300"
|
||
>
|
||
返回首页
|
||
</Button>
|
||
<Button
|
||
onClick={() => navigate(`/report/${reportId}`)}
|
||
className="flex-1 h-12 rounded-xl bg-gradient-to-r from-morandi-400 to-morandi-500 hover:from-morandi-500 hover:to-morandi-500 text-white"
|
||
>
|
||
查看原始报告
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 分享弹窗 */}
|
||
<AnimatePresence>
|
||
{showShareSheet && (
|
||
<>
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
onClick={() => setShowShareSheet(false)}
|
||
className="fixed inset-0 bg-black/40 z-40"
|
||
/>
|
||
<motion.div
|
||
initial={{ y: "100%" }}
|
||
animate={{ y: 0 }}
|
||
exit={{ y: "100%" }}
|
||
transition={{ type: "spring", damping: 25, stiffness: 300 }}
|
||
className="fixed bottom-0 left-0 right-0 bg-white rounded-t-3xl z-50 max-h-[70vh] overflow-y-auto"
|
||
>
|
||
<div className="mx-auto p-5">
|
||
{/* 拖拽指示器 */}
|
||
<div className="w-10 h-1 bg-gray-200 rounded-full mx-auto mb-4" />
|
||
|
||
<div className="flex items-center justify-between mb-5">
|
||
<h3 className="font-semibold text-gray-800 text-lg">分享报告</h3>
|
||
<button
|
||
onClick={() => setShowShareSheet(false)}
|
||
className="w-8 h-8 rounded-full hover:bg-gray-100 flex items-center justify-center"
|
||
>
|
||
<X className="w-5 h-5 text-gray-400" />
|
||
</button>
|
||
</div>
|
||
|
||
{/* 分享方式 */}
|
||
<div className="space-y-3 mb-6">
|
||
{/* 生成分享长图 */}
|
||
<button
|
||
onClick={() => {
|
||
setShowShareSheet(false);
|
||
navigate(`/share/${reportId}`);
|
||
}}
|
||
className="w-full flex items-center gap-4 py-4 px-4 bg-gradient-to-r from-morandi-100 to-morandi-50 hover:from-morandi-200 hover:to-morandi-100 rounded-2xl transition-colors"
|
||
>
|
||
<div className="w-12 h-12 bg-gradient-to-br from-morandi-400 to-morandi-500 rounded-xl flex items-center justify-center flex-shrink-0">
|
||
<ImageIcon className="w-6 h-6 text-white" />
|
||
</div>
|
||
<div className="text-left flex-1">
|
||
<div className="font-semibold text-gray-800">
|
||
生成分享长图
|
||
</div>
|
||
<div className="text-xs text-gray-500">
|
||
生成精美报告长图,一键保存到相册
|
||
</div>
|
||
</div>
|
||
<ChevronLeft className="w-5 h-5 text-gray-300 rotate-180" />
|
||
</button>
|
||
|
||
{/* 复制链接 */}
|
||
<button
|
||
onClick={handleCopyLink}
|
||
className="w-full flex items-center gap-4 py-4 px-4 bg-morandi-50 hover:bg-morandi-100 rounded-2xl transition-colors"
|
||
>
|
||
<div className="w-12 h-12 bg-morandi-400 rounded-xl flex items-center justify-center flex-shrink-0">
|
||
{linkCopied ? (
|
||
<Check className="w-6 h-6 text-white" />
|
||
) : (
|
||
<Link2 className="w-6 h-6 text-white" />
|
||
)}
|
||
</div>
|
||
<div className="text-left flex-1">
|
||
<div className="font-semibold text-gray-800">
|
||
{linkCopied ? "链接已复制" : "复制分享链接"}
|
||
</div>
|
||
<div className="text-xs text-gray-500">
|
||
{linkCopied ? "可以分享给家人或医生" : "复制链接发送给家人或医生"}
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
|
||
{/* 分享预览卡片 */}
|
||
<Card className="bg-morandi-50 border-morandi-100 rounded-2xl p-4 mb-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-12 h-12 bg-gradient-to-br from-morandi-400 to-morandi-500 rounded-xl flex items-center justify-center flex-shrink-0">
|
||
<FileText className="w-6 h-6 text-white" />
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<h4 className="font-medium text-gray-800 text-sm truncate">
|
||
{analysisData.categories[0]?.category || "检查报告"} · AI解读
|
||
</h4>
|
||
<p className="text-xs text-gray-500 mt-0.5 truncate">
|
||
{analysisData.overall_summary}
|
||
</p>
|
||
<div className="flex items-center gap-2 mt-1">
|
||
<span className="text-xs text-gray-400">{new Date(analysisData.created_at).toLocaleDateString("zh-CN")}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 隐私提示 */}
|
||
<div className="flex items-center gap-2 text-xs text-gray-400 mb-2">
|
||
<Info className="w-3.5 h-3.5 flex-shrink-0" />
|
||
<span>分享时将自动隐藏个人敏感信息,保护您的隐私</span>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
</>
|
||
)}
|
||
</AnimatePresence>
|
||
</div>
|
||
);
|
||
} |