跳转到内容

06 · 管理端 UI

管理端 UI 是系列中代码量最大的部分:AdminApp.tsx 统筹分区 Tab、书签卡片网格、搜索、脏数据检测,以及一整套对话框。

BookmarksAdmin(ThemeProvider 根)
└── BookmarksAdminApp(登录门控)
└── AdminApp(主编辑界面)
├── AdminHeaderActions(保存 / 导出 / 版本 / 退出)
├── AdminStats
├── SectionTabsNav + TabsContent
│ └── BookmarkSectionPanel
│ └── BookmarkCardGroup → BookmarkCard / BookmarkAddTile
└── Dialogs
├── EditDialog
├── DeleteConfirmDialog
├── SaveConfirmDialog
├── LeaveUnsavedDialog
├── RestoreConfirmDialog
├── VersionHistoryDialog
└── SectionManageDialog

AdminApp 用 React useState 持有:

状态用途
sections当前编辑中的完整树(深拷贝自 initial)
savedSections上次保存或初始快照,用于 sectionsEqual 判脏
selectedSection当前 Tab 索引
query搜索词(与管理端过滤逻辑共用 lib)
editContext正在编辑的书签 / 卡片 / 分区
deleteTarget待删除项
dragging拖拽中的书签 payload

脏检测:sectionsEqual(sections, savedSections)。离开页面、切换 Tab 前若脏,弹出 LeaveUnsavedDialog

编辑入口统一走 EditDialog,根据 EditContext 类型渲染不同字段:

  • Bookmark:title、url、description、badge、extraLinks(多行 title|url 文本)
  • Card:title
  • Section:title、stagger 开关

删除前 sectionCanDelete / cardCanDelete 检查是否至少保留一个 bookmark,防止存空数据。

新增书签通过 BookmarkAddTile 占位卡片触发,insertBookmark 工具函数插入并 normalizeSortOrders

原生 HTML5 DnD,不用第三方库:

  1. dragstart 记录 DragPayload(sectionIndex、cardIndex、bookmarkIndex)
  2. dragover 计算插入位置 getInsertIndex(按鼠标 Y 与卡片中线比较)
  3. drop 调用 swapBookmarks 或跨 card 移动
  4. dragend 清理高亮 class(adminDropZoneActiveClass

排序后 normalizeSortOrders 重算各层 sortOrder,与保存序列化逻辑一致。

保存到项目(仅 dev):

await saveSectionsToProject(authorization, sections);
// → POST /admin/api/save { sections }

成功后更新 savedSections,toast 提示。

导出 TS 文件(任意环境):

downloadTextFile("bookmarks.ts", serializeBookmarkSections(sections));

适合线上管理端无法写盘时,手动复制到本地。

VersionHistoryDialog 列出 db/data/versions/manifest.json 中的快照(最多 40 条)。每次 save 前 archiveVersion 写入 {id}.json。Restore 走 /admin/api/restore,同样只在 dev 生效。

  • Radix UI:Dialog、Tabs、Select、Tooltip
  • sonner:操作 toast
  • lucide-react:图标
  • Tailwind + cn():与公开页一致的语义样式

样式入口 src/styles/admin.css,根节点 .admin-root 隔离于 Starlight。

  1. AdminApp 增加 readOnly prop

  2. readOnly 为 true 时:

    • 隐藏保存按钮与 Add 图块
    • BookmarkCard 不绑定 drag 事件
  3. BookmarksAdminApp 里:readOnly={!isDev},使线上 build 自动只读

这与现有 isDev 保存限制理念一致,可进一步改善 UX(目前线上仍可编辑 UI,只是保存失败)。

  • 单文件 AdminApp 集中状态,对话框按「一次只开一个」模式驱动
  • 拖拽 + normalizeSortOrders 保证 UI 顺序与文件序列化一致
  • 导出 TS 是静态环境下的兜底写回路径