import SwiftUI import CoreModel import CoreUI import DesignSystem public struct HomeScreen: View { private let viewModel: HomeViewModel private let onNavigateToCheckin: () -> Void private let onNavigateToPlan: () -> Void private let onNavigateToAuth: () -> Void @State private var showLoginDialog = false public init( viewModel: HomeViewModel, onNavigateToCheckin: @escaping () -> Void, onNavigateToPlan: @escaping () -> Void, onNavigateToAuth: @escaping () -> Void ) { self.viewModel = viewModel self.onNavigateToCheckin = onNavigateToCheckin self.onNavigateToPlan = onNavigateToPlan self.onNavigateToAuth = onNavigateToAuth } public var body: some View { content .navigationTitle("首页") .task { viewModel.load() } } @ViewBuilder private var content: some View { switch viewModel.uiState { case .loading: LoadingView() case .error(let message): ErrorView(message: message) { viewModel.load() } case .success(let data, let isGuest): successContent(data, isGuest: isGuest) } } private func successContent(_ data: HomeData, isGuest: Bool) -> some View { ScrollView { VStack(spacing: DS.Spacing.md) { HeroCard(data: data) ModuleGrid(modules: data.modules) { module in handleModuleTap(module, isGuest: isGuest) } IntroSection(intros: data.intros) } .padding(DS.Spacing.md) } .background(DS.Color.brandPaper) .alert("登录提示", isPresented: $showLoginDialog) { Button("取消", role: .cancel) {} Button("去登录") { onNavigateToAuth() } } message: { Text("登录后使用此功能,是否前往登录?") } } private func handleModuleTap(_ module: HomeModule, isGuest: Bool) { if isGuest { showLoginDialog = true return } switch module.destination { case .checkin: onNavigateToCheckin() case .plan: onNavigateToPlan() case .about: break } } } // MARK: - Hero 卡 private struct HeroCard: View { let data: HomeData var body: some View { VStack(alignment: .leading, spacing: DS.Spacing.sm) { Text(data.greeting) .font(.caption.bold()) .foregroundStyle(DS.Color.brandLightAmber) .padding(.horizontal, DS.Spacing.sm) .padding(.vertical, DS.Spacing.xs) .background(DS.Color.brandGold.opacity(0.18)) .clipShape(Capsule()) Text(data.displayName) .font(.title2.bold()) .foregroundStyle(.white) Text(data.encouragement) .font(.subheadline) .foregroundStyle(DS.Color.brandCream) } .padding(DS.Spacing.lg) .frame(maxWidth: .infinity, alignment: .leading) .background(DS.Gradient.hero) .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) } } // MARK: - 模块宫格 private struct ModuleGrid: View { let modules: [HomeModule] let onTap: (HomeModule) -> Void private let columns = Array(repeating: GridItem(.flexible(), spacing: DS.Spacing.sm), count: 3) var body: some View { LazyVGrid(columns: columns, spacing: DS.Spacing.sm) { ForEach(modules) { module in ModuleItem(module: module, onTap: onTap) } } } } private struct ModuleItem: View { let module: HomeModule let onTap: (HomeModule) -> Void var body: some View { VStack(spacing: DS.Spacing.sm) { Text(module.emoji) .font(.title2) .frame(width: 44, height: 44) .background(DS.Color.brandChip) .clipShape(Circle()) Text(module.title) .font(.footnote.bold()) .foregroundStyle(DS.Color.brandDark) Text(module.description) .font(.caption2) .foregroundStyle(DS.Color.brandMuted) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) .padding(.vertical, DS.Spacing.md) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous)) .contentShape(Rectangle()) .onTapGesture { onTap(module) } } } // MARK: - 产品介绍 private struct IntroSection: View { let intros: [IntroCard] var body: some View { VStack(alignment: .leading, spacing: DS.Spacing.sm) { Text("产品介绍") .font(.title3.bold()) .foregroundStyle(DS.Color.brandDark) ForEach(intros, id: \.title) { intro in IntroItem(intro: intro) } } .frame(maxWidth: .infinity, alignment: .leading) } } private struct IntroItem: View { let intro: IntroCard var body: some View { VStack(alignment: .leading, spacing: DS.Spacing.xs) { Text(intro.title) .font(.subheadline.bold()) .foregroundStyle(DS.Color.brandGold) Text(intro.body) .font(.subheadline) .foregroundStyle(DS.Color.brandBrown) .lineSpacing(4) } .padding(DS.Spacing.md) .frame(maxWidth: .infinity, alignment: .leading) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) } }