Files
kangkang/康康/Features/Trends/CalendarYearGrid.swift
link2026 77a4ee1c37 缺少代码差异信息,无法生成具体的commit message。请提供code differences内容以便分析并生成符合Angular规范的提交信息。
当您提供代码差异后,我将按照以下格式生成:

```
<type>(<scope>): <subject>

<body>
```

其中type会根据更改类型选择(feat、fix、docs、style、refactor等),scope表示影响范围,subject简要描述变更内容,body详细说明修改内容。
2026-06-07 14:17:18 +08:00

113 lines
3.8 KiB
Swift

import SwiftUI
struct CalendarYearGrid: View {
let year: Int
let data: CalendarData
let onTapMonth: (Date) -> Void
private let calendar: Calendar = {
var c = Calendar(identifier: .gregorian)
c.firstWeekday = 2
c.locale = Locale.current
return c
}()
private var monthAnchors: [Date] {
(1...12).compactMap { m in
var comps = DateComponents()
comps.year = year; comps.month = m; comps.day = 1
return calendar.date(from: comps)
}
}
private let columns = Array(repeating: GridItem(.flexible(), spacing: 14), count: 3)
var body: some View {
LazyVGrid(columns: columns, spacing: 18) {
ForEach(monthAnchors, id: \.self) { anchor in
Button {
onTapMonth(anchor)
} label: {
MiniMonth(anchor: anchor, data: data, calendar: calendar)
}
.buttonStyle(.plain)
}
}
}
}
private struct MiniMonth: View {
let anchor: Date
let data: CalendarData
let calendar: Calendar
private var monthLabel: String {
anchor.formatted(.dateTime.month())
}
private var days: [Date] {
guard let interval = calendar.dateInterval(of: .month, for: anchor) else { return [] }
// range(of:.day,in:.month) , DST 1
let count = calendar.range(of: .day, in: .month, for: anchor)?.count ?? 30
return (0..<count).compactMap { calendar.date(byAdding: .day, value: $0, to: interval.start) }
}
private var leadingPadding: Int {
guard let first = days.first else { return 0 }
return (calendar.component(.weekday, from: first) - calendar.firstWeekday + 7) % 7
}
private let microColumns = Array(repeating: GridItem(.flexible(), spacing: 2), count: 7)
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(monthLabel)
.font(.tjScaled( 12, weight: .semibold))
.foregroundStyle(Tj.Palette.text)
LazyVGrid(columns: microColumns, spacing: 2) {
ForEach(0..<leadingPadding, id: \.self) { _ in
Color.clear.frame(height: 8)
}
ForEach(days, id: \.self) { d in
dot(for: d)
}
}
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
.fill(Tj.Palette.paper)
)
.overlay(
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
.strokeBorder(Tj.Palette.lineSoft, lineWidth: 1)
)
}
private func dot(for date: Date) -> some View {
let marks = data.marks(for: date, calendar: calendar)
let ranges = data.ranges(touching: date, calendar: calendar)
let color: Color = {
if marks.abnormalCount > 0 { return Tj.Palette.brick }
if let topSeverity = ranges.map(\.severity).max() {
switch topSeverity {
case 1, 2: return Tj.Palette.leaf
case 3: return Tj.Palette.amber
default: return Tj.Palette.brick
}
}
if marks.hasAnyEvent { return Tj.Palette.text3.opacity(0.6) }
return Tj.Palette.lineSoft
}()
let isToday = calendar.isDateInToday(date)
return RoundedRectangle(cornerRadius: 2, style: .continuous)
.fill(color)
.frame(height: 8)
.overlay(
RoundedRectangle(cornerRadius: 2, style: .continuous)
.strokeBorder(Tj.Palette.ink, lineWidth: isToday ? 1 : 0)
)
}
}