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

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

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

155 lines
5.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
import SwiftData
/// ·
/// MeView ; / / /
struct CustomMetricsListView: View {
@Query(sort: \CustomMonitorMetric.createdAt, order: .reverse)
private var metrics: [CustomMonitorMetric]
@Query private var indicators: [Indicator]
@State private var editingTarget: CustomMetricEditTarget?
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
hintBanner
if metrics.isEmpty {
emptyState
} else {
// seriesKey 使(O(indicators)), O(1) ,
// O(metrics × indicators) N+1
let usageCounts: [String: Int] = indicators.reduce(into: [:]) { acc, ind in
if let key = ind.seriesKey, !key.isEmpty { acc[key, default: 0] += 1 }
}
ForEach(metrics) { m in
Button {
editingTarget = CustomMetricEditTarget(metric: m)
} label: {
row(m, usage: usageCounts[m.seriesKey] ?? 0)
}
.buttonStyle(.plain)
}
}
}
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 32)
}
.background(Tj.Palette.sand.ignoresSafeArea())
.navigationTitle("自定义指标")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
editingTarget = CustomMetricEditTarget(metric: nil)
} label: {
Image(systemName: "plus")
.font(.tjScaled( 16, weight: .semibold))
}
}
}
.sheet(item: $editingTarget) { target in
CustomMetricEditor(existing: target.metric) { _ in }
}
}
// MARK: - subviews
private var hintBanner: some View {
HStack(spacing: 10) {
Image(systemName: "info.circle.fill")
.foregroundStyle(Tj.Palette.text3)
Text("自定义指标会出现在「+ 指标记录 → 长期监测」的 grid 里,可设提醒、进趋势")
.font(.tjScaled( 12))
.foregroundStyle(Tj.Palette.text2)
.fixedSize(horizontal: false, vertical: true)
Spacer(minLength: 0)
}
.padding(12)
.background(
RoundedRectangle(cornerRadius: Tj.Radius.sm, style: .continuous)
.fill(Tj.Palette.sand2.opacity(0.5))
)
}
private var emptyState: some View {
VStack(spacing: 14) {
Spacer(minLength: 40)
TjPlaceholder(label: String(appLoc: "还没有自定义指标"))
.frame(width: 220, height: 130)
Text("右上角 + 新建一个")
.font(.tjScaled( 12))
.foregroundStyle(Tj.Palette.text3)
Spacer()
}
.frame(maxWidth: .infinity)
}
private func row(_ m: CustomMonitorMetric, usage count: Int) -> some View {
HStack(spacing: 12) {
ZStack {
Circle().fill(Tj.Palette.leafSoft)
Image(systemName: m.icon)
.font(.tjScaled( 17, weight: .medium))
.foregroundStyle(Tj.Palette.ink)
}
.frame(width: 40, height: 40)
VStack(alignment: .leading, spacing: 3) {
Text(m.name)
.font(.tjScaled( 15, weight: .semibold))
.foregroundStyle(Tj.Palette.text)
.lineLimit(1)
HStack(spacing: 6) {
if !m.unit.isEmpty {
Text(m.unit)
.font(.tjScaled( 11, design: .monospaced))
.foregroundStyle(Tj.Palette.text3)
}
if !m.rangeText.isEmpty {
Text("·")
.font(.tjScaled( 11))
.foregroundStyle(Tj.Palette.text3)
Text(m.rangeText)
.font(.tjScaled( 11, design: .monospaced))
.foregroundStyle(Tj.Palette.text3)
}
}
}
Spacer(minLength: 8)
VStack(alignment: .trailing, spacing: 2) {
Text(count == 0 ? String(appLoc: "未使用") : String(appLoc: "\(count)"))
.font(.tjScaled( 11, weight: count > 0 ? .semibold : .regular))
.foregroundStyle(count > 0 ? Tj.Palette.ink : Tj.Palette.text3)
Image(systemName: "chevron.right")
.font(.tjScaled( 11, weight: .medium))
.foregroundStyle(Tj.Palette.text3)
}
}
.padding(14)
.background(
RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
.fill(Tj.Palette.paper)
)
.overlay(
RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
.strokeBorder(Tj.Palette.lineSoft, lineWidth: 1)
)
}
}
#Preview {
NavigationStack {
CustomMetricsListView()
}
.modelContainer(for: [
CustomMonitorMetric.self, Indicator.self,
UserProfile.self, MetricReminder.self,
], inMemory: true)
}