import SwiftUI import SwiftData /// 主页「健康日历」卡:当前一周横条 + 本月记录摘要。 /// 点整卡或某一天 → 打开 CalendarOverviewView 看月/年总览。自包含 @Query(对齐 TodayRemindersCard)。 struct HomeCalendarCard: View { @Query(sort: \Indicator.capturedAt, order: .reverse) private var indicators: [Indicator] @Query(sort: \Report.reportDate, order: .reverse) private var reports: [Report] @Query(sort: \DiaryEntry.createdAt, order: .reverse) private var diaries: [DiaryEntry] @Query(sort: \Symptom.startedAt, order: .reverse) private var symptoms: [Symptom] /// 打开总览时定位的日期(nil = 不展示)。 @State private var openDay: SelectedDay? private let calendar: Calendar = { var c = Calendar(identifier: .gregorian) c.firstWeekday = 2 c.locale = Locale.current return c }() @MainActor private var data: CalendarData { CalendarData.build( indicators: indicators, reports: reports, diaries: diaries, symptoms: symptoms ) } /// 本周一 → 本周日。 private var weekDays: [Date] { let today = calendar.startOfDay(for: .now) let weekdayIndex = (calendar.component(.weekday, from: today) - calendar.firstWeekday + 7) % 7 guard let monday = calendar.date(byAdding: .day, value: -weekdayIndex, to: today) else { return [] } return (0..<7).compactMap { calendar.date(byAdding: .day, value: $0, to: monday) } } /// 本月有记录的天数(指标/报告/日记/症状任一)。 private var daysWithRecordsThisMonth: Int { guard let interval = calendar.dateInterval(of: .month, for: .now) else { return 0 } let count = calendar.range(of: .day, in: .month, for: .now)?.count ?? 30 var n = 0 for i in 0.. 0 ? String(appLoc: "本月 \(n) 天有记录") : String(appLoc: "本月暂无记录") } private var weekStrip: some View { HStack(spacing: 6) { ForEach(weekDays, id: \.self) { day in dayCell(day) } } } private func dayCell(_ day: Date) -> some View { let marks = data.marks(for: day, calendar: calendar) let ranges = data.ranges(touching: day, calendar: calendar) let isToday = calendar.isDateInToday(day) let hasSymptom = !ranges.isEmpty return Button { openDay = SelectedDay(date: day) } label: { VStack(spacing: 5) { Text(weekdayLabel(day)) .font(.tjScaled( 10, weight: .medium)) .foregroundStyle(Tj.Palette.text3) ZStack { RoundedRectangle(cornerRadius: 9, style: .continuous) .fill(cellFill(isToday: isToday, hasSymptom: hasSymptom)) if isToday { RoundedRectangle(cornerRadius: 9, style: .continuous) .strokeBorder(Tj.Palette.ink, lineWidth: 1.2) } Text("\(calendar.component(.day, from: day))") .font(.tjScaled( 14, weight: isToday ? .bold : .regular)) .foregroundStyle(isToday ? Tj.Palette.ink : Tj.Palette.text) } .frame(height: 38) marksDots(marks) .frame(height: 5) } .frame(maxWidth: .infinity) .contentShape(Rectangle()) } .buttonStyle(.plain) } @ViewBuilder private func marksDots(_ marks: DayMarks) -> some View { HStack(spacing: 2) { if marks.abnormalCount > 0 { dot(Tj.Palette.brick) } else if marks.normalCount > 0 { dot(Tj.Palette.leaf) } if marks.reportCount > 0 { dot(Tj.Palette.ink2) } if marks.diaryCount > 0 { dot(Tj.Palette.text3.opacity(0.7)) } } } private func dot(_ color: Color) -> some View { Circle().fill(color).frame(width: 4, height: 4) } private func cellFill(isToday: Bool, hasSymptom: Bool) -> Color { if hasSymptom { return Tj.Palette.amber.opacity(0.18) } if isToday { return Tj.Palette.sand2 } return Tj.Palette.sand2.opacity(0.5) } private func weekdayLabel(_ day: Date) -> String { let labels = [ String(appLoc: "一"), String(appLoc: "二"), String(appLoc: "三"), String(appLoc: "四"), String(appLoc: "五"), String(appLoc: "六"), String(appLoc: "日") ] let idx = (calendar.component(.weekday, from: day) - calendar.firstWeekday + 7) % 7 return labels[idx] } }