feat(ui): UI 骨架基线 — 3 Tab + RecordSheet + Quick/Archive 流程占位

替换 Xcode 默认模板:
- 删除 ContentView/Item/__App
- 新增 App/TijiApp(SwiftData ModelContainer)、RootView(3 Tab + RecordSheet)
- DesignSystem:Tokens(色板/字体/圆角)+ Components(卡片/按钮/Chip)
- Models:Indicator / Report / DiaryEntry @Model 初版
- Features:Home / Quick(A1-A3)/ Archive(B1-B5)/ Record / Trends / Me 静态 UI

W2 AI 基座工作将在此基线上叠加。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
link2026
2026-05-25 14:49:21 +08:00
parent 96cae73d8f
commit c050865db5
23 changed files with 2586 additions and 97 deletions

View File

@@ -0,0 +1,124 @@
import SwiftUI
struct A3BatchItem {
let name: String
let value: String
let unit: String
let range: String
let status: IndicatorStatus
}
struct A3BatchView: View {
var onAddMore: () -> Void
var onFinish: () -> Void
var onBack: () -> Void
let items: [A3BatchItem] = [
.init(name: "低密度脂蛋白胆固醇", value: "3.84", unit: "mmol/L", range: "< 3.40", status: .high),
.init(name: "甘油三酯 TG", value: "1.78", unit: "mmol/L", range: "< 1.70", status: .high),
.init(name: "空腹血糖 GLU", value: "5.4", unit: "mmol/L", range: "3.96.1", status: .normal),
]
var body: some View {
VStack(spacing: 0) {
header
ScrollView(showsIndicators: false) {
VStack(spacing: 10) {
ForEach(Array(items.enumerated()), id: \.offset) { idx, it in
BatchRow(index: idx + 1, item: it)
}
addRow
}
.padding(.horizontal, 16)
.padding(.bottom, 16)
}
HStack(spacing: 10) {
Button {
onFinish()
} label: {
Text("全部保存(\(items.count)").frame(maxWidth: .infinity)
}
.buttonStyle(TjPrimaryButton())
}
.padding(.horizontal, 16)
.padding(.bottom, 14)
}
.background(Tj.Palette.sand.ignoresSafeArea())
}
private var header: some View {
HStack(spacing: 6) {
Button(action: onBack) {
Image(systemName: "chevron.left")
.font(.system(size: 18, weight: .semibold))
.foregroundStyle(Tj.Palette.text)
.frame(width: 36, height: 36)
}
VStack(alignment: .leading, spacing: 2) {
Text("本次已记录 \(items.count)")
.font(.system(size: 15, weight: .semibold))
.foregroundStyle(Tj.Palette.text)
Text("核对后一次保存")
.font(.system(size: 11))
.foregroundStyle(Tj.Palette.text3)
}
Spacer()
Text("· · ·")
.font(.system(size: 14, design: .monospaced))
.foregroundStyle(Tj.Palette.text3)
.padding(.trailing, 12)
}
.padding(.horizontal, 12)
.padding(.top, 4)
.padding(.bottom, 12)
}
private var addRow: some View {
Button(action: onAddMore) {
HStack(spacing: 8) {
Image(systemName: "camera").font(.system(size: 14))
Text("再拍一项")
.font(.system(size: 13))
}
.foregroundStyle(Tj.Palette.text3)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.overlay(
RoundedRectangle(cornerRadius: Tj.Radius.md, style: .continuous)
.strokeBorder(Tj.Palette.line, style: StrokeStyle(lineWidth: 1.5, dash: [4, 4]))
)
}
.buttonStyle(.plain)
}
}
private struct BatchRow: View {
let index: Int
let item: A3BatchItem
var body: some View {
HStack(spacing: 12) {
TjPlaceholder(label: "#\(index)")
.frame(width: 60, height: 44)
VStack(alignment: .leading, spacing: 2) {
Text(item.name)
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(Tj.Palette.text)
.lineLimit(1)
Text("范围 \(item.range) \(item.unit)")
.font(.system(size: 11))
.foregroundStyle(Tj.Palette.text3)
}
Spacer(minLength: 8)
VStack(alignment: .trailing, spacing: 2) {
Text(item.value)
.font(.system(size: 17, weight: .semibold))
.foregroundStyle(item.status == .high ? Tj.Palette.brick : Tj.Palette.text)
TjBadge(text: item.status == .high ? "偏高" : "正常",
style: item.status == .high ? .brick : .leaf)
}
}
.padding(12)
.tjCard()
}
}