feat: 添加分享结果卡片功能,形成用户增长闭环

- 新增 ShareCardModal 组件,支持餐厅匹配和盲盒契约两种分享卡片
- 卡片包含品牌标识、匹配结果、餐厅/想法详情、二维码
- 使用 html-to-image 生成高清 PNG,支持保存图片和 Web Share API 分享
- 餐厅图片通过 canvas 转 data URL 处理跨域
- 集成到 MatchResult(极速救场)和 BlindBox reveal(周末契约)
This commit is contained in:
2026-02-26 13:50:38 +08:00
parent 14b0aaece4
commit 08eb55ca41
5 changed files with 1018 additions and 49 deletions
+37 -7
View File
@@ -17,6 +17,7 @@ import {
} from "lucide-react";
import confetti from "canvas-confetti";
import { getCachedProfile, isRegistered } from "@/lib/userId";
import ShareCardModal from "@/components/ShareCardModal";
import type { UserProfile } from "@/types";
interface RoomInfo {
@@ -57,6 +58,7 @@ export default function BlindboxRoomPage() {
const [submitFlash, setSubmitFlash] = useState(false);
const [error, setError] = useState("");
const [showInvite, setShowInvite] = useState(false);
const [showShareCard, setShowShareCard] = useState(false);
const [toast, setToast] = useState("");
const boxControls = useAnimation();
@@ -522,13 +524,23 @@ export default function BlindboxRoomPage() {
</div>
</div>
<motion.button
onClick={() => { setPhase("pool"); setRevealedIdea(null); }}
className="flex h-10 items-center gap-2 rounded-full bg-surface px-5 text-xs font-semibold text-muted ring-1 ring-border transition-colors hover:bg-elevated"
whileTap={{ scale: 0.96 }}
>
</motion.button>
<div className="flex items-center gap-3">
<motion.button
onClick={() => setShowShareCard(true)}
className="flex h-10 items-center gap-2 rounded-full bg-purple-600 px-5 text-xs font-bold text-white shadow-lg shadow-purple-900/30 transition-colors hover:bg-purple-500"
whileTap={{ scale: 0.96 }}
>
<Share2 size={14} />
</motion.button>
<motion.button
onClick={() => { setPhase("pool"); setRevealedIdea(null); setShowShareCard(false); }}
className="flex h-10 items-center gap-2 rounded-full bg-surface px-5 text-xs font-semibold text-muted ring-1 ring-border transition-colors hover:bg-elevated"
whileTap={{ scale: 0.96 }}
>
</motion.button>
</div>
</motion.div>
)}
</AnimatePresence>
@@ -590,6 +602,24 @@ export default function BlindboxRoomPage() {
</>
)}
{revealedIdea && room && (
<ShareCardModal
open={showShareCard}
onClose={() => setShowShareCard(false)}
data={{
type: "blindbox",
idea: revealedIdea.content,
submitter: revealedIdea.user ?? undefined,
drawer: revealedIdea.drawnBy ?? undefined,
roomName: room.name,
}}
onToast={(msg) => {
setToast(msg);
setTimeout(() => setToast(""), 2200);
}}
/>
)}
{/* Toast */}
<AnimatePresence>
{toast && (