import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import BlogSidebar from '../components/BlogSidebar.jsx'; import { listPosts, searchPosts, getTags } from '../api/blog.js'; import { getPublicSettings } from '../api/settings.js'; /** 摘要:优先取 excerpt,否则从内容中剥离 markdown 符号截取(照搬 blog.js) */ function excerptOf(p) { if (p.excerpt) return p.excerpt; return (p.content || '').replace(/[#*`\[\]()>|~_]/g, '').slice(0, 200); } /** 博客列表页(迁移自 blog.html + blog.js loadPosts):瀑布流卡片,点击进详情; * 顶部搜索框(/api/blog/search)+ 标签云(/api/blog/tags,点击进 /tag/:name)+ 归档入口 */ export default function Blog() { const [settings, setSettings] = useState({}); const [posts, setPosts] = useState(null); // null=加载中 const [error, setError] = useState(''); // 搜索状态 const [query, setQuery] = useState(''); const [results, setResults] = useState(null); // null=未搜索 const [searching, setSearching] = useState(false); // 标签云 const [tags, setTags] = useState([]); useEffect(() => { getPublicSettings().then(setSettings).catch(() => {}); listPosts() .then((ps) => setPosts(ps || [])) .catch((e) => setError(e.message || '加载失败')); getTags() .then((ts) => setTags(ts || [])) .catch(() => setTags([])); }, []); const doSearch = async (e) => { e && e.preventDefault(); const q = query.trim(); if (!q) { setResults(null); return; } setSearching(true); try { const rs = await searchPosts(q); setResults(rs || []); } catch (err) { setResults([]); setError(err.message || '搜索失败'); } setSearching(false); }; const clearSearch = () => { setQuery(''); setResults(null); setError(''); }; // 搜索结果以列表呈现(区别于瀑布流卡片) const renderResults = () => (
搜索结果({results.length})
{results.length === 0 ? (
🔍

没有找到相关内容

) : (
{results.map((p) => (
{p.title}
{p.excerpt &&
{p.excerpt}
}
{p.author_name || '管理员'} · {p.created_at} {p.tags && {p.tags}}
))}
)}
); return (

博客

{/* 工具条:搜索框 + 归档 */}
search setQuery(e.target.value)} placeholder="搜索文章标题 / 内容..." aria-label="搜索文章" /> {query && ( )}
archive 归档
{/* 标签云 */} {tags.length > 0 && (
local_offer 标签
{tags.map((t) => ( {t.name} {t.count} ))}
)} {/* 搜索结果 / 瀑布流 */} {results !== null ? ( renderResults() ) : ( <> {!posts && !error && (
)} {error && (
⚠️

{error}

)} {posts && posts.length === 0 && (
📖

暂无文章

)} {posts && posts.length > 0 && (
{posts.map((p) => (
{p.title}
{excerptOf(p)}
{p.author_name || '管理员'} · {p.created_at}
))}
)} )}
); } /** 标签字号随文章数渐变(经典标签云效果):1 篇 ~12px,8 篇及以上 ~18px */ function tagSize(count) { const c = Number(count) || 1; const size = 12 + Math.min(c, 8) * 0.75; return size.toFixed(1) + 'px'; }