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

151
体己/RootView.swift Normal file
View File

@@ -0,0 +1,151 @@
import SwiftUI
enum TjTab: String, Hashable, CaseIterable {
case home, trend, me
var label: String {
switch self {
case .home: return "首页"
case .trend: return "趋势"
case .me: return "我的"
}
}
var icon: String {
switch self {
case .home: return "house"
case .trend: return "chart.line.uptrend.xyaxis"
case .me: return "person.circle"
}
}
}
enum ActiveFlow: Identifiable {
case quick, archive
var id: String { String(describing: self) }
}
struct RootView: View {
@State private var tab: TjTab = .home
@State private var showRecordSheet = false
@State private var activeFlow: ActiveFlow?
var body: some View {
VStack(spacing: 0) {
Group {
switch tab {
case .home: HomeView(onTapArchive: {})
case .trend: TrendsView()
case .me: MeView()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
TabBar(active: tab,
onTap: { tab = $0 },
onTapRecord: { showRecordSheet = true })
}
.background(Tj.Palette.sand.ignoresSafeArea())
.sheet(isPresented: $showRecordSheet) {
RecordSheet { kind in
showRecordSheet = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
switch kind {
case .quick: activeFlow = .quick
case .archive: activeFlow = .archive
case .diary: break
}
}
}
}
#if os(iOS)
.fullScreenCover(item: $activeFlow) { flow in
switch flow {
case .quick:
QuickCaptureFlow(onClose: { activeFlow = nil })
case .archive:
ArchiveFlow(onClose: { activeFlow = nil })
}
}
#else
.sheet(item: $activeFlow) { flow in
switch flow {
case .quick:
QuickCaptureFlow(onClose: { activeFlow = nil })
case .archive:
ArchiveFlow(onClose: { activeFlow = nil })
}
}
#endif
}
}
private struct TabBar: View {
let active: TjTab
let onTap: (TjTab) -> Void
let onTapRecord: () -> Void
var body: some View {
HStack(spacing: 0) {
tabItem(.home)
Color.clear.frame(width: 60, height: 1)
tabItem(.trend)
tabItem(.me)
}
.padding(.horizontal, 12)
.padding(.top, 10)
.padding(.bottom, 4)
.background(
Tj.Palette.paper
.overlay(alignment: .top) {
Rectangle()
.fill(Tj.Palette.lineSoft)
.frame(height: 1)
}
)
.overlay(alignment: .top) {
recordButton.offset(y: -22)
}
}
private func tabItem(_ t: TjTab) -> some View {
Button { onTap(t) } label: {
VStack(spacing: 4) {
Image(systemName: t.icon)
.font(.system(size: 20, weight: .regular))
.frame(width: 26, height: 26)
Text(t.label)
.font(.system(size: 11))
}
.foregroundStyle(active == t ? Tj.Palette.ink : Tj.Palette.text3)
.frame(maxWidth: .infinity)
.padding(.vertical, 4)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
private var recordButton: some View {
Button(action: onTapRecord) {
VStack(spacing: 4) {
ZStack {
Circle()
.fill(Tj.Palette.ink)
.shadow(color: Color(red: 0.157, green: 0.216, blue: 0.176).opacity(0.25),
radius: 12, x: 0, y: 4)
Image(systemName: "plus")
.font(.system(size: 20, weight: .semibold))
.foregroundStyle(Tj.Palette.paper)
}
.frame(width: 52, height: 52)
Text("记录")
.font(.system(size: 11))
.foregroundStyle(Tj.Palette.ink)
}
}
.buttonStyle(.plain)
}
}
#Preview {
RootView()
}