"use client"; import { useCallback, useEffect, useState } from "react"; import { Plus, Pencil, Trash2, Loader2, Save, X, ExternalLink, Megaphone, } from "lucide-react"; import { apiAdminListAnnouncements, apiAdminCreateAnnouncement, apiAdminUpdateAnnouncement, apiAdminDeleteAnnouncement, type Announcement, type AnnouncementColor, type AnnouncementInput, } from "@/lib/api"; import { formatDate } from "@/lib/format"; import { toast } from "@/lib/toast"; import ConfirmDialog from "@/components/ConfirmDialog"; import { AdminEmpty, AdminIconButton, AdminPageHeader, AdminSegmented, } from "@/components/admin"; const COLORS: { value: AnnouncementColor; label: string }[] = [ { value: "blue", label: "蓝色" }, { value: "green", label: "绿色" }, { value: "orange", label: "橙色" }, { value: "red", label: "红色" }, { value: "purple", label: "紫色" }, { value: "gray", label: "灰色" }, ]; type FormState = AnnouncementInput & { id: number | null }; type PublishKey = "published" | "draft"; const EMPTY_FORM: FormState = { id: null, title: "", content: "", tag: "公告", tag_color: "blue", published: true, }; export default function AnnouncementAdmin() { const [list, setList] = useState([]); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(""); const [form, setForm] = useState(null); const [saving, setSaving] = useState(false); const [deletingId, setDeletingId] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const load = useCallback(async () => { setLoading(true); try { const res = await apiAdminListAnnouncements(); setList(res.announcements); setLoadError(""); } catch (e) { setLoadError(e instanceof Error ? e.message : "加载公告失败"); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const startCreate = () => setForm({ ...EMPTY_FORM }); const startEdit = (a: Announcement) => setForm({ id: a.id, title: a.title, content: a.content, tag: a.tag, tag_color: a.tag_color, published: a.published, }); async function save() { if (!form) return; const payload: AnnouncementInput = { title: form.title.trim(), content: form.content.trim(), tag: form.tag.trim() || "公告", tag_color: form.tag_color, published: form.published, }; if (!payload.title || !payload.content) { toast("标题和内容不能为空"); return; } setSaving(true); try { if (form.id == null) { await apiAdminCreateAnnouncement(payload); toast("公告已发布", "ok"); } else { await apiAdminUpdateAnnouncement(form.id, payload); toast("公告已更新", "ok"); } setForm(null); await load(); } catch (e) { toast(e instanceof Error ? e.message : "保存失败"); } finally { setSaving(false); } } async function remove(a: Announcement) { setDeletingId(a.id); try { await apiAdminDeleteAnnouncement(a.id); setList((l) => l.filter((x) => x.id !== a.id)); if (form?.id === a.id) setForm(null); toast("公告已删除", "ok"); } catch (e) { toast(e instanceof Error ? e.message : "删除失败"); } finally { setDeletingId(null); setDeleteTarget(null); } } return (
新建公告 ) : undefined } />
{/* 编辑/新建表单:仅在新建或编辑时展开,空闲不占位 */} {form && (

{form.id == null ? "新建公告" : "编辑公告"}

setForm({ ...form, title: e.target.value })} placeholder="请输入公告标题" />
setForm({ ...form, tag: e.target.value })} placeholder="公告" />

标签颜色

{COLORS.map((c) => (

发布状态

role="group" ariaLabel="发布状态" value={form.published ? "published" : "draft"} onChange={(key) => setForm({ ...form, published: key === "published" })} options={[ { key: "published", label: "立即发布" }, { key: "draft", label: "存为草稿" }, ]} />