feat: 帖子收藏与用户关注
- 新增收藏功能:favorite 服务与 handler、FavoriteButton 组件 - 新增关注功能:follow 服务与 handler、FollowButton 组件 - 用户主页适配关注/粉丝与收藏展示 - flash 消息工具、广告与侧边栏组件微调
This commit is contained in:
@@ -24,16 +24,18 @@ import {
|
||||
import { defaultSidebarConfig, sidebarScopeLists } from "@/lib/sidebarMeta";
|
||||
import SidebarRenderer, { type SidebarBlockProps } from "@/components/sidebar/SidebarRenderer";
|
||||
import { postViewerCookieHeader } from "@/lib/cookies";
|
||||
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
|
||||
import { getMeCached, getPublicSettingsCached, getBoardsCached } from "@/lib/serverData";
|
||||
import CommentSection from "@/components/CommentSection";
|
||||
import PostActions from "@/components/PostActions";
|
||||
import LikeButton from "@/components/LikeButton";
|
||||
import FavoriteButton from "@/components/FavoriteButton";
|
||||
import { SiteDocBreadcrumb } from "@/components/site-doc";
|
||||
import MobilePostBar from "@/components/MobilePostBar";
|
||||
import Avatar from "@/components/Avatar";
|
||||
import RoleBadge from "@/components/RoleBadge";
|
||||
import LevelBadge from "@/components/LevelBadge";
|
||||
import BadgeChip from "@/components/BadgeChip";
|
||||
import { boardColor } from "@/components/BoardIcon";
|
||||
import MarkdownBody from "@/components/MarkdownBody";
|
||||
import { publicMarkdownForSeo } from "@/lib/hideBlocks";
|
||||
import PostAttachments from "@/components/PostAttachments";
|
||||
@@ -160,9 +162,14 @@ function RelatedPosts({
|
||||
function AuthorCardStacked({
|
||||
profile,
|
||||
allowComments,
|
||||
boardNames,
|
||||
boardColors,
|
||||
}: {
|
||||
profile: UserProfile;
|
||||
allowComments: boolean;
|
||||
/** 板块 id → 名称/配色:板块管理员徽章展示「{板块名}板块管理员」并对齐板块色 */
|
||||
boardNames?: Record<number, string>;
|
||||
boardColors?: Record<number, number>;
|
||||
}) {
|
||||
const u = profile.user;
|
||||
const signature = (u.signature || "").trim();
|
||||
@@ -192,7 +199,9 @@ function AuthorCardStacked({
|
||||
{(u.badges ?? []).slice(0, 2).map((ub) => (
|
||||
<BadgeChip key={ub.id} badge={ub.badge} />
|
||||
))}
|
||||
{u.role !== "user" && <RoleBadge role={u.role} boardIds={u.board_ids} />}
|
||||
{u.role !== "user" && (
|
||||
<RoleBadge role={u.role} boardIds={u.board_ids} boardNames={boardNames} boardColors={boardColors} />
|
||||
)}
|
||||
</div>
|
||||
<p className="meta mt-1 flex items-center gap-1.5 flex-wrap">
|
||||
<span>@{u.username}</span>
|
||||
@@ -307,13 +316,22 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
};
|
||||
const meFirst = await getMeCached(cookie || undefined);
|
||||
const canFetchComments = allowComments && (!commentsRequireLogin || !!meFirst.user);
|
||||
const [commentsData, profile, relatedData, sidebarData] = await Promise.all([
|
||||
const [commentsData, profile, relatedData, sidebarData, boardsData] = await Promise.all([
|
||||
canFetchComments ? fetchComments(id, commentPage, cookie) : Promise.resolve(emptyComments),
|
||||
fetchUserProfile(String(post.user.id), 1, cookie).catch(() => null),
|
||||
fetchPosts(1, 8, post.board_id, "recommended", "", false, cookie).catch(() => null),
|
||||
// 侧栏布局配置失败不阻塞页面,回落默认布局
|
||||
fetchSidebar(cookie).catch(() => null),
|
||||
// 板块名/配色:板块管理员徽章展示「{板块名}板块管理员」并对齐板块色
|
||||
getBoardsCached().catch(() => null),
|
||||
]);
|
||||
// 板块 id → 名称 / 配色下标(与板块列表、头像底色同一套 av-N 色板)
|
||||
const boardNames: Record<number, string> = {};
|
||||
const boardColors: Record<number, number> = {};
|
||||
for (const b of boardsData?.boards ?? []) {
|
||||
boardNames[b.id] = b.name;
|
||||
boardColors[b.id] = boardColor(b);
|
||||
}
|
||||
const sidebar = sidebarData ?? { config: defaultSidebarConfig(), widgets: [] };
|
||||
const me = meFirst;
|
||||
const comments = commentsData.comments;
|
||||
@@ -336,6 +354,8 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
authorProfile: profile ?? undefined,
|
||||
authorPosts,
|
||||
toc,
|
||||
boardNames,
|
||||
boardColors,
|
||||
};
|
||||
// 三档网格退化:双栏 / 仅左 / 仅右 / 单栏
|
||||
const railGridClass = !hasLeftRail && !hasRightRail
|
||||
@@ -462,6 +482,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
postId={id}
|
||||
liked={post.liked}
|
||||
likeCount={post.like_count}
|
||||
favorited={post.favorited === true}
|
||||
allowComments={allowComments}
|
||||
/>
|
||||
|
||||
@@ -550,6 +571,23 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
>
|
||||
{post.user.nickname}
|
||||
</Link>
|
||||
{/* 等级 + 管理员颁布徽章 + 站点角色徽章收拢为一组(详情接口已 Preload,随 post.user 返回) */}
|
||||
<div className="flex items-center gap-1.5 flex-wrap min-w-0">
|
||||
<LevelBadge level={post.user.level} className="shrink-0" />
|
||||
{(post.user.badges ?? []).slice(0, 2).map((ub) => (
|
||||
<BadgeChip key={ub.id} badge={ub.badge} className="shrink-0" />
|
||||
))}
|
||||
{post.user.role !== "user" && (
|
||||
<RoleBadge
|
||||
role={post.user.role}
|
||||
// board_ids 仅 /api/profile 返回;profile 拉取失败回落纯角色徽章
|
||||
boardIds={profile?.user.board_ids}
|
||||
boardNames={boardNames}
|
||||
boardColors={boardColors}
|
||||
className="shrink-0"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* 管理操作已收进单个下拉按钮;普通读者此处不渲染内容 */}
|
||||
<PostActions
|
||||
@@ -650,9 +688,9 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
<QuestionStatusCard postId={post.id} state={questionState} />
|
||||
) : null}
|
||||
|
||||
{/* 文末点赞:分隔线后居中,唯一的点赞入口(作者行不再放置) */}
|
||||
{/* 文末点赞/收藏:分隔线后居中,唯一的互动入口(作者行不再放置) */}
|
||||
<div
|
||||
className="mt-9 pt-7 flex justify-center"
|
||||
className="mt-9 pt-7 flex justify-center gap-3"
|
||||
style={{ borderTop: "1px solid var(--line)" }}
|
||||
>
|
||||
<LikeButton
|
||||
@@ -661,13 +699,19 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
|
||||
likeCount={post.like_count}
|
||||
variant="article"
|
||||
/>
|
||||
<FavoriteButton postId={id} favorited={post.favorited === true} variant="article" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 窄屏作者卡(lg 起由左栏承载,避免重复展示) */}
|
||||
{profile && (
|
||||
<div className="lg:hidden mt-6">
|
||||
<AuthorCardStacked profile={profile} allowComments={allowComments} />
|
||||
<AuthorCardStacked
|
||||
profile={profile}
|
||||
allowComments={allowComments}
|
||||
boardNames={boardNames}
|
||||
boardColors={boardColors}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user