import React from 'react'; import { makeStyles, shorthands, tokens, Button, Input, Textarea, Dropdown, Option, Card, Text } from '@fluentui/react-components'; import { getComments, type Comment as CommentType } from '../api'; import { deleteComment, modifyComment } from '../admin_api'; import { toast } from 'react-toastify'; const useStyles = makeStyles({ modalContent: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 'min(860px, 96vw)', backgroundColor: tokens.colorNeutralBackground1, boxShadow: tokens.shadow64, ...shorthands.borderRadius(tokens.borderRadiusXLarge), ...shorthands.padding(tokens.spacingVerticalL, tokens.spacingHorizontalXL), zIndex: 1001, display: 'flex', flexDirection: 'column', gap: tokens.spacingVerticalM, maxHeight: '80vh', }, titleRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', }, title: { fontSize: tokens.fontSizeBase600, fontWeight: tokens.fontWeightBold, }, closeButton: { position: 'absolute', right: tokens.spacingHorizontalM, top: tokens.spacingVerticalM, }, commentsList: { overflowY: 'auto', flex: 1, display: 'flex', flexDirection: 'column', gap: tokens.spacingVerticalM, ...shorthands.padding(tokens.spacingVerticalM), }, commentCard: { backgroundColor: tokens.colorNeutralBackground1, ...shorthands.borderRadius(tokens.borderRadiusLarge), ...shorthands.border('1px', 'solid', tokens.colorNeutralStroke1), boxShadow: tokens.shadow8, marginBottom: tokens.spacingVerticalS, padding: tokens.spacingHorizontalM, width: '100%', }, commentHeader: { display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', color: tokens.colorNeutralForeground1, }, nickname: { fontWeight: tokens.fontWeightSemibold, }, commentMeta: { color: tokens.colorNeutralForeground3, fontSize: tokens.fontSizeBase300, }, childComment: { marginLeft: tokens.spacingHorizontalL, borderLeft: `2px solid ${tokens.colorNeutralStroke2}`, paddingLeft: tokens.spacingHorizontalM, }, actionsRow: { display: 'flex', gap: tokens.spacingHorizontalS, marginTop: tokens.spacingVerticalS, }, editor: { display: 'flex', flexDirection: 'column', gap: tokens.spacingVerticalS, }, fieldRow: { display: 'flex', gap: tokens.spacingHorizontalS, }, fieldControl: { flex: 1, }, }); type AdminManageCommentsProps = { postId: number; onClose: () => void; }; const AdminManageComments: React.FC = ({ postId, onClose }) => { const styles = useStyles(); const [loading, setLoading] = React.useState(false); const [comments, setComments] = React.useState([]); const [editingId, setEditingId] = React.useState(null); const [nickname, setNickname] = React.useState(''); const [content, setContent] = React.useState(''); const [parentId, setParentId] = React.useState(0); const depthMap = React.useMemo(() => { const map = new Map(); const idToParent = new Map(); comments.forEach(c => idToParent.set(c.id, (c.parent_comment_id as any) ?? 0)); const calcDepth = (id: number) => { if (map.has(id)) return map.get(id)!; let d = 0; let current = id; const seen = new Set(); while (true) { seen.add(current); const p = idToParent.get(current) ?? 0; if (p === 0 || !idToParent.has(p) || seen.has(p)) break; d += 1; current = p; } map.set(id, d); return d; }; comments.forEach(c => calcDepth(c.id)); return map; }, [comments]); const loadComments = React.useCallback(async () => { setLoading(true); try { const list = await getComments(postId); setComments(list); } catch (e: any) { toast.error(`加载评论失败:${e?.message || e}`); } finally { setLoading(false); } }, [postId]); React.useEffect(() => { loadComments(); }, [loadComments]); const startEdit = (c: CommentType) => { setEditingId(c.id); setNickname(c.nickname || ''); setContent(c.content || ''); setParentId((c.parent_comment_id as any) ?? 0); }; const cancelEdit = () => { setEditingId(null); setNickname(''); setContent(''); setParentId(0); }; const submitEdit = async () => { if (editingId === null) return; if (parentId === editingId) { toast.error('父评论不能设置为自己'); return; } try { await modifyComment(editingId, content, Number(parentId), nickname); toast.success('修改评论成功'); setComments(prev => prev.map(c => c.id === editingId ? { ...c, content, nickname, parent_comment_id: Number(parentId) } : c)); cancelEdit(); } catch (e: any) { toast.error(e?.message || '修改评论失败'); } }; const handleDelete = async (id: number) => { if (!id && id !== 0) return; try { await deleteComment(id); toast.success('删除评论成功'); setComments(prev => prev.filter(c => c.id !== id)); } catch (e: any) { toast.error(e?.message || '删除评论失败'); } }; // 递归渲染函数工厂(用于显示树形结构) const renderComments = React.useMemo(() => { return renderCommentsFactory(comments, styles, startEdit, handleDelete); }, [comments, styles]); return (
评论管理
帖子 #{postId}
{editingId !== null && (
修改评论 #{editingId}
setNickname(d.value)} placeholder="用户名" /> setParentId(Number(data.optionValue))} > {comments.filter(c => c.id !== editingId).map(c => { const depth = depthMap.get(c.id) ?? 0; const indent = ' '.repeat(Math.max(0, depth * 2)); return ( ); })}