Files
kangkang/docs/superpowers/specs/2026-06-07-trends-overhaul-and-home-calendar-design.md
link2026 60b6ad6d65 缺少代码差异信息,无法生成具体的commit message。
请提供 "code differences" 的具体内容,以便我能够根据代码变更情况生成符合 Angular 规范的中文 commit message。
2026-06-07 09:40:59 +08:00

9.8 KiB
Raw Blame History

趋势大改 + 健康日历移至主页 — 设计文档

日期:2026-06-07 · 状态:已定方案(用户授权直接实现,免确认)

1. 背景与目标

当前「趋势」Tab(TrendsView.swift)把两件事混在一起:

  1. 健康日历(月/年视图 + 当日详情)—— 占据页面上半部分。
  2. 长期监测折线图(seriesSection)—— 页面下半部分。

两个问题:

  • 日历放错了地方。它是「总览记录情况」的入口,更适合放在主页(用户每天第一眼看的页面),而不是埋在趋势 Tab 里。
  • 趋势能力太弱SeriesBucket.build 只按 seriesKey 分桶,因此只有 8 个长期监测预设(血压/血糖/体温…)和自定义指标能成图。所有没有 seriesKey 的指标——报告里解析出来的化验项、VL 快拍、自由输入——即使在多份报告里反复出现(如「血红蛋白」体检了 3 次),也完全看不到趋势

目标

  1. 健康日历移到主页:主页新增一张紧凑的「健康日历」卡(当前周的横条 + 本月记录摘要),点击展开完整的月/年总览页(可切月视图/年视图、看当日详情)。
  2. 趋势 Tab 重构:对任何出现 ≥2 次的指标(不限于长期监测预设)做时间序列查看。趋势页变成一个「可成趋势的指标」总览列表(分长期监测 / 化验指标两段),点任一项进入详情页:大图表 + 参考范围带 + 统计摘要(最新/最高/最低/平均/对比上次)+ 时间范围筛选 + 数据点列表(点击跳当日详情)。

非目标(本次不做)

  • AI 趋势解读:需要 AIRuntime + TrendService 跑通,风险大、与本次「时间序列查看」正交。本次预留 UI 位但不接 LLM,留作后续。
  • 不改 SwiftData schema(无 @Model 字段变更,规避迁移丢数据风险)。
  • 不改 Localizable.xcstrings(新文案用 String(appLoc: "中文"),无对应词条时优雅回退到中文 key,符合既有大量用法;避免 xcstrings 噪声 diff)。
  • 不动 TabBar 5 槽骨架、不动录入流程。

2. 架构总览

主页 HomeView
  └─ HomeCalendarCard(自包含 @Query)   ← 新增
        当前周横条 + "本月 N 天有记录" + chevron
        tap → fullScreenCover(CalendarOverviewView)   ← 新增(从 TrendsView 抽出)

趋势 TrendsView(重写)
  └─ TrendSeriesList:两段 section
        ├─ 长期监测(kind=.monitor:seriesKey 分桶,含血压合并/自定义)
        └─ 化验指标趋势(kind=.lab:按 name+unit 分桶,≥2 点)
        每行 TrendRow:名称 + 最新值/状态 + mini sparkline + 条数·跨度
        tap → TrendDetailView(bucket)   ← 新增
              大图表 + 参考范围带 + 时间范围 chips + 统计摘要 + 数据点列表
              数据点 tap → DayDetailSheet(date)(复用)

数据层只扩展 SeriesBucket.build,UI 层新增 4 个文件、改 2 个文件、删 1 段。

3. 数据层:SeriesBucket 扩展

文件:Features/Trends/SeriesBucket.swift(改)

3.1 新增 kind 区分两段

enum SeriesKind { case monitor, lab }   // monitor=长期监测预设/自定义/血压;lab=按名分组的化验项

struct SeriesBucket: Identifiable {
    let id: String
    let title: String
    let unit: String
    let lines: [SeriesLine]
    let latestDate: Date
    let kind: SeriesKind          // 新增
    let sourceIndicatorIDs: [String]   // 新增:本桶包含的 Indicator persistentModelID 字符串,供详情页定位来源
    // ... SeriesLine / Point 不变
}

3.2 build 流程改为两段

  1. seriesKey 段(原逻辑,kind=.monitor):血压合并、单系列预设、自定义。这些桶里的 Indicator 标记为「已消费」。
  2. name 段(新,kind=.lab):对所有没有 seriesKey 的 Indicator,按 normalizedKey(name, unit) 分桶;每桶 ≥ minPoints 才保留。参考范围从该桶最新一条 Indicator 的 range 字符串解析。
  3. 两段合并返回,各自按 latestDate 倒序。详情/列表按 kind 分段。
// name 归一化:trim + 小写 + 折叠内部空白;unit 同样 trim。key = "name|unit"
static func normalizedKey(name: String, unit: String) -> String

// 解析参考范围字符串 → ClosedRange<Double>?
// 支持 "3.9-6.1" / "3.9~6.1" / "3.9 - 6.1";单边("<5.2"/">40"/"≤120")暂返回 nil(图不画带,正常)
static func parseRange(_ raw: String) -> ClosedRange<Double>?

去重:有 seriesKey 的指标只进 monitor 段;无 seriesKey 的只进 lab 段。即使同名也不混。 状态着色:lab 段每个 Point 的 status 直接取 Indicator.status(已由 VL/录入判定),无需重算。

4. UI:健康日历移至主页

4.1 CalendarOverviewView(新文件 Features/Calendar/CalendarOverviewView.swift)

把现 TrendsView 的日历部分原样抽出为独立页:modeSwitch(月/年)+ anchorBar(◀ 年月 ▶)+ calendarBody(CalendarMonthGrid/CalendarYearGrid)+ legend + 月视图下的 dayDetailInline

  • 自带 @Query(indicators/reports/diaries/symptoms/profiles/customMetrics)。
  • 接收可选 initialDate(从主页某天进入时定位选中)。
  • 包在 NavigationStack,标题「健康日历」,右上「完成」关闭(用于 fullScreenCover)。
  • CalendarMonthGrid / CalendarYearGrid / CalendarMarkers / DayDetailSheet 不改,直接复用。

4.2 HomeCalendarCard(新文件 Features/Home/HomeCalendarCard.swift)

自包含组件(对齐 TodayRemindersCard 模式):

  • 自带 @Query,CalendarData.build 计算标记。
  • 当前周横条:周一→周日 7 个紧凑日格(日期数字 + 标记圆点,复用 DayMarks 颜色规则:异常红 / 报告灰 / 正常绿 / 日记浅灰;有进行中症状则该格底色淡 amber)。今天高亮。
  • 顶部标题「健康日历」+ 右侧「本月 N 天有记录 ›」。
  • 整卡可点 → fullScreenCover(CalendarOverviewView());点某一天 → 带 initialDate 进入。
  • 样式走 .tjCard(),放在主页 greeting 之后、TodayRemindersCard 之前。

4.3 HomeView 改动

body 的 VStack 在 greeting 后插入 HomeCalendarCard()。其余不动。

5. UI:趋势 Tab 重构

5.1 TrendsView(重写)

移除所有日历相关代码(已迁到主页)。新结构:

  • header「趋势」。
  • 若无可成趋势的桶 → 空状态(「还没有可成趋势的指标 / 同一指标记录满 2 次后会出现在这里」)。
  • 否则两段:
    • 长期监测(kind == .monitor):标题 + 计数。
    • 化验指标趋势(kind == .lab):标题 + 计数。
    • 每段 ForEach 渲染 TrendRow,点击 push/present TrendDetailView
  • 导航:TrendsViewNavigationStack,行用 NavigationLink 进详情(趋势 Tab 当前无 NavigationStack,新增之)。

5.2 TrendRow(新文件 Features/Trends/TrendRow.swift)

紧凑行:

  • 左:指标名 + 「N 条 · 近 X 个月」副标题。
  • 中:mini sparkline(小号 Chart,height≈36,无坐标轴,单/双线,异常点红)。
  • 右:最新值 + 单位(异常红)+ chevron。
  • .tjCard(bordered: true)

5.3 TrendDetailView(新文件 Features/Trends/TrendDetailView.swift)

接收 bucket: SeriesBucket,自带 @Query 用于数据点→来源跳转。

  • 大图表(height≈220):复用 SeriesChartCard 的绘制逻辑(参考范围带 + catmullRom 折线 + 点 + 双线图例),但加坐标轴、按所选时间范围裁剪 domain。
  • 时间范围 chips:全部 / 近1年 / 近6月 / 近3月(仅当跨度 > 该范围才显示对应 chip)。切换裁剪图表点 + 重算 domain + 重算统计。
  • 统计摘要卡:最新值(带状态)/ 对比上次(Δ 绝对值+百分比+升降箭头,跨参考范围边界标红)/ 最低 / 最高 / 平均 / 记录数 / 时间跨度。文案模板拼装,不走 LLM。
  • AI 解读占位:一行灰字「AI 解读即将上线」(预留,不接 LLM)。
  • 数据点列表(倒序):日期 + 值+单位 + 状态箭头/徽章;onTapGestureDayDetailSheet(date:)(复用现有 sheet,给出当天来源上下文)。
  • 标题 = bucket.title。

血压(双线)在详情页:统计摘要按「收缩/舒张」分别给最新值;列表每行显示「收缩/舒张」两值。

6. 受影响文件清单

新增

  • Features/Calendar/CalendarOverviewView.swift
  • Features/Home/HomeCalendarCard.swift
  • Features/Trends/TrendRow.swift
  • Features/Trends/TrendDetailView.swift

修改

  • Features/Trends/SeriesBucket.swift(加 kind / sourceIndicatorIDs / name 段 / parseRange
  • Features/Trends/TrendsView.swift(删日历,重写为趋势列表 + NavigationStack)
  • Features/Home/HomeView.swift(插入 HomeCalendarCard)
  • 康康.xcodeproj/project.pbxproj(新文件加入 target — 若用 file-system-synchronized group 则免改;需确认)

不改:CalendarMonthGrid/YearGrid/Markers/DayDetailSheetSeriesChartCard(详情页复用其绘制思路,可抽 helper 或直接内置)、Models、xcstrings、RootView/TabBar、录入流程。

7. 验证

  • 构建无错误/无新警告(DEVELOPER_DIR 指完整 Xcode,touch 强制重编 — 见记忆 build-from-cli)。
  • 主页:日历卡显示当前周标记;点卡进总览;月/年切换;点某天→当日详情正确。
  • 趋势:制造同名指标 ≥2 条(如手动录两次「血红蛋白」或两份报告同含一项)→ 出现在「化验指标趋势」段;预设监测仍在「长期监测」段;详情图表/统计/数据点跳转正确;血压双线正常。
  • 空状态:全新库时两个页面都给出友好空态。

8. 风险与回退

  • range 解析覆盖不全:单边区间("<5.2")暂不画带,图仍可用 —— 可接受,后续增强。
  • lab 段噪声:同名但单位不同的指标会分成两桶(key 含 unit)—— 正确行为。若用户名字录入不一致(「血红蛋白」vs「Hb」)会分开 —— demo 可接受,不做模糊归并。
  • pbxproj:若新文件未自动入 target,构建会报 missing symbol;届时手动加 build file 引用。