fix: 空评论列表 500,并完善撤回审计与角色说明

空切片 JSON 统一为数组;撤回保留正文供站长阅读,成员侧居中提示与重新编辑;头像支持拖放;角色弹窗补全职责说明并汇入封禁。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 00:23:55 +08:00
parent 42c395a864
commit 8c5de13899
29 changed files with 835 additions and 405 deletions

View File

@@ -5,13 +5,17 @@ import { usePathname } from "next/navigation";
import { apiGetSettings } from "@/lib/api";
import { applyAccentTheme } from "@/lib/theme";
import { realtime, RT_SETTINGS_CHANGED, type RtSettingsData } from "@/lib/realtime";
import {
VISIBLE_RECONCILE_GAP_MS,
onDocumentVisible,
} from "@/lib/visibilityReconcile";
// 站点主题色同步器:
// 主题色由管理员在后台修改后,其他用户已打开的标签页不会因软导航重新执行根布局
// SSR,本组件通过 WS 推送 + 标签页重新可见/获焦时探测公开设置,发现变化即热替换
// SSR,本组件通过 WS 推送 + 标签页重新可见时探测公开设置,发现变化即热替换
// accent CSS 变量(复用 lib/theme 的运行时 style 节点),无需 F5 即可换肤。
// 焦点/可见性事件可能短时间连续触发,限制实际请求间隔
const MIN_FETCH_GAP_MS = 10_000;
const MIN_FETCH_GAP_MS = VISIBLE_RECONCILE_GAP_MS;
export default function ThemeSync({ initialAccent }: { initialAccent: string }) {
const pathname = usePathname();
@@ -56,19 +60,15 @@ export default function ThemeSync({ initialAccent }: { initialAccent: string })
if (data) apply((data.accent || "").trim().toLowerCase());
});
const onActive = () => {
if (document.visibilityState === "visible") void sync();
};
document.addEventListener("visibilitychange", onActive);
window.addEventListener("focus", onActive);
// 仅 visibility:window focus 与 visibility 常连发,统一走限频即可
const offVisible = onDocumentVisible(() => void sync(), MIN_FETCH_GAP_MS);
// 离开外观页等 pathname 变化时立刻对齐一次(受 MIN_FETCH_GAP 限频)
void sync();
return () => {
cancelled = true;
offPush();
document.removeEventListener("visibilitychange", onActive);
window.removeEventListener("focus", onActive);
offVisible();
};
}, [pathname]);