polish(admin): 抽出后台控件包并统一各页布局与交互
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Plus,
|
||||
Pencil,
|
||||
@@ -24,6 +23,12 @@ import {
|
||||
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: "蓝色" },
|
||||
@@ -35,6 +40,7 @@ const COLORS: { value: AnnouncementColor; label: string }[] = [
|
||||
];
|
||||
|
||||
type FormState = AnnouncementInput & { id: number | null };
|
||||
type PublishKey = "published" | "draft";
|
||||
|
||||
const EMPTY_FORM: FormState = {
|
||||
id: null,
|
||||
@@ -130,26 +136,18 @@ export default function AnnouncementAdmin() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* 页头 */}
|
||||
<div className="flex items-center justify-between gap-3 flex-wrap mb-5">
|
||||
<div>
|
||||
<h1 className="text-[22px] sm:text-[26px] font-extrabold tracking-tight flex items-center gap-2.5" style={{ color: "var(--ink)" }}>
|
||||
<span
|
||||
className="w-9 h-9 rounded-xl flex items-center justify-center"
|
||||
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
|
||||
>
|
||||
<Megaphone size={18} />
|
||||
</span>
|
||||
站点公告管理
|
||||
</h1>
|
||||
<p className="meta mt-1.5 text-[12.5px]">发布、编辑与下线右栏站点公告,支持自定义标签与标签颜色</p>
|
||||
</div>
|
||||
{form?.id == null && (
|
||||
<button onClick={startCreate} className="btn btn-primary">
|
||||
<Plus size={15} /> 新建公告
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<AdminPageHeader
|
||||
icon={Megaphone}
|
||||
title="站点公告管理"
|
||||
description="发布、编辑与下线右栏站点公告,支持自定义标签与标签颜色"
|
||||
actions={
|
||||
form?.id == null ? (
|
||||
<button type="button" onClick={startCreate} className="btn btn-primary">
|
||||
<Plus size={15} /> 新建公告
|
||||
</button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 items-start">
|
||||
{/* 编辑/新建表单:仅在新建或编辑时展开,空闲不占位 */}
|
||||
@@ -204,25 +202,17 @@ export default function AnnouncementAdmin() {
|
||||
</div>
|
||||
|
||||
<p className="field-label">发布状态</p>
|
||||
<div className="flex items-center gap-2 mb-4" role="group" aria-label="发布状态">
|
||||
<button
|
||||
type="button"
|
||||
className="j13-tab"
|
||||
aria-pressed={form.published}
|
||||
onClick={() => setForm({ ...form, published: true })}
|
||||
style={form.published ? { background: "var(--accent-soft)", borderColor: "color-mix(in srgb, var(--accent) 38%, var(--line))", color: "var(--accent)" } : undefined}
|
||||
>
|
||||
立即发布
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="j13-tab"
|
||||
aria-pressed={!form.published}
|
||||
onClick={() => setForm({ ...form, published: false })}
|
||||
style={!form.published ? { background: "var(--gold-soft)", borderColor: "color-mix(in srgb, var(--gold) 38%, var(--line))", color: "var(--gold)" } : undefined}
|
||||
>
|
||||
存为草稿
|
||||
</button>
|
||||
<div className="mb-4">
|
||||
<AdminSegmented<PublishKey>
|
||||
role="group"
|
||||
ariaLabel="发布状态"
|
||||
value={form.published ? "published" : "draft"}
|
||||
onChange={(key) => setForm({ ...form, published: key === "published" })}
|
||||
options={[
|
||||
{ key: "published", label: "立即发布" },
|
||||
{ key: "draft", label: "存为草稿" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="field-label" htmlFor="anno-content">
|
||||
@@ -237,7 +227,7 @@ export default function AnnouncementAdmin() {
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-2.5 mt-4">
|
||||
<button onClick={save} disabled={saving} className="btn btn-primary flex-1 justify-center">
|
||||
<button type="button" onClick={save} disabled={saving} className="btn btn-primary flex-1 justify-center">
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader2 size={15} className="animate-spin" /> 保存中…
|
||||
@@ -249,6 +239,7 @@ export default function AnnouncementAdmin() {
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setForm(null)}
|
||||
disabled={saving}
|
||||
className="btn btn-line"
|
||||
@@ -279,7 +270,11 @@ export default function AnnouncementAdmin() {
|
||||
) : loadError ? (
|
||||
<p className="alert-error m-4">{loadError}</p>
|
||||
) : list.length === 0 ? (
|
||||
<p className="meta text-center py-16 text-[13px]">还没有公告,点击「新建公告」发布第一条吧</p>
|
||||
<AdminEmpty
|
||||
icon={<Megaphone size={22} />}
|
||||
title="还没有公告"
|
||||
description="点击「新建公告」发布第一条吧"
|
||||
/>
|
||||
) : (
|
||||
list.map((a) => (
|
||||
<div
|
||||
@@ -310,38 +305,26 @@ export default function AnnouncementAdmin() {
|
||||
</div>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
{a.published && (
|
||||
<Link
|
||||
<AdminIconButton
|
||||
href={`/announcement/${a.id}`}
|
||||
target="_blank"
|
||||
className="w-8 h-8 inline-flex items-center justify-center rounded-full transition-colors hover:bg-[var(--accent-soft)] focus-visible:bg-[var(--accent-soft)]"
|
||||
style={{ color: "var(--ink-3)" }}
|
||||
data-tip="查看"
|
||||
aria-label="查看公告"
|
||||
tip="查看"
|
||||
ariaLabel="查看公告"
|
||||
>
|
||||
<ExternalLink size={15} />
|
||||
</Link>
|
||||
</AdminIconButton>
|
||||
)}
|
||||
<button
|
||||
onClick={() => startEdit(a)}
|
||||
className="w-8 h-8 inline-flex items-center justify-center rounded-full transition-colors hover:bg-[var(--accent-soft)] focus-visible:bg-[var(--accent-soft)]"
|
||||
style={{ color: "var(--ink-3)" }}
|
||||
data-tip="编辑"
|
||||
aria-label="编辑公告"
|
||||
>
|
||||
<AdminIconButton tip="编辑" ariaLabel="编辑公告" onClick={() => startEdit(a)}>
|
||||
<Pencil size={15} />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeleteTarget(a)}
|
||||
</AdminIconButton>
|
||||
<AdminIconButton
|
||||
tip="删除"
|
||||
ariaLabel="删除公告"
|
||||
tone="danger"
|
||||
disabled={deletingId === a.id}
|
||||
className="w-8 h-8 inline-flex items-center justify-center rounded-full transition-colors hover:bg-[var(--danger-soft)] disabled:opacity-50"
|
||||
style={{ color: "var(--ink-3)" }}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.color = "var(--danger)")}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.color = "var(--ink-3)")}
|
||||
data-tip="删除"
|
||||
aria-label="删除公告"
|
||||
onClick={() => setDeleteTarget(a)}
|
||||
>
|
||||
{deletingId === a.id ? <Loader2 size={15} className="animate-spin" /> : <Trash2 size={15} />}
|
||||
</button>
|
||||
</AdminIconButton>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user