feat: 全站评论开关,并统一后台表单控件

关闭评论后前台隐藏评论能力;外观/设置/公告改用 AdminField 与 Stepper。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 01:22:00 +08:00
parent 442af625f6
commit d93635c961
27 changed files with 989 additions and 479 deletions

View File

@@ -16,6 +16,7 @@ export default function UserProfileContent({
commentData,
commentError,
page,
allowComments = true,
}: {
userId: string;
profile: UserProfile;
@@ -23,6 +24,7 @@ export default function UserProfileContent({
commentData: UserCommentsResponse | null;
commentError: boolean;
page: number;
allowComments?: boolean;
}) {
return (
<div>
@@ -50,9 +52,11 @@ export default function UserProfileContent({
</h2>
<div className="flex items-center gap-4 meta text-[12px]">
<span className="tabular-nums">{new Date(post.created_at).toLocaleDateString("zh-CN")}</span>
<span className="inline-flex items-center gap-1">
<IconComment size={12} /> {post.comment_count}
</span>
{allowComments ? (
<span className="inline-flex items-center gap-1">
<IconComment size={12} /> {post.comment_count}
</span>
) : null}
<span className="inline-flex items-center gap-1">
<IconEye size={12} /> {post.view_count}
</span>

View File

@@ -7,7 +7,7 @@ import {
} from "lucide-react";
import { fetchUserProfile, fetchUserComments, type UserProfile, type UserCommentsResponse } from "@/lib/api";
import { authCookieHeader } from "@/lib/cookies";
import { getMeCached } from "@/lib/serverData";
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
import UserProfileContent from "./UserProfileContent";
import ProfileForm from "./ProfileForm";
import PasswordForm from "./PasswordForm";
@@ -28,9 +28,12 @@ export async function generateMetadata({ params }: UserPageProps): Promise<Metad
const { id } = await params;
try {
const data = await fetchUserProfile(id, 1);
const allowComments = (await getPublicSettingsCached()).allow_comments !== false;
return {
title: data.user.nickname || data.user.username,
description: `查看 ${data.user.nickname} 的资料、发帖与评论`,
description: allowComments
? `查看 ${data.user.nickname} 的资料、发帖与评论`
: `查看 ${data.user.nickname} 的资料与发帖`,
};
} catch {
return { title: "用户不存在" };
@@ -53,6 +56,7 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
const cookie = authCookieHeader(await cookies());
const me = await getMeCached(cookie || undefined);
const allowComments = (await getPublicSettingsCached()).allow_comments !== false;
let data: UserProfile;
try {
@@ -75,13 +79,14 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
// 判断是否本人(决定能否看到 settings / security tab)
const isOwner = me.user?.id === data.user.id;
const currentUser = me.user;
const effectiveTab: UserTab =
let effectiveTab: UserTab =
(tab === "settings" || tab === "security") && !isOwner ? "posts" : tab;
if (!allowComments && effectiveTab === "comments") effectiveTab = "posts";
// 评论 tab:SSR 直出对应分页
let commentData: UserCommentsResponse | null = null;
let commentError = false;
if (effectiveTab === "comments") {
if (allowComments && effectiveTab === "comments") {
try {
commentData = await fetchUserComments(id, page);
} catch {
@@ -96,7 +101,9 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
// 导航项(桌面左栏 / 移动 chips 共用定义)
const navItems: { key: UserTab; label: string; count?: number; icon: React.ReactNode; ownerOnly?: boolean }[] = [
{ key: "posts", label: "发帖", count: data.stats.post_count, icon: <FileText size={15} /> },
{ key: "comments", label: "评论", count: data.stats.comment_count, icon: <MessageCircle size={15} /> },
...(allowComments
? [{ key: "comments" as const, label: "评论", count: data.stats.comment_count, icon: <MessageCircle size={15} /> }]
: []),
{ key: "settings", label: "资料设置", icon: <Settings size={15} />, ownerOnly: true },
{ key: "security", label: "安全设置", icon: <KeyRound size={15} />, ownerOnly: true },
];
@@ -106,7 +113,7 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
// 统计条数据
const heroStats = [
{ label: "发帖", value: data.stats.post_count },
{ label: "评论", value: data.stats.comment_count },
...(allowComments ? [{ label: "评论", value: data.stats.comment_count }] : []),
{ label: "积分", value: data.stats.points, foot: data.stats.streak > 0 ? `连签 ${data.stats.streak} 天` : undefined },
];
@@ -227,7 +234,10 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
</div>
{/* 统计条 */}
<div className="relative z-10 grid grid-cols-3 mt-4 border-t" style={{ borderColor: "var(--line)" }}>
<div
className={`relative z-10 grid mt-4 border-t ${heroStats.length === 3 ? "grid-cols-3" : "grid-cols-2"}`}
style={{ borderColor: "var(--line)" }}
>
{heroStats.map((s, i) => (
<div
key={s.label}
@@ -315,6 +325,7 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
commentData={commentData}
commentError={commentError}
page={page}
allowComments={allowComments}
/>
) : effectiveTab === "settings" && isOwner && currentUser ? (
<div>
@@ -423,7 +434,11 @@ export default async function UserPage({ params, searchParams }: UserPageProps)
</li>
<li className="flex gap-2">
<span className="shrink-0 mt-px" style={{ color: "var(--accent)" }}>·</span>
<span>昵称和头像会公开展示在帖子、评论和个人主页。</span>
<span>
{allowComments
? "昵称和头像会公开展示在帖子、评论和个人主页。"
: "昵称和头像会公开展示在帖子和个人主页。"}
</span>
</li>
<li className="flex gap-2">
<span className="shrink-0 mt-px" style={{ color: "var(--accent)" }}>·</span>