735 lines
28 KiB
TypeScript
735 lines
28 KiB
TypeScript
import { useState, useEffect, useRef, useCallback } from "react";
|
||
import { useNavigate, useParams } from "react-router";
|
||
import { motion, AnimatePresence } from "motion/react";
|
||
import {
|
||
ChevronLeft,
|
||
Share2,
|
||
CheckCircle2,
|
||
AlertCircle,
|
||
Info,
|
||
Loader2,
|
||
Heart,
|
||
Baby,
|
||
TrendingUp,
|
||
Download,
|
||
Check,
|
||
Image as ImageIcon,
|
||
Smartphone,
|
||
} from "lucide-react";
|
||
import { toPng } from "html-to-image";
|
||
import { Card } from "../components/ui/card";
|
||
import { Badge } from "../components/ui/badge";
|
||
import { Button } from "../components/ui/button";
|
||
import { generateShareData, generateWxacode } from "../services/api"
|
||
import bridge from "../services/bridge";
|
||
|
||
type SaveStatus = "idle" | "generating" | "success" | "error";
|
||
|
||
/** 检测是否支持微信小程序分享(wx.miniProgram 必须存在) */
|
||
function supportsWxMiniProgramShare(): boolean {
|
||
try {
|
||
const w = (window as any).wx;
|
||
return !!(w && w.miniProgram && typeof w.miniProgram.navigateBack === 'function');
|
||
} catch { return false; }
|
||
}
|
||
|
||
|
||
/** 模块级状态:避免构建压缩时变量名不一致导致 ReferenceError */
|
||
const _wp = { url: null as string | null, tick: 0 };
|
||
export function SharePage() {
|
||
const navigate = useNavigate();
|
||
const { reportId } = useParams();
|
||
const [loading, setLoading] = useState(true);
|
||
const [shareData, setShareData] = useState<any>(null);
|
||
const [saveStatus, setSaveStatus] = useState<SaveStatus>("idle");
|
||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||
const [linkCopied, setLinkCopied] = useState(false);
|
||
const [canShareToWechat, setCanShareToWechat] = useState(false);
|
||
const wxacodeUrl = useRef<string | null>(null);
|
||
const [, forceUpdate] = useState(0);
|
||
const cardRef = useRef<HTMLDivElement>(null);
|
||
|
||
useEffect(() => {
|
||
setCanShareToWechat(supportsWxMiniProgramShare());
|
||
}, []);
|
||
|
||
// 复制链接(客户端优先,bridge 降级)
|
||
const handleCopyLink = useCallback(async () => {
|
||
const shareUrl = `${window.location.origin}/app_03/share/${reportId}`;
|
||
let copied = false;
|
||
// 尝试 navigator.clipboard
|
||
try {
|
||
await navigator.clipboard.writeText(shareUrl);
|
||
copied = true;
|
||
} catch {}
|
||
// 降级:execCommand
|
||
if (!copied) {
|
||
const ta = document.createElement('textarea');
|
||
ta.value = shareUrl;
|
||
ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0';
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
try { copied = document.execCommand('copy'); } catch {}
|
||
document.body.removeChild(ta);
|
||
}
|
||
// 再降级:bridge.copyLink(依赖 postMessage,web-view 中可能不即时)
|
||
if (!copied) {
|
||
bridge.copyLink(shareUrl);
|
||
}
|
||
setLinkCopied(true);
|
||
setTimeout(() => setLinkCopied(false), 2000);
|
||
}, [reportId]);
|
||
|
||
useEffect(() => {
|
||
if (!reportId) return;
|
||
generateShareData(reportId)
|
||
.then((data) => {
|
||
setShareData(data.share_data);
|
||
})
|
||
.catch(() => {/* keep null */})
|
||
.finally(() => setLoading(false));
|
||
}, [reportId]);
|
||
|
||
// 太阳码:shareData 就绪后异步生成,blob URL 存到 wxacodeUrl
|
||
useEffect(() => {
|
||
if (!shareData) return;
|
||
let cancelled = false;
|
||
generateWxacode({ width: 430 })
|
||
.then((url) => {
|
||
if (!cancelled) { _wp.url = url; forceUpdate(n => n + 1); }
|
||
})
|
||
.catch((err) => {
|
||
console.warn("[SharePage] 太阳码生成失败:", err);
|
||
});
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [shareData]);
|
||
|
||
// 卸载时清理 blob URL
|
||
useEffect(() => {
|
||
return () => {
|
||
if (_wp.url?.startsWith("blob:")) URL.revokeObjectURL(_wp.url);
|
||
};
|
||
}, []);
|
||
|
||
// 生成长图
|
||
const generateImage = useCallback(async (): Promise<string | null> => {
|
||
if (!cardRef.current) return null;
|
||
|
||
// 等待太阳码渲染完成(blob URL 需浏览器解码)
|
||
if (shareData && !_wp.url) {
|
||
await new Promise<void>((resolve) => {
|
||
const start = Date.now();
|
||
const tick = () => {
|
||
if (_wp.url || Date.now() - start > 1500) {
|
||
resolve();
|
||
return;
|
||
}
|
||
setTimeout(tick, 100);
|
||
};
|
||
tick();
|
||
});
|
||
if (!_wp.url) {
|
||
console.warn("[SharePage] 太阳码未就绪,截图中可能缺失二维码");
|
||
}
|
||
}
|
||
|
||
try {
|
||
// html-to-image: 将 DOM 节点转为 PNG data URL
|
||
const dataUrl = await toPng(cardRef.current, {
|
||
cacheBust: true,
|
||
pixelRatio: 2, // 2x 高清
|
||
backgroundColor: "#ffffff",
|
||
style: {
|
||
// 确保截图时移除圆角阴影的裁切问题
|
||
borderRadius: "0",
|
||
},
|
||
});
|
||
return dataUrl;
|
||
} catch (err) {
|
||
console.error("生成图片失败:", err);
|
||
return null;
|
||
}
|
||
}, [shareData]);
|
||
|
||
// 一键保存到相册 / 下载
|
||
const handleSaveImage = useCallback(async () => {
|
||
setSaveStatus("generating");
|
||
|
||
try {
|
||
const dataUrl = imageUrl || (await generateImage());
|
||
if (!dataUrl) {
|
||
setSaveStatus("error");
|
||
setTimeout(() => setSaveStatus("idle"), 2000);
|
||
return;
|
||
}
|
||
|
||
if (!imageUrl) setImageUrl(dataUrl);
|
||
|
||
// 尝试使用 Web Share API(移动端可直接分享/保存到相册)
|
||
if (navigator.share && navigator.canShare) {
|
||
try {
|
||
const response = await fetch(dataUrl);
|
||
const blob = await response.blob();
|
||
const file = new File([blob], `孕育无忧-报告解读-${reportId}.png`, {
|
||
type: "image/png",
|
||
});
|
||
|
||
if (navigator.canShare({ files: [file] })) {
|
||
await navigator.share({
|
||
files: [file],
|
||
title: "孕育无忧 · AI 报告解读",
|
||
});
|
||
setSaveStatus("success");
|
||
setTimeout(() => setSaveStatus("idle"), 3000);
|
||
return;
|
||
}
|
||
} catch (shareErr) {
|
||
// 用户取消分享或不支持,降级到下载
|
||
if ((shareErr as Error).name === "AbortError") {
|
||
setSaveStatus("idle");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 降级方案:触发下载(iOS Safari / Android 浏览器)
|
||
const link = document.createElement("a");
|
||
link.download = `孕育无忧-报告解读-${reportId}.png`;
|
||
link.href = dataUrl;
|
||
link.style.display = "none";
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
|
||
setSaveStatus("success");
|
||
setTimeout(() => setSaveStatus("idle"), 3000);
|
||
} catch (err) {
|
||
console.error("保存失败:", err);
|
||
setSaveStatus("error");
|
||
setTimeout(() => setSaveStatus("idle"), 2000);
|
||
}
|
||
}, [imageUrl, generateImage, reportId]);
|
||
|
||
// 预览:在页面加载数据后自动生成图片预览
|
||
useEffect(() => {
|
||
if (shareData && cardRef.current && !imageUrl) {
|
||
// 延迟一帧确保 DOM 渲染完毕
|
||
const timer = setTimeout(async () => {
|
||
const url = await generateImage();
|
||
if (url) setImageUrl(url);
|
||
}, 500);
|
||
return () => clearTimeout(timer);
|
||
}
|
||
}, [shareData, generateImage, imageUrl]);
|
||
|
||
if (loading || !shareData) {
|
||
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>
|
||
);
|
||
}
|
||
|
||
const getStatusColor = (status: string) => {
|
||
switch (status) {
|
||
case "normal":
|
||
return {
|
||
bg: "bg-morandi-sage-100",
|
||
text: "text-morandi-sage-600",
|
||
icon: "text-morandi-sage-400",
|
||
};
|
||
case "attention":
|
||
return {
|
||
bg: "bg-morandi-rose-100",
|
||
text: "text-morandi-600",
|
||
icon: "text-morandi-rose-400",
|
||
};
|
||
default:
|
||
return {
|
||
bg: "bg-gray-100",
|
||
text: "text-gray-600",
|
||
icon: "text-gray-400",
|
||
};
|
||
}
|
||
};
|
||
|
||
const statusColors = getStatusColor(shareData.overall_status);
|
||
|
||
const getSaveButtonContent = () => {
|
||
switch (saveStatus) {
|
||
case "generating":
|
||
return (
|
||
<>
|
||
<Loader2 className="w-5 h-5 animate-spin" />
|
||
<span>正在生成图片...</span>
|
||
</>
|
||
);
|
||
case "success":
|
||
return (
|
||
<>
|
||
<Check className="w-5 h-5" />
|
||
<span>已保存到相册</span>
|
||
</>
|
||
);
|
||
case "error":
|
||
return (
|
||
<>
|
||
<AlertCircle className="w-5 h-5" />
|
||
<span>保存失败,请重试</span>
|
||
</>
|
||
);
|
||
default:
|
||
return (
|
||
<>
|
||
<Download className="w-5 h-5" />
|
||
<span>保存图片到相册</span>
|
||
</>
|
||
);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="min-h-dvh bg-morandi-50 pb-28">
|
||
{/* 顶部导航 */}
|
||
<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">
|
||
<Share2 className="w-5 h-5 text-white" />
|
||
</div>
|
||
<div>
|
||
<h1 className="font-semibold text-gray-800">分享长图</h1>
|
||
<p className="text-xs text-gray-500">生成精美报告长图</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mx-auto p-4 space-y-5">
|
||
{/* 图片预览区域 */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
>
|
||
{/* 如果已经生成了图片,直接展示图片(可长按保存) */}
|
||
{imageUrl ? (
|
||
<div className="relative">
|
||
<img
|
||
src={imageUrl}
|
||
alt="分享长图"
|
||
className="w-full rounded-3xl shadow-lg"
|
||
style={{ userSelect: "none" }}
|
||
/>
|
||
{/* 长按提示覆盖层 */}
|
||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/30 to-transparent rounded-b-3xl px-4 py-3">
|
||
<p className="text-white text-xs text-center opacity-80">
|
||
💡 长按图片也可直接保存
|
||
</p>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="flex items-center justify-center py-16 bg-white rounded-3xl shadow-lg">
|
||
<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>
|
||
)}
|
||
</motion.div>
|
||
|
||
{/* 隐藏的 HTML 卡片(用于截图,不展示给用户) */}
|
||
<div
|
||
style={{
|
||
position: "absolute",
|
||
left: "-9999px",
|
||
top: 0,
|
||
width: "375px", // 固定 iPhone 宽度,保证截图一致
|
||
}}
|
||
>
|
||
<div ref={cardRef} style={{ background: "#ffffff", padding: "0" }}>
|
||
<ShareCardContent shareData={shareData} getStatusColor={getStatusColor} statusColors={statusColors} />
|
||
</div>
|
||
</div>
|
||
|
||
{/* 操作提示 */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: 0.1 }}
|
||
>
|
||
<Card className="bg-morandi-50 border-morandi-200 rounded-2xl p-4">
|
||
<div className="flex items-start gap-3">
|
||
<Info className="w-5 h-5 text-morandi-400 flex-shrink-0 mt-0.5" />
|
||
<div>
|
||
<h4 className="font-semibold text-gray-800 text-sm mb-2">💡 分享说明</h4>
|
||
<div className="space-y-1.5">
|
||
<p className="text-xs text-gray-600 leading-relaxed">
|
||
• 点击下方按钮,一键保存图片到手机相册
|
||
</p>
|
||
<p className="text-xs text-gray-600 leading-relaxed">
|
||
• 也可以长按上方图片直接保存
|
||
</p>
|
||
<p className="text-xs text-gray-600 leading-relaxed">
|
||
• 个人信息已自动脱敏处理,保护您的隐私
|
||
</p>
|
||
</div>
|
||
</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 space-y-3">
|
||
{/* 微信分享:仅 wx.miniProgram 可用时显示 */}
|
||
{canShareToWechat ? (
|
||
<Button
|
||
onClick={() => bridge.share({
|
||
title: '孕育无忧 · 报告解读',
|
||
summary: shareData?.overall_summary || shareData?.emotional_summary || '',
|
||
href: window.location.href,
|
||
scene: 'WXSceneSession',
|
||
})}
|
||
className="w-full h-14 rounded-2xl text-base font-semibold flex items-center justify-center gap-2 bg-[#07C160] hover:bg-[#06AD56] text-white shadow-lg shadow-green-300/30"
|
||
>
|
||
<Share2 className="w-5 h-5" />
|
||
<span>分享给微信好友</span>
|
||
</Button>
|
||
) : (
|
||
<Card className="bg-blue-50 border-blue-200 rounded-2xl p-4">
|
||
<div className="flex items-start gap-3">
|
||
<Smartphone className="w-5 h-5 text-blue-400 flex-shrink-0 mt-0.5" />
|
||
<div>
|
||
<p className="text-sm font-medium text-blue-700 mb-1">📱 如何分享</p>
|
||
<p className="text-xs text-blue-600 leading-relaxed">
|
||
长按上方预览图片 → 保存到相册,然后通过微信发送给好友
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
)}
|
||
<Button
|
||
onClick={handleCopyLink}
|
||
className="w-full h-12 rounded-2xl text-sm font-medium flex items-center justify-center gap-2 border-2 border-gray-200 bg-white hover:bg-gray-50 text-gray-600"
|
||
>
|
||
{linkCopied ? <Check className="w-4 h-4 text-green-500" /> : null}
|
||
<span>{linkCopied ? "已复制链接" : "复制链接分享"}</span>
|
||
</Button>
|
||
<Button
|
||
onClick={handleSaveImage}
|
||
disabled={saveStatus === "generating"}
|
||
className={`w-full h-14 rounded-2xl text-base font-semibold flex items-center justify-center gap-2 transition-all ${
|
||
saveStatus === "success"
|
||
? "bg-morandi-sage-400 hover:bg-morandi-sage-500 text-white"
|
||
: saveStatus === "error"
|
||
? "bg-red-400 hover:bg-red-500 text-white"
|
||
: "bg-gradient-to-r from-morandi-400 to-morandi-500 hover:from-morandi-500 hover:to-morandi-600 text-white shadow-lg shadow-morandi-300/30"
|
||
}`}
|
||
>
|
||
{getSaveButtonContent()}
|
||
</Button>
|
||
<p className="text-center text-xs text-gray-400 mt-2">
|
||
分享到微信或保存图片到相册
|
||
<span className="ml-2 opacity-40">v26070203</span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 分享卡片内容组件(纯 HTML/CSS,不使用 motion 动画,用于截图)
|
||
* 独立提取,确保截图时样式完整、无动画干扰
|
||
*/
|
||
function ShareCardContent({
|
||
shareData,
|
||
getStatusColor,
|
||
statusColors,
|
||
}: {
|
||
shareData: any;
|
||
getStatusColor: (status: string) => { bg: string; text: string; icon: string };
|
||
statusColors: { bg: string; text: string; icon: string };
|
||
}) {
|
||
return (
|
||
<div style={{ background: "#ffffff", fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" }}>
|
||
<div style={{ padding: "24px", display: "flex", flexDirection: "column", gap: "20px" }}>
|
||
{/* 标题区 */}
|
||
<div style={{ textAlign: "center", paddingBottom: "20px", borderBottom: "2px solid #f3f4f6" }}>
|
||
<div
|
||
style={{
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: "8px",
|
||
padding: "8px 16px",
|
||
background: "linear-gradient(135deg, #b8a9c9, #9d8ab5)",
|
||
color: "white",
|
||
borderRadius: "9999px",
|
||
fontSize: "14px",
|
||
fontWeight: 600,
|
||
marginBottom: "16px",
|
||
}}
|
||
>
|
||
<Heart style={{ width: 16, height: 16, fill: "white", color: "white" }} />
|
||
<span>孕育无忧 · AI 智能解读</span>
|
||
</div>
|
||
<h2
|
||
style={{
|
||
fontSize: "24px",
|
||
fontWeight: 700,
|
||
color: "#1f2937",
|
||
marginBottom: "8px",
|
||
marginTop: "0",
|
||
}}
|
||
>
|
||
{shareData.report_type}
|
||
</h2>
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: "12px", fontSize: "14px", color: "#6b7280" }}>
|
||
{shareData.pregnancy_week && <span>孕 {shareData.pregnancy_week} 周</span>}
|
||
{shareData.user_name && (
|
||
<>
|
||
<span>·</span>
|
||
<span>{shareData.user_name}</span>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 整体评估 */}
|
||
<div className={`${statusColors.bg} rounded-2xl p-5`}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: "12px", marginBottom: "12px" }}>
|
||
<div
|
||
style={{
|
||
width: 48,
|
||
height: 48,
|
||
background: "rgba(255,255,255,0.5)",
|
||
borderRadius: "12px",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
}}
|
||
>
|
||
<Baby className={`w-6 h-6 ${statusColors.icon}`} />
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<h3 style={{ fontWeight: 700, color: "#1f2937", fontSize: "18px", margin: 0 }}>
|
||
整体评估
|
||
</h3>
|
||
<Badge className={`${statusColors.bg} ${statusColors.text} mt-1`}>
|
||
{shareData.overall_status === "normal" ? "一切正常" : "需要关注"}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
<p className={`text-sm ${statusColors.text} leading-relaxed`} style={{ margin: 0 }}>
|
||
{shareData.emotional_summary || shareData.overall_summary}
|
||
</p>
|
||
</div>
|
||
|
||
{/* 详细指标 */}
|
||
{shareData.categories && shareData.categories.length > 0 && (
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
||
<h3 style={{ fontWeight: 700, color: "#1f2937", fontSize: "18px", display: "flex", alignItems: "center", gap: "8px", margin: 0 }}>
|
||
<TrendingUp className="w-5 h-5 text-morandi-400" />
|
||
详细指标
|
||
</h3>
|
||
{shareData.categories.map((category: any, idx: number) => {
|
||
const catColors = getStatusColor(category.status);
|
||
return (
|
||
<div key={idx} className={`${catColors.bg} rounded-2xl p-4`}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: "8px", marginBottom: "12px" }}>
|
||
<h4 style={{ fontWeight: 600, color: "#1f2937", margin: 0 }}>{category.category}</h4>
|
||
<Badge className={`${catColors.bg} ${catColors.text} text-xs`}>
|
||
{category.status === "normal" ? "正常" : "需关注"}
|
||
</Badge>
|
||
</div>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||
{category.indicators.map((ind: any, indIdx: number) => (
|
||
<div key={indIdx} style={{ background: "rgba(255,255,255,0.7)", borderRadius: "12px", padding: "12px" }}>
|
||
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: "8px" }}>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontWeight: 600, color: "#1f2937", fontSize: "14px", marginBottom: "4px" }}>
|
||
{ind.name}
|
||
{ind.nickname && (
|
||
<span style={{ fontSize: "12px", color: "#9ca3af", marginLeft: "8px" }}>({ind.nickname})</span>
|
||
)}
|
||
</div>
|
||
{ind.value && (
|
||
<div style={{ fontSize: "18px", fontWeight: 700, color: "#7c6995" }}>
|
||
{ind.value} {ind.unit}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<Badge className={`text-xs ${
|
||
ind.status === "normal"
|
||
? "bg-morandi-sage-100 text-morandi-sage-600"
|
||
: "bg-morandi-rose-100 text-morandi-rose-600"
|
||
}`}>
|
||
{ind.status === "normal" ? "正常" : ind.status === "high" ? "偏高" : "偏低"}
|
||
</Badge>
|
||
</div>
|
||
{ind.what_is_it && (
|
||
<p style={{ fontSize: "12px", color: "#6b7280", marginBottom: "8px", margin: "0 0 8px 0" }}>
|
||
{ind.what_is_it}
|
||
</p>
|
||
)}
|
||
{ind.suggestion && (
|
||
<div style={{ background: "#faf7fc", borderRadius: "8px", padding: "8px", marginTop: "8px" }}>
|
||
<p style={{ fontSize: "12px", color: "#4b5563", margin: 0 }}>💡 {ind.suggestion}</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
|
||
{/* 行动建议 */}
|
||
{shareData.action_items && shareData.action_items.length > 0 && (
|
||
<div>
|
||
<h3 style={{ fontWeight: 700, color: "#1f2937", fontSize: "18px", marginBottom: "12px", marginTop: 0 }}>
|
||
接下来该做什么?
|
||
</h3>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||
{shareData.action_items.map((item: any, idx: number) => (
|
||
<div
|
||
key={idx}
|
||
style={{
|
||
borderRadius: "12px",
|
||
padding: "12px",
|
||
background: item.priority === "high" ? "#fce4ec" : "#faf7fc",
|
||
}}
|
||
>
|
||
<div style={{ display: "flex", alignItems: "flex-start", gap: "8px" }}>
|
||
<span
|
||
style={{
|
||
flexShrink: 0,
|
||
width: 20,
|
||
height: 20,
|
||
background: "#b8a9c9",
|
||
color: "white",
|
||
borderRadius: "50%",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
fontSize: "12px",
|
||
fontWeight: 700,
|
||
marginTop: "2px",
|
||
}}
|
||
>
|
||
{idx + 1}
|
||
</span>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontWeight: 600, color: "#1f2937", fontSize: "14px", marginBottom: "4px" }}>
|
||
{item.title || item.content}
|
||
</div>
|
||
{item.description && (
|
||
<p style={{ fontSize: "12px", color: "#6b7280", margin: 0 }}>{item.description}</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 温馨提示 */}
|
||
{shareData.tips && shareData.tips.length > 0 && (
|
||
<div className="bg-morandi-50 rounded-2xl p-4">
|
||
<h4 style={{ fontWeight: 600, color: "#1f2937", fontSize: "14px", marginBottom: "8px", display: "flex", alignItems: "center", gap: "8px", marginTop: 0 }}>
|
||
<Heart className="w-4 h-4 text-morandi-400" />
|
||
温馨提示
|
||
</h4>
|
||
<ul style={{ listStyle: "none", padding: 0, margin: 0, display: "flex", flexDirection: "column", gap: "4px" }}>
|
||
{shareData.tips.map((tip: string, idx: number) => (
|
||
<li key={idx} style={{ fontSize: "12px", color: "#6b7280", display: "flex", alignItems: "flex-start", gap: "8px" }}>
|
||
<span style={{ color: "#b8a9c9" }}>•</span>
|
||
<span>{tip}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
|
||
{/* 太阳码区(截图包含) */}
|
||
{_wp.url && (
|
||
<div
|
||
style={{
|
||
textAlign: "center",
|
||
paddingTop: "20px",
|
||
borderTop: "1px dashed #e5e7eb",
|
||
}}
|
||
>
|
||
<img
|
||
src={_wp.url}
|
||
alt="扫码下载孕育无忧"
|
||
crossOrigin="anonymous"
|
||
style={{
|
||
width: 200,
|
||
height: 200,
|
||
borderRadius: 8,
|
||
background: "#ffffff",
|
||
}}
|
||
/>
|
||
<p
|
||
style={{
|
||
marginTop: 8,
|
||
fontSize: 12,
|
||
color: "#9ca3af",
|
||
lineHeight: 1.5,
|
||
margin: "8px 0 0 0",
|
||
}}
|
||
>
|
||
微信扫码下载「孕育无忧」APP
|
||
<br />
|
||
长按图片也可保存到相册
|
||
</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* 底部声明 */}
|
||
<div style={{ paddingTop: "20px", borderTop: "2px solid #f3f4f6", textAlign: "center" }}>
|
||
<p style={{ fontSize: "12px", color: "#9ca3af", marginBottom: "4px", margin: "0 0 4px 0" }}>
|
||
本报告由 AI 生成,仅供参考,不能替代医生诊断
|
||
</p>
|
||
<p style={{ fontSize: "12px", color: "#9ca3af", marginBottom: "8px", margin: "0 0 8px 0" }}>
|
||
如有疑问请及时咨询专业医生
|
||
</p>
|
||
<div
|
||
style={{
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
gap: "4px",
|
||
fontSize: "11px",
|
||
color: "#b8a9c9",
|
||
padding: "4px 12px",
|
||
background: "#faf7fc",
|
||
borderRadius: "9999px",
|
||
}}
|
||
>
|
||
<Heart style={{ width: 12, height: 12, fill: "#b8a9c9", color: "#b8a9c9" }} />
|
||
<span>孕育无忧 · 兜爸心选+</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|