feat: 就地通过评审,并为已过审用户提供默认可关的免审发布
帖/评管理入口可直接通过;站点默认信任已有 published 内容的用户后续直发。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -58,7 +58,7 @@ export default function AppearanceAdmin({ initialAccent }: { initialAccent: stri
|
||||
if (!normalized || saving) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await apiUpdateSiteSettings(submitHex);
|
||||
const res = await apiUpdateSiteSettings({ accent: submitHex });
|
||||
const next = (res.accent || "").toLowerCase();
|
||||
setSavedHex(next);
|
||||
applyAccentTheme(next || null);
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
apiAdminRejectPost,
|
||||
apiAdminApproveComment,
|
||||
apiAdminRejectComment,
|
||||
apiUpdateSiteSettings,
|
||||
type PendingCommentItem,
|
||||
type PendingCounts,
|
||||
type Post,
|
||||
@@ -47,10 +48,15 @@ export default function ModerationAdmin({
|
||||
initialPosts,
|
||||
initialPostsTotal,
|
||||
initError,
|
||||
canConfigureSettings,
|
||||
initialTrustReviewedPublish,
|
||||
}: {
|
||||
initialPosts: Post[];
|
||||
initialPostsTotal: number;
|
||||
initError: string;
|
||||
/** 超管/站长可改站点免审策略(后端 PermSettings) */
|
||||
canConfigureSettings: boolean;
|
||||
initialTrustReviewedPublish: boolean;
|
||||
}) {
|
||||
const [tab, setTab] = useState<Tab>("posts");
|
||||
const [counts, setCounts] = useState<PendingCounts>({ posts: initialPostsTotal, comments: 0 });
|
||||
@@ -73,12 +79,30 @@ export default function ModerationAdmin({
|
||||
const [confirmKey, setConfirmKey] = useState<string | null>(null);
|
||||
const confirmTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const [trustReviewedPublish, setTrustReviewedPublish] = useState(initialTrustReviewedPublish);
|
||||
const [savingTrust, setSavingTrust] = useState(false);
|
||||
|
||||
const refreshCounts = useCallback(() => {
|
||||
apiAdminPendingCounts()
|
||||
.then(setCounts)
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const toggleTrustReviewedPublish = async () => {
|
||||
if (!canConfigureSettings || savingTrust) return;
|
||||
const next = !trustReviewedPublish;
|
||||
setSavingTrust(true);
|
||||
try {
|
||||
const res = await apiUpdateSiteSettings({ trust_reviewed_publish: next });
|
||||
setTrustReviewedPublish(res.trust_reviewed_publish);
|
||||
toast(res.trust_reviewed_publish ? "已开启:过审用户后续免审" : "已关闭:所有普通用户均需审核", "ok");
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : "保存失败");
|
||||
} finally {
|
||||
setSavingTrust(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refreshCounts();
|
||||
}, [refreshCounts]);
|
||||
@@ -215,6 +239,40 @@ export default function ModerationAdmin({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{canConfigureSettings && (
|
||||
<section
|
||||
className="panel p-4 sm:p-5 mb-5 flex items-start justify-between gap-4 flex-wrap"
|
||||
aria-label="审核策略"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="text-[14.5px] font-semibold" style={{ color: "var(--ink)" }}>
|
||||
已过审用户后续免审
|
||||
</h2>
|
||||
<p className="meta mt-1 text-[12.5px] leading-relaxed">
|
||||
开启后,若用户已有通过审核的帖子或评论,后续发帖与评论将直接公开,无需再次进入待审队列。默认开启。
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={trustReviewedPublish}
|
||||
disabled={savingTrust}
|
||||
onClick={() => void toggleTrustReviewedPublish()}
|
||||
className="shrink-0 relative h-7 w-12 rounded-full transition-colors disabled:opacity-60"
|
||||
style={{
|
||||
background: trustReviewedPublish ? "var(--accent)" : "var(--line-2)",
|
||||
}}
|
||||
data-tip={trustReviewedPublish ? "点击关闭" : "点击开启"}
|
||||
>
|
||||
<span
|
||||
className="absolute top-0.5 left-0.5 h-6 w-6 rounded-full bg-[var(--panel)] shadow transition-transform"
|
||||
style={{ transform: trustReviewedPublish ? "translateX(1.25rem)" : "translateX(0)" }}
|
||||
/>
|
||||
<span className="sr-only">{trustReviewedPublish ? "已开启" : "已关闭"}</span>
|
||||
</button>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 队列切换 */}
|
||||
<div className="flex items-center gap-1.5 mb-4 flex-wrap" role="tablist" aria-label="审核队列">
|
||||
{(
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { Metadata } from "next";
|
||||
import { cookies } from "next/headers";
|
||||
import { authCookieHeader } from "@/lib/cookies";
|
||||
import { fetchAdminPendingPosts, type Post } from "@/lib/api";
|
||||
import { getMeCached } from "@/lib/serverData";
|
||||
import { canModerateAny } from "@/lib/roles";
|
||||
import { getMeCached, getPublicSettingsCached } from "@/lib/serverData";
|
||||
import { canModerateAny, isSuperOrOwner } from "@/lib/roles";
|
||||
import Forbidden from "../Forbidden";
|
||||
import ModerationAdmin from "./ModerationAdmin";
|
||||
|
||||
@@ -33,11 +33,16 @@ export default async function ModerationPage() {
|
||||
initError = e instanceof Error ? e.message : "获取待审核内容失败";
|
||||
}
|
||||
|
||||
const canConfigureSettings = isSuperOrOwner(me.user.role);
|
||||
const settings = await getPublicSettingsCached();
|
||||
|
||||
return (
|
||||
<ModerationAdmin
|
||||
initialPosts={initialPosts}
|
||||
initialPostsTotal={initialTotal}
|
||||
initError={initError}
|
||||
canConfigureSettings={canConfigureSettings}
|
||||
initialTrustReviewedPublish={settings.trust_reviewed_publish !== false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user