完善站点运营设置:分路由管理、登录可见评论、邮件模板内联与关闭注册 SSR。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -34,12 +34,10 @@ import {
|
||||
apiAdminRejectPost,
|
||||
apiAdminApproveComment,
|
||||
apiAdminRejectComment,
|
||||
apiUpdateSiteSettings,
|
||||
type AdminContentComment,
|
||||
type AdminContentCounts,
|
||||
type AdminContentPost,
|
||||
type AdminContentStatus,
|
||||
type PublicSettings,
|
||||
} from "@/lib/api";
|
||||
import { canModerateAuthor } from "@/lib/roles";
|
||||
import { formatRelative } from "@/lib/format";
|
||||
@@ -58,8 +56,6 @@ import {
|
||||
AdminSearchField,
|
||||
AdminSegmented,
|
||||
AdminStatusChip,
|
||||
AdminStepper,
|
||||
AdminSwitch,
|
||||
contentStatusChip,
|
||||
} from "@/components/admin";
|
||||
|
||||
@@ -184,29 +180,6 @@ function PendingActions({
|
||||
);
|
||||
}
|
||||
|
||||
/** 策略项:标题、控件与保存按钮成组换行,不拆散单项 */
|
||||
function PolicyChip({
|
||||
title,
|
||||
hint,
|
||||
control,
|
||||
instant,
|
||||
}: {
|
||||
title: string;
|
||||
hint?: string;
|
||||
control: ReactNode;
|
||||
instant?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="admin-content-policy-chip" data-tip={hint || undefined}>
|
||||
<span className="admin-content-policy-chip-copy">
|
||||
<span className="admin-content-policy-chip-label">{title}</span>
|
||||
{instant ? <span className="admin-content-policy-chip-how">立即生效</span> : null}
|
||||
</span>
|
||||
<div className="admin-content-policy-chip-ctrl shrink-0 flex items-center gap-1.5">{control}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ColumnShell({
|
||||
icon,
|
||||
title,
|
||||
@@ -290,10 +263,8 @@ export default function ContentAdmin({
|
||||
initialCommentsTotal,
|
||||
initialCounts,
|
||||
initError,
|
||||
canConfigureSettings,
|
||||
canPurge,
|
||||
viewerRole,
|
||||
initialSettings,
|
||||
}: {
|
||||
initialKind: ContentKind;
|
||||
initialStatus: AdminContentStatus;
|
||||
@@ -303,10 +274,8 @@ export default function ContentAdmin({
|
||||
initialCommentsTotal: number;
|
||||
initialCounts: AdminContentCounts;
|
||||
initError: string;
|
||||
canConfigureSettings: boolean;
|
||||
canPurge: boolean;
|
||||
viewerRole: string;
|
||||
initialSettings: PublicSettings;
|
||||
}) {
|
||||
// 窄屏分段:lg 以下只显示一栏;PC 双栏同时可见
|
||||
const [mobileKind, setMobileKind] = useState<ContentKind>(initialKind);
|
||||
@@ -338,11 +307,6 @@ export default function ContentAdmin({
|
||||
|
||||
const [actingKey, setActingKey] = useState<string | null>(null);
|
||||
|
||||
const [trustOn, setTrustOn] = useState(initialSettings.trust_reviewed_publish);
|
||||
const [cooldown, setCooldown] = useState(initialSettings.post_cooldown_hours);
|
||||
const [postLinkNewTab, setPostLinkNewTab] = useState(initialSettings.post_link_new_tab !== false);
|
||||
const [savingPolicy, setSavingPolicy] = useState(false);
|
||||
|
||||
const [confirm, setConfirm] = useState<{
|
||||
title: string;
|
||||
message: string;
|
||||
@@ -463,65 +427,6 @@ export default function ContentAdmin({
|
||||
);
|
||||
};
|
||||
|
||||
const toggleTrust = async () => {
|
||||
if (!canConfigureSettings || savingPolicy) return;
|
||||
setSavingPolicy(true);
|
||||
try {
|
||||
const res = await apiUpdateSiteSettings({ trust_reviewed_publish: !trustOn });
|
||||
setTrustOn(res.trust_reviewed_publish);
|
||||
toast(
|
||||
res.trust_reviewed_publish
|
||||
? "已开启过审免审(帖子与评论)"
|
||||
: "已关闭过审免审(帖子与评论将按规则待审)",
|
||||
"ok"
|
||||
);
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : "保存失败");
|
||||
} finally {
|
||||
setSavingPolicy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const saveCooldown = async () => {
|
||||
if (!canConfigureSettings || savingPolicy) return;
|
||||
const n = Math.max(0, Math.min(168, Math.round(Number(cooldown)) || 0));
|
||||
setSavingPolicy(true);
|
||||
try {
|
||||
const res = await apiUpdateSiteSettings({ post_cooldown_hours: n });
|
||||
setCooldown(res.post_cooldown_hours);
|
||||
toast(
|
||||
res.post_cooldown_hours === 0
|
||||
? "已关闭新用户发帖冷静期"
|
||||
: `新用户发帖冷静期已设为 ${res.post_cooldown_hours} 小时(评论与聊天不受限)`,
|
||||
"ok"
|
||||
);
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : "保存失败");
|
||||
} finally {
|
||||
setSavingPolicy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const togglePostLinkNewTab = async () => {
|
||||
if (!canConfigureSettings || savingPolicy) return;
|
||||
const next = !postLinkNewTab;
|
||||
setSavingPolicy(true);
|
||||
try {
|
||||
const res = await apiUpdateSiteSettings({ post_link_new_tab: next });
|
||||
setPostLinkNewTab(res.post_link_new_tab !== false);
|
||||
toast(
|
||||
res.post_link_new_tab !== false
|
||||
? "帖子与评论外链将在新标签打开"
|
||||
: "帖子与评论外链将在当前页打开",
|
||||
"ok"
|
||||
);
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : "保存失败");
|
||||
} finally {
|
||||
setSavingPolicy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const postsToolbar = (
|
||||
<div className="admin-content-col-toolbar">
|
||||
<div className="admin-content-status-scroll">
|
||||
@@ -922,73 +827,9 @@ export default function ContentAdmin({
|
||||
<AdminPageHeader
|
||||
icon={FolderOpen}
|
||||
title="内容管理"
|
||||
description="审核与管理帖子、评论。共用策略作用于全站发帖与评论,与下方列表筛选无关。"
|
||||
description="审核与管理帖子、评论。"
|
||||
/>
|
||||
|
||||
{canConfigureSettings && (
|
||||
<div className="admin-content-policy" role="group" aria-label="全站内容策略">
|
||||
<div className="admin-content-policy-lead">
|
||||
<span className="admin-content-policy-title">共用策略</span>
|
||||
<span className="admin-content-policy-scope">
|
||||
过审免审与外链开关改完立即写入;冷静期需点保存。失败时恢复为已确认值。
|
||||
</span>
|
||||
</div>
|
||||
<div className="admin-content-policy-row">
|
||||
<PolicyChip
|
||||
title="过审免审"
|
||||
instant
|
||||
hint="帖子 / 评论:用户曾有过审内容后,后续发帖与评论直接公开,不再进待审。修改后立即生效。"
|
||||
control={
|
||||
<AdminSwitch
|
||||
checked={trustOn}
|
||||
disabled={savingPolicy}
|
||||
onChange={() => void toggleTrust()}
|
||||
label="过审免审(帖子与评论)"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<PolicyChip
|
||||
title="新用户冷静期"
|
||||
hint="仅限制新注册用户发帖(0=关闭,上限 168 小时)。评论与聊天不受此限制。需点保存后生效。"
|
||||
control={
|
||||
<div className="flex items-center gap-1.5">
|
||||
<AdminStepper
|
||||
value={cooldown}
|
||||
onChange={setCooldown}
|
||||
min={0}
|
||||
max={168}
|
||||
unit="小时"
|
||||
ariaLabel="新用户发帖冷静期小时数"
|
||||
disabled={savingPolicy}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-line btn-sm"
|
||||
disabled={savingPolicy}
|
||||
onClick={() => void saveCooldown()}
|
||||
>
|
||||
{savingPolicy ? "保存中…" : "保存"}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<PolicyChip
|
||||
title="外链新开"
|
||||
instant
|
||||
hint="帖子与评论中的外链在新标签打开。修改后立即生效。"
|
||||
control={
|
||||
<AdminSwitch
|
||||
checked={postLinkNewTab}
|
||||
disabled={savingPolicy}
|
||||
onChange={() => void togglePostLinkNewTab()}
|
||||
label="外链新开(帖子与评论)"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 窄屏:顶部分段切换双栏 */}
|
||||
<div className="admin-content-kind lg:hidden shrink-0" role="tablist" aria-label="内容类型">
|
||||
<button
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
type AdminContentPost,
|
||||
type AdminContentStatus,
|
||||
} from "@/lib/api";
|
||||
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
|
||||
import { canModerateAny, isSuperOrOwner, isOwner } from "@/lib/roles";
|
||||
import { getMeCached } from "@/lib/serverData";
|
||||
import { canModerateAny, isOwner } from "@/lib/roles";
|
||||
import Forbidden from "../Forbidden";
|
||||
import ContentAdmin, { type ContentKind } from "./ContentAdmin";
|
||||
|
||||
@@ -51,7 +51,7 @@ export default async function AdminContentPage({ searchParams }: ContentPageProp
|
||||
let initError = "";
|
||||
|
||||
// PC 双栏同时展示:SSR 并行预取帖子与评论
|
||||
const [postsSettled, commentsSettled, counts, settings] = await Promise.all([
|
||||
const [postsSettled, commentsSettled, counts] = await Promise.all([
|
||||
fetchAdminContentPosts(1, initialStatus, cookie || undefined)
|
||||
.then((res) => ({ ok: true as const, res }))
|
||||
.catch((e) => ({
|
||||
@@ -65,7 +65,6 @@ export default async function AdminContentPage({ searchParams }: ContentPageProp
|
||||
error: e instanceof Error ? e.message : "获取评论失败",
|
||||
})),
|
||||
fetchAdminContentCounts(cookie || undefined),
|
||||
getPublicSettingsCached(),
|
||||
]);
|
||||
|
||||
if (postsSettled.ok) {
|
||||
@@ -92,10 +91,8 @@ export default async function AdminContentPage({ searchParams }: ContentPageProp
|
||||
initialCommentsTotal={initialCommentsTotal}
|
||||
initialCounts={counts}
|
||||
initError={initError}
|
||||
canConfigureSettings={isSuperOrOwner(me.user.role)}
|
||||
canPurge={isOwner(me.user.role)}
|
||||
viewerRole={me.user.role}
|
||||
initialSettings={settings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user