- 添加导航条目和分类数据模型 - 实现导航卡片网格展示 - 支持点击卡片打开链接 - 添加主题样式(渐变色、卡片阴影) - 区分系统默认和自定义条目 - 添加分类统计信息"
117 lines
6.5 KiB
Markdown
117 lines
6.5 KiB
Markdown
# 提交网站表单设计
|
||
|
||
> 已按本文实现(含「错误色 / 可点建议 / 提交后刷新」三条确认)。代码在
|
||
> `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`) | 复用主题里已经配好的提示条,不新增一套浮层 |
|