// // ResetPasswordView.swift // punchnet // // Created by 安礼成 on 2026/3/9. // import SwiftUI import Observation // MARK: - 注册根视图 struct RegisterRootView: View { @State private var registerModel = RegisterModel() var body: some View { ZStack { // 背景毛玻璃 VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow) .ignoresSafeArea() Group { switch registerModel.stage { case .requestVerifyCode(let username): RegisterRequestVerifyCodeView(username: username ?? "") case .submitVerifyCode(let username): RegisterSubmitVerifyCodeView(username: username) case .setPassword(let username): RegisterSetPasswordView(username: username) } } .transition(.asymmetric( insertion: .move(edge: registerModel.transitionEdge).combined(with: .opacity), removal: .move(edge: registerModel.transitionEdge == .trailing ? .leading : .trailing).combined(with: .opacity) )) } .environment(registerModel) } } // MARK: - 封装的输入框组件 struct PunchTextField: View { let icon: String let placeholder: String @Binding var text: String var isSecure: Bool = false var isDisabled: Bool = false var body: some View { HStack(spacing: 12) { Image(systemName: icon) .foregroundColor(.secondary) .frame(width: 20) if isSecure { SecureField(placeholder, text: $text) .textFieldStyle(.plain) } else { TextField(placeholder, text: $text) .textFieldStyle(.plain) .disabled(isDisabled) } } .padding(10) .background(Color.primary.opacity(isDisabled ? 0.02 : 0.05)) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color.primary.opacity(0.1), lineWidth: 1) ) } } // MARK: - 第一步:获取验证码 struct RegisterRequestVerifyCodeView: View { @Environment(RegisterModel.self) var registerModel @State var username: String = "" @State private var isProcessing = false // 错误提示 @State private var showAlert: Bool = false @State private var errorMessage: String = "" var body: some View { VStack(spacing: 24) { headerSection(title: "创建个人网络", subtitle: "输入手机号或邮箱开始注册") VStack(spacing: 16) { PunchTextField(icon: "person.crop.circle", placeholder: "手机号 / 邮箱", text: $username) } .frame(width: 280) Button(action: { self.requestVerifyCode() }) { Text("获取验证码") .fontWeight(.medium) .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) .frame(width: 280) .disabled(!SDLUtil.isValidIdentifyContact(username) || isProcessing) Spacer() } .padding(40) .alert(isPresented: $showAlert) { Alert(title: Text("提示"), message: Text(self.errorMessage)) } } private func requestVerifyCode() { self.isProcessing = true Task { @MainActor in if username.isEmpty { self.showAlert = true self.errorMessage = "手机号/邮箱为空" self.isProcessing = false return } switch SDLUtil.identifyContact(username) { case .email, .phone: do { //_ = try await self.registerModel.requestVerifyCode(username: username) withAnimation(.spring(duration: 0.6, bounce: 0.2)) { self.registerModel.stage = .submitVerifyCode(username: username) self.registerModel.transitionEdge = .trailing } } catch { self.showAlert = true self.errorMessage = error.localizedDescription } case .invalid: self.showAlert = true self.errorMessage = "手机号/邮箱格式错误" } self.isProcessing = false } } } // MARK: - 第二步:验证 struct RegisterSubmitVerifyCodeView: View { @Environment(RegisterModel.self) var registerModel let username: String @State private var code: String = "" @State private var isProcessing = false // 错误提示 @State private var showAlert: Bool = false @State private var errorMessage: String = "" // 重新发送是否可以使用 @State private var isEnabled: Bool = false @State private var remainingSeconds = 60 @State private var timer: Timer? = nil // 判断验证码是否正确 var validInputCode: Bool { return !self.code.isEmpty && self.code.count == 4 && self.code.allSatisfy {$0.isNumber} } var body: some View { VStack(spacing: 24) { headerSection(title: "身份验证", subtitle: "验证码已发送至 \(username)") VStack(alignment: .trailing, spacing: 16) { PunchTextField(icon: "envelope.badge", placeholder: "输入 4 位验证码", text: $code) Button { self.resendVerifyCodeAction() } label: { if isEnabled { Text("没有收到?重新获取") } else { Text("重新获取 (\(remainingSeconds)s)") } } .buttonStyle(.link) .font(.caption) .disabled(!isEnabled) // 倒计时期间禁用按钮 } .frame(width: 280) VStack(spacing: 12) { Button(action: { self.submitVerifyCode() }) { Text("验证并设置密码") .fontWeight(.medium) .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) .disabled(!self.validInputCode) Button("返回上一步") { withAnimation(.spring(duration: 0.6, bounce: 0.2)) { self.registerModel.stage = .requestVerifyCode(username: self.username) self.registerModel.transitionEdge = .leading } } .buttonStyle(.plain) .foregroundColor(.secondary) } .frame(width: 280) Spacer() } .padding(40) .alert(isPresented: $showAlert) { Alert(title: Text("提示"), message: Text(errorMessage)) } .task { await self.startCountdown() } } // 重新发送验证码 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 startCountdown() async { self.isEnabled = false self.remainingSeconds = 60 for sec in (1...self.remainingSeconds).reversed() { self.remainingSeconds = sec try? await Task.sleep(nanoseconds: 1_000_000_000) } self.isEnabled = true } // 提交验证码 private func submitVerifyCode() { self.isProcessing = true Task { @MainActor in do { //_ = try await self.registerModel.submitVerifyCode(username: username, verifyCode: self.code) withAnimation(.spring(duration: 0.6, bounce: 0.2)) { self.registerModel.stage = .setPassword(username: username) self.registerModel.transitionEdge = .trailing } } catch { self.showAlert = true self.errorMessage = error.localizedDescription } self.isProcessing = false } } } // MARK: - 第三步:设置密码 struct RegisterSetPasswordView: View { @Environment(RegisterModel.self) var registerModel let username: String @State private var password = "" @State private var confirm = "" @State private var isProcessing = false // 错误提示 @State private var errorMessage: String? // 提示错误信息 var passwordError: String? { if password.isEmpty || confirm.isEmpty { return nil } if password != confirm { return "两次输入的密码不一致" } if password.count < 8 { return "密码至少需要 8 位" } return nil } var body: some View { VStack(spacing: 24) { headerSection(title: "设置安全密码", subtitle: "最后一步,请确保密码足够强大") VStack(spacing: 12) { PunchTextField(icon: "lock.shield", placeholder: "新密码", text: $password, isSecure: true) PunchTextField(icon: "lock.shield", placeholder: "确认密码", text: $confirm, isSecure: true) if let error = passwordError { Text(error) .font(.caption) .foregroundColor(.red) .frame(width: 280, alignment: .leading) } } .frame(width: 280) Button(action: { self.handleRegister() }) { if isProcessing { ProgressView() .controlSize(.small) } else { Text("完成注册") .fontWeight(.medium) .frame(maxWidth: .infinity) } } .buttonStyle(.borderedProminent) .controlSize(.large) .frame(width: 280) .disabled(passwordError != nil) Spacer() } .padding(40) } private func handleRegister() { self.isProcessing = true Task { @MainActor in do { // _ = try await self.registerModel.register(username: username, password: self.password) } catch { self.errorMessage = error.localizedDescription } self.isProcessing = false } } } // MARK: - 辅助视图 extension View { func headerSection(title: String, subtitle: String) -> some View { VStack(spacing: 8) { Image(systemName: "shield.lefthalf.filled") .font(.system(size: 42)) .foregroundStyle(.blue.gradient) Text(title) .font(.title2.bold()) Text(subtitle) .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) } } }