import SwiftUI import SwiftData struct HomeView: View { var onTapArchive: () -> Void = {} @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] @MainActor private var recentEntries: [TimelineEntry] { let all = TimelineEntry.from(indicators: indicators) + reports.map(TimelineEntry.from(report:)) + diaries.map(TimelineEntry.from(diary:)) + symptoms.map(TimelineEntry.from(symptom:)) return all.sorted { $0.date > $1.date }.prefix(6).map { $0 } } private var recentGrouped: [(section: DateSection, items: [TimelineEntry])] { TimelineGrouping.group(recentEntries) } var body: some View { ScrollView(showsIndicators: false) { VStack(alignment: .leading, spacing: 0) { greeting .padding(.top, 4) .padding(.bottom, 18) OngoingSymptomsCard() .padding(.bottom, 18) recentSection .padding(.bottom, 22) archiveSection } .padding(.horizontal, 20) .padding(.bottom, 20) } .background(Tj.Palette.sand.ignoresSafeArea()) } private var greeting: some View { HStack(alignment: .top) { VStack(alignment: .leading, spacing: 4) { Text(todayLine) .font(.system(size: 12)) .tracking(1) .foregroundStyle(Tj.Palette.text3) Text(greetingWord) .font(.tjTitle()) .foregroundStyle(Tj.Palette.text) } Spacer() TjLockChip() .padding(.top, 4) } } private var todayLine: String { let f = DateFormatter() f.locale = Locale(identifier: "zh_CN") f.dateFormat = "M 月 d 日 · EEE" return f.string(from: Date()) } private var greetingWord: String { switch Calendar.current.component(.hour, from: Date()) { case 5..<12: return "早安" case 12..<18: return "下午好" default: return "晚上好" } } private var recentSection: some View { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .lastTextBaseline) { Text("最近记录").font(.tjH2()).foregroundStyle(Tj.Palette.text) Spacer() Button(action: onTapArchive) { Text("全部 ›") .font(.system(size: 12)) .foregroundStyle(Tj.Palette.text3) } .buttonStyle(.plain) } if recentEntries.isEmpty { emptyRecent } else { VStack(alignment: .leading, spacing: 14) { ForEach(recentGrouped, id: \.section) { group in VStack(alignment: .leading, spacing: 8) { Text(group.section.label) .font(.system(size: 11, weight: .semibold)) .tracking(0.5) .foregroundStyle(Tj.Palette.text3) VStack(spacing: 10) { ForEach(group.items) { entry in TimelineRow(entry: entry) } } } } } } } } private var emptyRecent: some View { HStack { Text("还没有任何记录,点底部 + 号开始第一条") .font(.system(size: 13)) .foregroundStyle(Tj.Palette.text3) Spacer() } .padding(.vertical, 14) .padding(.horizontal, 16) .tjCard(bordered: true) } private var archiveSection: some View { VStack(alignment: .leading, spacing: 10) { Text("影像档案").font(.tjH2()).foregroundStyle(Tj.Palette.text) Button(action: onTapArchive) { HStack(spacing: 14) { TjPlaceholder(label: "档案 · \(reports.count)") .frame(width: 56, height: 56) VStack(alignment: .leading, spacing: 2) { Text("我的报告档案") .font(.system(size: 14, weight: .semibold)) .foregroundStyle(Tj.Palette.text) Text("\(reports.count) 份 · \(indicators.count) 项指标 · 端侧加密") .font(.system(size: 11)) .foregroundStyle(Tj.Palette.text3) } Spacer() Image(systemName: "chevron.right") .font(.system(size: 14, weight: .medium)) .foregroundStyle(Tj.Palette.text3) } .padding(14) .tjCard(bordered: true) } .buttonStyle(.plain) } } } #Preview { HomeView() }