import React, { useCallback, useEffect, useState } from 'react'; import { useParams, Link } from 'react-router-dom'; import * as blogApi from '../api/blog.js'; import { me } from '../api/auth.js'; import { getToken } from '../api/client.js'; import MarkdownRenderer from '../components/MarkdownRenderer.jsx'; import { showSnackbar } from '../lib/utils.js'; /** * 博客详情页(路由 /blog/:id,SPA 内走前端路由,SSR 直链由后端处理): * 文章正文(MarkdownRenderer)+ 评论列表/发表 + 编辑按钮(作者/管理员可见)。 */ export default function BlogDetail() { const { id } = useParams(); const [post, setPost] = useState(null); const [comments, setComments] = useState([]); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [commentText, setCommentText] = useState(''); const [submitting, setSubmitting] = useState(false); const load = useCallback(async () => { setLoading(true); setError(''); try { const p = await blogApi.getPost(id); setPost(p); const cs = await blogApi.listComments(id); setComments(cs || []); } catch (e) { setError(e.message || '加载失败'); } finally { setLoading(false); } }, [id]); useEffect(() => { load(); if (getToken()) { me().then(setUser).catch(() => setUser(null)); } else { setUser(null); } }, [load]); const canEdit = user && post && (user.role === 'admin' || user.id === post.author_id); const submitComment = async () => { const content = commentText.trim(); if (!content) { showSnackbar('评论不能为空'); return; } setSubmitting(true); try { await blogApi.createComment(id, content); showSnackbar('评论已发表'); setCommentText(''); await load(); } catch (e) { showSnackbar(e.message); } setSubmitting(false); }; if (loading) { return
; } if (error || !post) { return (
⚠️

{error || '文章不存在'}

返回博客
); } return (
arrow_back 返回列表

{post.title}

{post.author_name || '管理员'} · {post.created_at} {canEdit && ( 编辑 )}

评论 ({comments.length})

{comments.length === 0 ? (

暂无评论

) : ( comments.map((c) => (
{c.author_name || '游客'} · {c.created_at}
{c.content}
)) )} {user ? (