refactor: 提取 useShare hook,统一分享和剪贴板逻辑
新增 useShare(Web Share API + clipboard fallback), 消除 QrInviteModal、BlindboxRoomPage、ShareCardModal 三处重复的分享代码。
This commit is contained in:
@@ -25,6 +25,7 @@ import { getCachedProfile, isRegistered } from "@/lib/userId";
|
|||||||
import ShareCardModal from "@/components/ShareCardModal";
|
import ShareCardModal from "@/components/ShareCardModal";
|
||||||
import Button from "@/components/Button";
|
import Button from "@/components/Button";
|
||||||
import { useToast } from "@/hooks/useToast";
|
import { useToast } from "@/hooks/useToast";
|
||||||
|
import { useShare } from "@/hooks/useShare";
|
||||||
import { BlindboxRoomSkeleton } from "@/components/Skeleton";
|
import { BlindboxRoomSkeleton } from "@/components/Skeleton";
|
||||||
import type { UserProfile } from "@/types";
|
import type { UserProfile } from "@/types";
|
||||||
|
|
||||||
@@ -391,32 +392,21 @@ export default function BlindboxRoomPage() {
|
|||||||
setTimeout(frame, 200);
|
setTimeout(frame, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyCode = async () => {
|
const { share, copyToClipboard } = useShare();
|
||||||
if (!room) return;
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(room.code);
|
|
||||||
toast.show("房间号已复制");
|
|
||||||
} catch { /* ignore */ }
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleShare = async () => {
|
const handleCopyCode = useCallback(
|
||||||
|
() => room ? copyToClipboard(room.code, "房间号已复制") : undefined,
|
||||||
|
[room, copyToClipboard],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleShare = useCallback(() => {
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
const url = typeof window !== "undefined" ? `${window.location.origin}/blindbox/${room.code}` : "";
|
const url = typeof window !== "undefined" ? `${window.location.origin}/blindbox/${room.code}` : "";
|
||||||
const shareData = {
|
share(
|
||||||
title: `周末契约 · ${room.name}`,
|
{ title: `周末契约 · ${room.name}`, text: `来和我一起玩周末盲盒吧!房间号:${room.code}`, url },
|
||||||
text: `来和我一起玩周末盲盒吧!房间号:${room.code}`,
|
handleCopyCode,
|
||||||
url,
|
);
|
||||||
};
|
}, [room, share, handleCopyCode]);
|
||||||
try {
|
|
||||||
if (navigator.share && navigator.canShare?.(shareData)) {
|
|
||||||
await navigator.share(shareData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error && e.name === "AbortError") return;
|
|
||||||
}
|
|
||||||
handleCopyCode();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCreator = profile?.id === room?.creatorId;
|
const isCreator = profile?.id === room?.creatorId;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { SceneType } from "@/types";
|
|||||||
import { getSceneConfig } from "@/lib/sceneConfig";
|
import { getSceneConfig } from "@/lib/sceneConfig";
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
import Button from "@/components/Button";
|
import Button from "@/components/Button";
|
||||||
import { useToast } from "@/hooks/useToast";
|
import { useShare } from "@/hooks/useShare";
|
||||||
|
|
||||||
interface QrInviteModalProps {
|
interface QrInviteModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -22,40 +22,22 @@ export default function QrInviteModal({
|
|||||||
roomId,
|
roomId,
|
||||||
scene = "eat",
|
scene = "eat",
|
||||||
}: QrInviteModalProps) {
|
}: QrInviteModalProps) {
|
||||||
const toast = useToast();
|
const { share, copyToClipboard } = useShare();
|
||||||
const sceneConfig = getSceneConfig(scene);
|
const sceneConfig = getSceneConfig(scene);
|
||||||
const inviteUrl =
|
const inviteUrl =
|
||||||
typeof window !== "undefined"
|
typeof window !== "undefined"
|
||||||
? `${window.location.origin}/invite/${roomId}`
|
? `${window.location.origin}/invite/${roomId}`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const handleCopy = useCallback(async () => {
|
const handleCopy = useCallback(
|
||||||
try {
|
() => copyToClipboard(inviteUrl, "邀请链接已复制,快去发给朋友吧!"),
|
||||||
await navigator.clipboard.writeText(inviteUrl);
|
[inviteUrl, copyToClipboard],
|
||||||
toast.show("邀请链接已复制,快去发给朋友吧!");
|
);
|
||||||
} catch {
|
|
||||||
toast.show("复制失败,请手动复制链接");
|
|
||||||
}
|
|
||||||
}, [inviteUrl, toast]);
|
|
||||||
|
|
||||||
const handleShare = useCallback(async () => {
|
const handleShare = useCallback(
|
||||||
const shareData = {
|
() => share({ title: sceneConfig.shareTitle, text: sceneConfig.shareText, url: inviteUrl }, handleCopy),
|
||||||
title: sceneConfig.shareTitle,
|
[inviteUrl, sceneConfig, share, handleCopy],
|
||||||
text: sceneConfig.shareText,
|
);
|
||||||
url: inviteUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (navigator.share && navigator.canShare?.(shareData)) {
|
|
||||||
await navigator.share(shareData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error && e.name === "AbortError") return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCopy();
|
|
||||||
}, [inviteUrl, handleCopy, sceneConfig]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={onClose}>
|
<Modal open={open} onClose={onClose}>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { QRCodeSVG } from "qrcode.react";
|
|||||||
import { toPng } from "html-to-image";
|
import { toPng } from "html-to-image";
|
||||||
import type { Restaurant, MatchType, SceneType } from "@/types";
|
import type { Restaurant, MatchType, SceneType } from "@/types";
|
||||||
import { useToast } from "@/hooks/useToast";
|
import { useToast } from "@/hooks/useToast";
|
||||||
|
import { useShare } from "@/hooks/useShare";
|
||||||
|
|
||||||
type ShareCardData =
|
type ShareCardData =
|
||||||
| {
|
| {
|
||||||
@@ -832,6 +833,8 @@ export default function ShareCardModal({
|
|||||||
toast.show("图片已保存");
|
toast.show("图片已保存");
|
||||||
}, [handleGenerate, toast, data]);
|
}, [handleGenerate, toast, data]);
|
||||||
|
|
||||||
|
const { share: nativeShare } = useShare();
|
||||||
|
|
||||||
const handleShare = useCallback(async () => {
|
const handleShare = useCallback(async () => {
|
||||||
const png = await handleGenerate();
|
const png = await handleGenerate();
|
||||||
if (!png) {
|
if (!png) {
|
||||||
@@ -840,20 +843,12 @@ export default function ShareCardModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const file = dataUrlToFile(png, "NoWhatever.png");
|
const file = dataUrlToFile(png, "NoWhatever.png");
|
||||||
|
const shared = await nativeShare({ files: [file] });
|
||||||
const shareData: ShareData = { files: [file] };
|
if (!shared) {
|
||||||
try {
|
|
||||||
if (navigator.canShare?.(shareData)) {
|
|
||||||
await navigator.share(shareData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error && e.name === "AbortError") return;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadDataUrl(png, "NoWhatever.png");
|
downloadDataUrl(png, "NoWhatever.png");
|
||||||
toast.show("图片已保存,快去分享吧!");
|
toast.show("图片已保存,快去分享吧!");
|
||||||
}, [handleGenerate, toast]);
|
}
|
||||||
|
}, [handleGenerate, toast, nativeShare]);
|
||||||
|
|
||||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||||
if (e.target === backdropRef.current) onClose();
|
if (e.target === backdropRef.current) onClose();
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { useToast } from "@/hooks/useToast";
|
||||||
|
|
||||||
|
export function useShare() {
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const copyToClipboard = useCallback(
|
||||||
|
async (text: string, successMsg = "已复制") => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
toast.show(successMsg);
|
||||||
|
} catch {
|
||||||
|
toast.show("复制失败,请手动复制");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[toast],
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Try the native Web Share API; calls `fallback` if unavailable. Returns true if native share was triggered. */
|
||||||
|
const share = useCallback(
|
||||||
|
async (data: ShareData, fallback?: () => void): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
if (navigator.share && navigator.canShare?.(data)) {
|
||||||
|
await navigator.share(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error && e.name === "AbortError") return true;
|
||||||
|
}
|
||||||
|
fallback?.();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { share, copyToClipboard };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user