"use client"; import { useMemo, useState } from "react"; import { Check, Link2, Loader2, Pencil, Plus, RotateCcw, Trash2, X, } from "lucide-react"; import { apiAdminCreateFriendLink, apiAdminDeleteFriendLink, apiAdminListFriendLinks, apiAdminSetFriendLinkStatus, apiAdminUpdateFriendLink, type FriendLink, type FriendLinkAdminInput, type FriendLinkStatus, } from "@/lib/api"; import { AdminPageHeader, AdminSettingsPage, AdminEmpty } from "@/components/admin"; import { densitySrcSet, isOwnImage, pickVariantWidth, variantURL } from "@/lib/responsiveImage"; interface Props { initialLinks: FriendLink[]; initError: string; } const FIELD_CLASS = "w-full rounded-lg px-3 py-2 text-[14px] outline-none transition-colors"; const FIELD_STYLE = { background: "var(--panel-2)", color: "var(--ink)", border: "1px solid var(--line)", }; const TABS: { key: FriendLinkStatus; label: string }[] = [ { key: "pending", label: "待审批" }, { key: "approved", label: "已通过" }, { key: "rejected", label: "已拒绝" }, ]; interface FormState { name: string; url: string; description: string; logo_url: string; category: string; reciprocal_url: string; sort_order: number; } const EMPTY_FORM: FormState = { name: "", url: "", description: "", logo_url: "", category: "", reciprocal_url: "", sort_order: 0, }; export default function FriendLinksAdmin({ initialLinks, initError }: Props) { const [links, setLinks] = useState(initialLinks); const [tab, setTab] = useState("pending"); const [editorOpen, setEditorOpen] = useState(false); const [editingId, setEditingId] = useState(null); const [form, setForm] = useState(EMPTY_FORM); const [saving, setSaving] = useState(false); const [busyId, setBusyId] = useState(null); const [error, setError] = useState(initError); const counts = useMemo(() => { const c = { pending: 0, approved: 0, rejected: 0 } as Record; for (const l of links) c[l.status] = (c[l.status] || 0) + 1; return c; }, [links]); const filtered = useMemo( () => links.filter((l) => l.status === tab).sort((a, b) => a.sort_order - b.sort_order || a.id - b.id), [links, tab] ); const refresh = async () => { try { const res = await apiAdminListFriendLinks(); setLinks(res.links ?? []); setError(""); } catch (e) { setError(e instanceof Error ? e.message : "加载失败"); } }; const openCreate = () => { setEditingId(null); setForm(EMPTY_FORM); setEditorOpen(true); }; const openEdit = (l: FriendLink) => { setEditingId(l.id); setForm({ name: l.name, url: l.url, description: l.description || "", logo_url: l.logo_url || "", category: l.category || "", reciprocal_url: l.reciprocal_url || "", sort_order: l.sort_order, }); setEditorOpen(true); }; const closeEditor = () => { setEditorOpen(false); setEditingId(null); setForm(EMPTY_FORM); }; const set = (k: keyof FormState, v: string | number) => setForm((f) => ({ ...f, [k]: v })); const submit = async (e: React.FormEvent) => { e.preventDefault(); if (saving) return; setSaving(true); setError(""); try { const body: FriendLinkAdminInput = { name: form.name.trim(), url: form.url.trim(), description: form.description.trim() || undefined, logo_url: form.logo_url.trim() || undefined, category: form.category.trim() || undefined, reciprocal_url: form.reciprocal_url.trim() || undefined, sort_order: form.sort_order, }; if (editingId == null) { await apiAdminCreateFriendLink(body); } else { await apiAdminUpdateFriendLink(editingId, body); } closeEditor(); await refresh(); } catch (err) { setError(err instanceof Error ? err.message : "保存失败"); } finally { setSaving(false); } }; const setStatus = async (id: number, status: FriendLinkStatus) => { let rejectReason = ""; if (status === "rejected") { const r = window.prompt("请输入拒绝理由(可选)", ""); if (r === null) return; rejectReason = r; } setBusyId(id); setError(""); try { await apiAdminSetFriendLinkStatus(id, status, rejectReason || undefined); await refresh(); } catch (e) { setError(e instanceof Error ? e.message : "操作失败"); } finally { setBusyId(null); } }; const remove = async (id: number) => { if (!window.confirm("确定删除该友情链接?此操作不可撤销。")) return; setBusyId(id); setError(""); try { await apiAdminDeleteFriendLink(id); await refresh(); } catch (e) { setError(e instanceof Error ? e.message : "删除失败"); } finally { setBusyId(null); } }; return ( 新建 } /> {error ?

{error}

: null}
{TABS.map((t) => { const active = tab === t.key; const n = counts[t.key] || 0; return ( ); })}
{filtered.length === 0 ? ( } title={tab === "pending" ? "暂无待审批申请" : tab === "approved" ? "暂无已收录友链" : "暂无被拒绝的申请"} description={tab === "pending" ? "用户提交的申请会出现在这里" : undefined} /> ) : (
    {filtered.map((l) => (
  • {l.logo_url ? ( // 36px 位:本站图走 48/96 变体;外链保持原样 // eslint-disable-next-line @next/next/no-img-element ) : ( )}
    {l.name} {l.category ? ( {l.category} ) : null} #{l.id} · 顺序 {l.sort_order}
    {l.description ? (

    {l.description}

    ) : null} {l.reciprocal_url ? (

    回链: {l.reciprocal_url}

    ) : null} {l.status === "rejected" && l.reject_reason ? (

    拒绝理由:{l.reject_reason}

    ) : null}
    {busyId === l.id ? ( ) : null} {l.status === "pending" ? ( <> ) : null} {l.status === "rejected" ? ( ) : null} {l.status === "approved" ? ( <> ) : null} {l.status === "pending" ? ( ) : null}
  • ))}
)}
{editorOpen ? (
e.stopPropagation()} >

{editingId == null ? "新建友情链接" : "编辑友情链接"}

) : null}
); }