"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { Ellipsis, ExternalLink, FileText, Plus } from "lucide-react"; import { apiAdminDeleteSitePage, apiAdminPurgeSitePage, type SitePage } from "@/lib/api"; import { formatDateTime, formatRelative } from "@/lib/format"; import { sitePagePublicPath } from "@/lib/sitePagePath"; import { useUrlStyle } from "@/components/SiteBrandProvider"; import { toast } from "@/lib/toast"; import ConfirmDialog from "@/components/ConfirmDialog"; import { AdminEmpty, AdminPageHeader, AdminSegmented, AdminStatusChip, } from "@/components/admin"; type StatusFilter = "all" | "published" | "draft"; function readStatus(raw: string | null): StatusFilter { if (raw === "published" || raw === "draft") return raw; return "all"; } function listHref(status: StatusFilter): string { return status === "all" ? "/admin/pages" : `/admin/pages?status=${status}`; } function newHref(status: StatusFilter): string { return status === "all" ? "/admin/pages/new" : `/admin/pages/new?status=${status}`; } function editHref(id: number, status: StatusFilter): string { const base = `/admin/pages/${id}/edit`; return status === "all" ? base : `${base}?status=${status}`; } function placeLabel(p: SitePage): string { if (!p.show_in_sidebar) return "不在侧栏"; return p.published ? "首页侧栏" : "首页侧栏(发布后)"; } export default function PagesAdmin({ initial, initError, }: { initial: SitePage[]; initError: string; }) { const router = useRouter(); const searchParams = useSearchParams(); const urlStyle = useUrlStyle(); const statusFilter = readStatus(searchParams.get("status")); const [list, setList] = useState(initial); const [loadError] = useState(initError); const [deletingId, setDeletingId] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [menuId, setMenuId] = useState(null); const [menuUp, setMenuUp] = useState(false); const menuRef = useRef(null); const publishedCount = useMemo( () => list.filter((p) => p.published && !p.deleted).length, [list] ); const draftCount = useMemo( () => list.filter((p) => !p.published && !p.deleted).length, [list] ); const filtered = useMemo(() => { if (statusFilter === "published") return list.filter((p) => p.published); if (statusFilter === "draft") return list.filter((p) => !p.published); return list; }, [list, statusFilter]); useEffect(() => { if (menuId == null) return; const onPointer = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) setMenuId(null); }; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setMenuId(null); }; document.addEventListener("mousedown", onPointer); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onPointer); document.removeEventListener("keydown", onKey); }; }, [menuId]); function setStatus(next: StatusFilter) { router.replace(listHref(next), { scroll: false }); } async function remove(p: SitePage) { setDeletingId(p.id); try { if (p.deleted) { // 已软删的页面:彻底删除(不可恢复,释放 slug) await apiAdminPurgeSitePage(p.id); setList((l) => l.filter((x) => x.id !== p.id)); toast("页面已彻底删除,地址可重新使用", "ok"); } else { await apiAdminDeleteSitePage(p.id); setList((l) => l.map((x) => (x.id === p.id ? { ...x, deleted: true } : x))); toast("页面已删除,可在列表中彻底删除以释放地址", "ok"); } } catch (e) { toast(e instanceof Error ? e.message : "删除失败"); } finally { setDeletingId(null); setDeleteTarget(null); setMenuId(null); } } return (
新建单页 } />
ariaLabel="单页状态筛选" size="sm" value={statusFilter} onChange={setStatus} options={[ { key: "all", label: "全部", count: list.length }, { key: "published", label: "已发布", count: publishedCount }, { key: "draft", label: "草稿", count: draftCount }, ]} />
单页列表 共 {filtered.length} 条
{loadError ? (

{loadError}

) : filtered.length === 0 ? ( } title={list.length === 0 ? "还没有单页" : "该状态下暂无单页"} description={list.length === 0 ? "创建关于本站等内容页" : "试试切换筛选条件"} /> ) : (
{filtered.map((p) => { const updated = p.updated_at || ""; const path = sitePagePublicPath(p.slug, urlStyle); return ( ); })}
单页列表
页面 状态 展示位置 排序 最近更新 操作
{p.deleted ? ( {p.title} ) : ( {p.title} )}

{path}

{p.excerpt ?

{p.excerpt}

: null}
{p.deleted ? ( 已删除 ) : ( {p.published ? "已发布" : "草稿"} )} {placeLabel(p)} {p.sort_order} {updated ? ( ) : ( "—" )}
{!p.deleted ? ( 编辑 ) : null} {p.published && !p.deleted ? ( 查看 ) : null}
{menuId === p.id ? (
) : null}
)} {list.length === 0 && !loadError ? (
新建第一页
) : null} {list.length >= 100 ? (

后台一次最多列出 100 条。

) : null}
deletingId == null && setDeleteTarget(null)} onConfirm={() => { if (deleteTarget) void remove(deleteTarget); }} />
); }