feat: 帖子收藏与用户关注
- 新增收藏功能:favorite 服务与 handler、FavoriteButton 组件 - 新增关注功能:follow 服务与 handler、FollowButton 组件 - 用户主页适配关注/粉丝与收藏展示 - flash 消息工具、广告与侧边栏组件微调
This commit is contained in:
77
frontend/components/FollowButton.tsx
Normal file
77
frontend/components/FollowButton.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { UserPlus, UserCheck } from "lucide-react";
|
||||
import { apiToggleFollow } from "@/lib/api";
|
||||
import { toast } from "@/lib/toast";
|
||||
|
||||
interface FollowButtonProps {
|
||||
userId: string | number;
|
||||
following: boolean;
|
||||
size?: "default" | "sm";
|
||||
}
|
||||
|
||||
export default function FollowButton({
|
||||
userId,
|
||||
following: initialFollowing,
|
||||
size = "default",
|
||||
}: FollowButtonProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [following, setFollowing] = useState(initialFollowing);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// router.refresh()(登录/登出后)服务端会重新下发关注态,同步到本地
|
||||
useEffect(() => {
|
||||
setFollowing(initialFollowing);
|
||||
}, [initialFollowing]);
|
||||
|
||||
const handleClick = async () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
const wasFollowing = following;
|
||||
setFollowing(!wasFollowing);
|
||||
try {
|
||||
const res = await apiToggleFollow(userId);
|
||||
if (res.error === "未登录") {
|
||||
router.push(`/login?redirect=${encodeURIComponent(pathname)}`);
|
||||
return;
|
||||
}
|
||||
if (typeof res.following === "boolean") {
|
||||
setFollowing(res.following);
|
||||
// 关注数变化后刷新页面统计(导航计数、Hero 统计)
|
||||
router.refresh();
|
||||
} else {
|
||||
throw new Error(res.error || "FAIL");
|
||||
}
|
||||
} catch {
|
||||
setFollowing(wasFollowing);
|
||||
toast("操作失败,请稍后重试");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const sizeCls = size === "sm" ? "px-2.5 py-1 gap-1 text-[12px]" : "btn-sm";
|
||||
const iconSize = size === "sm" ? 12 : 14;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleClick}
|
||||
disabled={loading}
|
||||
data-tip={following ? "取消关注" : "关注"}
|
||||
aria-label={following ? "取消关注" : "关注"}
|
||||
aria-pressed={following}
|
||||
className={`inline-flex items-center justify-center gap-1.5 rounded-full border font-semibold transition-colors duration-150 disabled:opacity-50 ${
|
||||
following
|
||||
? "border-[var(--line-2)] text-[var(--ink-2)] hover:border-[var(--danger)] hover:text-[var(--danger)]"
|
||||
: "border-[var(--accent)] text-[var(--accent)] hover:bg-[color-mix(in_srgb,var(--accent)_10%,var(--panel))]"
|
||||
} ${sizeCls}`}
|
||||
style={{ background: "var(--panel)" }}
|
||||
>
|
||||
{following ? <UserCheck size={iconSize} /> : <UserPlus size={iconSize} />}
|
||||
{following ? "已关注" : "关注"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user