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(null); const [saveStatus, setSaveStatus] = useState("idle"); const [imageUrl, setImageUrl] = useState(null); const [linkCopied, setLinkCopied] = useState(false); const [canShareToWechat, setCanShareToWechat] = useState(false); const wxacodeUrl = useRef(null); const [, forceUpdate] = useState(0); const cardRef = useRef(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 => { if (!cardRef.current) return null; // 等待太阳码渲染完成(blob URL 需浏览器解码) if (shareData && !_wp.url) { await new Promise((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 (

正在准备分享内容...

); } 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 ( <> 正在生成图片... ); case "success": return ( <> 已保存到相册 ); case "error": return ( <> 保存失败,请重试 ); default: return ( <> 保存图片到相册 ); } }; return (
{/* 顶部导航 */}

分享长图

生成精美报告长图

{/* 图片预览区域 */} {/* 如果已经生成了图片,直接展示图片(可长按保存) */} {imageUrl ? (
分享长图 {/* 长按提示覆盖层 */}

💡 长按图片也可直接保存

) : (

正在生成长图预览...

)}
{/* 隐藏的 HTML 卡片(用于截图,不展示给用户) */}
{/* 操作提示 */}

💡 分享说明

• 点击下方按钮,一键保存图片到手机相册

• 也可以长按上方图片直接保存

• 个人信息已自动脱敏处理,保护您的隐私

{/* 底部固定操作栏 */}
{/* 微信分享:仅 wx.miniProgram 可用时显示 */} {canShareToWechat ? ( ) : (

📱 如何分享

长按上方预览图片 → 保存到相册,然后通过微信发送给好友

)}

分享到微信或保存图片到相册 v26070203

); } /** * 分享卡片内容组件(纯 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 (
{/* 标题区 */}
孕育无忧 · AI 智能解读

{shareData.report_type}

{shareData.pregnancy_week && 孕 {shareData.pregnancy_week} 周} {shareData.user_name && ( <> · {shareData.user_name} )}
{/* 整体评估 */}

整体评估

{shareData.overall_status === "normal" ? "一切正常" : "需要关注"}

{shareData.emotional_summary || shareData.overall_summary}

{/* 详细指标 */} {shareData.categories && shareData.categories.length > 0 && (

详细指标

{shareData.categories.map((category: any, idx: number) => { const catColors = getStatusColor(category.status); return (

{category.category}

{category.status === "normal" ? "正常" : "需关注"}
{category.indicators.map((ind: any, indIdx: number) => (
{ind.name} {ind.nickname && ( ({ind.nickname}) )}
{ind.value && (
{ind.value} {ind.unit}
)}
{ind.status === "normal" ? "正常" : ind.status === "high" ? "偏高" : "偏低"}
{ind.what_is_it && (

{ind.what_is_it}

)} {ind.suggestion && (

💡 {ind.suggestion}

)}
))}
); })}
)} {/* 行动建议 */} {shareData.action_items && shareData.action_items.length > 0 && (

接下来该做什么?

{shareData.action_items.map((item: any, idx: number) => (
{idx + 1}
{item.title || item.content}
{item.description && (

{item.description}

)}
))}
)} {/* 温馨提示 */} {shareData.tips && shareData.tips.length > 0 && (

温馨提示

    {shareData.tips.map((tip: string, idx: number) => (
  • • {tip}
  • ))}
)} {/* 太阳码区(截图包含) */} {_wp.url && (
扫码下载孕育无忧

微信扫码下载「孕育无忧」APP
长按图片也可保存到相册

)} {/* 底部声明 */}

本报告由 AI 生成,仅供参考,不能替代医生诊断

如有疑问请及时咨询专业医生

孕育无忧 · 兜爸心选+
); }