- 新增 AuthManager (ObservableObject) 集中管理鉴权状态: - session 恢复 → token 验证 → 自动刷新 - 登出自动重定向到登录页 - NotificationCenter 监听 401 实现全局踢回 - APIClient 新增 401 自动 refresh + 单次重试 - App.swift 重构鉴权门控: - 去掉 hasCompletedOnboarding 绕过鉴权漏洞 - 拆分为 SplashScreen / PreLoginFlow / PostLoginOnboardingFlow / ContentView - LoginPage 移除"跳过"按钮 - KeychainHelper 实现 token 安全存储 - APIModels 对齐后端 Prisma schema (UserProfile/KnowledgeBase/ReviewCard 等) - APIService 简化 AuthService,token 管理迁移至 AuthManager - 新增 8 个 ViewModel 接入 API: ProfileViewModel, LibraryViewModel, StudyViewModel, ActiveRecallViewModel, AIAnalysisViewModel, ReviewViewModel, ActivityViewModel - 新增 EditProfilePage 编辑资料页 - 新增 NotificationListView 通知列表页 - AIHomeView 修复"检测中"卡住 (改用公开 GET / 健康检查) - SettingsView 登出调用 AuthManager.signOut() 实现重定向 - 修复 NotificationItem 命名冲突、Combine import 缺失等编译错误 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
114 lines
5.5 KiB
Swift
114 lines
5.5 KiB
Swift
import SwiftUI
|
|
|
|
struct EditProfilePage: View {
|
|
@StateObject private var viewModel = ProfileViewModel()
|
|
@State private var nickname: String = ""
|
|
@State private var learningIdentity: String = ""
|
|
@State private var learningDirection: String = ""
|
|
@State private var bio: String = ""
|
|
@State private var currentGoal: String = ""
|
|
@State private var saved = false
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
Color.zxBg0.ignoresSafeArea()
|
|
ScrollView {
|
|
VStack(spacing: 16) {
|
|
sectionHeader("基本信息")
|
|
VStack(spacing: 0) {
|
|
ZXEditField(title: "昵称", text: $nickname, placeholder: "你的昵称")
|
|
ZXSettingDivider()
|
|
ZXEditField(title: "学习身份", text: $learningIdentity, placeholder: "如:考研学生、软件工程师")
|
|
}
|
|
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
|
|
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
|
|
|
|
sectionHeader("学习档案")
|
|
VStack(spacing: 0) {
|
|
ZXEditField(title: "学习方向", text: $learningDirection, placeholder: "如:机器学习、公考申论")
|
|
ZXSettingDivider()
|
|
ZXEditField(title: "当前目标", text: $currentGoal, placeholder: "如:通过 6 月 CFA 一级")
|
|
}
|
|
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
|
|
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
|
|
|
|
sectionHeader("个人简介")
|
|
VStack(spacing: 0) {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Text("简介").font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035)
|
|
TextEditor(text: $bio)
|
|
.frame(minHeight: 100)
|
|
.scrollContentBackground(.hidden)
|
|
.padding(12)
|
|
.background(Color.zxFill003)
|
|
.clipShape(RoundedRectangle(cornerRadius: 14))
|
|
.overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.zxBorder008, lineWidth: 1))
|
|
.tint(Color.zxPurple)
|
|
}.padding(.horizontal, 16).padding(.vertical, 14)
|
|
}
|
|
.background(Color.zxFill004).clipShape(RoundedRectangle(cornerRadius: 20))
|
|
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.zxBorder006, lineWidth: 1))
|
|
|
|
Button {
|
|
Task {
|
|
_ = try? await UserService.shared.updateProfile(UpdateProfileRequest(
|
|
nickname: nickname.isEmpty ? nil : nickname, avatarUrl: nil
|
|
))
|
|
_ = try? await UserService.shared.updateProfileDetail(UpdateProfileDataRequest(
|
|
learningIdentity: learningIdentity.isEmpty ? nil : learningIdentity,
|
|
learningDirection: learningDirection.isEmpty ? nil : learningDirection,
|
|
bio: bio.isEmpty ? nil : bio,
|
|
currentGoal: currentGoal.isEmpty ? nil : currentGoal
|
|
))
|
|
saved = true
|
|
}
|
|
} label: {
|
|
Text(saved ? "已保存" : "保存修改")
|
|
.font(.system(size: 14, weight: .bold))
|
|
.foregroundColor(.white)
|
|
.frame(maxWidth: .infinity)
|
|
.frame(height: 52)
|
|
.background(ZXGradient.ctaPurple)
|
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
|
}
|
|
}
|
|
.padding(.horizontal, 20).padding(.top, 8).padding(.bottom, 80)
|
|
}
|
|
.scrollIndicators(.hidden)
|
|
}
|
|
.navigationTitle("编辑资料")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbarBackground(.hidden, for: .navigationBar)
|
|
.task {
|
|
await viewModel.loadProfile()
|
|
nickname = viewModel.userProfile?.nickname ?? ""
|
|
learningIdentity = viewModel.profileData?.learningIdentity ?? ""
|
|
learningDirection = viewModel.profileData?.learningDirection ?? ""
|
|
bio = viewModel.profileData?.bio ?? ""
|
|
currentGoal = viewModel.profileData?.currentGoal ?? ""
|
|
}
|
|
}
|
|
|
|
private func sectionHeader(_ text: String) -> some View {
|
|
Text(text).font(.system(size: 12, weight: .semibold)).foregroundColor(Color.zxF035).tracking(0.5).padding(.top, 4)
|
|
}
|
|
}
|
|
|
|
struct ZXEditField: View {
|
|
let title: String
|
|
@Binding var text: String
|
|
let placeholder: String
|
|
|
|
var body: some View {
|
|
HStack(spacing: 12) {
|
|
Text(title).font(.system(size: 14, weight: .semibold)).foregroundColor(Color.zxF0).frame(width: 72, alignment: .leading)
|
|
TextField(placeholder, text: $text)
|
|
.font(.system(size: 14))
|
|
.tint(Color.zxPurple)
|
|
.foregroundColor(Color.zxF0)
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, 16).padding(.vertical, 14)
|
|
}
|
|
}
|