diff --git a/src/views/Community/components/DynamicNewsCard/PageNavigationButton.js b/src/views/Community/components/DynamicNewsCard/PageNavigationButton.js deleted file mode 100644 index 723e89cd..00000000 --- a/src/views/Community/components/DynamicNewsCard/PageNavigationButton.js +++ /dev/null @@ -1,83 +0,0 @@ -// src/views/Community/components/DynamicNewsCard/PageNavigationButton.js -// 翻页导航按钮组件 - -import React from 'react'; -import { IconButton, useColorModeValue } from '@chakra-ui/react'; -import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons'; - -/** - * 翻页导航按钮组件 - * @param {Object} props - * @param {'prev'|'next'} props.direction - 按钮方向(prev=上一页,next=下一页) - * @param {number} props.currentPage - 当前页码 - * @param {number} props.totalPages - 总页数 - * @param {Function} props.onPageChange - 翻页回调 - * @param {string} props.mode - 显示模式(只在carousel/grid模式下显示) - */ -const PageNavigationButton = ({ - direction, - currentPage, - totalPages, - onPageChange, - mode -}) => { - // 主题适配 - const arrowBtnBg = useColorModeValue('rgba(255, 255, 255, 0.9)', 'rgba(0, 0, 0, 0.6)'); - const arrowBtnHoverBg = useColorModeValue('rgba(255, 255, 255, 1)', 'rgba(0, 0, 0, 0.8)'); - - // 根据方向计算配置 - const isPrev = direction === 'prev'; - const isNext = direction === 'next'; - - const Icon = isPrev ? ChevronLeftIcon : ChevronRightIcon; - const position = isPrev ? 'left' : 'right'; - const label = isPrev ? '上一页' : '下一页'; - const targetPage = isPrev ? currentPage - 1 : currentPage + 1; - const shouldShow = isPrev - ? currentPage > 1 - : currentPage < totalPages; - const isDisabled = isNext ? currentPage >= totalPages : false; - - // 判断是否显示(只在单排/双排模式显示) - const shouldRender = shouldShow && (mode === 'carousel' || mode === 'grid'); - - if (!shouldRender) return null; - - const handleClick = () => { - console.log( - `%c🔵 [翻页] 点击${label}: 当前页${currentPage} → 目标页${targetPage} (共${totalPages}页)`, - 'color: #3B82F6; font-weight: bold;' - ); - onPageChange(targetPage); - }; - - return ( - } - position="absolute" - {...{ [position]: 0 }} - top="50%" - transform="translateY(-50%)" - zIndex={2} - onClick={handleClick} - variant="ghost" - size="md" - w="40px" - h="40px" - minW="40px" - borderRadius="full" - bg={arrowBtnBg} - boxShadow="0 2px 8px rgba(0, 0, 0, 0.15)" - _hover={{ - bg: arrowBtnHoverBg, - boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)', - transform: 'translateY(-50%) scale(1.05)' - }} - isDisabled={isDisabled} - aria-label={label} - title={label} - /> - ); -}; - -export default PageNavigationButton; diff --git a/src/views/Community/components/DynamicNewsCard/hooks/useInfiniteScroll.js b/src/views/Community/components/DynamicNewsCard/hooks/useInfiniteScroll.js deleted file mode 100644 index 17ccfb8c..00000000 --- a/src/views/Community/components/DynamicNewsCard/hooks/useInfiniteScroll.js +++ /dev/null @@ -1,88 +0,0 @@ -// src/views/Community/components/DynamicNewsCard/hooks/useInfiniteScroll.js -// 无限滚动 Hook - -import { useEffect, useRef, useCallback } from 'react'; - -/** - * 无限滚动 Hook - * 监听容器滚动事件,当滚动到底部附近时触发加载更多数据 - * - * @param {Object} options - 配置选项 - * @param {Function} options.onLoadMore - 加载更多回调函数(返回 Promise) - * @param {boolean} options.hasMore - 是否还有更多数据 - * @param {boolean} options.isLoading - 是否正在加载 - * @param {number} options.threshold - 触发阈值(距离底部多少像素时触发,默认200px) - * @returns {Object} { containerRef } - 容器引用 - */ -export const useInfiniteScroll = ({ - onLoadMore, - hasMore = true, - isLoading = false, - threshold = 200 -}) => { - const containerRef = useRef(null); - const isLoadingRef = useRef(false); - - // 滚动处理函数 - const handleScroll = useCallback(() => { - const container = containerRef.current; - - // 检查条件:容器存在、未加载中、还有更多数据 - if (!container || isLoadingRef.current || !hasMore) { - return; - } - - const { scrollTop, scrollHeight, clientHeight } = container; - const distanceToBottom = scrollHeight - scrollTop - clientHeight; - - // 距离底部小于阈值时触发加载 - if (distanceToBottom < threshold) { - console.log( - '%c⬇️ [懒加载] 触发加载下一页', - 'color: #8B5CF6; font-weight: bold;', - { - scrollTop, - scrollHeight, - clientHeight, - distanceToBottom, - threshold - } - ); - - isLoadingRef.current = true; - - // 调用加载函数并更新状态 - onLoadMore() - .then(() => { - console.log('%c✅ [懒加载] 加载完成', 'color: #10B981; font-weight: bold;'); - }) - .catch((error) => { - console.error('%c❌ [懒加载] 加载失败', 'color: #DC2626; font-weight: bold;', error); - }) - .finally(() => { - isLoadingRef.current = false; - }); - } - }, [onLoadMore, hasMore, threshold]); - - // 绑定滚动事件 - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - // 添加滚动监听 - container.addEventListener('scroll', handleScroll, { passive: true }); - - // 清理函数 - return () => { - container.removeEventListener('scroll', handleScroll); - }; - }, [handleScroll]); - - // 更新 loading 状态的 ref - useEffect(() => { - isLoadingRef.current = isLoading; - }, [isLoading]); - - return { containerRef }; -}; diff --git a/src/views/Community/components/EventDiscussionModal.js b/src/views/Community/components/EventDiscussionModal.js deleted file mode 100644 index c5731937..00000000 --- a/src/views/Community/components/EventDiscussionModal.js +++ /dev/null @@ -1,614 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - Modal, - ModalOverlay, - ModalContent, - ModalHeader, - ModalBody, - ModalCloseButton, - Box, - Text, - VStack, - HStack, - Avatar, - Textarea, - Button, - Divider, - useToast, - Badge, - Flex, - IconButton, - Menu, - MenuButton, - MenuList, - MenuItem, - useColorModeValue, - Spinner, - Center, - Collapse, - Input, -} from '@chakra-ui/react'; -import { - ChatIcon, - TimeIcon, - DeleteIcon, - EditIcon, - ChevronDownIcon, - TriangleDownIcon, - TriangleUpIcon, -} from '@chakra-ui/icons'; -import { FaHeart, FaRegHeart, FaComment } from 'react-icons/fa'; -import { format } from 'date-fns'; -import { zhCN } from 'date-fns/locale'; -import { eventService } from '../../../services/eventService'; -import { logger } from '../../../utils/logger'; - -const EventDiscussionModal = ({ isOpen, onClose, eventId, eventTitle, discussionType = '事件讨论' }) => { - const [posts, setPosts] = useState([]); - const [newPostContent, setNewPostContent] = useState(''); - const [newPostTitle, setNewPostTitle] = useState(''); - const [loading, setLoading] = useState(false); - const [submitting, setSubmitting] = useState(false); - const [expandedPosts, setExpandedPosts] = useState({}); - const [postComments, setPostComments] = useState({}); - const [replyContents, setReplyContents] = useState({}); - const [loadingComments, setLoadingComments] = useState({}); - - const toast = useToast(); - const bgColor = useColorModeValue('white', 'gray.800'); - const borderColor = useColorModeValue('gray.200', 'gray.600'); - const hoverBg = useColorModeValue('gray.50', 'gray.700'); - - // 加载帖子列表 - const loadPosts = async () => { - if (!eventId) return; - - setLoading(true); - try { - const response = await fetch(`/api/events/${eventId}/posts?sort=latest&page=1&per_page=20`, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include' - }); - const result = await response.json(); - - if (response.ok && result.success) { - setPosts(result.data || []); - logger.debug('EventDiscussionModal', '帖子列表加载成功', { - eventId, - postsCount: result.data?.length || 0 - }); - } else { - logger.error('EventDiscussionModal', 'loadPosts', new Error('API返回错误'), { - eventId, - status: response.status, - message: result.message - }); - toast({ - title: '加载帖子失败', - status: 'error', - duration: 3000, - isClosable: true, - }); - } - } catch (error) { - logger.error('EventDiscussionModal', 'loadPosts', error, { eventId }); - toast({ - title: '加载帖子失败', - status: 'error', - duration: 3000, - isClosable: true, - }); - } finally { - setLoading(false); - } - }; - - // 加载帖子的评论 - const loadPostComments = async (postId) => { - setLoadingComments(prev => ({ ...prev, [postId]: true })); - try { - const response = await fetch(`/api/posts/${postId}/comments?sort=latest`, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include' - }); - const result = await response.json(); - - if (response.ok && result.success) { - setPostComments(prev => ({ ...prev, [postId]: result.data || [] })); - logger.debug('EventDiscussionModal', '评论加载成功', { - postId, - commentsCount: result.data?.length || 0 - }); - } - } catch (error) { - logger.error('EventDiscussionModal', 'loadPostComments', error, { postId }); - } finally { - setLoadingComments(prev => ({ ...prev, [postId]: false })); - } - }; - - // 切换展开/收起评论 - const togglePostComments = async (postId) => { - const isExpanded = expandedPosts[postId]; - if (!isExpanded) { - // 展开时加载评论 - await loadPostComments(postId); - } - setExpandedPosts(prev => ({ ...prev, [postId]: !isExpanded })); - }; - - // 提交新帖子 - const handleSubmitPost = async () => { - if (!newPostContent.trim()) return; - - setSubmitting(true); - try { - const response = await fetch(`/api/events/${eventId}/posts`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ - title: newPostTitle.trim(), - content: newPostContent.trim(), - content_type: 'text', - }) - }); - const result = await response.json(); - - if (response.ok && result.success) { - setNewPostContent(''); - setNewPostTitle(''); - loadPosts(); - logger.info('EventDiscussionModal', '帖子发布成功', { - eventId, - postId: result.data?.id - }); - toast({ - title: '帖子发布成功', - status: 'success', - duration: 2000, - isClosable: true, - }); - } else { - logger.error('EventDiscussionModal', 'handleSubmitPost', new Error('API返回错误'), { - eventId, - message: result.message - }); - toast({ - title: result.message || '帖子发布失败', - status: 'error', - duration: 3000, - isClosable: true, - }); - } - } catch (error) { - logger.error('EventDiscussionModal', 'handleSubmitPost', error, { eventId }); - toast({ - title: '帖子发布失败', - status: 'error', - duration: 3000, - isClosable: true, - }); - } finally { - setSubmitting(false); - } - }; - - // 删除帖子 - const handleDeletePost = async (postId) => { - if (!window.confirm('确定要删除这个帖子吗?')) return; - - try { - const response = await fetch(`/api/posts/${postId}`, { - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include' - }); - const result = await response.json(); - - if (response.ok && result.success) { - loadPosts(); - logger.info('EventDiscussionModal', '帖子删除成功', { postId }); - toast({ - title: '帖子已删除', - status: 'success', - duration: 2000, - isClosable: true, - }); - } else { - logger.error('EventDiscussionModal', 'handleDeletePost', new Error('API返回错误'), { - postId, - message: result.message - }); - toast({ - title: result.message || '删除失败', - status: 'error', - duration: 3000, - isClosable: true, - }); - } - } catch (error) { - logger.error('EventDiscussionModal', 'handleDeletePost', error, { postId }); - toast({ - title: '删除失败', - status: 'error', - duration: 3000, - isClosable: true, - }); - } - }; - - // 点赞帖子 - const handleLikePost = async (postId) => { - try { - const response = await fetch(`/api/posts/${postId}/like`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include' - }); - const result = await response.json(); - - if (response.ok && result.success) { - // 更新帖子列表中的点赞状态 - setPosts(prev => prev.map(post => - post.id === postId - ? { ...post, likes_count: result.likes_count, liked: result.liked } - : post - )); - logger.debug('EventDiscussionModal', '点赞操作成功', { - postId, - liked: result.liked, - likesCount: result.likes_count - }); - } - } catch (error) { - logger.error('EventDiscussionModal', 'handleLikePost', error, { postId }); - toast({ - title: '操作失败', - status: 'error', - duration: 2000, - isClosable: true, - }); - } - }; - - // 提交评论 - const handleSubmitComment = async (postId) => { - const content = replyContents[postId]; - if (!content?.trim()) return; - - try { - const response = await fetch(`/api/posts/${postId}/comments`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', - body: JSON.stringify({ - content: content.trim(), - }) - }); - const result = await response.json(); - - if (response.ok && result.success) { - setReplyContents(prev => ({ ...prev, [postId]: '' })); - // 重新加载该帖子的评论 - await loadPostComments(postId); - // 更新帖子的评论数 - setPosts(prev => prev.map(post => - post.id === postId - ? { ...post, comments_count: (post.comments_count || 0) + 1 } - : post - )); - logger.info('EventDiscussionModal', '评论发布成功', { - postId, - commentId: result.data?.id - }); - toast({ - title: '评论发布成功', - status: 'success', - duration: 2000, - isClosable: true, - }); - } - } catch (error) { - logger.error('EventDiscussionModal', 'handleSubmitComment', error, { postId }); - toast({ - title: '评论发布失败', - status: 'error', - duration: 3000, - isClosable: true, - }); - } - }; - - // 删除评论 - const handleDeleteComment = async (commentId, postId) => { - if (!window.confirm('确定要删除这条评论吗?')) return; - - try { - const response = await fetch(`/api/comments/${commentId}`, { - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include' - }); - const result = await response.json(); - - if (response.ok && result.success) { - // 重新加载该帖子的评论 - await loadPostComments(postId); - // 更新帖子的评论数 - setPosts(prev => prev.map(post => - post.id === postId - ? { ...post, comments_count: Math.max(0, (post.comments_count || 0) - 1) } - : post - )); - logger.info('EventDiscussionModal', '评论删除成功', { commentId, postId }); - toast({ - title: '评论已删除', - status: 'success', - duration: 2000, - isClosable: true, - }); - } - } catch (error) { - logger.error('EventDiscussionModal', 'handleDeleteComment', error, { commentId, postId }); - toast({ - title: '删除失败', - status: 'error', - duration: 3000, - isClosable: true, - }); - } - }; - - useEffect(() => { - if (isOpen) { - loadPosts(); - } - }, [isOpen, eventId]); - - return ( - - - - - - - - {discussionType} - - {eventTitle && ( - - {eventTitle} - - )} - - - - - - {/* 发布新帖子 */} - - setNewPostTitle(e.target.value)} - placeholder="帖子标题(可选)" - size="sm" - mb={2} - /> -