Files
kangkang/康康/Features/Trends/TrendsView.swift
link2026 de19d7abcd 根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
```
docs(readme): 更新文档说明

- 添加了项目使用指南
- 完善了API接口说明
- 修正了一些文字错误
```

注:由于未提供具体的代码差异信息,以上为示例格式。请提供具体的代码变更内容以便生成准确的commit message。
2026-06-17 08:35:59 +08:00

179 lines
6.5 KiB
Swift

import SwiftUI
import SwiftData
/// Tab;:
/// 2 ,(seriesKey)()
struct TrendsView: View {
@Query(sort: \Indicator.capturedAt, order: .reverse)
private var indicators: [Indicator]
@Query private var profiles: [UserProfile]
@Query private var customMetrics: [CustomMonitorMetric]
private var profile: UserProfile? { profiles.first }
/// :,(bucket.title)
@State private var searching = false
@State private var query = ""
private var seriesBuckets: [SeriesBucket] {
SeriesBucket.build(from: indicators,
profile: profile,
customMetrics: customMetrics)
}
private func filtered(_ buckets: [SeriesBucket]) -> [SeriesBucket] {
let q = query.trimmingCharacters(in: .whitespaces)
guard !q.isEmpty else { return buckets }
return buckets.filter { $0.title.localizedCaseInsensitiveContains(q) }
}
var body: some View {
// SeriesBucket.build ,monitor / lab /
// () build ~7
let series = seriesBuckets
let monitor = filtered(series.filter { $0.kind == .monitor })
let lab = filtered(series.filter { $0.kind == .lab })
return NavigationStack {
ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 18) {
header.padding(.top, 4)
if series.isEmpty {
emptyState
} else if monitor.isEmpty && lab.isEmpty {
noMatchState
} else {
if !monitor.isEmpty {
section(title: String(appLoc: "长期监测"), buckets: monitor)
}
if !lab.isEmpty {
section(title: String(appLoc: "化验指标趋势"), buckets: lab)
}
}
}
.padding(.horizontal, 20)
.padding(.bottom, 24)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(Tj.Palette.sand.ignoresSafeArea())
.navigationBarHidden(true)
}
}
private var header: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .lastTextBaseline) {
Text("趋势")
.font(.tjTitle(26))
.foregroundStyle(Tj.Palette.text)
Spacer()
searchToggle
}
if searching { searchField }
}
}
private var searchToggle: some View {
Button {
withAnimation(.easeInOut(duration: 0.18)) {
searching.toggle()
if !searching { query = "" }
}
} label: {
Image(systemName: searching ? "xmark" : "magnifyingglass")
.font(.tjScaled( 15, weight: .semibold))
.foregroundStyle(Tj.Palette.text)
.frame(width: 36, height: 36)
.background(Circle().fill(Tj.Palette.sand2))
}
.buttonStyle(.plain)
.accessibilityLabel(searching ? String(appLoc: "关闭搜索") : String(appLoc: "搜索指标"))
}
private var searchField: some View {
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
.font(.tjScaled( 13))
.foregroundStyle(Tj.Palette.text3)
TextField(String(appLoc: "搜索指标名"), text: $query)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.foregroundStyle(Tj.Palette.text)
.tint(Tj.Palette.ink)
if !query.isEmpty {
Button { query = "" } label: {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Tj.Palette.text3)
}
.buttonStyle(.plain)
}
}
.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)
)
}
private var noMatchState: some View {
VStack(spacing: 12) {
TjPlaceholder(label: String(appLoc: "没有匹配「\(query)」的指标"))
.frame(height: 120)
.frame(maxWidth: 260)
}
.frame(maxWidth: .infinity)
.padding(.top, 60)
}
private func section(title: String, buckets: [SeriesBucket]) -> some View {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .lastTextBaseline) {
Text(title)
.font(.tjH2())
.foregroundStyle(Tj.Palette.text)
Text("\(buckets.count)")
.font(.tjScaled( 12))
.foregroundStyle(Tj.Palette.text3)
Spacer()
}
VStack(spacing: 12) {
ForEach(buckets) { bucket in
NavigationLink {
TrendDetailView(bucket: bucket)
} label: {
TrendRow(bucket: bucket)
}
.buttonStyle(.plain)
}
}
}
}
private var emptyState: some View {
VStack(spacing: 12) {
TjPlaceholder(label: String(appLoc: "还没有可成趋势的指标"))
.frame(height: 120)
.frame(maxWidth: 260)
Text("同一指标记录满 2 次后,会在这里出现时间序列")
.font(.tjScaled( 12))
.foregroundStyle(Tj.Palette.text3)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity)
.padding(.top, 60)
}
}
#Preview {
TrendsView()
.modelContainer(for: [
Indicator.self, Report.self, DiaryEntry.self, Symptom.self, Asset.self
], inMemory: true)
}