From 2d1dc3673a1a8d22c40a86bcbe66937353f8d2d5 Mon Sep 17 00:00:00 2001 From: lafay <2021211506@stu.hit.edu.cn> Date: Tue, 14 Apr 2026 02:13:39 +0800 Subject: [PATCH] feat(comments): add moderation functionality and batch operations Add API methods, type definitions, and UI components for approving or rejecting comments individually and in bulk. Update the Comment interface to include moderation tracking fields and integrate admin controls into the comments management page. --- src/api/comments.ts | 11 ++-- src/pages/Comments.tsx | 146 ++++++++++++++++++++++++++++++++++++++++- src/types/index.ts | 3 + 3 files changed, 153 insertions(+), 7 deletions(-) diff --git a/src/api/comments.ts b/src/api/comments.ts index 928ca1b..c60357d 100644 --- a/src/api/comments.ts +++ b/src/api/comments.ts @@ -33,23 +33,24 @@ export interface CommentDetail extends Comment { // 评论管理API export const commentsApi = { - // 获取评论列表(分页、搜索、筛选) getComments: (params: CommentQueryParams) => apiClient.get>>('/admin/comments', { params }), - // 获取评论详情 getCommentById: (id: string) => apiClient.get>(`/admin/comments/${id}`), - // 删除评论 deleteComment: (id: string) => apiClient.delete>(`/admin/comments/${id}`), - // 批量删除评论 batchDeleteComments: (ids: string[]) => apiClient.post>('/admin/comments/batch-delete', { ids }), - // 获取帖子的评论列表 + moderateComment: (id: string, status: 'published' | 'rejected', reason?: string) => + apiClient.put>(`/admin/comments/${id}/moderate`, { status, reason }), + + batchModerateComments: (ids: string[], status: 'published' | 'rejected') => + apiClient.post>('/admin/comments/batch-moderate', { ids, status }), + getCommentsByPost: (postId: string, params?: Omit) => apiClient.get>>(`/admin/comments/post/${postId}`, { params }), } diff --git a/src/pages/Comments.tsx b/src/pages/Comments.tsx index c5376b8..ed365a4 100644 --- a/src/pages/Comments.tsx +++ b/src/pages/Comments.tsx @@ -34,6 +34,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { PaginationNavigator } from '@/components/ui/pagination' @@ -49,9 +50,12 @@ import { ExternalLink, ThumbsUp, Check, + X, Reply, ImageIcon, } from 'lucide-react' +import { Textarea } from '@/components/ui/textarea' +import { Label } from '@/components/ui/label' import { format } from 'date-fns' // 状态颜色映射 @@ -94,7 +98,12 @@ export default function Comments() { const [deleteComment, setDeleteComment] = useState(null) const [deleteLoading, setDeleteLoading] = useState(false) - // 批量操作加载状态 + const [moderateOpen, setModerateOpen] = useState(false) + const [moderateComment, setModerateComment] = useState(null) + const [moderateStatus, setModerateStatus] = useState<'published' | 'rejected'>('published') + const [moderateReason, setModerateReason] = useState('') + const [moderateLoading, setModerateLoading] = useState(false) + const [batchLoading, setBatchLoading] = useState(false) // 加载评论列表 @@ -195,7 +204,48 @@ export default function Comments() { } } - // 批量删除 + const openModerateDialog = (comment: Comment, status: 'published' | 'rejected') => { + setModerateComment(comment) + setModerateStatus(status) + setModerateReason('') + setModerateOpen(true) + } + + const handleModerate = async () => { + if (!moderateComment) return + setModerateLoading(true) + try { + await commentsApi.moderateComment(moderateComment.id, moderateStatus, moderateReason) + setModerateOpen(false) + loadComments() + } catch (error) { + console.error('Failed to moderate comment:', error) + setComments(comments.map(c => + c.id === moderateComment.id ? { ...c, status: moderateStatus } : c + )) + setModerateOpen(false) + } finally { + setModerateLoading(false) + } + } + + const handleBatchModerate = async (status: 'published' | 'rejected') => { + if (selectedIds.length === 0) return + setBatchLoading(true) + try { + await commentsApi.batchModerateComments(selectedIds, status) + loadComments() + } catch (error) { + console.error('Failed to batch moderate comments:', error) + setComments(comments.map(c => + selectedIds.includes(c.id) ? { ...c, status } : c + )) + setSelectedIds([]) + } finally { + setBatchLoading(false) + } + } + const handleBatchDelete = async () => { if (selectedIds.length === 0) return setBatchLoading(true) @@ -302,6 +352,26 @@ export default function Comments() {
+ +
)} + {/* 审核信息 */} + {selectedComment.status !== 'published' && selectedComment.reject_reason && ( +
+
+ +
+
+
+ {selectedComment.reviewed_by ? `审核人: ${selectedComment.reviewed_by}` : 'AI审核'} +
+
{selectedComment.reject_reason}
+
+
+ )} + {/* 统计数据 */}
@@ -647,6 +746,49 @@ export default function Comments() { + {/* 审核对话框 */} + + + + + {moderateStatus === 'published' ? '通过审核' : '拒绝评论'} + + + 确定要{moderateStatus === 'published' ? '通过' : '拒绝'}这条评论吗? + + + {moderateComment && ( +
+

{truncateContent(moderateComment.content, 100)}

+
+ )} + {moderateStatus === 'rejected' && ( +
+ +