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 └── SectionManageDialogAdminApp 用 React useState 持有:
| 状态 | 用途 |
|---|---|
sections | 当前编辑中的完整树(深拷贝自 initial) |
savedSections | 上次保存或初始快照,用于 sectionsEqual 判脏 |
selectedSection | 当前 Tab 索引 |
query | 搜索词(与管理端过滤逻辑共用 lib) |
editContext | 正在编辑的书签 / 卡片 / 分区 |
deleteTarget | 待删除项 |
dragging | 拖拽中的书签 payload |
脏检测:sectionsEqual(sections, savedSections)。离开页面、切换 Tab 前若脏,弹出 LeaveUnsavedDialog。
CRUD 操作
Section titled “CRUD 操作”编辑入口统一走 EditDialog,根据 EditContext 类型渲染不同字段:
- Bookmark:title、url、description、badge、extraLinks(多行
title|url文本) - Card:title
- Section:title、stagger 开关
删除前 sectionCanDelete / cardCanDelete 检查是否至少保留一个 bookmark,防止存空数据。
新增书签通过 BookmarkAddTile 占位卡片触发,insertBookmark 工具函数插入并 normalizeSortOrders。
原生 HTML5 DnD,不用第三方库:
dragstart记录DragPayload(sectionIndex、cardIndex、bookmarkIndex)dragover计算插入位置getInsertIndex(按鼠标 Y 与卡片中线比较)drop调用swapBookmarks或跨 card 移动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 生效。
UI 技术栈
Section titled “UI 技术栈”- Radix UI:Dialog、Tabs、Select、Tooltip
- sonner:操作 toast
- lucide-react:图标
- Tailwind + cn():与公开页一致的语义样式
样式入口 src/styles/admin.css,根节点 .admin-root 隔离于 Starlight。
练习:只读预览模式
Section titled “练习:只读预览模式”-
在
AdminApp增加readOnlyprop -
当
readOnly为 true 时:- 隐藏保存按钮与 Add 图块
BookmarkCard不绑定 drag 事件
-
在
BookmarksAdminApp里:readOnly={!isDev},使线上 build 自动只读
这与现有 isDev 保存限制理念一致,可进一步改善 UX(目前线上仍可编辑 UI,只是保存失败)。
- 单文件
AdminApp集中状态,对话框按「一次只开一个」模式驱动 - 拖拽 +
normalizeSortOrders保证 UI 顺序与文件序列化一致 - 导出 TS 是静态环境下的兜底写回路径