import SwiftUI import CoreModel import CoreUI import DesignSystem public struct ProfileScreen: View { private let viewModel: ProfileViewModel private let onNavigateToAuth: () -> Void @State private var showLogoutConfirm = false public init(viewModel: ProfileViewModel, onNavigateToAuth: @escaping () -> Void) { self.viewModel = viewModel 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 .loggedOut: LoggedOutView(onNavigateToAuth: onNavigateToAuth) case .error(let message): ErrorView(message: message) { viewModel.load() } case .success(let profile): successContent(profile) } } private func successContent(_ profile: UserProfile) -> some View { ScrollView { VStack(spacing: DS.Spacing.md) { ProfileHeaderCard(profile: profile) InfoCard(profile: profile) if !profile.classGroups.isEmpty { ClassGroupCard(groups: profile.classGroups) } Button("退出登录", role: .destructive) { showLogoutConfirm = true } .frame(maxWidth: .infinity) .buttonStyle(.bordered) } .padding(DS.Spacing.md) } .background(DS.Color.brandPaper) .alert("退出登录", isPresented: $showLogoutConfirm) { Button("取消", role: .cancel) {} Button("确定", role: .destructive) { viewModel.logout() } } message: { Text("确定要退出登录吗?退出后需要重新登录。") } } } // MARK: - 头像卡 private struct ProfileHeaderCard: View { let profile: UserProfile var body: some View { HStack(spacing: DS.Spacing.md) { ZStack(alignment: .bottomTrailing) { Text(String(profile.displayName.prefix(1))) .font(.title.bold()) .foregroundStyle(.white) .frame(width: 56, height: 56) .background(DS.Color.brandGold) .clipShape(Circle()) Text("📷") .font(.caption2) .frame(width: 20, height: 20) .background(DS.Color.brandGold) .clipShape(Circle()) .overlay(Circle().stroke(DS.Color.brandPaper, lineWidth: 2)) } VStack(alignment: .leading, spacing: 2) { HStack(spacing: DS.Spacing.xs) { Text(profile.displayName) .font(.title3.bold()) .foregroundStyle(DS.Color.brandDark) Text(profile.role) .font(.caption.bold()) .foregroundStyle(DS.Color.brandGold) .padding(.horizontal, DS.Spacing.sm) .padding(.vertical, 2) .background(DS.Color.brandChip) .clipShape(Capsule()) } Text(profile.studentId) .font(.caption) .foregroundStyle(DS.Color.brandMuted) } Spacer() } .padding(DS.Spacing.md) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) } } // MARK: - 信息卡 private struct InfoCard: View { let profile: UserProfile var body: some View { VStack(alignment: .leading, spacing: DS.Spacing.sm) { Text("个人信息") .font(.subheadline.bold()) .foregroundStyle(DS.Color.brandDark) LabeledRow(label: "姓名", value: profile.name) LabeledRow(label: "学号", value: profile.studentId) LabeledRow(label: "手机号", value: profile.phone ?? "未绑定") LabeledRow(label: "角色", value: profile.role) LabeledRow(label: "小组", value: profile.group ?? "未分组") LabeledRow(label: "登录方式", value: profile.loginType) } .padding(DS.Spacing.md) .frame(maxWidth: .infinity, alignment: .leading) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) } } private struct ClassGroupCard: View { let groups: [ClassGroup] var body: some View { VStack(alignment: .leading, spacing: DS.Spacing.sm) { Text("小组分组") .font(.subheadline.bold()) .foregroundStyle(DS.Color.brandDark) ForEach(groups) { group in LabeledRow(label: group.period, value: "\(group.group) · \(group.role)") } } .padding(DS.Spacing.md) .frame(maxWidth: .infinity, alignment: .leading) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) } } private struct LabeledRow: View { let label: String let value: String var body: some View { HStack { Text(label) .font(.subheadline) .foregroundStyle(DS.Color.brandMuted) Spacer() Text(value) .font(.subheadline) .foregroundStyle(DS.Color.brandDark) } .padding(.vertical, DS.Spacing.xs) } }