feat(moderation): 帖评删除占位感知与硬删清场

管理删帖须填类型与理由并展示专用页;评论软删占位、版主可硬删不占楼。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 07:01:04 +08:00
parent 2b27703e6c
commit bcb4f6a603
15 changed files with 825 additions and 116 deletions

View File

@@ -18,12 +18,13 @@ import {
fetchComments,
fetchPosts,
fetchUserProfile,
postDeleteTypeLabel,
type UserProfile,
type PostListItem,
type Board,
} from "@/lib/api";
import { authCookieHeader } from "@/lib/cookies";
import { getMeCached } from "@/lib/serverData";
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
import CommentSection from "@/components/CommentSection";
import PostActions from "@/components/PostActions";
import LikeButton from "@/components/LikeButton";
@@ -50,9 +51,16 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
try {
// 带登录态:作者/管理团队看待审帖时标签标题也应是真实标题而非"帖子不存在"
const { post } = await fetchPostDetail(id, authCookieHeader(await cookies()));
if (post.tombstone) {
return {
title: post.title || "帖子已删除",
description: "该帖子已被删除",
robots: { index: false, follow: false },
};
}
const desc = post.content_locked
? post.access_hint || "部分内容需满足条件后可见"
: post.content.slice(0, 160);
: (post.content || "").slice(0, 160);
return {
title: post.title,
description: desc,
@@ -383,10 +391,63 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
notFound();
}
// 评论 / 当前用户 / 作者资料 / 同板块热议并行拉取;侧栏数据失败一律静默降级,不阻塞正文
const [commentsData, me, profile, relatedData] = await Promise.all([
// 软删专用页:列表已不可见,原链接展示删除说明
if (post.tombstone) {
const typeLabel = postDeleteTypeLabel(post.delete_type);
return (
<div className="max-w-lg mx-auto py-16 px-4 text-center">
<p
className="inline-flex items-center rounded-full px-3 py-1 text-[12px] font-semibold mb-4"
style={{ background: "var(--danger-soft)", color: "var(--danger)" }}
>
帖子已删除
</p>
<h1 className="text-[20px] sm:text-[22px] font-extrabold mb-3" style={{ color: "var(--ink)" }}>
{post.title || "(无标题)"}
</h1>
{post.staff_deleted ? (
<div
className="panel text-left p-5 sm:p-6 mt-2"
style={{ borderColor: "var(--danger-soft)" }}
>
<p className="text-[14px] font-medium mb-3" style={{ color: "var(--ink)" }}>
此帖已被管理员删除
</p>
{typeLabel && (
<p className="text-[13px] mb-2" style={{ color: "var(--ink-2)" }}>
<span style={{ color: "var(--ink-3)" }}>类型:</span>
{typeLabel}
</p>
)}
{post.delete_reason && (
<p className="text-[13px] leading-relaxed" style={{ color: "var(--ink-2)" }}>
<span style={{ color: "var(--ink-3)" }}>理由:</span>
{post.delete_reason}
</p>
)}
</div>
) : (
<p className="meta text-[14px] mt-2">作者已删除此帖</p>
)}
<div className="mt-8 flex items-center justify-center gap-3">
<Link href="/" className="btn btn-line btn-sm">
返回首页
</Link>
{post.board?.id ? (
<Link href={`/?board=${post.board_id}`} className="btn btn-primary btn-sm">
浏览板块
</Link>
) : null}
</div>
</div>
);
}
// 评论 / 当前用户 / 站点设置 / 作者资料 / 同板块热议并行拉取;侧栏数据失败一律静默降级,不阻塞正文
const [commentsData, me, siteSettings, profile, relatedData] = await Promise.all([
fetchComments(id, commentPage, cookie),
getMeCached(cookie || undefined),
getPublicSettingsCached(),
fetchUserProfile(String(post.user.id), 1, cookie).catch(() => null),
fetchPosts(1, 8, post.board_id, "recommended", "", false, cookie).catch(() => null),
]);
@@ -554,6 +615,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
pinned={post.pinned}
recommended={post.recommended}
status={post.status}
title={post.title}
/>
</div>
@@ -659,6 +721,7 @@ export default async function PostDetailPage({ params, searchParams }: PageProps
page={commentsData.page}
size={commentsData.size}
user={me.user}
allowComments={siteSettings.allow_comments}
acceptMode={acceptMode}
acceptedCommentId={
post.question?.accepted_comment_id ||