From 5f8f492f0e7949d66dcd5d17b881224ca3701acb Mon Sep 17 00:00:00 2001 From: link2026 Date: Tue, 26 May 2026 19:47:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(indicator):=20=E9=95=BF=E6=9C=9F=E7=9B=91?= =?UTF-8?q?=E6=B5=8B=E9=A2=84=E8=AE=BE=E6=94=AF=E6=8C=81=E9=95=BF=E6=8C=89?= =?UTF-8?q?=E9=9A=90=E8=97=8F=20+=20=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UserProfile 加 hiddenPresetMetrics: [String],存被隐藏的 MonitorMetric.rawValue - IndicatorQuickSheet monitorTile 加 contextMenu 隐藏入口 - section label 右侧"已隐藏 N 个 ›"chip 触发 HiddenMonitorRestoreSheet - 纯 UI 过滤,不动 Indicator 历史 / Trends 折线 / MetricReminder Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Indicator/IndicatorQuickSheet.swift | 158 +++++++++++++++++- 康康/Models/UserProfile.swift | 7 + 2 files changed, 163 insertions(+), 2 deletions(-) diff --git a/康康/Features/Indicator/IndicatorQuickSheet.swift b/康康/Features/Indicator/IndicatorQuickSheet.swift index 627458a..2a7988e 100644 --- a/康康/Features/Indicator/IndicatorQuickSheet.swift +++ b/康康/Features/Indicator/IndicatorQuickSheet.swift @@ -62,6 +62,9 @@ struct IndicatorQuickSheet: View { @State private var selectedCustom: CustomMonitorMetric? @State private var editingCustom: CustomMetricEditTarget? + // 隐藏管理 sheet 触发态 + @State private var showHiddenSheet: Bool = false + private static var defaultReminderTime: Date { Calendar.current.date(bySettingHour: 8, minute: 0, second: 0, of: .now) ?? .now } @@ -167,10 +170,16 @@ struct IndicatorQuickSheet: View { private var monitorGridSection: some View { VStack(alignment: .leading, spacing: 8) { - sectionLabel("长期监测(进趋势)") + HStack { + sectionLabel("长期监测(进趋势)") + Spacer() + if !hiddenSet.isEmpty { + hiddenCountChip + } + } let columns = [GridItem(.flexible()), GridItem(.flexible())] LazyVGrid(columns: columns, spacing: 8) { - ForEach(MonitorMetric.allCases) { m in + ForEach(visibleMonitorMetrics) { m in monitorTile(m) } ForEach(customMetrics) { cm in @@ -179,6 +188,12 @@ struct IndicatorQuickSheet: View { addCustomTile } } + .sheet(isPresented: $showHiddenSheet) { + HiddenMonitorRestoreSheet( + hiddenMetrics: hiddenMonitorMetrics, + onRestore: { unhideMonitor($0) } + ) + } .sheet(item: $editingCustom) { target in CustomMetricEditor(existing: target.metric) { saved in // 新建后自动选中,删除后清空选择 @@ -303,6 +318,13 @@ struct IndicatorQuickSheet: View { ) } .buttonStyle(.plain) + .contextMenu { + Button(role: .destructive) { + hideMonitor(m) + } label: { + Label("隐藏", systemImage: "eye.slash") + } + } } private var labPresetSection: some View { @@ -785,6 +807,59 @@ struct IndicatorQuickSheet: View { return "\(fmt(r.lowerBound))–\(fmt(r.upperBound))" } + // MARK: - hidden preset 管理 + + private var hiddenSet: Set { + Set(profile?.hiddenPresetMetrics ?? []) + } + + private var visibleMonitorMetrics: [MonitorMetric] { + MonitorMetric.allCases.filter { !hiddenSet.contains($0.rawValue) } + } + + private var hiddenMonitorMetrics: [MonitorMetric] { + MonitorMetric.allCases.filter { hiddenSet.contains($0.rawValue) } + } + + private var hiddenCountChip: some View { + Button { + showHiddenSheet = true + } label: { + HStack(spacing: 3) { + Text("已隐藏 \(hiddenSet.count)") + .font(.system(size: 11, weight: .medium)) + Image(systemName: "chevron.right") + .font(.system(size: 9, weight: .semibold)) + } + .foregroundStyle(Tj.Palette.text2) + .padding(.horizontal, 10) + .padding(.vertical, 4) + .background(Capsule().fill(Tj.Palette.sand2)) + } + .buttonStyle(.plain) + } + + private func hideMonitor(_ m: MonitorMetric) { + let profile = UserProfileStore.loadOrCreate(in: ctx) + guard !profile.hiddenPresetMetrics.contains(m.rawValue) else { return } + profile.hiddenPresetMetrics.append(m.rawValue) + profile.updatedAt = .now + try? ctx.save() + if selectedMonitor == m { + clearMonitor() + } + } + + private func unhideMonitor(_ m: MonitorMetric) { + guard let profile = profile else { return } + profile.hiddenPresetMetrics.removeAll { $0 == m.rawValue } + profile.updatedAt = .now + try? ctx.save() + if profile.hiddenPresetMetrics.isEmpty { + showHiddenSheet = false + } + } + // MARK: - apply preset private func applyMonitor(_ m: MonitorMetric) { @@ -1020,6 +1095,85 @@ struct CustomMetricEditTarget: Identifiable { var id: String { metric?.seriesKey ?? "_new_" } } +/// 已隐藏的长期监测预设恢复列表。点"显示"把对应 metric 从 hiddenPresetMetrics 移除。 +private struct HiddenMonitorRestoreSheet: View { + let hiddenMetrics: [MonitorMetric] + let onRestore: (MonitorMetric) -> Void + + @Environment(\.dismiss) private var dismiss + + var body: some View { + VStack(spacing: 0) { + Capsule() + .fill(Tj.Palette.line) + .frame(width: 40, height: 4) + .padding(.top, 10) + .padding(.bottom, 14) + + HStack { + Text("已隐藏的长期监测") + .font(.tjH2()) + .foregroundStyle(Tj.Palette.text) + Spacer() + Button("完成") { dismiss() } + .font(.system(size: 14)) + .foregroundStyle(Tj.Palette.ink) + } + .padding(.horizontal, 20) + .padding(.bottom, 12) + + ScrollView { + VStack(spacing: 8) { + ForEach(hiddenMetrics) { m in + row(m) + } + } + .padding(.horizontal, 20) + .padding(.bottom, 24) + } + } + .background(Tj.Palette.sand) + .presentationDetents([.medium]) + .presentationBackground(Tj.Palette.sand) + .presentationCornerRadius(Tj.Radius.xl) + } + + private func row(_ m: MonitorMetric) -> some View { + HStack(spacing: 12) { + Image(systemName: m.icon) + .font(.system(size: 16, weight: .medium)) + .foregroundStyle(Tj.Palette.ink) + .frame(width: 32, height: 32) + .background(Circle().fill(Tj.Palette.amber.opacity(0.25))) + + Text(m.displayName) + .font(.system(size: 15, weight: .medium)) + .foregroundStyle(Tj.Palette.text) + + Spacer() + + Button("显示") { + onRestore(m) + } + .font(.system(size: 13, weight: .semibold)) + .foregroundStyle(Tj.Palette.paper) + .padding(.horizontal, 14) + .padding(.vertical, 6) + .background(Capsule().fill(Tj.Palette.ink)) + } + .padding(.horizontal, 12) + .padding(.vertical, 10) + .background( + RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) + .fill(Tj.Palette.paper) + ) + .overlay( + RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) + .strokeBorder(Tj.Palette.line, lineWidth: 1) + ) + } +} + #Preview { IndicatorQuickSheet() .modelContainer(for: [ diff --git a/康康/Models/UserProfile.swift b/康康/Models/UserProfile.swift index e0d855c..6406a3e 100644 --- a/康康/Models/UserProfile.swift +++ b/康康/Models/UserProfile.swift @@ -18,6 +18,11 @@ final class UserProfile { // —— 当前用药 —— var currentMedications: [String] + // —— UI 偏好 —— + // 用户在 IndicatorQuickSheet 长按隐藏的 MonitorMetric.rawValue。 + // 只影响录入 grid,不影响 Indicator 历史 / Trends / Reminder。 + var hiddenPresetMetrics: [String] + var updatedAt: Date init(birthYear: Int? = nil, @@ -29,6 +34,7 @@ final class UserProfile { chronicConditions: [String] = [], familyHistory: [String] = [], currentMedications: [String] = [], + hiddenPresetMetrics: [String] = [], updatedAt: Date = .now) { self.birthYear = birthYear self.biologicalSexRaw = biologicalSexRaw @@ -39,6 +45,7 @@ final class UserProfile { self.chronicConditions = chronicConditions self.familyHistory = familyHistory self.currentMedications = currentMedications + self.hiddenPresetMetrics = hiddenPresetMetrics self.updatedAt = updatedAt } }