diff --git a/punchnet/Views/AppContext.swift b/punchnet/Views/AppContext.swift index 2cfd4a0..d678f18 100644 --- a/punchnet/Views/AppContext.swift +++ b/punchnet/Views/AppContext.swift @@ -17,14 +17,14 @@ class AppContext { // 当前的场景 enum AppScene { - case login + case login(username: String?) case logined case register case resetPassword } // 当前app所处的场景 - var appScene: AppScene = .login + var appScene: AppScene = .login(username: nil) init(noticePort: Int) { self.noticePort = noticePort diff --git a/punchnet/Views/Login/LoginView.swift b/punchnet/Views/Login/LoginView.swift index ff98382..e3e9849 100644 --- a/punchnet/Views/Login/LoginView.swift +++ b/punchnet/Views/Login/LoginView.swift @@ -12,6 +12,8 @@ struct LoginView: View { @Environment(UserContext.self) var userContext: UserContext @State private var authMethod: AuthMethod = .account + var username: String? + enum AuthMethod: String, CaseIterable { case account = "账户登录" case token = "密钥认证" @@ -54,7 +56,7 @@ struct LoginView: View { LoginTokenView() .transition(.move(edge: .trailing).combined(with: .opacity)) } else { - LoginAccountView() + LoginAccountView(username: self.username ?? "") .transition(.move(edge: .leading).combined(with: .opacity)) } } @@ -88,8 +90,8 @@ struct LoginAccountView: View { @Environment(UserContext.self) var userContext: UserContext @Environment(AppContext.self) var appContext: AppContext - @State private var username = "" - @State private var password = "" + @State var username: String = "" + @State private var password: String = "" @State private var isLoading = false // 错误提示 diff --git a/punchnet/Views/Register/RegisterModel.swift b/punchnet/Views/Register/RegisterModel.swift index e68786a..6cd1c52 100644 --- a/punchnet/Views/Register/RegisterModel.swift +++ b/punchnet/Views/Register/RegisterModel.swift @@ -12,10 +12,10 @@ import SwiftUI @Observable class RegisterModel { - enum Stage { - case requestVerifyCode(username: String?) - case submitVerifyCode(username: String, sessionId: Int) - case setPassword(sessionId: Int) + enum Stage: Equatable { + case requestVerifyCode + case submitVerifyCode + case setPassword case success } @@ -28,7 +28,11 @@ class RegisterModel { } } - var stage: Stage = .requestVerifyCode(username: nil) + // 保存临时变量 + var username: String = "" + var sessionId: Int = 0 + + var stage: Stage = .requestVerifyCode var transitionEdge: Edge = .trailing // 默认从右进入 private let baseParams: [String: Any] = [ diff --git a/punchnet/Views/Register/RegisterView.swift b/punchnet/Views/Register/RegisterView.swift index 27717ed..2ea1a12 100644 --- a/punchnet/Views/Register/RegisterView.swift +++ b/punchnet/Views/Register/RegisterView.swift @@ -10,6 +10,7 @@ import Observation // MARK: - 注册根视图 struct RegisterRootView: View { @State private var registerModel = RegisterModel() + @Environment(AppContext.self) private var appContext: AppContext var body: some View { ZStack { @@ -19,12 +20,12 @@ struct RegisterRootView: View { Group { switch registerModel.stage { - case .requestVerifyCode(let username): - RegisterRequestVerifyCodeView(username: username ?? "") - case .submitVerifyCode(let username, let sessionId): - RegisterSubmitVerifyCodeView(username: username, sessionId: sessionId) - case .setPassword(let sessionId): - RegisterSetPasswordView(sessionId: sessionId) + case .requestVerifyCode: + RegisterRequestVerifyCodeView() + case .submitVerifyCode: + RegisterSubmitVerifyCodeView() + case .setPassword: + RegisterSetPasswordView() case .success: RegisterSuccessView() } @@ -35,6 +36,34 @@ struct RegisterRootView: View { )) } .environment(registerModel) + // --- 核心改动:使用 overlay --- + .overlay(alignment: .topLeading) { + // 仅在非成功页面显示返回按钮 + switch registerModel.stage { + case .success: + EmptyView() + default: + Button(action: { + // 执行返回逻辑,例如重置到登录 + withAnimation(.spring(duration: 0.6, bounce: 0.2)) { + self.appContext.appScene = .login(username: nil) + } + }) { + HStack { + Image(systemName: "chevron.left") + .font(.system(size: 16, weight: .semibold)) + .padding(5) + + Text("首页") + .font(.system(size: 16, weight: .regular)) + } + .contentShape(Rectangle()) // 扩大点击热区 + } + .buttonStyle(.plain) + .padding([.top, .leading], 16) // 控制距离窗口边缘的边距 + .transition(.opacity) // 按钮出现的动画 + } + } .frame(width: 500, height: 400) } } @@ -74,8 +103,7 @@ struct PunchTextField: View { // MARK: - 第一步:获取验证码 struct RegisterRequestVerifyCodeView: View { - @Environment(RegisterModel.self) var registerModel - @State var username: String = "" + @Environment(RegisterModel.self) var registerModel: RegisterModel @State private var isProcessing = false // 错误提示 @@ -83,16 +111,18 @@ struct RegisterRequestVerifyCodeView: View { @State private var errorMessage: String = "" var body: some View { + @Bindable var model = registerModel + VStack(spacing: 24) { headerSection(title: "创建个人网络", subtitle: "输入邮箱开始注册") VStack(spacing: 16) { - PunchTextField(icon: "person.crop.circle", placeholder: "邮箱", text: $username) + PunchTextField(icon: "person.crop.circle", placeholder: "邮箱", text: $model.username) } .frame(width: 280) Button(action: { - self.requestVerifyCode() + self.requestVerifyCode(username: model.username) }) { Text("获取验证码") .fontWeight(.medium) @@ -101,7 +131,7 @@ struct RegisterRequestVerifyCodeView: View { .buttonStyle(.borderedProminent) .controlSize(.large) .frame(width: 280) - .disabled(!SDLUtil.isValidIdentifyContact(username) || isProcessing) + .disabled(!SDLUtil.isValidIdentifyContact(model.username) || isProcessing) Spacer() } @@ -111,7 +141,7 @@ struct RegisterRequestVerifyCodeView: View { } } - private func requestVerifyCode() { + private func requestVerifyCode(username: String) { self.isProcessing = true Task { @MainActor in if username.isEmpty { @@ -126,7 +156,10 @@ struct RegisterRequestVerifyCodeView: View { do { let registerSession = try await self.registerModel.requestVerifyCode(username: username) withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.registerModel.stage = .submitVerifyCode(username: username, sessionId: registerSession.sessionId) + self.registerModel.stage = .submitVerifyCode + self.registerModel.username = username + self.registerModel.sessionId = registerSession.sessionId + self.registerModel.transitionEdge = .trailing } } catch let err as SDLAPIError { @@ -148,10 +181,7 @@ struct RegisterRequestVerifyCodeView: View { // MARK: - 第二步:验证 struct RegisterSubmitVerifyCodeView: View { - @Environment(RegisterModel.self) var registerModel - - let username: String - let sessionId: Int + @Environment(RegisterModel.self) var registerModel: RegisterModel @State private var code: String = "" @State private var isProcessing = false @@ -172,13 +202,15 @@ struct RegisterSubmitVerifyCodeView: View { var body: some View { VStack(spacing: 24) { - headerSection(title: "身份验证", subtitle: "验证码已发送至 \(username)") + headerSection(title: "身份验证", subtitle: "验证码已发送至 \(registerModel.username)") VStack(alignment: .trailing, spacing: 16) { PunchTextField(icon: "envelope.badge", placeholder: "输入 6 位验证码", text: $code) Button { - self.resendVerifyCodeAction() + Task { @MainActor in + await self.resendVerifyCodeAction(username: registerModel.username) + } } label: { if isEnabled { Text("没有收到?重新获取") @@ -194,7 +226,9 @@ struct RegisterSubmitVerifyCodeView: View { VStack(spacing: 12) { Button(action: { - self.submitVerifyCode() + Task { @MainActor in + await self.submitVerifyCode(sessionId: registerModel.sessionId) + } }) { Text("验证并设置密码") .fontWeight(.medium) @@ -206,7 +240,8 @@ struct RegisterSubmitVerifyCodeView: View { Button("返回上一步") { withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.registerModel.stage = .requestVerifyCode(username: self.username) + self.registerModel.stage = .requestVerifyCode + self.registerModel.transitionEdge = .leading } } @@ -227,17 +262,15 @@ struct RegisterSubmitVerifyCodeView: View { } // 重新发送验证码 - private func resendVerifyCodeAction() { - Task { - do { - let result = try await self.registerModel.requestVerifyCode(username: username) - print("send verify code result: \(result)") - } catch let err { - print("resend verify get error: \(err)") - } - // 重新计时 - await self.startCountdown() + private func resendVerifyCodeAction(username: String) async { + do { + let result = try await self.registerModel.requestVerifyCode(username: username) + print("send verify code result: \(result)") + } catch let err { + print("resend verify get error: \(err)") } + // 重新计时 + await self.startCountdown() } // 重新倒计时 @@ -252,32 +285,31 @@ struct RegisterSubmitVerifyCodeView: View { } // 提交验证码 - private func submitVerifyCode() { + private func submitVerifyCode(sessionId: Int) async { self.isProcessing = true - Task { @MainActor in - do { - _ = try await self.registerModel.submitVerifyCode(sessionId: sessionId, verifyCode: self.code) - withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.registerModel.stage = .setPassword(sessionId: sessionId) - self.registerModel.transitionEdge = .trailing - } - } catch let err as SDLAPIError { - self.showAlert = true - self.errorMessage = err.message - } catch let err { - self.showAlert = true - self.errorMessage = err.localizedDescription - } - + defer { self.isProcessing = false } + do { + _ = try await self.registerModel.submitVerifyCode(sessionId: sessionId, verifyCode: self.code) + withAnimation(.spring(duration: 0.6, bounce: 0.2)) { + self.registerModel.stage = .setPassword + self.registerModel.transitionEdge = .trailing + } + } catch let err as SDLAPIError { + self.showAlert = true + self.errorMessage = err.message + } catch let err { + self.showAlert = true + self.errorMessage = err.localizedDescription + } } } // MARK: - 第三步:设置密码 struct RegisterSetPasswordView: View { - @Environment(RegisterModel.self) var registerModel - let sessionId: Int + @Environment(RegisterModel.self) var registerModel: RegisterModel + @State private var password = "" @State private var confirm = "" @State private var isProcessing = false @@ -318,7 +350,9 @@ struct RegisterSetPasswordView: View { .frame(width: 280) Button(action: { - self.handleRegister() + Task { @MainActor in + await self.handleRegister(sessionId: registerModel.sessionId) + } }) { if isProcessing { ProgressView() @@ -342,32 +376,33 @@ struct RegisterSetPasswordView: View { } } - private func handleRegister() { + private func handleRegister(sessionId: Int) async { self.isProcessing = true - Task { @MainActor in - do { - _ = try await self.registerModel.register(sessionId: sessionId, password: self.password) - withAnimation(.spring(duration: 0.6, bounce: 0.2)) { - self.registerModel.stage = .success - self.registerModel.transitionEdge = .trailing - } - } catch let err as SDLAPIError { - self.showAlert = true - self.errorMessage = err.message - } catch { - self.showAlert = true - self.errorMessage = "注册失败,重稍后重试" - } + defer { self.isProcessing = false } + do { + _ = try await self.registerModel.register(sessionId: sessionId, password: self.password) + withAnimation(.spring(duration: 0.6, bounce: 0.2)) { + self.registerModel.stage = .success + self.registerModel.transitionEdge = .trailing + } + } catch let err as SDLAPIError { + self.showAlert = true + self.errorMessage = err.message + } catch { + self.showAlert = true + self.errorMessage = "注册失败,重稍后重试" + } } } // MARK: 第四步 注册成功 struct RegisterSuccessView: View { - @Environment(\.dismiss) private var dismiss // 获取关闭窗口的能力 - + @Environment(AppContext.self) private var appContext: AppContext + @Environment(RegisterModel.self) private var registerModel: RegisterModel + // MARK: - 动画状态 @State private var animateIcon: Bool = false // 用于呼吸灯效果 @State private var animateText: Bool = false // 用于文本延迟浮现 @@ -410,7 +445,9 @@ struct RegisterSuccessView: View { Button(action: { // 关闭当前注册窗口 - dismiss() + withAnimation(.spring(duration: 0.6, bounce: 0.2)) { + self.appContext.appScene = .login(username: registerModel.username) + } }) { Text("立即开始使用") .fontWeight(.bold) diff --git a/punchnet/Views/ResetPassword/ResetPasswordModel.swift b/punchnet/Views/ResetPassword/ResetPasswordModel.swift index d32cc35..f3f9882 100644 --- a/punchnet/Views/ResetPassword/ResetPasswordModel.swift +++ b/punchnet/Views/ResetPassword/ResetPasswordModel.swift @@ -12,7 +12,7 @@ import SwiftUI @Observable class ResetPasswordModel { - enum Stage { + enum Stage: Equatable { case requestVerifyCode(username: String?) case submitVerifyCode(username: String, sessionId: Int) case resetPassword(username: String, sessionId: Int) diff --git a/punchnet/Views/ResetPassword/ResetPasswordView.swift b/punchnet/Views/ResetPassword/ResetPasswordView.swift index af60669..7645bb1 100644 --- a/punchnet/Views/ResetPassword/ResetPasswordView.swift +++ b/punchnet/Views/ResetPassword/ResetPasswordView.swift @@ -10,6 +10,7 @@ import Observation // MARK: - 1. 根视图 struct ResetPasswordRootView: View { @State private var resetPasswordModel = ResetPasswordModel() + @Environment(AppContext.self) private var appContext: AppContext var body: some View { ZStack { @@ -35,6 +36,30 @@ struct ResetPasswordRootView: View { )) } .environment(resetPasswordModel) + .overlay(alignment: .topLeading) { + // 仅在非成功页面显示返回按钮 + if resetPasswordModel.stage != .success { + Button(action: { + // 执行返回逻辑,例如重置到登录 + withAnimation(.spring(duration: 0.6, bounce: 0.2)) { + self.appContext.appScene = .login(username: nil) + } + }) { + HStack { + Image(systemName: "chevron.left") + .font(.system(size: 16, weight: .semibold)) + .padding(5) + + Text("首页") + .font(.system(size: 16, weight: .regular)) + } + .contentShape(Rectangle()) // 扩大点击热区 + } + .buttonStyle(.plain) + .padding([.top, .leading], 16) // 控制距离窗口边缘的边距 + .transition(.opacity) // 按钮出现的动画 + } + } .frame(width: 500, height: 400) } } diff --git a/punchnet/Views/RootView.swift b/punchnet/Views/RootView.swift index be3521d..bf57034 100644 --- a/punchnet/Views/RootView.swift +++ b/punchnet/Views/RootView.swift @@ -18,7 +18,7 @@ struct RootView: View { // 主要界面 Group { switch appContext.appScene { - case .login: + case .login(username: let username): LoginView() case .logined: NetworkView()