import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { Typography, Card, Avatar, Tag, Space, Button, Divider, Input, message, Upload, Tooltip } from 'antd'; import { UserOutlined, ClockCircleOutlined, EyeOutlined, CheckCircleFilled, LeftOutlined, UploadOutlined, EditOutlined } from '@ant-design/icons'; import { getTopicDetail, createReply, uploadMedia } from '../api'; import { useAuth } from '../context/AuthContext'; import LoginModal from '../components/LoginModal'; import CreateTopicModal from '../components/CreateTopicModal'; import ReactMarkdown from 'react-markdown'; import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import 'katex/dist/katex.min.css'; const { Title, Text } = Typography; const { TextArea } = Input; const ForumDetail = () => { const { id } = useParams(); const navigate = useNavigate(); const { user } = useAuth(); const [loading, setLoading] = useState(true); const [topic, setTopic] = useState(null); const [replyContent, setReplyContent] = useState(''); const [submitting, setSubmitting] = useState(false); const [loginModalVisible, setLoginModalVisible] = useState(false); // Edit Topic State const [editModalVisible, setEditModalVisible] = useState(false); // Reply Image State const [replyUploading, setReplyUploading] = useState(false); const [replyMediaIds, setReplyMediaIds] = useState([]); const fetchTopic = async () => { try { const res = await getTopicDetail(id); setTopic(res.data); } catch (error) { console.error(error); message.error('加载失败'); } finally { setLoading(false); } }; const hasFetched = React.useRef(false); useEffect(() => { if (!hasFetched.current) { fetchTopic(); hasFetched.current = true; } }, [id]); const handleSubmitReply = async () => { if (!user) { setLoginModalVisible(true); return; } if (!replyContent.trim()) { message.warning('请输入回复内容'); return; } setSubmitting(true); try { await createReply({ topic: id, content: replyContent, media_ids: replyMediaIds // Send uploaded media IDs }); message.success('回复成功'); setReplyContent(''); setReplyMediaIds([]); // Reset media IDs fetchTopic(); // Refresh to show new reply } catch (error) { console.error(error); message.error('回复失败'); } finally { setSubmitting(false); } }; const handleReplyUpload = async (file) => { const formData = new FormData(); formData.append('file', file); formData.append('media_type', file.type.startsWith('video') ? 'video' : 'image'); setReplyUploading(true); try { const res = await uploadMedia(formData); if (res.data.id) { setReplyMediaIds(prev => [...prev, res.data.id]); } let url = res.data.file; if (url) url = url.replace(/\\/g, '/'); if (url && !url.startsWith('http')) { const baseURL = import.meta.env.VITE_API_URL || 'http://localhost:8000'; const host = baseURL.replace(/\/api\/?$/, ''); if (!url.startsWith('/')) url = '/' + url; url = `${host}${url}`; } url = url.replace(/([^:]\/)\/+/g, '$1'); const insertText = file.type.startsWith('video') ? `\n\n` : `\n![${file.name}](${url})\n`; setReplyContent(prev => prev + insertText); message.success('上传成功'); } catch (error) { console.error(error); message.error('上传失败'); } finally { setReplyUploading(false); } return false; }; if (loading) return
Loading...
; if (!topic) return
Topic not found
; const markdownComponents = { // eslint-disable-next-line no-unused-vars code({node, inline, className, children, ...props}) { const match = /language-(\w+)/.exec(className || '') return !inline && match ? ( {String(children).replace(/\n$/, '')} ) : ( {children} ) }, // eslint-disable-next-line no-unused-vars img({node, ...props}) { return ( ); } }; return (
{/* Debug Info: Remove in production */} {/*
User ID: {user?.id} ({typeof user?.id})
Topic Author: {topic.author} ({typeof topic.author})
Match: {String(topic.author) === String(user?.id) ? 'Yes' : 'No'}
*/} {user && String(topic.author) === String(user.id) && ( )}
{/* Topic Content */}
{topic.is_pinned && 置顶} {topic.product_info && {topic.product_info.name}} {topic.title} } /> {topic.author_info?.nickname} {topic.is_verified_owner && ( )} {new Date(topic.created_at).toLocaleString()} {topic.view_count} 阅读
{topic.content}
{(() => { if (topic.media && topic.media.length > 0) { return topic.media.filter(m => m.media_type === 'video').map((media) => (
)); } return null; })()}
{/* Replies List */}
{topic.replies?.length || 0} 条回复 {topic.replies?.map((reply, index) => (
} />
{reply.author_info?.nickname} {new Date(reply.created_at).toLocaleString()} #{index + 1}
{reply.content}
))}
{/* Reply Form */} 发表回复 {user ? ( <>