Files
jiang13-bbs/frontend/components/LikeButton.tsx
freefire 5507552ca5 polish(frontend): 侧栏与徽章可读性,智能悬停提示
降权全站字重;优化右栏公告/数据块;类型状态徽章用固定浅色区分;TipHost 仅在文案不一致、非文字或省略时显示。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 03:54:14 +08:00

88 lines
2.7 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { useRouter, usePathname } from "next/navigation";
import { IconHeart } from "./Icons";
import { apiToggleLike } from "@/lib/api";
import { toast } from "@/lib/toast";
interface LikeButtonProps {
postId: string;
liked: boolean;
likeCount: number;
variant?: "default" | "compact" | "article";
}
export default function LikeButton({
postId,
liked: initialLiked,
likeCount: initialCount,
variant = "default",
}: LikeButtonProps) {
const router = useRouter();
const pathname = usePathname();
const [liked, setLiked] = useState(initialLiked);
const [count, setCount] = useState(initialCount);
const [loading, setLoading] = useState(false);
// router.refresh()(登录/登出后)服务端会重新下发 liked 态,同步到本地,
// 避免客户端状态停留在刷新前的用户视角
useEffect(() => {
setLiked(initialLiked);
setCount(initialCount);
}, [initialLiked, initialCount]);
const handleClick = async () => {
if (loading) return;
setLoading(true);
// 乐观更新:先给反馈,失败再回滚
const wasLiked = liked;
setLiked(!wasLiked);
setCount((c) => c + (wasLiked ? -1 : 1));
try {
const res = await apiToggleLike(postId);
if (res.error === "未登录") {
router.push(`/login?redirect=${encodeURIComponent(pathname)}`);
return;
}
if (typeof res.liked === "boolean") {
setLiked(res.liked);
setCount(res.like_count ?? count);
} else {
throw new Error(res.error || "FAIL");
}
} catch {
setLiked(wasLiked);
setCount((c) => c + (wasLiked ? 1 : -1));
toast("操作失败,请稍后重试");
} finally {
setLoading(false);
}
};
const sizeCls =
variant === "compact"
? "px-2.5 py-1.5 gap-1 text-[12.5px]"
: variant === "article"
? "px-7 py-3 gap-2 text-[15px] min-w-[148px]"
: "px-4 py-2 gap-1.5 text-sm";
const iconSize = variant === "compact" ? 13 : variant === "article" ? 18 : 15;
return (
<button
onClick={handleClick}
disabled={loading}
data-tip={liked ? "取消点赞" : "点赞"}
aria-label={liked ? "取消点赞" : "点赞"}
aria-pressed={liked}
className={`j13-like inline-flex items-center justify-center tabular-nums rounded-full border disabled:opacity-50 ${liked ? "is-liked" : ""} ${sizeCls}`}
>
<span key={String(liked)} className={liked ? "j13-heart-pop inline-flex" : "inline-flex"}>
<IconHeart size={iconSize} filled={liked} />
</span>
<span>{count}</span>
{variant === "article" && <span>{liked ? "已赞" : "点赞"}</span>}
</button>
);
}