import { useState, useEffect } from 'react'; import type { FeedDetail as FeedDetailType, Comment } from '@/api/types'; import { getFeedDetail, getFeedComments, toggleLike, toggleFavorite, postComment, replyComment } from '@/api/endpoints'; import { Badge } from '@/components/ui/Badge'; import { Spinner } from '@/components/ui/Spinner'; import { Button } from '@/components/ui/Button'; import { Textarea } from '@/components/ui/Textarea'; import { CommentTree } from './CommentTree'; import { formatNumber, formatTime } from '@/lib/formatters'; import { useToast } from '@/context/ToastContext'; interface Props { feedId: string; xsecToken: string; onClose: () => void; onUserClick?: (userId: string, xsecToken: string) => void; } export function FeedDetail({ feedId, xsecToken, onClose, onUserClick }: Props) { const { toast } = useToast(); const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [currentImage, setCurrentImage] = useState(0); const [comments, setComments] = useState([]); const [commentsLoading, setCommentsLoading] = useState(false); const [commentsHasMore, setCommentsHasMore] = useState(false); const [commentsTotalCount, setCommentsTotalCount] = useState(0); const [commentsMaxCount, setCommentsMaxCount] = useState(20); const [liked, setLiked] = useState(false); const [favorited, setFavorited] = useState(false); const [actionLoading, setActionLoading] = useState(null); const [commentText, setCommentText] = useState(''); const [replyTarget, setReplyTarget] = useState<{ commentId: string; userId: string; nickname: string } | null>(null); const [replyText, setReplyText] = useState(''); useEffect(() => { setLoading(true); setError(null); setComments([]); void getFeedDetail(feedId, xsecToken) .then((res) => { if (res.success && res.data) { setDetail(res.data); setLiked(res.data.isLiked); setFavorited(res.data.isFavorited); // Use comments from __INITIAL_STATE__ (first page, ~10-20). if (res.data.comments.length > 0) { setComments(res.data.comments); setCommentsTotalCount(res.data.commentCount); setCommentsHasMore(res.data.commentCount > res.data.comments.length); } } else { setError(res.error?.message || 'Failed to load detail'); } }) .catch((err) => setError(err instanceof Error ? err.message : 'Error')) .finally(() => setLoading(false)); }, [feedId, xsecToken]); const handleLoadComments = async (maxCount = commentsMaxCount) => { setCommentsLoading(true); try { const res = await getFeedComments(feedId, xsecToken, 'default', maxCount); if (res.success && res.data) { setComments(res.data.comments); setCommentsHasMore(res.data.hasMore); setCommentsTotalCount(res.data.totalCount); } } catch { toast('error', '加载评论失败'); } finally { setCommentsLoading(false); } }; const handleLoadMore = () => { const nextCount = commentsMaxCount * 2; setCommentsMaxCount(nextCount); void handleLoadComments(nextCount); }; const handleToggleLike = async () => { setActionLoading('like'); try { const res = await toggleLike(feedId, xsecToken); if (res.success && res.data) { setLiked(res.data.liked); toast('success', res.data.liked ? '已点赞' : '已取消点赞'); } } catch (err) { toast('error', err instanceof Error ? err.message : '操作失败'); } finally { setActionLoading(null); } }; const handleToggleFavorite = async () => { setActionLoading('favorite'); try { const res = await toggleFavorite(feedId, xsecToken); if (res.success && res.data) { setFavorited(res.data.favorited); toast('success', res.data.favorited ? '已收藏' : '已取消收藏'); } } catch (err) { toast('error', err instanceof Error ? err.message : '操作失败'); } finally { setActionLoading(null); } }; const handleComment = async () => { if (!commentText.trim()) { toast('warning', '评论内容不能为空'); return; } setActionLoading('comment'); try { await postComment(feedId, xsecToken, commentText); toast('success', '评论已发布'); setCommentText(''); } catch (err) { toast('error', err instanceof Error ? err.message : '操作失败'); } finally { setActionLoading(null); } }; const handleReply = async () => { if (!replyText.trim()) { toast('warning', '回复内容不能为空'); return; } setActionLoading('reply'); try { await replyComment({ feed_id: feedId, xsec_token: xsecToken, content: replyText, comment_id: replyTarget?.commentId, user_id: replyTarget?.userId, }); toast('success', '回复已发布'); setReplyText(''); setReplyTarget(null); } catch (err) { toast('error', err instanceof Error ? err.message : '操作失败'); } finally { setActionLoading(null); } }; return (

笔记详情

{loading && (
)} {error && (
{error}
)} {detail && (
{/* Images */} {detail.images.length > 0 && (
{detail.images.length > 1 && (
{detail.images.map((img, i) => ( ))}
)}
)} {/* Video */} {detail.videoUrl && (
视频笔记

{detail.videoUrl}

)} {/* Title & Content */}

{detail.title}

{detail.description}

{/* Tags */} {detail.tags.length > 0 && (
{detail.tags.map((tag) => ( #{tag} ))}
)} {/* Stats */}
{[ { label: '点赞', value: detail.likeCount }, { label: '收藏', value: detail.collectCount }, { label: '评论', value: detail.commentCount }, ].map((s) => (

{formatNumber(s.value)}

{s.label}

))}
{/* Interaction buttons */}
{/* Author */}
onUserClick?.(detail.user.id, detail.xsecToken)} > {detail.user.avatar && ( )}

{detail.user.nickname}

{[detail.ipLocation, formatTime(detail.createTime)].filter(Boolean).join(' · ') || '暂无信息'}

{/* IDs for interaction */}

Feed ID: {detail.id}

xsec_token: {detail.xsecToken}

User ID: {detail.user.id}

{/* Comment input */}
{replyTarget && (
回复 @{replyTarget.nickname}
)}