- TOP_OFFSET 88 -> 104,搜索框与卡片间距 28 -> 44 - contentOffset 与 page-scroll margin/height 共用常量 - 验证 content/sider top=164
370 lines
26 KiB
Markdown
370 lines
26 KiB
Markdown
# Stash 后台与编辑能力方案(讨论稿)
|
||
|
||
> 目标:登录后能改「左侧布局」「分区布局」「页头滚动信息」,并且能编辑内容。
|
||
> 状态:**待评审**,评审通过后按 §8 分期实现。
|
||
|
||
## 1. 结论先行
|
||
|
||
| 问题 | 建议 | 理由 |
|
||
| --- | --- | --- |
|
||
| 要不要单独的设置页? | **要**,`/admin`,只放「全局配置」 | 布局、页头、侧栏是低频、结构化、跨页面的配置,塞进当前页会让页面变编辑器 |
|
||
| 内容(条目/文章)在哪编辑 | **就地编辑**:详情页的 `[编辑]` 打开右侧 Drawer | 编辑需要上下文预览;跳设置页会丢上下文 |
|
||
| 本地服务面板纳入设置吗 | **只纳入「显隐/位置」**,数据本身不进设置 | 项目数、连接状态是运行时只读状态,不是可编辑配置 |
|
||
| 详情页要不要独立设置页 | **不要** | 内容编辑用 Drawer,全局配置才进 `/admin` |
|
||
|
||
一句话:**全局配置进 `/admin`,单个内容就地改。**
|
||
|
||
## 2. 登录方案(单用户、本地)
|
||
|
||
- 账号:单管理员(`admin`),密码用 **argon2id** 哈希存库;首次启动若无密码,生成随机密码打印到控制台 / 或首访引导设置。
|
||
- 会话:登录成功下发 **HttpOnly + SameSite=Lax + Secure(可选)** 的 Cookie(随机 256bit token,库里存哈希,7 天过期,可撤销)。
|
||
- 写操作:所有 `POST/PATCH/DELETE` 校验会话 + **CSRF token**(双提交 Cookie 或自定义头)。
|
||
- 读操作:页面与 `GET /api/*` 保持公开(本地工具,保持只读可用);写接口一律要求登录。
|
||
- 频控:登录失败 5 次锁 1 分钟,防止本地暴破。
|
||
- 不做的:多用户/角色、OAuth、找回密码(单用户场景用「重置密码」命令)。
|
||
|
||
## 3. 信息架构:`/admin`
|
||
|
||
| Tab | 内容 | 存储 |
|
||
| --- | --- | --- |
|
||
| 布局 | 侧栏项顺序/显隐/分组、分区卡片顺序、每卡展示的分组与条数 | `settings.layout` |
|
||
| 页头 | 滚动信息条目(文本/标签/强调色)、速度、是否启用;品牌名 | `settings.header` |
|
||
| 内容 | 分区、分组、条目的增删改与排序(批量) | 现有内容库表 |
|
||
| 系统 | 修改密码、本地服务面板显隐、备份/导出、恢复默认 | `settings.system` / `auth` |
|
||
|
||
侧栏与页头的实时预览:设置页右侧放一个缩小的 `Layout` 预览(复用组件库)。
|
||
|
||
## 4. 编辑模型
|
||
|
||
### 4.1 内容编辑(就地)
|
||
|
||
- 详情页 `[编辑]` → `Drawer`(宽 480)表单:标题、标签、描述、正文(Markdown)、封面、链接;
|
||
- 保存:`PATCH /api/items/:id`,乐观锁(`updatedAt`),冲突时提示「内容已被修改,刷新后重试」;
|
||
- 成功:`toast.success` + 局部刷新当前条目(不整页刷新)。
|
||
|
||
### 4.2 全局配置(`/admin`)
|
||
|
||
- 表单保存走 `PATCH /api/settings`,整块 JSON 校验后落库;
|
||
- 提供「恢复默认」与「导出 JSON / 导入」。
|
||
|
||
## 5. 数据模型
|
||
|
||
```sql
|
||
-- 设置:一个 key 一段 JSON,带版本号用于乐观锁
|
||
settings(key TEXT PRIMARY KEY, value TEXT NOT NULL, version INTEGER NOT NULL, updated_at TEXT);
|
||
|
||
-- 管理员(单行)
|
||
admin(id INTEGER PRIMARY KEY CHECK (id = 1), username TEXT, password_hash TEXT, updated_at TEXT);
|
||
|
||
-- 会话
|
||
sessions(token_hash TEXT PRIMARY KEY, created_at TEXT, expires_at TEXT, last_seen_at TEXT);
|
||
|
||
-- 审计(谁在什么时候改了什么)
|
||
audit(id INTEGER PRIMARY KEY AUTOINCREMENT, at TEXT, action TEXT, target TEXT, detail TEXT);
|
||
```
|
||
|
||
**迁移问题**:当前 `PRAGMA user_version = 15` 且「删库重建」。加这些表要引入**第一套迁移**(v15 → v16),否则老库升级会坏。这是必须先解决的前置项。
|
||
|
||
## 6. API 草案
|
||
|
||
```
|
||
POST /api/auth/login {username,password} -> set-cookie
|
||
POST /api/auth/logout
|
||
GET /api/auth/session -> {authenticated, username}
|
||
PATCH /api/auth/password {current,next}
|
||
|
||
GET /api/settings -> {layout, header, system, version}
|
||
PATCH /api/settings {partial} + If-Match: version
|
||
|
||
PATCH /api/items/:id 条目编辑(乐观锁)
|
||
POST /api/categories 新建分区
|
||
PATCH /api/categories/:id 分区配置(展示块、预览条数、色条)
|
||
DELETE /api/items/:id
|
||
```
|
||
|
||
## 7. 没被提到、但必须一起考虑的
|
||
|
||
### 安全
|
||
1. **XSS**:正文是 Markdown,现有渲染器不处理 HTML;编辑后必须转义/白名单,否则登录用户可自注入脚本(虽然是本地单用户,仍要防)。
|
||
2. **CSRF**:Cookie 会话必须配 token 或 `SameSite=Strict`。
|
||
3. **上传**:封面/配图上传的存储路径、大小限制、MIME 校验、防目录穿越(public 是静态托管目录)。
|
||
4. **密码**:哈希算法与参数、首次设置流程、重置命令。
|
||
5. **会话**:过期、撤销、记住我、并发登录。
|
||
6. **CSP / 安全头**:内嵌页面与 iconfont 的 CSP 白名单。
|
||
|
||
### 数据与迁移
|
||
7. **迁移机制**:现在没有迁移,`user_version` 只是标记;加表必须写迁移 + 升级测试。
|
||
8. **默认值与校验**:设置 JSON 的 schema 校验、缺字段回退默认、坏 JSON 不能白屏。
|
||
9. **备份/恢复**:编辑前自动备份(`stash.db.bak`)、一键导出/导入。
|
||
10. **审计**:谁改了什么(单用户也需要排查)。
|
||
11. **乐观锁/冲突**:多标签页同时编辑。
|
||
12. **草稿/发布**:编辑后立即生效,还是保存草稿再发布?(内容库现在没有状态字段)
|
||
13. **撤销**:误删条目能否恢复(软删除 + 回收站)。
|
||
|
||
### 编辑体验
|
||
14. **未保存离开**:Drawer 关闭前的确认(beforeunload / 二次确认)。
|
||
15. **表单校验**:必填、长度、URL 合法性、错误定位。
|
||
16. **图片上传**:拖拽、进度、失败重试。
|
||
17. **排序交互**:拖拽排序(新组件),键盘可达的排序替代方案。
|
||
18. **实时预览**:Markdown 编辑预览(复用 `ArticleBody`)。
|
||
19. **移动端编辑**:Drawer 全宽、软键盘遮挡、保存按钮吸底。
|
||
20. **可访问性**:Drawer 焦点陷阱(已有 `<dialog>`)、表单 label/错误关联、快捷键。
|
||
21. **编辑模式提示**:登录后页面出现明显「编辑中」状态,避免误操作。
|
||
|
||
### 工程
|
||
22. **SSE 推送**:编辑后前端已有 `live` 通道,需要定义「设置变更」是否也推送。
|
||
23. **缓存**:设置读取缓存与失效策略。
|
||
24. **并发写 SQLite**:WAL、事务边界、写冲突重试。
|
||
25. **测试**:登录/权限/迁移/设置校验/编辑接口的单测 + e2e。
|
||
26. **权限边界**:未来多用户时,哪些接口要按角色收敛(现在先留 `require_auth` 中间件层)。
|
||
|
||
## 8. 分期建议
|
||
|
||
| 阶段 | 范围 | 依赖 |
|
||
| --- | --- | --- |
|
||
| **P0** | 迁移机制 v16 + admin/sessions/settings 表 + 登录/登出/会话 + 登录页 + 写接口鉴权 | 无 |
|
||
| **P1** | `/admin` 设置页:页头滚动信息、侧栏项显隐/顺序、分区卡配置 | P0 |
|
||
| **P2** | 内容就地编辑(Drawer 表单 + 乐观锁 + 富文本/图片上传) | P0 + 迁移 |
|
||
| **P3** | 备份/恢复、审计、回收站、导入导出 | P1/P2 |
|
||
|
||
## 9. 组件库缺口(P1/P2 会用到)
|
||
|
||
| 缺口 | 说明 |
|
||
| --- | --- |
|
||
| `Textarea` | 多行输入(Markdown、描述),当前只有单行 `Input` |
|
||
| `ColorPicker` | 分区色条 / 标签色 |
|
||
| `SortableList` | 拖拽排序 + 键盘上/下移 |
|
||
| 表单布局 | `FormField` 有了,但「两列/分组/折叠」还需要页面组合或补 `Form` |
|
||
| `Tabs`/`Drawer`/`Switch`/`Toast` | 已有,可直接用 |
|
||
|
||
## 10. 开放决策(需拍板)
|
||
|
||
1. 设置入口:独立 `/admin` 页面(推荐)/ 右侧设置抽屉 / 两者都有。
|
||
2. 登录范围:单用户密码(推荐)/ 仅局域网免密 + 编辑口令 / 多用户。
|
||
3. 读接口是否也要登录:公开(推荐)/ 全站需登录。
|
||
4. 内容编辑是否要「草稿 → 发布」两态:先不要(推荐,保持即时生效)/ 要。
|
||
5. 编辑图片:先只接受 URL(推荐,P2 后期再做上传)/ 首期就要上传。
|
||
|
||
## 11. 实施记录
|
||
|
||
### P26(2026-10-31,顶部留白加高)
|
||
|
||
- 首页顶部偏移 `TOP_OFFSET` 从 88 调到 104,搜索框与内容卡片之间的空白从 28px 增加到 44px;
|
||
- Layout `contentOffset` 与 `#page-scroll` 的 `margin-top`/`height` 使用同一个常量,左右两栏仍从 164 对齐开始;
|
||
- 验证:content/sider top=164,search top=76 / bottom=120。
|
||
|
||
### P25(2026-10-31,去掉顶部块的接缝)
|
||
|
||
- 删除 `.top-dock` 整块:不再有 60–148 的半透明填充与底边,避免出现一条横向分割线;
|
||
- 搜索框继续 fixed 悬浮,页头保持实底;顶部与内容之间只剩页面背景的自然渐变;
|
||
- 背景图渐隐保持 80%,与内容起点无硬边;
|
||
- 验证:`.top-dock` 数量 0,左侧与右侧顶部背景连续。
|
||
|
||
### P24(2026-10-31,独立的半透明顶部块)
|
||
|
||
- 页头恢复实底(`headerVariant="solid"`),去掉 `.app > header` 的透明度覆盖;
|
||
- 页头下方新增独立 `.top-dock`:`position: fixed; top:60px; height:88px`,背景 28% 页面色 + `blur(10px)` + 半透明底边,作为承载搜索框的单独块,而不是让整条看起来像背景填充;
|
||
- 搜索框保持 60% 半透明;
|
||
- 背景图渐隐层恢复 80%(撤掉上一轮为露出背景做的 65%);
|
||
- 验证:dock top=60、height=88、alpha=0.28;header 纯白;搜索框可见;无 mask 元素。
|
||
|
||
### P23(2026-10-31,去掉遮罩、顶部透明)
|
||
|
||
- 删除 `scroll-fade` 整条遮罩及创建逻辑,页面不再有顶部渐隐/模糊层;
|
||
- 页头改为 `headerVariant="blur"`,并用 `.app > header` 把底色降到 55%;搜索框底色降到 60% 并保留轻微 blur;
|
||
- 背景图渐隐层高度从 80% 调到 65%,让页头到内容之间那一条保持背景原色,不再发白;
|
||
- 验证:`.scroll-fade` 数量为 0;header 背景 alpha=0.55;搜索框 alpha=0.60。
|
||
|
||
### P22(2026-10-31,遮罩合并为整条)
|
||
|
||
- 去掉内容区自带的 `.content-veil`,只保留一条固定遮罩;
|
||
- 遮罩宽度从侧栏左边缘一直覆盖到内容区右边缘(实测 left=50、width=1340),左右同一水平高度(top=148),不再像两个块拼接;
|
||
- 渐变、模糊参数不变,滚动后整体淡入。
|
||
|
||
### P21(2026-10-31,侧栏虚化遮罩与透明度)
|
||
|
||
- 侧栏顶部增加独立 fixed 遮罩层:位置跟随侧栏左右/宽度,固定在滚动区顶部(y=148),滚动后淡入;
|
||
- 遮罩用 `backdrop-filter: blur(14px)` + 45%→20%→透明的页面色渐变,既虚化又不挡内容;不能挂在侧栏内部(会被吸底 `translateY` 带走);
|
||
- 左侧资料卡 / 本地服务 / 导航卡改成 64% 不透明 + `backdrop-filter` 毛玻璃,露出页面背景;
|
||
- 修复 Tailwind/构建器只保留 `-webkit-backdrop-filter` 导致 Chromium 不生效的问题:只写标准属性;
|
||
- 验证:滚动后遮罩 top=148、opacity=1、backdrop-filter=blur(14px) saturate(1.1),卡片背景 alpha=0.64。
|
||
|
||
### P20(2026-10-31,双栏非对称吸底滚动)
|
||
|
||
- 两栏顶部对齐:去掉首页侧栏额外的 `padding-top`,两栏都从 `#page-scroll` 顶部(y=148)开始;
|
||
- 隐藏滚动条:`#page-scroll` 使用 `scrollbar-width: none` / `::-webkit-scrollbar { display:none }`;
|
||
- 非对称吸底:仍共用一个滚动容器;滚动时用 `translateY(max(0, scrollTop - (侧栏高度 - 视口高度)))` 补偿左栏,左侧内容到底后吸附在视口底部,右侧继续滚动;
|
||
- 右侧先到底、左侧更长时不补偿,两栏同步滚到整体底部;`ResizeObserver` 在内容变化时重算;
|
||
- 验证:初始左右 top 均为 148;滚动 120/300/600 时左侧 top 稳定在 144、bottom 贴住 900,右侧正常滚动;页面无滚动条。
|
||
|
||
### P19(2026-10-31,左右两栏共用滚动容器)
|
||
|
||
- 不再用 `sider.scrollTop = content.scrollTop` 模拟同步:把 Layout 的 body(`#page-scroll`)设为滚动容器,`margin-top: 88px`、高度 `calc(100% - 88px)`,左右两栏都在这个容器里;
|
||
- 搜索框仍 fixed 在页头下方;滚动内容从搜索框下方开始;左右两栏位移完全一致(滚动 300px 后两边 top 都是 -152);
|
||
- 内容区 `overflow: visible`,侧栏高度改为 auto;返回顶部与路由切换都改用 `#page-scroll`;
|
||
- 验证:滚动后两栏 top 相同、搜索框位置不变、返回顶部回到 0、切分组后滚动归零。
|
||
|
||
### P18(2026-10-31,搜索框下滚动、本地服务排序、拖拽末位修复)
|
||
|
||
- 前台恢复「内容区内部滚动」:页头与搜索框固定,滚动区域从搜索框下方开始;侧栏不再独立滚动,内容区滚动时同步设置侧栏 `scrollTop`,滚轮在侧栏上也驱动内容区;
|
||
- 本地服务卡片正式进入 `card` 表(v22,key=`local-service`,sort=-1),后台卡片列表可拖动排序,首页按顺序渲染;后端禁止删除、改名、配置分组;
|
||
- 修复拖拽排序:向下拖时插到目标之后,倒数第二项可以拖到最后一项;
|
||
- 验证:搜索框滚动前后位置不变;内容滚动 300px 时侧栏 `scrollTop` 跟随(受可滚高度限制);本地服务拖到末位、倒数第二拖到末位均落库;首页顺序与后台一致;删除本地服务返回 400。
|
||
|
||
### P17(2026-10-31,侧栏卡片显隐、本地服务卡片、同步滚动)
|
||
|
||
- 侧边栏设置顶部新增「侧栏卡片」:本地管理员 / 本地服务各一个开关,即时保存到 `settings.sidebar.show_profile / show_service`;与下方分组导航分区显示;
|
||
- 首页恢复「本地服务」卡片(由 `/api/projects` 运行时生成,应用网格),固定排在最前;移除卡片右上角三点;
|
||
- 首页卡片后台新增只读「本地服务」表:列出项目名 / 访问地址,标记不可删除,不提供删除/改名;
|
||
- 前台放弃整屏内滚动,改为页面整体滚动,左右两栏同步滚动;
|
||
- 验证:开关即落库、前台隐藏对应卡片、三点数量为 0、本地服务表无删除按钮、滚动 300px 两栏同步位移 300px。
|
||
|
||
### P16(2026-10-31,格式下沉到条目)
|
||
|
||
- 数据库 v21:`entry.format`(apps / posters / articles),老数据按所属根分组的 `view_mode` 自动回填;新库 seed 按分区写入;
|
||
- 分组管理移除「格式」字段与显示,根分组只作为容器;
|
||
- 内容弹窗新增「格式」选择,列表增加「格式」列;同一条目可单独设为应用/海报/图文;
|
||
- 前台改为按 `entry.format` 渲染:首页卡片、分组页同一分组内可同时出现应用网格与海报/图文网格;详情页按条目格式选择模板;
|
||
- 验证:迁移后 20 应用 / 13 海报;在应用分组内新建海报条目后,内容列表、首页卡片、分组页均能混合展示。
|
||
|
||
### P15(2026-10-31,卡片新建弹窗、标题保存/删除、拖动排序)
|
||
|
||
- 新建卡片改为弹窗:输入标题 → 确认新建 / 取消关闭;创建后自动选中新表;
|
||
- 卡片标题右侧增加「保存」和「删除」按钮,标题按 Enter 或保存按钮提交;
|
||
- 左侧卡片支持 HTML5 拖动排序,拖放后按顺序写入 `card.sort`,刷新后首页卡片顺序一致;
|
||
- 标签栏与内容之间补 20px 间距;左侧卡片增加拖动手柄和提示;
|
||
- 验证:弹窗取消/确认、保存/删除按钮、拖动 `sadf`/新卡到首位后 `/api/catalog2` 顺序变化。
|
||
|
||
### P14(2026-10-31,首页卡片改为侧栏 + 表格)
|
||
|
||
- 首页卡片不再纵向堆叠:左侧是卡片列表(名称 + 已选分组数),右侧是当前卡片的分组表格(分组名称 / 预览条数 / 展示开关);
|
||
- 「新建卡片」即新增一张表,创建后自动选中;卡片标题、删除在表格上方;
|
||
- 表格内开关、条数即时保存,未选分组显示 —;侧栏卡片可一键切换;
|
||
- 验证:切换卡片表格同步、新建后自动选中、表格开关即时写入 `card_group`、无卡片级保存按钮。
|
||
|
||
### P13(2026-10-31,首页卡片按侧边栏逻辑)
|
||
|
||
- 每张卡片的分组勾选改为与侧边栏一致:标题在左、开关在右、勾选/取消即时保存(`PUT /api/cards/{id}/groups`),去掉每张卡的「保存」按钮;
|
||
- 卡片标题、单组预览条数改为失焦保存;条数清空表示使用默认;
|
||
- 卡片删除仍保留图标按钮;验证:无卡片级保存按钮、开关在右、勾选后 `card_group` 即时更新、条数 2 与改名均落库。
|
||
|
||
### P12(2026-10-31,内容/侧边栏交互微调)
|
||
|
||
- 内容页与条目弹窗的分组下拉只显示分组名,去掉格式后缀与缩进空格,选项统一左对齐;
|
||
- 侧边栏页:开关移到右侧,拨动即写入 `settings.sidebar.tree_hidden`;移除「保存侧边栏 / 撤销改动 / 保存」按钮,保存成功用 Toast 提示;
|
||
- 验证:下拉选项无括号、x 对齐;侧边栏无保存按钮、开关在右、切换后设置即时落库。
|
||
|
||
### P11(2026-10-31,内容页列表化)
|
||
|
||
- 内容页改为与分组管理一致的列表:标题 / 跳转链接 / 所属分组 / 操作;
|
||
- 选中主分组时递归列出其所有子分组的条目(显示所属分组),选子分组只列该子树;
|
||
- 编辑弹窗始终显示「标识」字段(编辑时只读,创建时可填);
|
||
- 条目接口补齐图文的 `href`,列表统一展示跳转链接(空值显示 —);
|
||
- 验证:主分组 20 条、子分组 4 条;编辑弹窗标识为 `github` 且只读;`cargo test` 59 passed。
|
||
|
||
### P10(2026-10-31,分组管理页重做)
|
||
|
||
- 参考 `TT-Helper/需求/5297/1.html` 重做分组管理:树形表格(分组名称 / 排序权重 / 关联网页数 / 状态 / 操作),去掉「展开全部 / 折叠全部」,新建根分组放左侧;
|
||
- 分组弹窗:上级分组(可移动、防环)、名称、格式(根)、**图标(可留空)**、排序权重、状态;
|
||
- 数据层:`node.enabled`(迁移 v20)控制启停,禁用分组在前台侧栏/卡片隐藏;`node.icon` 支持 iconfont 名、字符或空;
|
||
- 排序修复:同一父级下编辑排序权重不再被当成「移动」重置为末尾;
|
||
- 验证:无图标/有图标、排序 5、禁用后前台隐藏、折叠行均通过;`cargo test` 59 passed。
|
||
|
||
### P9(2026-10-30,前台切换)
|
||
|
||
- **前台**:`AppNext` 改为读 `/api/catalog2`,首页按 `card` 勾选的分组渲染(可混合格式,每组条数独立),侧栏按分组树 + `tree_hidden` 渲染,分组页 `/g/<key>`、条目页 `/g/<key>/i/<id>`;
|
||
- **详情**:条目在分组子树内递归查找,面包屑显示完整祖先链;编辑仍用 Drawer + `PATCH /api/items`;
|
||
- **服务端**:SPA 回落增加 `/g/` 前缀;
|
||
- **兼容**:旧 `/c/...` 地址按第一段作为分组 key 跳转;旧 `/api/catalog`、旧后台组件暂时保留;
|
||
- 验证:首页卡片、混合格式卡片(应用 + 海报)、侧栏隐藏指定分组、分组页/条目页均通过。
|
||
|
||
### P8(2026-10-30,子分组开关)
|
||
|
||
- **与类型解耦**:分区新增 `allow_groups` 开关(迁移 v18);三种类型共用同一模板,开启后可建分组,关闭后平铺;
|
||
- **默认值**:老库升级时应用类开启、海报/图文关闭;已有分组的非应用分区自动开启;新建分区按类型给默认值,之后可随时改;
|
||
- **平铺兼容**:关闭开关时读取会把(历史)分组内条目一起平铺,内容不丢;首页卡片若指向分组会自动挪回分区;
|
||
- **后台**:「分区」每行新增「子分组」开关,**拨动即保存并立刻出现/收起分组管理**;「内容」弹窗的分组选择随开关出现;「侧栏导航」的建组入口同理;空分组不在侧栏显示;
|
||
- 验证:推荐电影(海报)开启开关 → 建组 → 加海报 → 详情按组显示;关闭 → 恢复平铺且条目仍在;`cargo test` 59 passed。
|
||
|
||
### P7(2026-10-30,统一内容模型)
|
||
|
||
- **模型统一**:分区(section)是容器,分组(group)是任意类型的可选子容器,条目可挂在分区或分组上;`view_mode` 只决定渲染方式;
|
||
- **catalog**:所有类型都输出 `groups`(带条目)与 `items`(直属分区条目);
|
||
- **渲染**:详情页三种类型都支持分组(多组显示小标题,直属条目进「未分组」);侧栏子菜单、首页卡片预览分组对所有类型生效;
|
||
- **后台**:「分区」「侧栏导航」对所有类型可建组;「内容」弹窗的分组选择新增「直属分区」,编辑可换组;
|
||
- **修复**:首页卡片配置为「整个分区」时应用类预览为空;catalog 条目查询改为按节点取,避免分组重复/错漏;
|
||
- 测试:59 passed。
|
||
|
||
### P6(2026-10-30)
|
||
|
||
- **后台新增内容**:`POST /api/items`(`key` + `nodeKey` + 条目字段)、`DELETE /api/items/{key}`;「内容」Tab 用弹窗新增,列表逐条编辑(含换分区/分组)与删除;
|
||
- **分组 CRUD**:`POST /api/categories/{id}/groups`、`PATCH /api/groups/{id}`(改名/侧栏显隐)、`DELETE /api/groups/{id}`(级联组内条目);「分区」Tab 内联管理,「侧栏导航」Tab 也可直接新建子分组;
|
||
- **卡片预览分组**:`PATCH /api/categories/{id}` 增加 `blockNodeKey`,可在「分区」Tab 选择首页卡片预览哪个分组;
|
||
- 后端校验:应用类条目必须挂在分组上;条目移动通过 `PATCH /api/items/{key}` 的 `nodeKey`;
|
||
- 测试:59 passed(新增条目创建/移动/删除 3 例、分组 2 例)。
|
||
|
||
### P5(2026-10-30)
|
||
|
||
- **跳转修复**:保存条目时规范化 `href`(`www.zhihu.com` → `https://www.zhihu.com`,站内路径保留,`javascript:` / `data:` 等协议拒绝),前端渲染再做一次兜底;
|
||
- **侧栏子分类**:`settings.sidebar` 增加 `hidden_groups` / `shown_groups` / `group_order`,侧栏 Tab 展示分组并支持单独显隐、上下移;
|
||
- **归类页**:新增 `PATCH /api/categories/{id}`,`/admin` 的「归类」Tab 可改分区类型(应用/海报/图文)、标题、图标、色条、预览条数;切换类型时自动迁移条目与首页卡片指向;
|
||
- 测试:54 passed(新增 href 规范化 3 例、分类更新 3 例)。
|
||
|
||
### P4(2026-10-29,收尾)
|
||
|
||
- 分区增删:`POST /api/categories`、`DELETE /api/categories/{id}`(需登录,级联内容,`local` 不可删),首页与侧栏共用;
|
||
- 资料设置:头像 URL / 名称 / 角色 / 简介 / 位置,应用到侧栏资料卡;
|
||
- 操作条:进入详情页记一次浏览、点赞可切换(localStorage 记状态)、分享复制链接、跳转外链;
|
||
- 交互细节:`[编辑]` 与标题垂直居中并留 12px 间距;设置页操作按钮与内容拉开间距;保存改用 Toast(2.4s 自动消失);设置页 Tabs 不再出现滚动条;
|
||
- 登录密码框增加显示/隐藏切换(错误后仍可用);
|
||
- 测试:48 passed(新增 categories 2 例、settings 资料校验)。
|
||
|
||
|
||
### P0(已完成,2026-10-29)
|
||
|
||
- 迁移机制:`user_version 15 → 16`,`schema::migrate` 逐级升级;v15 老库启动时自动补 `admin` / `session` / `setting` 三表;
|
||
- 登录:`POST /api/auth/setup`(仅首次)、`/login`、`/logout`、`PATCH /api/auth/password`;argon2id + sha256(token) 落库;
|
||
- 会话:HttpOnly + SameSite=Strict + Path=/,7 天;改密后所有会话失效;
|
||
- 防护:Origin 同源校验、连续失败 5 次锁 60 秒;
|
||
- 前端:`/login` 独立壳(入口按 pathname 分流)、页头登录/退出登录切换;
|
||
- 测试:后端 37 个单测(新增迁移 2 个 + 登录 4 个),登录流程 e2e 手动脚本验证通过。
|
||
|
||
**未做(P1/P2)**:`/admin` 设置页、侧栏/页头编辑、内容就编辑、`setting` 表的读写接口。
|
||
|
||
### P1(已完成,2026-10-29)
|
||
|
||
- 登录改为**页内弹窗**(`LoginDialog`):`/login` 旧地址重定向回首页,不再单开页面;
|
||
- 设置读写:`GET /api/settings`(公开)、`PATCH /api/settings`(需登录,`version` 乐观锁,冲突 409、校验失败 400);
|
||
- `setting` 表存 `key='site'` 的 JSON:页头(启用 / 速度 / 条目与标签/色调)、侧栏(隐藏 / 顺序);
|
||
- `/admin` 设置页:Tabs(页头滚动信息 / 侧栏导航),条目支持增删、上移下移、占位符 `{count}`/`{project}`/`{status}`;
|
||
- 首页应用设置:滚动条目的文本/标签/速度/启用,侧栏按顺序与显隐渲染;
|
||
- 测试:42 passed(新增 settings 5 例:默认值、版本递增、冲突、校验、坏 JSON 回落)。
|
||
|
||
### P1 扩展(2026-10-29,同日)
|
||
|
||
- 首页卡片可配置:显隐、顺序、标题覆盖、每卡预览条数(`settings.sections`,前端 `HomeView` 应用);
|
||
- 页头滚动信息支持**启用/禁用**(`header.enabled`,关闭后页头中间留空);
|
||
- 「编辑」按钮只在登录后出现(`canEdit`,未登录时详情页与文章页不渲染 `[编辑]`);
|
||
- 侧栏「首页」与卡片顺序相互独立:侧栏管导航,卡片管首页布局。
|
||
|
||
|
||
### P2(已完成,2026-10-29)
|
||
|
||
- 库新增 `Textarea`(stash-ui v0.2.0):`rows` / `resize` / `invalid`;
|
||
- 条目编辑:`GET /api/items/{key}`(公开读)、`PATCH /api/items/{key}`(需登录);
|
||
- 乐观锁:`entry.updated_at`(迁移 v17),冲突返回 409;
|
||
- 可编辑字段:标题、简介、标签、一句话评价、正文 Markdown、封面、跳转地址;
|
||
- 前端:详情页 `[编辑]` → `Drawer` 表单;保存后 `catalog.reload()` 局部刷新,不整页刷新;
|
||
- 审计:每次条目更新写入 `audit` 表;
|
||
- 测试:46 passed(新增 entries 4 例:更新/标签替换、冲突、空标题、不存在)。
|
||
|
||
### P3(部分完成,2026-10-29)
|
||
|
||
- 审计表 `audit`:记录 `item.update`、`settings.update`;
|
||
- 备份:`GET /api/backup`(需登录)用 `VACUUM INTO` 生成一致性副本并以附件下载;
|
||
- 系统设置 Tab:修改密码(成功后所有会话失效)、下载备份;
|
||
- **未做**:恢复/导入(需要停机替换文件或在线校验导入流程)、回收站/软删除、图片上传、拖拽排序(当前用上移/下移按钮替代)。
|
||
|
||
|
||
|