"use client"; import { useState, useEffect, useCallback } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { ArrowLeft, Search, Edit, Trash2, Plus, FileText, Eye, FileText as PostIcon, Rss, Link2, GripVertical, X, CheckSquare, Square, } from "lucide-react"; import Pagination from "@/components/Pagination"; interface PostItem { slug: string; title: string; date: string; draft: boolean; categories: string[]; author: string; } export default function HomePage() { const router = useRouter(); const [posts, setPosts] = useState([]); const [total, setTotal] = useState(0); const [totalPages, setTotalPages] = useState(0); const [page, setPage] = useState(1); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(true); const [totalCount, setTotalCount] = useState(0); // Stats const [stats, setStats] = useState<{ posts: number; feeds: number; links: number } | null>(null); // Batch const [selectMode, setSelectMode] = useState(false); const [selected, setSelected] = useState>(new Set()); const [batchAction, setBatchAction] = useState<"categories" | "tags" | "draft" | null>(null); const [batchValue, setBatchValue] = useState(""); const [batching, setBatching] = useState(false); useEffect(() => { fetch("/api/stats").then(r => r.json()).then(setStats).catch(() => {}); }, []); const fetchPosts = useCallback(async () => { setLoading(true); try { const p = Math.max(1, Math.floor(page)); const params = new URLSearchParams({ page: p.toString(), search: search || "", perPage: "20" }); const res = await fetch(`/api/posts?${params}`); if (!res.ok) throw new Error("加载失败"); const data = await res.json(); setPosts(data.posts || []); setTotal(data.total || 0); setTotalPages(Math.max(1, data.totalPages || 1)); setTotalCount(data.totalCount || 0); } catch { setPosts([]); setTotal(0); setTotalPages(1); } finally { setLoading(false); } }, [page, search]); useEffect(() => { fetchPosts(); }, [fetchPosts]); const handleDelete = async (slug: string, title: string) => { if (!confirm(`确定删除「${title}」吗?`)) return; try { const res = await fetch(`/api/posts/${slug}`, { method: "DELETE" }); const data = await res.json(); if (!res.ok) { alert("删除失败:" + (data.error || "未知错误")); return; } fetchPosts(); } catch (e) { alert("删除失败:" + (e instanceof Error ? e.message : String(e))); } }; const handlePreview = async (slug: string) => { await fetch("/api/hugo"); window.open(`/preview/${slug}.html`, "_blank"); }; const toggleSelect = (slug: string) => { setSelected((prev) => { const next = new Set(prev); next.has(slug) ? next.delete(slug) : next.add(slug); return next; }); }; const toggleAll = () => { if (selected.size === posts.length) setSelected(new Set()); else setSelected(new Set(posts.map((p) => p.slug))); }; const handleBatchApply = async () => { if (selected.size === 0 || !batchAction) return; setBatching(true); try { const body: Record = { slugs: Array.from(selected), action: batchAction, }; if (batchAction === "draft") { body.draft = batchValue === "true"; } else if (batchAction === "categories") { body.categories = batchValue.split("、").map((s) => s.trim()).filter(Boolean); } else if (batchAction === "tags") { body.tags = batchValue.split("、").map((s) => s.trim()).filter(Boolean); } const res = await fetch("/api/posts/batch", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) throw new Error("批量操作失败"); setSelected(new Set()); setBatchAction(null); setBatchValue(""); setSelectMode(false); fetchPosts(); } catch {} finally { setBatching(false); } }; const formatDate = (d: string) => { if (!d) return "-"; return new Date(d).toLocaleDateString("zh-CN"); }; return (
{/* Dashboard stats: hidden on mobile, full cards on >=sm */} {stats && (
文章总数
{stats.posts}
订阅源
{stats.feeds}
友链
{stats.links}
)} {/* Header */}

文章管理

共 {totalCount} 篇文章

{/* Batch toggle */} 新建
{/* Batch action bar */} {selectMode && selected.size > 0 && (
已选 {selected.size} 篇 {batchAction === "draft" ? ( ) : batchAction ? ( setBatchValue(e.target.value)} className="flex-1 bg-card border border-line rounded-md px-2 py-1.5 text-[12px] focus:outline-none focus:border-accent/30" placeholder={batchAction === "tags" ? "用顿号分隔" : "用顿号分隔"} /> ) : null} {batchAction && batchValue && ( )}
)} {/* Search + (mobile) quick actions */}
{ setSearch(e.target.value); setPage(1); }} placeholder="搜索标题、分类..." className="w-full pl-9 pr-4 py-2.5 bg-card border border-line rounded-md text-[13px] text-text placeholder:text-muted/40 focus:outline-none focus:border-accent/30 transition-colors" />
{/* Article list */} {loading ? (
加载中...
) : posts.length === 0 ? (

{search ? "没有匹配的文章" : "暂无文章"}

) : (
{/* Select all checkbox */} {selectMode && (
)} {posts.map((post, i) => (
{/* Select checkbox */} {selectMode && ( )}
{post.draft ? ( 草稿 ) : ( 已发布 )}
{formatDate(post.date)} {post.categories.length > 0 && <>|{post.categories.join("、")}}
{!selectMode && (
)}
))}
)} {total > 0 &&

第 {page} 页 / 共 {totalPages} 页({total} 篇)

}
); }