feat: 实现完整的板块级RBAC内容审核系统
- 新增站长/超级管理员/管理员/板块管理员四级角色体系 - 实现实时权限快照加载与细粒度权限校验 - 新增内容审核队列与前后端页面 - 重构用户权限管理与站点管理逻辑 - 新增评论/帖子审核状态与通知推送 - 优化前端权限控制与角色徽章展示 - 修正数据库迁移与默认值问题
This commit is contained in:
@@ -3,18 +3,20 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { Pencil, Trash2, Pin, Star, SlidersHorizontal, ChevronDown, Check } from "lucide-react";
|
||||
import { Pencil, Trash2, Pin, Star, SlidersHorizontal, ChevronDown, Check, Loader2, X } from "lucide-react";
|
||||
import { apiMe, apiDeletePost, apiTogglePin, apiToggleRecommend, type User } from "@/lib/api";
|
||||
import { canModerateBoard, isAdminOrAbove } from "@/lib/roles";
|
||||
import { toast } from "@/lib/toast";
|
||||
|
||||
interface PostActionsProps {
|
||||
postId: string;
|
||||
authorId: number;
|
||||
boardId: number;
|
||||
pinned: number;
|
||||
recommended: boolean;
|
||||
}
|
||||
|
||||
export default function PostActions({ postId, authorId, pinned, recommended }: PostActionsProps) {
|
||||
export default function PostActions({ postId, authorId, boardId, pinned, recommended }: PostActionsProps) {
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -22,6 +24,7 @@ export default function PostActions({ postId, authorId, pinned, recommended }: P
|
||||
const [isPinned, setIsPinned] = useState(pinned > 0);
|
||||
const [isRecommended, setIsRecommended] = useState(recommended);
|
||||
const [acting, setActing] = useState(false);
|
||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -47,16 +50,16 @@ export default function PostActions({ postId, authorId, pinned, recommended }: P
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const canManage = user && (user.id === authorId || user.role === "admin");
|
||||
const isAdmin = user?.role === "admin";
|
||||
// 作者本人或有该板块审核权的管理团队可管理;置顶/加精仅管理员及以上
|
||||
const canManage = !!user && (user.id === authorId || canModerateBoard(user, boardId));
|
||||
const isAdmin = !!user && isAdminOrAbove(user.role);
|
||||
|
||||
const handleDelete = async () => {
|
||||
setOpen(false);
|
||||
if (!confirm("确定要删除这篇帖子吗?此操作不可恢复。")) return;
|
||||
setDeleting(true);
|
||||
try {
|
||||
const res = await apiDeletePost(postId);
|
||||
if (res.message) {
|
||||
setConfirmDelete(false);
|
||||
router.push("/");
|
||||
toast("帖子已删除", "ok");
|
||||
} else {
|
||||
@@ -172,7 +175,10 @@ export default function PostActions({ postId, authorId, pinned, recommended }: P
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
onClick={handleDelete}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setConfirmDelete(true);
|
||||
}}
|
||||
disabled={deleting}
|
||||
className={`${itemCls} items-center`}
|
||||
style={{ color: "var(--danger)" }}
|
||||
@@ -182,6 +188,59 @@ export default function PostActions({ postId, authorId, pinned, recommended }: P
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{confirmDelete && (
|
||||
<div
|
||||
className="fixed inset-0 z-[1000] flex items-center justify-center p-4"
|
||||
style={{ background: "color-mix(in srgb, var(--ink) 55%, transparent)" }}
|
||||
onClick={() => !deleting && setConfirmDelete(false)}
|
||||
>
|
||||
<div
|
||||
className="j13-toast-in panel w-full max-w-[400px] rounded-2xl p-6"
|
||||
style={{ boxShadow: "var(--shadow-lg)" }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<span
|
||||
className="w-12 h-12 rounded-full flex items-center justify-center shrink-0"
|
||||
style={{ background: "var(--danger-soft)", color: "var(--danger)" }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Trash2 size={22} />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3 className="text-[16px] font-extrabold tracking-tight" style={{ color: "var(--ink)" }}>
|
||||
删除帖子
|
||||
</h3>
|
||||
<p className="meta mt-1.5 text-[13px] leading-relaxed">
|
||||
确定要删除这篇帖子吗?此操作不可恢复。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2 mt-5">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-line"
|
||||
onClick={() => setConfirmDelete(false)}
|
||||
disabled={deleting}
|
||||
>
|
||||
<X size={14} /> 取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger"
|
||||
onClick={handleDelete}
|
||||
disabled={deleting}
|
||||
>
|
||||
{deleting && <Loader2 size={15} className="animate-spin" />}
|
||||
确认删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user