195 lines
5.6 KiB
JavaScript
195 lines
5.6 KiB
JavaScript
// src/components/EventCommentSection/EventCommentSection.js
|
||
/**
|
||
* 事件评论区主组件
|
||
* 功能:整合评论列表 + 评论输入框,管理评论数据
|
||
*/
|
||
|
||
import React, { useState, useEffect, useCallback } from 'react';
|
||
import {
|
||
Box,
|
||
VStack,
|
||
Heading,
|
||
Badge,
|
||
HStack,
|
||
Divider,
|
||
useColorModeValue,
|
||
useToast,
|
||
} from '@chakra-ui/react';
|
||
import { useAuth } from '../../contexts/AuthContext';
|
||
import { eventService } from '../../services/eventService';
|
||
import { logger } from '../../utils/logger';
|
||
import CommentList from './CommentList';
|
||
import CommentInput from './CommentInput';
|
||
|
||
/**
|
||
* 事件评论区组件
|
||
* @param {Object} props
|
||
* @param {number} props.eventId - 事件 ID
|
||
*/
|
||
const EventCommentSection = ({ eventId }) => {
|
||
const { user } = useAuth();
|
||
const toast = useToast();
|
||
const dividerColor = useColorModeValue('gray.200', 'gray.600');
|
||
const headingColor = useColorModeValue('gray.700', 'gray.200');
|
||
const sectionBg = useColorModeValue('gray.50', 'gray.750');
|
||
|
||
// 状态管理
|
||
const [comments, setComments] = useState([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const [commentText, setCommentText] = useState('');
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [totalCount, setTotalCount] = useState(0); // 总评论数(从后端获取)
|
||
|
||
// 加载评论列表
|
||
const loadComments = useCallback(async () => {
|
||
if (!eventId) return;
|
||
|
||
setLoading(true);
|
||
try {
|
||
// 加载第1页,每页5条评论
|
||
const result = await eventService.getPosts(eventId, 'latest', 1, 5);
|
||
if (result.success) {
|
||
setComments(result.data || []);
|
||
// 保存总评论数(从 pagination.total 读取)
|
||
setTotalCount(result.pagination?.total || result.data?.length || 0);
|
||
logger.info('EventCommentSection', '评论加载成功', {
|
||
eventId,
|
||
count: result.data?.length || 0,
|
||
total: result.pagination?.total || 0,
|
||
});
|
||
}
|
||
} catch (error) {
|
||
logger.error('EventCommentSection', 'loadComments', error, { eventId });
|
||
toast({
|
||
title: '加载评论失败',
|
||
description: error.message || '请稍后重试',
|
||
status: 'error',
|
||
duration: 3000,
|
||
isClosable: true,
|
||
});
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [eventId, toast]);
|
||
|
||
// 发表评论
|
||
const handleSubmitComment = useCallback(async () => {
|
||
if (!commentText.trim()) {
|
||
toast({
|
||
title: '请输入评论内容',
|
||
status: 'warning',
|
||
duration: 2000,
|
||
isClosable: true,
|
||
});
|
||
return;
|
||
}
|
||
|
||
setSubmitting(true);
|
||
try {
|
||
const result = await eventService.createPost(eventId, {
|
||
content: commentText.trim(),
|
||
content_type: 'text',
|
||
});
|
||
|
||
if (result.success) {
|
||
// 乐观更新:立即将新评论添加到本地 state,避免重新加载导致的闪烁
|
||
const newComment = {
|
||
id: result.data?.id || `comment_optimistic_${Date.now()}`,
|
||
content: commentText.trim(),
|
||
content_type: 'text',
|
||
author: {
|
||
id: user?.id || 'current_user',
|
||
username: user?.username || '当前用户',
|
||
avatar: user?.avatar || null,
|
||
},
|
||
created_at: new Date().toISOString(),
|
||
likes_count: 0,
|
||
is_liked: false,
|
||
};
|
||
|
||
// 将新评论追加到列表末尾(最新评论在底部)
|
||
setComments([...comments, newComment]);
|
||
// 总评论数 +1
|
||
setTotalCount(totalCount + 1);
|
||
|
||
toast({
|
||
title: '评论发布成功',
|
||
status: 'success',
|
||
duration: 2000,
|
||
isClosable: true,
|
||
});
|
||
setCommentText(''); // 清空输入框
|
||
// ✅ 不再调用 loadComments(),避免 loading 状态导致高度闪烁
|
||
|
||
logger.info('EventCommentSection', '评论发布成功(乐观更新)', {
|
||
eventId,
|
||
content: commentText.trim(),
|
||
commentId: newComment.id,
|
||
});
|
||
} else {
|
||
throw new Error(result.message || '评论发布失败');
|
||
}
|
||
} catch (error) {
|
||
logger.error('EventCommentSection', 'handleSubmitComment', error, { eventId });
|
||
toast({
|
||
title: '评论发布失败',
|
||
description: error.message || '请稍后重试',
|
||
status: 'error',
|
||
duration: 3000,
|
||
isClosable: true,
|
||
});
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
}, [eventId, commentText, toast, comments, user, totalCount]);
|
||
|
||
// 初始加载评论
|
||
useEffect(() => {
|
||
loadComments();
|
||
}, [loadComments]);
|
||
|
||
return (
|
||
<Box>
|
||
{/* 标题栏 */}
|
||
<HStack
|
||
spacing={3}
|
||
mb={4}
|
||
p={3}
|
||
bg={sectionBg}
|
||
borderRadius="md"
|
||
>
|
||
<Heading size="sm" color={headingColor}>
|
||
讨论区
|
||
</Heading>
|
||
<Badge colorScheme="blue" fontSize="sm" borderRadius="full" px={2}>
|
||
{totalCount} 条评论
|
||
</Badge>
|
||
</HStack>
|
||
|
||
<Divider borderColor={dividerColor} mb={4} />
|
||
|
||
{/* 评论列表 */}
|
||
<Box mb={4}>
|
||
<CommentList comments={comments} loading={loading} />
|
||
</Box>
|
||
|
||
{/* 评论输入框(仅登录用户显示) */}
|
||
{user && (
|
||
<Box>
|
||
<Divider borderColor={dividerColor} mb={4} />
|
||
<CommentInput
|
||
value={commentText}
|
||
onChange={(e) => setCommentText(e.target.value)}
|
||
onSubmit={handleSubmitComment}
|
||
isSubmitting={submitting}
|
||
maxLength={500}
|
||
placeholder="说点什么..."
|
||
/>
|
||
</Box>
|
||
)}
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
export default EventCommentSection;
|