- 添加导航条目和分类数据模型 - 实现导航卡片网格展示 - 支持点击卡片打开链接 - 添加主题样式(渐变色、卡片阴影) - 区分系统默认和自定义条目 - 添加分类统计信息"
6.5 KiB
6.5 KiB
提交网站表单设计
已按本文实现(含「错误色 / 可点建议 / 提交后刷新」三条确认)。代码在
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. 三点确认结果
- 错误色:已新增
AppColors.danger = #E5484D与它 8% 的叠加色dangerFaint,只用在错误提示和错误边框上。 - 分组建议:保留,做成可点胶囊;只列
editable == true的分组, 内置分组不出现(点了也进不去)。 - 提交成功后刷新:保留,用静默刷新——页面继续显示旧内容, 拉到新数据再整体替换,不会闪一下「正在加载」。
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) |
复用主题里已经配好的提示条,不新增一套浮层 |