import SwiftUI struct ProfileView: View { @StateObject private var viewModel = ProfileViewModel() var body: some View { ZStack { ZXGradient.page.ignoresSafeArea() ScrollView { VStack(spacing: 16) { HStack { Text("我的").font(.system(size: 22, weight: .heavy)).foregroundColor(Color.zxF0).tracking(-0.5) Spacer() NavigationLink(value: Route.notificationList) { Image("icon-notifications").resizable().scaledToFit().frame(width: 22, height: 22).foregroundColor(Color.zxF05) } .accessibilityLabel("通知中心") NavigationLink(value: Route.settings) { Image("icon-settings").resizable().scaledToFit().frame(width: 22, height: 22).foregroundColor(Color.zxF05) } .accessibilityLabel("设置") }.padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 4) profileCard VStack(spacing: 0) { NavigationLink(value: Route.goalSetting) { ZXProfileMenuRow(icon: "target", title: "学习目标设置", desc: "调整你的学习目标") }.foregroundColor(.primary) ZXProfileDivider() NavigationLink(value: Route.settings) { ZXProfileMenuRow(icon: "bell", title: "复习提醒", desc: "间隔复习通知设置") }.foregroundColor(.primary) ZXProfileDivider() NavigationLink(value: Route.methodPreference) { ZXProfileMenuRow(icon: "puzzlepiece", title: "学习方法偏好", desc: "回忆 · 费曼 · 间隔") }.foregroundColor(.primary) ZXProfileDivider() NavigationLink(value: Route.feedbackForm) { ZXProfileMenuRow(icon: "bubble.left", title: "帮助与反馈", desc: "问题报告 · 功能建议") }.foregroundColor(.primary) }.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20)).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1)) assetsSection.padding(.bottom, 120) }.padding(.horizontal, 20) }.scrollIndicators(.hidden) } .task { await viewModel.loadAll() } .navigationDestination(for: Route.self) { $0.destination } } private var defaultAvatarIcon: some View { ZStack { Circle().fill(Color.zxPurpleBG(0.2)).frame(width: 80, height: 80) Image("icon-brain") .font(.system(size: 36)) .foregroundColor(Color.zxPurple) } } private var profileCard: some View { let profile = viewModel.userProfile return NavigationLink(value: Route.editProfile) { VStack(spacing: 16) { HStack { ZStack { if let urlStr = profile?.avatarUrl, let url = URL(string: urlStr) { AsyncImage(url: url) { phase in switch phase { case .success(let img): img.resizable().scaledToFill() default: defaultAvatarIcon } } .frame(width: 80, height: 80) .clipShape(Circle()) } else { defaultAvatarIcon } } VStack(alignment: .leading, spacing: 4) { Text(profile?.nickname ?? "学习者").font(.system(size: 20, weight: .bold)).foregroundColor(Color.zxF0) Text(profile?.email ?? "").font(.system(size: 12)).foregroundColor(Color.zxF04) } Spacer(); Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03) } HStack(spacing: 0) { ZXProfileStat(value: "\(viewModel.summary?.activeDays ?? 0)", label: "活跃天", color: Color.zxOrange); ZXProfileStat(value: "\(viewModel.summary?.totalCardsReviewed ?? 0)", label: "复习卡片", color: Color.zxPurple); ZXProfileStat(value: "\(viewModel.summary?.totalMinutes ?? 0)", label: "分钟", color: Color.zxTeal) } }.padding(20).background(ZXGradient.profileCard).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color(hex: "#7C6EFA", opacity: 0.2), lineWidth: 1)).clipShape(RoundedRectangle(cornerRadius: 20)) }.foregroundColor(.primary) .accessibilityLabel("编辑个人资料,\(profile?.nickname ?? "学习者")") .accessibilityHint("双击查看和编辑个人资料") } private var assetsSection: some View { VStack(spacing: 16) { // 学习资产 VStack(spacing: 0) { NavigationLink(value: Route.libraryCreate) { HStack { Image("icon-books").resizable().scaledToFit().frame(width: 20, height: 20).foregroundColor(Color.zxF05) Text("学习资产").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0) Spacer() Text("\(viewModel.kbCount)库 \(viewModel.itemCount)项 \(viewModel.cardCount)卡").font(.system(size: 12)).foregroundColor(Color.zxF04) Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03) }.padding(.horizontal, 16).padding(.vertical, 14) }.foregroundColor(.primary) ZXProfileDivider() NavigationLink(value: Route.notificationList) { HStack { Image("icon-bell-on").resizable().scaledToFit().frame(width: 20, height: 20).foregroundColor(Color.zxF05) Text("消息中心").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0) Spacer(); Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03) }.padding(.horizontal, 16).padding(.vertical, 14) }.foregroundColor(.primary) ZXProfileDivider() HStack { Image("icon-storage").resizable().scaledToFit().frame(width: 20, height: 20).foregroundColor(Color.zxF05) VStack(alignment: .leading, spacing: 2) { Text("存储空间").font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0) Text(viewModel.formattedStorage).font(.system(size: 12)).foregroundColor(Color.zxF04) } Spacer(); Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03) }.padding(.horizontal, 16).padding(.vertical, 14) }.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20)).overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1)) } } } struct ZXProfileStat: View { let v: String; let l: String; let c: Color; var body: some View { VStack(spacing: 2) { Text(v).font(.system(size: 18, weight: .bold)).foregroundColor(c); Text(l).font(.system(size: 12)).foregroundColor(Color.zxF04) }.frame(maxWidth: .infinity) } init(value: String, label: String, color: Color) { self.v = value; self.l = label; self.c = color } } struct ZXProfileMenuRow: View { let icon: String; let title: String; let desc: String var body: some View { HStack(spacing: 12) { Image(systemName: icon).font(.system(size: 18)).foregroundColor(Color.zxF05).frame(width: 36, height: 36).background(Color.zxFill006).clipShape(RoundedRectangle(cornerRadius: 10)); VStack(alignment: .leading, spacing: 2) { Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0); Text(desc).font(.system(size: 12)).foregroundColor(Color.zxF03) }; Spacer(); Image("icon-chevron-right").resizable().scaledToFit().frame(width: 16, height: 16).foregroundColor(Color.zxF03) }.padding(.horizontal, 16).padding(.vertical, 14).accessibilityLabel("\(title):\(desc)") } } struct ZXProfileDivider: View { var body: some View { Rectangle().fill(Color.zxBorder008).frame(height: 1).padding(.leading, 64) } } struct ZXAchievementBadge: View { let icon: String; let label: String; let color: Color var body: some View { VStack(spacing: 6) { Image(systemName: icon).font(.system(size: 22)).foregroundColor(color).frame(width: 48, height: 48).background(color.opacity(0.12)).clipShape(RoundedRectangle(cornerRadius: 14)).overlay(RoundedRectangle(cornerRadius: 14).stroke(color.opacity(0.25), lineWidth: 1)); Text(label).font(.system(size: 10, weight: .semibold)).foregroundColor(Color.zxF04) }.frame(maxWidth: .infinity) } }