import SwiftUI import SwiftData import Combine struct OngoingSymptomsCard: View { @Query(filter: #Predicate { $0.endedAt == nil }, sort: \Symptom.startedAt, order: .reverse) private var ongoing: [Symptom] @State private var ending: Symptom? @State private var tick: Date = .now private let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() var body: some View { if ongoing.isEmpty { EmptyView() } else { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 8) { Circle() .fill(Tj.Palette.brick) .frame(width: 7, height: 7) Text("持续中") .font(.tjH2()) .foregroundStyle(Tj.Palette.text) Text("\(ongoing.count) 个") .font(.system(size: 12)) .foregroundStyle(Tj.Palette.text3) Spacer() } VStack(spacing: 8) { ForEach(ongoing) { sym in row(sym) } } } .onReceive(timer) { now in tick = now } .sheet(item: $ending) { sym in SymptomEndSheet(symptom: sym) } } } private func row(_ sym: Symptom) -> some View { let interval = max(0, tick.timeIntervalSince(sym.startedAt)) let isLong = interval >= 3 * 24 * 3600 return HStack(spacing: 12) { VStack(alignment: .leading, spacing: 4) { HStack(spacing: 8) { Text(sym.name) .font(.system(size: 15, weight: .semibold)) .foregroundStyle(Tj.Palette.text) severityDot(sym.severity) } Text("已持续 \(formatDuration(interval))") .font(.system(size: 12)) .foregroundStyle(isLong ? Tj.Palette.brick : Tj.Palette.text3) } Spacer(minLength: 8) Button { ending = sym } label: { Text("结束") .font(.system(size: 12, weight: .semibold)) .foregroundStyle(Tj.Palette.text) .padding(.horizontal, 12) .padding(.vertical, 6) .background( Capsule().fill(Tj.Palette.sand2) ) } .buttonStyle(.plain) } .padding(.horizontal, 14) .padding(.vertical, 12) .background( RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous) .fill(Tj.Palette.paper) .overlay(alignment: .leading) { Rectangle() .fill(severityColor(sym.severity)) .frame(width: 3) .clipShape( UnevenRoundedRectangle( topLeadingRadius: Tj.Radius.sm, bottomLeadingRadius: Tj.Radius.sm, bottomTrailingRadius: 0, topTrailingRadius: 0 ) ) } ) .shadow(color: Color(red: 0.196, green: 0.157, blue: 0.098).opacity(0.04), radius: 2, x: 0, y: 1) } private func severityDot(_ value: Int) -> some View { HStack(spacing: 2) { ForEach(1...5, id: \.self) { i in Circle() .fill(i <= value ? severityColor(value) : Tj.Palette.line) .frame(width: 5, height: 5) } } } private func severityColor(_ value: Int) -> Color { switch value { case 1, 2: return Tj.Palette.leaf case 3: return Tj.Palette.amber default: return Tj.Palette.brick } } }