子项目层级 · UI / 交互设计稿 v2

相对 v1 的修订:① 创建「记录」与「子项目」完全并行、互不强绑; ② 标记色推荐「可选 + 自动默认」;③ 纯时间 = 跨子项目扁平时间线, 只有记录与记录之间的时间间隔,不存在子项目分隔。 添加入口:AppBar 加号为主,列表底部为辅;顶栏只保留 + / ⋯ 两键,搜索收进 more,避免三按钮过挤。

创建路径并行可选 标记色:可选 + 自动色板 纯时间:扁平 · 无组间隔 顶栏:+ · ⋯(搜索进 more) 分组模式才有折叠 / 组内 gap

A. 可交互主原型

主入口:顶栏「+」→ 添加记录 / 新建子项目。顶栏仅两键:+ · ⋯;搜索、排序、多选、分享进 more。
次入口:列表底部「添加…」。居中「跳转底部」FAB 保留。

9:41●●● 100%
‹
日本之旅
+
⋯
+
添加…
0 条已选

时间线管理

新建子项目

只创建容器,不必立刻写记录。

标记色 可选 · 默认自动分配

B. 对你三条修改意见的结论

三条全部采纳。创建路径并行、标记色可选、纯时间彻底扁平——这三点让「子项目」成为可选组织层,而不是第二条强制流程。

1. 创建子项目 ≠ 必须创建记录 — 同意

  • 「记录」与「子项目」是两个独立入口,同级可选,无先后强约束。
  • 空子项目合法终态:允许只建阶段壳子,改天再填,或永远不填。
  • 添加记录时归属为可选:未归组 / 某个子项目;不默认绑架到「刚建的那个」除非用户显式选择或我们提供「最近使用」快捷(仍可跳过)。
  • 空状态文案避免 CTA 强迫感:用「暂无记录」而非主按钮「必须添加第一条」。

2. 创建时是否需要标记色 — 建议:要,但是可选

  • 需要颜色能力:收拢摘要卡左边线、纯时间角标、标题圆点都依赖色相做扫描识别;没有色标,分组视觉会塌成灰块。
  • 创建时不要强制挑选:默认「自动」从固定色板按已有子项目数量取下一色;用户可点选覆盖。
  • 色板建议 6–8 色、高对比、色盲相对友好;支持之后在子项目菜单里改色。
  • 「未归组」固定中性灰,不占用户色板位。
  • 若完全不要颜色:纯时间角标只能靠文字,信息密度差一档——不推荐作默认。

3. 纯时间排序的间隔语义 — 同意,并写死规则

按子项目分组
纯时间排序
列表结构
子项目块依次排列
全部记录按时间一条河
子项目 UI
标题栏 + 可折叠 + 摘要卡
无标题栏 / 无折叠 / 仅记录角标
间隔 (gap)
仅组内相邻记录之间
全局相邻记录之间(与现网一致)
「子项目之间」
块与块有分区感(标题分隔)
无任何子项目级间隔/分隔
  • 纯时间若仍渲染组头或组间空白,就和分组模式只差「是否折叠」,产品差异被抹掉——你的判断正确。
  • 记录级 gap(「1 天」「2 天」)在两种模式都保留;它表达的是时间距离,不是子项目边界。

4. 修订后的创建路径

+
统一入口「添加…」 选择 A:添加记录(可选手项目归属,可空)· 选择 B:新建子项目(名称 + 可选色,可空容器)
☑
事后整理 多选记录 → 合并为子项目(命名 + 可选色)
···
单条迁移 移入子项目 / 移到未归组 — 与创建无耦合

5. 添加按钮放哪里 + 顶栏信息架构

采纳 A,并收敛顶栏:只留「+」与「⋯」两键;搜索放进 more。
主入口 AppBar +;次入口列表底「添加…」;FAB 仍专管「跳转底部」。归档隐藏 +。
方案
评价
A · 已定
AppBar +(主)+ 列表底(辅)
任意滚动可加;读完时间线仍可顺手补;与跳转底部 FAB 不抢位
顶栏
‹ 标题 + ⋯
两键封顶;+ 是最高频写操作,⋯ 收纳低频/工具型
搜索
放进 ⋯ more 菜单首项
搜索非每次必用;进 more 换顶栏呼吸感。进入后顶栏可变搜索态(输入框 + 清除/取消)
  • ⋯ 菜单建议顺序:搜索记录 → 分享时间轴 → 切换排序(分组/纯时间 · 升/降序)→ 多选记录 → 取消
  • 点 + → 「添加记录 / 新建子项目」sheet(子项目未落地前可先只进添加记录)
  • 搜索态:用顶栏 title 区变搜索框,或在 body 顶部展开搜索条(与现网一致);退出搜索后回到 + · ⋯
  • 搜索态仍可保留 +(搜完想记一条不必先退出);归档只读时隐藏 +
  • 不推荐把 + 也塞进 more:会把最高频写操作藏一级

C. 状态对照 · 分组展开 / 分组收拢 / 纯时间扁平

注意第三列:跨子项目连续排列,只有记录间时间 gap,没有组头、没有组间距。

D. 数据字段(含标记色)

sub_projects (
  id, project_id, title,
  color TEXT NULL,          -- null = auto from palette by sort_order index
  sort_order INTEGER,
  created_at, updated_at
)
timeline_records.sub_project_id  NULLABLE  -- null = 未归组

// pure time projection
records
  .sortedBy(createdAt, asc|desc)
  .map(r => RecordCard(badge: r.subproject?))   // no headers, no group gaps
  .withInterRecordGaps()                        // same as today's timeline

// group projection
for sp in subprojects.orderedBy(sort_order):
  header(sp) + (expanded ? records+gaps : peek)
ungrouped section (optional, same rules)

文件:docs/subproject-ui-design.html · v2.2(顶栏 + · ⋯,搜索进 more)· 设计稿 only,未改应用代码。浏览器打开即可操作。