暂无同步到的公开项目
diff --git a/frontend/src/pages/admin/AdminPagesPage.tsx b/frontend/src/pages/admin/AdminPagesPage.tsx
index d922007..d7b31bc 100644
--- a/frontend/src/pages/admin/AdminPagesPage.tsx
+++ b/frontend/src/pages/admin/AdminPagesPage.tsx
@@ -1,14 +1,21 @@
import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import { FileText, Plus, Pencil, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
+import { Badge } from '@/components/ui/badge';
import { Switch } from '@/components/ui/switch';
import { Spinner } from '@/components/ui/spinner';
import {
- Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle,
-} from '@/components/ui/dialog';
-import ArticleEditor from '../../components/ArticleEditor';
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from '@/components/ui/alert-dialog';
import { notify } from '@/lib/notify';
import { api } from '../../api/client';
import { useAdminGuard } from '../../layouts/AdminLayout';
@@ -16,37 +23,17 @@ import type { SitePage } from '../../api/types';
import { invalidateSitePagesCache } from '../../hooks/useSitePages';
import AdminSortableList, { SortableDragHandle, SortableMoveButtons } from '../../components/admin/AdminSortableList';
import { persistSortOrderChanges, shouldShowSortableMoveButtons } from '../../utils/sortOrder';
+import { formatTime } from '../../utils/content';
import { cn } from '@/lib/utils';
-const EMPTY: Partial = {
- title: '',
- slug: '',
- content: '',
- published: false,
- sort_order: 0,
- show_in_footer: true,
- show_in_nav: false,
-};
-
-function slugify(title: string): string {
- const s = title.trim().toLowerCase()
- .replace(/\s+/g, '-')
- .replace(/[^a-z0-9-]/g, '')
- .replace(/-+/g, '-')
- .replace(/^-|-$/g, '');
- return s.slice(0, 64);
-}
-
-/** 后台:自定义单页管理 */
+/** 后台:自定义单页列表 */
export default function AdminPagesPage() {
+ const nav = useNavigate();
const { ready } = useAdminGuard();
const [rows, setRows] = useState([]);
const [loading, setLoading] = useState(true);
- const [dialogOpen, setDialogOpen] = useState(false);
- const [form, setForm] = useState>({ ...EMPTY });
- const [editingId, setEditingId] = useState(null);
- const [saving, setSaving] = useState(false);
const [reordering, setReordering] = useState(false);
+ const [togglingId, setTogglingId] = useState(null);
const load = () => {
setLoading(true);
@@ -58,40 +45,7 @@ export default function AdminPagesPage() {
useEffect(() => { if (ready) load(); }, [ready]);
- const openCreate = () => {
- setEditingId(null);
- setForm({ ...EMPTY });
- setDialogOpen(true);
- };
-
- const openEdit = (p: SitePage) => {
- setEditingId(p.id);
- setForm({ ...p });
- setDialogOpen(true);
- };
-
- const save = async () => {
- setSaving(true);
- try {
- if (editingId) {
- await api.adminUpdatePage(editingId, form);
- notify.success('单页已更新');
- } else {
- await api.adminCreatePage(form);
- notify.success('单页已创建');
- }
- invalidateSitePagesCache();
- setDialogOpen(false);
- load();
- } catch (e: unknown) {
- notify.error(e instanceof Error ? e.message : '保存失败');
- } finally {
- setSaving(false);
- }
- };
-
const remove = async (id: number) => {
- if (!window.confirm('确定删除该单页?')) return;
try {
await api.adminDeletePage(id);
invalidateSitePagesCache();
@@ -102,12 +56,36 @@ export default function AdminPagesPage() {
}
};
+ const togglePublished = async (page: SitePage, next: boolean) => {
+ const prev = page.published;
+ setTogglingId(page.id);
+ setRows(list => list.map(r => (r.id === page.id ? { ...r, published: next } : r)));
+ try {
+ await api.adminSetPagePublished(page.id, next);
+ invalidateSitePagesCache();
+ notify.success(next ? '已发布' : '已取消发布');
+ } catch (e: unknown) {
+ setRows(list => list.map(r => (r.id === page.id ? { ...r, published: prev } : r)));
+ notify.error(e instanceof Error ? e.message : '操作失败');
+ } finally {
+ setTogglingId(null);
+ }
+ };
+
const handlePageReorder = async (reordered: SitePage[]) => {
const before = [...rows];
setReordering(true);
try {
const after = await persistSortOrderChanges(before, reordered, page =>
- api.adminUpdatePage(page.id, { sort_order: page.sort_order }),
+ api.adminUpdatePage(page.id, {
+ title: page.title,
+ slug: page.slug,
+ content: page.content,
+ published: page.published,
+ sort_order: page.sort_order,
+ show_in_footer: page.show_in_footer,
+ show_in_nav: page.show_in_nav,
+ }),
);
setRows(after);
invalidateSitePagesCache();
@@ -121,17 +99,18 @@ export default function AdminPagesPage() {
};
const showMoveButtons = shouldShowSortableMoveButtons(rows.length);
+ const busy = reordering || togglingId != null;
if (!ready) return null;
return (
-
+
-
+
{loading ? : (
@@ -145,12 +124,13 @@ export default function AdminPagesPage() {
权重 |
发布 |
展示 |
+ 更新时间 |
操作 |
{rows.length === 0 ? (
- | 暂无单页 |
+ | 暂无单页 |
) : (
}
- {p.title} |
+
+
+ |
/page/{p.slug} |
{p.sort_order} |
- {p.published ? '是' : '否'} |
- {[p.show_in_footer && '页脚', p.show_in_nav && '导航'].filter(Boolean).join('、') || '—'} |
+
+
+ |
+
+
+ {p.show_in_footer && 页脚}
+ {p.show_in_nav && 导航}
+ {!p.show_in_footer && !p.show_in_nav && '—'}
+
+ |
+ {p.updated_at ? formatTime(p.updated_at) : '—'} |
-
-
+
+
+
+
+
+
+
+ 删除单页「{p.title}」?
+
+ 删除后不可恢复,前台链接 /page/{p.slug} 将失效。
+
+
+
+ 取消
+ remove(p.id)}>删除
+
+
+
|
)}
@@ -187,65 +229,6 @@ export default function AdminPagesPage() {