22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 1 | import React, { useState, useEffect } from "react"; |
TRM-coding | fa3ffdf | 2025-06-09 22:47:42 +0800 | [diff] [blame] | 2 | import { useParams, useNavigate } from "react-router-dom"; |
22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 3 | import { API_BASE_URL } from "./config"; |
TRM-coding | fa3ffdf | 2025-06-09 22:47:42 +0800 | [diff] [blame] | 4 | import HomeIcon from "@mui/icons-material/Home"; |
| 5 | import MovieIcon from "@mui/icons-material/Movie"; |
| 6 | import TvIcon from "@mui/icons-material/Tv"; |
| 7 | import MusicNoteIcon from "@mui/icons-material/MusicNote"; |
| 8 | import AnimationIcon from "@mui/icons-material/Animation"; |
| 9 | import SportsEsportsIcon from "@mui/icons-material/SportsEsports"; |
| 10 | import SportsMartialArtsIcon from "@mui/icons-material/SportsMartialArts"; |
| 11 | import PersonIcon from "@mui/icons-material/Person"; |
| 12 | import AccountCircleIcon from "@mui/icons-material/AccountCircle"; |
| 13 | import ForumIcon from "@mui/icons-material/Forum"; |
| 14 | import HelpIcon from "@mui/icons-material/Help"; |
| 15 | import ArrowBackIcon from "@mui/icons-material/ArrowBack"; |
| 16 | import ReplyIcon from "@mui/icons-material/Reply"; |
| 17 | import VisibilityIcon from "@mui/icons-material/Visibility"; |
| 18 | import "./SharedStyles.css"; |
| 19 | |
| 20 | const navItems = [ |
| 21 | { label: "首页", icon: <HomeIcon className="emerald-nav-icon" />, path: "/home", type: "home" }, |
| 22 | { label: "电影", icon: <MovieIcon className="emerald-nav-icon" />, path: "/movie", type: "movie" }, |
| 23 | { label: "剧集", icon: <TvIcon className="emerald-nav-icon" />, path: "/tv", type: "tv" }, |
| 24 | { label: "音乐", icon: <MusicNoteIcon className="emerald-nav-icon" />, path: "/music", type: "music" }, |
| 25 | { label: "动漫", icon: <AnimationIcon className="emerald-nav-icon" />, path: "/anime", type: "anime" }, |
| 26 | { label: "游戏", icon: <SportsEsportsIcon className="emerald-nav-icon" />, path: "/game", type: "game" }, |
| 27 | { label: "体育", icon: <SportsMartialArtsIcon className="emerald-nav-icon" />, path: "/sport", type: "sport" }, |
| 28 | { label: "资料", icon: <PersonIcon className="emerald-nav-icon" />, path: "/info", type: "info" }, |
| 29 | { label: "论坛", icon: <ForumIcon className="emerald-nav-icon" />, path: "/forum", type: "forum" }, |
| 30 | { label: "发布", icon: <AccountCircleIcon className="emerald-nav-icon" />, path: "/publish", type: "publish" }, |
| 31 | { label: "求种", icon: <HelpIcon className="emerald-nav-icon" />, path: "/begseed", type: "help" }, |
| 32 | ]; |
| 33 | |
| 34 | // 论坛详情页文字雨内容 |
| 35 | const forumDetailTexts = [ |
| 36 | "讨论", "回复", "交流", "观点", "见解", "思考", "分享", "互动", |
| 37 | "对话", "评论", "深度", "专业", "洞察", "分析", "探讨", "解答" |
| 38 | ]; |
wht | 6a1b678 | 2025-06-06 19:14:59 +0800 | [diff] [blame] | 39 | |
| 40 | export default function PostDetailPage() { |
| 41 | const { postId } = useParams(); |
TRM-coding | fa3ffdf | 2025-06-09 22:47:42 +0800 | [diff] [blame] | 42 | const navigate = useNavigate(); |
22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 43 | const [post, setPost] = useState(null); |
| 44 | const [replies, setReplies] = useState([]); |
| 45 | const [newReply, setNewReply] = useState(''); |
TRM-coding | fa3ffdf | 2025-06-09 22:47:42 +0800 | [diff] [blame] | 46 | const [userInfo, setUserInfo] = useState({ avatar_url: '', username: '' }); |
| 47 | const [userPT, setUserPT] = useState({ magic: 0, ratio: 0, upload: 0, download: 0 }); |
| 48 | |
| 49 | useEffect(() => { |
| 50 | // 获取用户信息 |
| 51 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)'); |
| 52 | const userId = match ? match[2] : null; |
| 53 | if (userId) { |
| 54 | fetch(`${API_BASE_URL}/api/get-userpt?userid=${encodeURIComponent(userId)}`) |
| 55 | .then(res => res.json()) |
| 56 | .then(data => { |
| 57 | setUserInfo({ avatar_url: data.user.avatar_url, username: data.user.username }); |
| 58 | setUserPT({ |
| 59 | magic: data.magic_value || data.magic || 0, |
| 60 | ratio: data.share_ratio || data.share || 0, |
| 61 | upload: data.upload_amount || data.upload || 0, |
| 62 | download: data.download_amount || data.download || 0, |
| 63 | }); |
| 64 | }) |
| 65 | .catch(err => console.error('Fetching user profile failed', err)); |
| 66 | } |
| 67 | }, []); |
22301133 | 9e29215 | 2025-06-08 00:34:37 +0800 | [diff] [blame] | 68 | |
| 69 | // function to load post details and its replies |
| 70 | const fetchDetail = () => { |
| 71 | fetch(`${API_BASE_URL}/api/forum-detail?postid=${postId}`) |
| 72 | .then(res => res.json()) |
| 73 | .then(data => { |
| 74 | console.log("Fetched post detail:", data); |
| 75 | const p = data.post || data; |
| 76 | const formattedPost = { |
| 77 | post_id: p.postid, |
| 78 | title: p.posttitle, |
| 79 | content: p.postcontent, |
| 80 | author_id: p.postuserid, |
| 81 | author_name: p.author?.username || '', |
| 82 | created_at: new Date(p.posttime).toLocaleString(), |
| 83 | reply_count: p.replytime, |
| 84 | view_count: p.readtime, |
| 85 | }; |
| 86 | const formattedReplies = (data.replies || []).map(r => ({ |
| 87 | reply_id: r.replyid, |
| 88 | post_id: r.postid || postId, |
| 89 | content: r.content, |
| 90 | author_id: r.authorid, |
| 91 | author_name: r.author?.username || '', |
| 92 | created_at: new Date(r.createdAt).toLocaleString(), |
| 93 | })); |
| 94 | setPost(formattedPost); |
| 95 | setReplies(formattedReplies); |
| 96 | }) |
| 97 | .catch(err => console.error(err)); |
| 98 | }; |
| 99 | |
| 100 | useEffect(() => { |
| 101 | fetchDetail(); |
| 102 | }, [postId]); |
| 103 | |
| 104 | // post a new reply to backend |
| 105 | const handleReply = () => { |
| 106 | const match = document.cookie.match('(^|;)\\s*userId=([^;]+)'); |
| 107 | const userId = match ? match[2] : null; |
| 108 | if (!userId) { |
| 109 | alert('请先登录后再回复'); |
| 110 | return; |
| 111 | } |
| 112 | fetch(`${API_BASE_URL}/api/forum-reply`, { |
| 113 | method: 'POST', |
| 114 | headers: { 'Content-Type': 'application/json' }, |
| 115 | body: JSON.stringify({ postid: postId, replycontent: newReply, replyuserid: userId }), |
| 116 | }) |
| 117 | .then(res => res.json()) |
| 118 | .then(() => { |
| 119 | setNewReply(''); |
| 120 | fetchDetail(); |
| 121 | }) |
| 122 | .catch(err => console.error(err)); |
| 123 | }; |
| 124 | |
TRM-coding | fa3ffdf | 2025-06-09 22:47:42 +0800 | [diff] [blame] | 125 | if (!post) return ( |
| 126 | <div className="emerald-home-container"> |
| 127 | <div className="emerald-content"> |
| 128 | <div style={{ |
| 129 | textAlign: 'center', |
| 130 | padding: '100px 20px', |
| 131 | color: '#2d5016', |
| 132 | fontSize: '18px', |
| 133 | background: 'rgba(255, 255, 255, 0.95)', |
| 134 | borderRadius: '25px', |
| 135 | margin: '50px auto', |
| 136 | maxWidth: '600px' |
| 137 | }}> |
| 138 | <ForumIcon style={{ fontSize: 64, marginBottom: '20px', color: '#90ee90' }} /> |
| 139 | <div>加载帖子详情中...</div> |
wht | 6a1b678 | 2025-06-06 19:14:59 +0800 | [diff] [blame] | 140 | </div> |
| 141 | </div> |
TRM-coding | fa3ffdf | 2025-06-09 22:47:42 +0800 | [diff] [blame] | 142 | </div> |
| 143 | ); |
| 144 | |
| 145 | return ( |
| 146 | <div className="emerald-home-container"> |
| 147 | {/* 文字雨背景效果 */} |
| 148 | <div className="forum-text-rain"> |
| 149 | {forumDetailTexts.map((text, index) => ( |
| 150 | <div key={index} className="text-drop" style={{ |
| 151 | left: `${(index * 3.5) % 100}%`, |
| 152 | animationDelay: `${(index * 0.8) % 10}s`, |
| 153 | animationDuration: `${8 + (index % 5)}s` |
wht | 6a1b678 | 2025-06-06 19:14:59 +0800 | [diff] [blame] | 154 | }}> |
TRM-coding | fa3ffdf | 2025-06-09 22:47:42 +0800 | [diff] [blame] | 155 | {text} |
wht | 6a1b678 | 2025-06-06 19:14:59 +0800 | [diff] [blame] | 156 | </div> |
| 157 | ))} |
| 158 | </div> |
TRM-coding | fa3ffdf | 2025-06-09 22:47:42 +0800 | [diff] [blame] | 159 | |
| 160 | {/* 浮动园林装饰元素 */} |
| 161 | <div className="floating-garden-elements"> |
| 162 | <div className="garden-element">💭</div> |
| 163 | <div className="garden-element">📖</div> |
| 164 | <div className="garden-element">💡</div> |
| 165 | <div className="garden-element">✨</div> |
| 166 | </div> |
| 167 | |
| 168 | <div className="emerald-content"> |
| 169 | {/* NeuraFlux用户栏 */} |
| 170 | <div className="emerald-user-bar"> |
| 171 | <div className="emerald-user-avatar" onClick={() => navigate('/user')}> |
| 172 | {userInfo.avatar_url ? ( |
| 173 | <img src={userInfo.avatar_url} alt="用户头像" style={{ width: 38, height: 38, borderRadius: '50%', objectFit: 'cover' }} /> |
| 174 | ) : ( |
| 175 | <AccountCircleIcon style={{ fontSize: 38, color: 'white' }} /> |
| 176 | )} |
| 177 | </div> |
| 178 | <div className="emerald-brand-section"> |
| 179 | <div className="emerald-brand-icon">⚡</div> |
| 180 | <div className="emerald-user-label">NeuraFlux</div> |
| 181 | </div> |
| 182 | <div className="emerald-user-stats"> |
| 183 | <span className="emerald-stat-item"> |
| 184 | 魔力值: <span className="emerald-stat-value">{userPT.magic}</span> |
| 185 | </span> |
| 186 | <span className="emerald-stat-item"> |
| 187 | 分享率: <span className="emerald-stat-value">{userPT.ratio}</span> |
| 188 | </span> |
| 189 | <span className="emerald-stat-item"> |
| 190 | 上传: <span className="emerald-stat-value">{userPT.upload}GB</span> |
| 191 | </span> |
| 192 | <span className="emerald-stat-item"> |
| 193 | 下载: <span className="emerald-stat-value">{userPT.download}GB</span> |
| 194 | </span> |
| 195 | </div> |
| 196 | </div> |
| 197 | |
| 198 | {/* NeuraFlux导航栏 */} |
| 199 | <nav className="emerald-nav-bar"> |
| 200 | {navItems.map((item) => ( |
| 201 | <div |
| 202 | key={item.label} |
| 203 | className={`emerald-nav-item ${item.label === "论坛" ? "active" : ""}`} |
| 204 | data-type={item.type} |
| 205 | onClick={() => navigate(item.path)} |
| 206 | > |
| 207 | {item.icon} |
| 208 | <span className="emerald-nav-label">{item.label}</span> |
| 209 | </div> |
| 210 | ))} |
| 211 | </nav> |
| 212 | |
| 213 | {/* 返回按钮 */} |
| 214 | <div className="post-detail-header"> |
| 215 | <button className="back-to-forum-btn" onClick={() => navigate('/forum')}> |
| 216 | <ArrowBackIcon style={{ marginRight: '8px', fontSize: '20px' }} /> |
| 217 | 返回论坛 |
| 218 | </button> |
| 219 | </div> |
| 220 | |
| 221 | {/* 帖子详情内容区域 */} |
| 222 | <div className="emerald-content-section"> |
| 223 | {/* 原帖内容 */} |
| 224 | <div className="post-detail-main"> |
| 225 | <div className="post-detail-header-info"> |
| 226 | <div className="post-author-section"> |
| 227 | <div className="post-author-avatar"> |
| 228 | <AccountCircleIcon style={{ fontSize: 48, color: '#2d5016' }} /> |
| 229 | </div> |
| 230 | <div className="post-author-details"> |
| 231 | <h2 className="post-author-name">{post.author_name}</h2> |
| 232 | <span className="post-publish-time">{post.created_at}</span> |
| 233 | </div> |
| 234 | </div> |
| 235 | <div className="post-stats-section"> |
| 236 | <div className="stat-badge"> |
| 237 | <VisibilityIcon style={{ fontSize: '18px', marginRight: '4px' }} /> |
| 238 | <span>{post.view_count} 浏览</span> |
| 239 | </div> |
| 240 | <div className="stat-badge"> |
| 241 | <ReplyIcon style={{ fontSize: '18px', marginRight: '4px' }} /> |
| 242 | <span>{post.reply_count} 回复</span> |
| 243 | </div> |
| 244 | </div> |
| 245 | </div> |
| 246 | |
| 247 | <div className="post-content-section"> |
| 248 | <h1 className="post-detail-title">{post.title}</h1> |
| 249 | <div className="post-detail-content"> |
| 250 | {post.content} |
| 251 | </div> |
| 252 | </div> |
| 253 | </div> |
| 254 | |
| 255 | {/* 回复列表 */} |
| 256 | <div className="replies-section"> |
| 257 | <h3 className="replies-title"> |
| 258 | <ReplyIcon style={{ marginRight: '8px', color: '#2d5016' }} /> |
| 259 | 全部回复 ({replies.length}) |
| 260 | </h3> |
| 261 | |
| 262 | {replies.length > 0 ? ( |
| 263 | <div className="replies-list"> |
| 264 | {replies.map((reply, index) => ( |
| 265 | <div key={reply.reply_id} className="reply-card"> |
| 266 | <div className="reply-index">#{index + 1}</div> |
| 267 | <div className="reply-content-wrapper"> |
| 268 | <div className="reply-author-info"> |
| 269 | <AccountCircleIcon style={{ fontSize: 24, color: '#2d5016', marginRight: '8px' }} /> |
| 270 | <span className="reply-author-name">{reply.author_name}</span> |
| 271 | <span className="reply-time">{reply.created_at}</span> |
| 272 | </div> |
| 273 | <div className="reply-content">{reply.content}</div> |
| 274 | </div> |
| 275 | </div> |
| 276 | ))} |
| 277 | </div> |
| 278 | ) : ( |
| 279 | <div className="no-replies"> |
| 280 | <ForumIcon style={{ fontSize: 48, color: '#90ee90', marginBottom: '16px' }} /> |
| 281 | <p>还没有人回复,快来抢沙发吧!</p> |
| 282 | </div> |
| 283 | )} |
| 284 | </div> |
| 285 | |
| 286 | {/* 回复输入框 */} |
| 287 | <div className="reply-input-section"> |
| 288 | <h3 className="reply-input-title">发表回复</h3> |
| 289 | <div className="reply-input-wrapper"> |
| 290 | <textarea |
| 291 | placeholder="写下你的想法和观点..." |
| 292 | value={newReply} |
| 293 | onChange={e => setNewReply(e.target.value)} |
| 294 | className="reply-textarea" |
| 295 | rows="4" |
| 296 | /> |
| 297 | <div className="reply-actions"> |
| 298 | <div className="reply-tips"> |
| 299 | 💡 支持理性讨论,拒绝恶意灌水 |
| 300 | </div> |
| 301 | <button onClick={handleReply} className="submit-reply-btn"> |
| 302 | <ReplyIcon style={{ marginRight: '6px', fontSize: '18px' }} /> |
| 303 | 发布回复 |
| 304 | </button> |
| 305 | </div> |
| 306 | </div> |
| 307 | </div> |
| 308 | </div> |
wht | 6a1b678 | 2025-06-06 19:14:59 +0800 | [diff] [blame] | 309 | </div> |
| 310 | </div> |
| 311 | ); |
| 312 | } |