Files
Navius/docs/submit-site-form.md
T
CNWei 1c98340fc5 feat: 实现导航页基础功能
- 添加导航条目和分类数据模型
- 实现导航卡片网格展示
- 支持点击卡片打开链接
- 添加主题样式(渐变色、卡片阴影)
- 区分系统默认和自定义条目
- 添加分类统计信息"
2026-09-19 21:53:15 +08:00

117 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 提交网站表单设计
> 已按本文实现(含「错误色 / 可点建议 / 提交后刷新」三条确认)。代码在
> `frontend/lib/features/home/presentation/widgets/submit_site_dialog.dart`。
> 实现与本文有几处按实际取舍收敛,差异记在第 8 节。
## 1. 目标与约束
- 入口:侧栏底部的「提交网站」按钮(现在是个空按钮)。原型里没有这个表单,
所以这里定完之后再动手写。
- 只管**用户提交**这一条路(`POST /api/sites`)。接口推送是给第三方用的,
走令牌,不在页面上暴露。
- **表单里没有「选择分组」这个动作**:后端不接受 `categoryId`,只接受分组名,
而且提交的内容永远不会落进内置分组。表单要顺着这个规则设计,
不能让用户以为可以把站点塞进「常用推荐」「素材资源」这些内置分组。
- 视觉只能用现有设计令牌(`index3.html` 那套),不引入新的配色和圆角体系。
## 2. 交互流程
```
点侧栏「提交网站」
│
▼
弹出对话框(遮罩 + 白底卡片,居中)
│ 填写(改动某个字段时清掉它上一次的错误)
▼
点「提交」
├─ 有字段不合格 → 定位到第一个不合格的字段并提示,不发请求
└─ 都合格 → 按钮进入「提交中…」,禁用整张表单
├─ 201 → 关掉对话框 + 顶部提示「已提交,感谢」+ 重新拉一次 /api/nav
└─ 其它 → 留在对话框里,顶部显示后端返回的原因,可以改了再提交
```
关闭方式:右上角 ×、点遮罩、「取消」按钮、Esc 键。提交中不允许关闭(避免请求还没回来
就把界面收了)。
## 3. 字段定义
字段与后端 `POST /api/sites` 的入参一一对应,校验规则也和后端保持一致——
前端先拦一遍是为了即时反馈,后端那一遍才是权威。
| 字段 | 控件 | 必填 | 限制 | 不合格时的提示 | 后端字段 |
| --- | --- | --- | --- | --- | --- |
| 网站名称 | 单行输入 | 是 | ≤ 60 字 | 请填写网站名称 / 网站名称最多 60 个字 | `title` |
| 网站地址 | 单行输入 | 是 | 可解析的 http(s),不能是本机或内网地址 | 请填写网站地址 / 地址要以 http:// 或 https:// 开头 / 不能填本机或内网地址 | `url` |
| 一句话描述 | 多行输入(3 行) | 否 | ≤ 120 字 | 描述最多 120 个字 | `description` |
| 分组 | 单行输入 | 是 | ≤ 30 字 | 请填写分组名称 | `group` |
| 子分组 | 单行输入 | 否 | ≤ 30 字 | 子分组名称最多 30 个字 | `subGroup` |
| 你的称呼或邮箱 | 单行输入 | 否 | ≤ 120 字 | 最多 120 个字 | `submittedBy` |
### 3.1 分组与子分组怎么填
- 两个都是**填名字**,不是选内置分类。placeholder 分别写:
「例如:AI 工具(会创建你自己的分组)」「例如:模型社区(可不填)」。
- 分组下面给一行浅色说明:**「提交的网站会放在你自己的分组里,不会进内置分类。」**
这句话必须留着,否则用户会以为能提交到「常用推荐」。
- 名字和已有的用户分组重名时会**复用**那个分组,不会重复创建——
所以两个输入框下面给一排可点的建议:分组建议列出现有用户分组,
子分组建议列出所选分组下已有的用户子分组(数据来自 `/api/nav` 里
`editable == true` 的分组)。点一下即填入,不想用就直接手打新名字。
- 子分组留空表示直接放在一级分组下。
## 4. 布局与视觉
沿用现有令牌,不新增尺寸:
| 部分 | 取值 |
| --- | --- |
| 遮罩 | `rgba(17, 24, 39, .45)` |
| 卡片 | 白底 + 1px `#e2e8f0` 描边 + 12px 圆角 + `cardShadowHover` |
| 卡片宽度 | 480px,两侧留 24px(窄屏时自适应) |
| 卡片内边距 | 24px |
| 标题 | 「提交网站」,`AppTextStyles.sectionTitle`,右侧一个 × 关闭按钮 |
| 字段标签 | 13px,`--text-sub`,与输入框间隔 6px |
| 输入框 | 复用搜索框那套:44px 高、1px 描边、12px 圆角、聚焦时主色边框 + 3px 光晕 |
| 多行框 | 同上,高度 88px(3 行),垂直内边距 10px |
| 字段间距 | 16px |
| 底部按钮 | 右侧并排:`取消`(描边次级)+ `提交`(主色实底,8px 圆角——和侧栏「提交网站」一致) |
| 字段错误文案 | 12px,紧随输入框下方,间隔 4px |
## 5. 状态
| 状态 | 表现 |
| --- | --- |
| 初始 | 所有输入为空,「提交」可点 |
| 校验失败 | 不合格的字段旁显示提示,输入框边框变错误色;不发请求 |
| 提交中 | 「提交」文案变「提交中…」并禁用,整张表单只读,× / 遮罩 / Esc 都不响应 |
| 提交失败 | 卡片顶部一条错误条,显示后端 `{"error": "…"}` 里的原话;表单恢复可编辑 |
| 提交成功 | 关闭卡片,弹一条「已提交,感谢」提示,并**静默**重新拉一次 `/api/nav`(不闪加载页) |
## 6. 三点确认结果
1. **错误色**:已新增 `AppColors.danger = #E5484D` 与它 8% 的叠加色
`dangerFaint`,只用在错误提示和错误边框上。
2. **分组建议**:保留,做成可点胶囊;只列 `editable == true` 的分组,
内置分组不出现(点了也进不去)。
3. **提交成功后刷新**:保留,用静默刷新——页面继续显示旧内容,
拉到新数据再整体替换,不会闪一下「正在加载」。
## 7. 实现落点
| 文件 | 职责 |
| --- | --- |
| `features/home/presentation/widgets/submit_site_dialog.dart` | 对话框本体:字段、校验提示、提交按钮 |
| `features/home/data/home_repository.dart` | 契约加一个 `submitSite(...)` |
| `features/home/data/http_home_repository.dart` | 实现:`POST /api/sites`,把后端的中文错误原样抛给界面 |
| `features/home/data/mock_home_repository.dart` | 测试用:记录调用,不真的发请求 |
| `features/home/presentation/pages/home_page.dart` | 「提交网站」按钮改为打开对话框;成功后静默重新取数并提示 |
## 8. 实现与本文的差异
| 本文原写法 | 实际实现 | 原因 |
| --- | --- | --- |
| 失焦时校验 | 提交时统一校验,改动字段时清掉该字段的错误 | 少一套焦点状态,行为对用户更可预期 |
| 底部按钮是「主色胶囊」 | 主色实底 + 8px 圆角 | `index3.html` 的调色板里没有胶囊圆角,侧栏「提交网站」也是 8px |
| 成功提示在页面顶部 | 底部提示条(`SnackBar`) | 复用主题里已经配好的提示条,不新增一套浮层 |