diff --git a/frontend/components/PostInteractPanels.tsx b/frontend/components/PostInteractPanels.tsx
index eb174dc..6d2c151 100644
--- a/frontend/components/PostInteractPanels.tsx
+++ b/frontend/components/PostInteractPanels.tsx
@@ -17,6 +17,7 @@ import {
} from "@/lib/api";
import { toast } from "@/lib/toast";
import Avatar from "@/components/Avatar";
+import { useAllowComments } from "@/components/CommentsPolicyProvider";
export default function PostInteractPanels({
postId,
@@ -177,6 +178,7 @@ function DeadlineCountdown({
/** 正文下方的问答状态卡(未解决 / 仅标记已解决);有采纳答案时改用 AcceptedAnswerCard */
export function QuestionStatusCard({ postId, state }: { postId: number; state: QuestionState }) {
+ const allowComments = useAllowComments();
const router = useRouter();
const [busy, setBusy] = useState(false);
@@ -241,7 +243,7 @@ export function QuestionStatusCard({ postId, state }: { postId: number; state: Q
{state.solved
- ? state.accepted_comment_id
+ ? state.accepted_comment_id && allowComments
? (
<>
已采纳{" "}
@@ -255,7 +257,9 @@ export function QuestionStatusCard({ postId, state }: { postId: number; state: Q
>
)
: "作者已标记为已解决"
- : "可在评论旁采纳答案,或手动标记已解决"}
+ : allowComments
+ ? "可在评论旁采纳答案,或手动标记已解决"
+ : "可由作者标记为已解决"}
@@ -466,6 +470,7 @@ function PollPanel({
}
function BountyPanel({ state }: { postId: number; state: BountyState }) {
+ const allowComments = useAllowComments();
const statusBadge = state.accepted_comment_id
? { cls: "badge-status badge-status-solved", label: "已采纳" }
: state.expired || state.refunded
@@ -507,7 +512,7 @@ function BountyPanel({ state }: { postId: number; state: BountyState }) {
{state.points} 积分
{bodyHint ? ` · ${bodyHint}` : null}
- {state.can_accept ? " · 在评论旁点击「采纳」发放积分" : null}
+ {state.can_accept && allowComments ? " · 在评论旁点击「采纳」发放积分" : null}
{!state.accepted_comment_id && state.ends_at ? (
@@ -518,7 +523,7 @@ function BountyPanel({ state }: { postId: number; state: BountyState }) {
/>
) : null}
- {state.accepted_comment_id > 0 && (
+ {allowComments && state.accepted_comment_id > 0 && (
已采纳{" "}
new Date(post.created_at).getTime() + 60_000;
+ const replied =
+ allowComments &&
+ lastReply &&
+ new Date(lastReply.created_at).getTime() > new Date(post.created_at).getTime() + 60_000;
const pt = normalizePostType(post.post_type);
const statusBadge = pt !== "discussion" ? typeStatusLabel(pt, post.type_status) : "";
const statusClass = statusBadge ? typeStatusClass(pt, post.type_status) : "";
@@ -115,7 +120,7 @@ export default function PostRow({
{formatRelative(lastReply!.created_at)}
)}
- {!replied && post.comment_count === 0 && (
+ {!replied && allowComments && post.comment_count === 0 && (
· 暂无回复
@@ -124,15 +129,17 @@ export default function PostRow({
-
-
- {post.comment_count}
-
+ {allowComments ? (
+
+
+ {post.comment_count}
+
+ ) : null}
{showViews && (
void;
+ placeholder?: string;
+ maxLength?: number;
+ as?: "input" | "textarea";
+ type?: string;
+ autoComplete?: string;
+ disabled?: boolean;
+ className?: string;
+ minHeight?: string;
+}) {
+ const handle = (e: ChangeEvent) =>
+ onChange(e.target.value);
+
+ return (
+
+
+
+ {maxLength != null && (
+
+ {value.length}/{maxLength}
+
+ )}
+
+ {hint != null && hint !== "" && (
+
{hint}
+ )}
+ {as === "textarea" ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ );
+}
diff --git a/frontend/components/admin/AdminSearchField.tsx b/frontend/components/admin/AdminSearchField.tsx
index 8720c4e..6bf3b36 100644
--- a/frontend/components/admin/AdminSearchField.tsx
+++ b/frontend/components/admin/AdminSearchField.tsx
@@ -3,7 +3,7 @@
import { Search, X } from "lucide-react";
import type { FormEvent } from "react";
-/** 左图标、可清除、Enter 提交;不要再并排「搜索」按钮 */
+/** 左图标、可清除、Enter 提交;壳层聚焦与聊天搜索一致 */
export default function AdminSearchField({
value,
onChange,
@@ -30,11 +30,11 @@ export default function AdminSearchField({
};
return (
-
-
- {p.comment_count}
-
+ {allowComments ? (
+
+ {p.comment_count}
+
+ ) : null}
{p.like_count}
@@ -188,7 +196,7 @@ function BoardHot({ posts }: { posts: PostListItem[] }) {
/* ---------- 5. 本板活跃 ---------- */
-function BoardActive({ users }: { users: ActiveUser[] }) {
+function BoardActive({ users, allowComments }: { users: ActiveUser[]; allowComments: boolean }) {
return (
@@ -221,17 +229,23 @@ function BoardActive({ users }: { users: ActiveUser[] }) {
发帖 {u.post_count}
-
- 回复 {u.comment_count}
-
+ {allowComments ? (
+
+ 回复 {u.comment_count}
+
+ ) : null}
- {u.post_count + u.comment_count}
+ {allowComments ? u.post_count + u.comment_count : u.post_count}
@@ -252,6 +266,7 @@ type BoardPanelsProps = {
hot: PostListItem[];
activeUsers: ActiveUser[];
checkin?: CheckinStatus | null;
+ allowComments: boolean;
};
function BoardPanelsBody(props: BoardPanelsProps) {
@@ -259,9 +274,9 @@ function BoardPanelsBody(props: BoardPanelsProps) {
<>
-
-
-
+
+
+
>
);
}
diff --git a/frontend/components/home/CommunityPanels.tsx b/frontend/components/home/CommunityPanels.tsx
index 09960e5..e8e5fb5 100644
--- a/frontend/components/home/CommunityPanels.tsx
+++ b/frontend/components/home/CommunityPanels.tsx
@@ -27,7 +27,15 @@ import { formatCompact, formatShortDate } from "@/lib/format";
/* ---------- 站点数据(2×2 bento 统计瓦片) ---------- */
-function PulseCard({ stats, checkin }: { stats: OverviewStats; checkin?: CheckinStatus | null }) {
+function PulseCard({
+ stats,
+ checkin,
+ allowComments,
+}: {
+ stats: OverviewStats;
+ checkin?: CheckinStatus | null;
+ allowComments: boolean;
+}) {
const cells = [
{
icon:
,
@@ -43,13 +51,25 @@ function PulseCard({ stats, checkin }: { stats: OverviewStats; checkin?: Checkin
foot: `+${formatCompact(stats.today_posts)} 今日`,
up: true,
},
- {
- icon:
,
- label: "回复总数",
- value: formatCompact(stats.comments),
- foot: `+${formatCompact(stats.today_comments)} 今日`,
- up: true,
- },
+ ...(allowComments
+ ? [
+ {
+ icon:
,
+ label: "回复总数",
+ value: formatCompact(stats.comments),
+ foot: `+${formatCompact(stats.today_comments)} 今日`,
+ up: true,
+ },
+ ]
+ : [
+ {
+ icon:
,
+ label: "今日新帖",
+ value: formatCompact(stats.today_posts),
+ foot: "本日新增",
+ up: stats.today_posts > 0,
+ },
+ ]),
{
icon:
,
label: "在线用户",
@@ -138,7 +158,7 @@ function Announcements({ items }: { items: AnnouncementSummary[] }) {
/* ---------- 热门榜 ---------- */
-function HotRanking({ posts }: { posts: PostListItem[] }) {
+function HotRanking({ posts, allowComments }: { posts: PostListItem[]; allowComments: boolean }) {
return (
@@ -157,9 +177,11 @@ function HotRanking({ posts }: { posts: PostListItem[] }) {
{p.title}
-
- {p.comment_count}
-
+ {allowComments ? (
+
+ {p.comment_count}
+
+ ) : null}
{p.like_count}
@@ -211,7 +233,7 @@ function NewUsers({ users }: { users: NewUser[] }) {
/* ---------- 活跃成员(本周发帖 / 回复) ---------- */
-function ActiveUsers({ users }: { users: ActiveUser[] }) {
+function ActiveUsers({ users, allowComments }: { users: ActiveUser[]; allowComments: boolean }) {
return (
@@ -241,17 +263,23 @@ function ActiveUsers({ users }: { users: ActiveUser[] }) {
发帖 {u.post_count}
-
- 回复 {u.comment_count}
-
+ {allowComments ? (
+
+ 回复 {u.comment_count}
+
+ ) : null}
- {u.post_count + u.comment_count}
+ {allowComments ? u.post_count + u.comment_count : u.post_count}
@@ -309,46 +337,44 @@ type PanelsProps = {
announcements: AnnouncementSummary[];
newUsers: NewUser[];
checkin?: CheckinStatus | null;
+ allowComments: boolean;
};
-export function CommunityPanels({
+function CommunityPanelsBody({
stats,
hot,
activeUsers,
announcements,
newUsers,
checkin,
+ allowComments,
}: PanelsProps) {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
+
+export function CommunityPanels(props: PanelsProps) {
return (
);
}
-export function CommunityPanelsMobile({
- stats,
- hot,
- activeUsers,
- announcements,
- newUsers,
- checkin,
-}: PanelsProps) {
+export function CommunityPanelsMobile(props: PanelsProps) {
return (
);
}
diff --git a/frontend/components/home/FeedTabs.tsx b/frontend/components/home/FeedTabs.tsx
index 5cb26bf..226506d 100644
--- a/frontend/components/home/FeedTabs.tsx
+++ b/frontend/components/home/FeedTabs.tsx
@@ -4,6 +4,7 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { useRef } from "react";
import { MessageSquareText, Clock, Sparkles } from "lucide-react";
+import { useAllowComments } from "@/components/CommentsPolicyProvider";
export type FeedSort = "latest" | "new" | "recommended";
@@ -22,18 +23,20 @@ function tabHref(key: FeedSort, boardId: number) {
export default function FeedTabs({ current, boardId = 0 }: { current: FeedSort; boardId?: number }) {
const router = useRouter();
const tabRefs = useRef<(HTMLAnchorElement | null)[]>([]);
+ const allowComments = useAllowComments();
+ const keys = allowComments ? TAB_KEYS : TAB_KEYS.filter((k) => k !== "latest");
const handleKeyDown = (e: React.KeyboardEvent, i: number) => {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
e.preventDefault();
- const next = (i + (e.key === "ArrowRight" ? 1 : TAB_KEYS.length - 1)) % TAB_KEYS.length;
+ const next = (i + (e.key === "ArrowRight" ? 1 : keys.length - 1)) % keys.length;
tabRefs.current[next]?.focus();
- router.push(tabHref(TAB_KEYS[next], boardId));
+ router.push(tabHref(keys[next], boardId));
};
return (
- {TAB_KEYS.map((key, i) => {
+ {keys.map((key, i) => {
const Icon = key === "latest" ? MessageSquareText : key === "new" ? Clock : Sparkles;
return (