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

@@ -11,6 +11,9 @@ interface AvatarPickerProps {
name: string;
src: string;
size?: number;
/** 外部拖入的文件:非空时打开裁剪弹窗并进入裁剪 */
incomingFile?: File | null;
onIncomingConsumed?: () => void;
}
/**
@@ -23,10 +26,13 @@ interface AvatarPickerProps {
* 另外监听广播,使页面上若存在多个 picker 实例也保持一致;
* 并随 src prop(SSR 校正)同步,避免 useState 初始值造成数据漂移。
*/
export default function AvatarPicker({ name, src, size = 56 }: AvatarPickerProps) {
export default function AvatarPicker({
name, src, size = 56, incomingFile, onIncomingConsumed,
}: AvatarPickerProps) {
const router = useRouter();
const [open, setOpen] = useState(false);
const [current, setCurrent] = useState(src);
const [seedFile, setSeedFile] = useState<File | null>(null);
// SSR props 校正(router.refresh 后服务端数据回流)
useEffect(() => setCurrent(src), [src]);
@@ -36,13 +42,26 @@ export default function AvatarPicker({ name, src, size = 56 }: AvatarPickerProps
if (typeof patch.avatar === "string") setCurrent(patch.avatar);
}), []);
// 外层拖入:打开弹窗并交给裁剪流程
useEffect(() => {
if (!incomingFile) return;
setSeedFile(incomingFile);
setOpen(true);
}, [incomingFile]);
const handleChanged = (avatar: string) => {
setCurrent(avatar);
setOpen(false);
setSeedFile(null);
emitUserUpdate({ avatar });
router.refresh();
};
const handleSeedConsumed = () => {
setSeedFile(null);
onIncomingConsumed?.();
};
return (
<>
<button
@@ -66,9 +85,14 @@ export default function AvatarPicker({ name, src, size = 56 }: AvatarPickerProps
<AvatarCropModal
open={open}
onClose={() => setOpen(false)}
onClose={() => {
setOpen(false);
setSeedFile(null);
}}
currentAvatar={current}
onChanged={handleChanged}
seedFile={seedFile}
onSeedConsumed={handleSeedConsumed}
/>
</>
);