diff --git a/punchnet/Views/Register/RegisterModel.swift b/punchnet/Views/Register/RegisterModel.swift index 73c2508..78b58b4 100644 --- a/punchnet/Views/Register/RegisterModel.swift +++ b/punchnet/Views/Register/RegisterModel.swift @@ -47,10 +47,12 @@ class RegisterModel { var params: [String: Any] = [ "username": username, "password": password, + "version": SystemConfig.version_name, + "system": SystemConfig.systemInfo ] params.merge(baseParams) {$1} - return try await SDLAPIClient.doPost(path: "/auth/resetPassword", params: params, as: String.self) + return try await SDLAPIClient.doPost(path: "/auth/register", params: params, as: String.self) } } diff --git a/punchnet/Views/Register/RegisterView.swift b/punchnet/Views/Register/RegisterView.swift index 3aff20d..a6dbd19 100644 --- a/punchnet/Views/Register/RegisterView.swift +++ b/punchnet/Views/Register/RegisterView.swift @@ -5,291 +5,330 @@ // Created by 安礼成 on 2026/3/9. // import SwiftUI +import Observation +// MARK: - 注册根视图 struct RegisterRootView: View { - @State private var registerModel: RegisterModel = RegisterModel() + @State private var registerModel = RegisterModel() var body: some View { - Group { - switch registerModel.stage { - case .requestVerifyCode: - RegisterRequestVerifyCodeView() - case .submitVerifyCode(let username): - RegisterSubmitVerifyCodeView(username: username) - case .setPassword(username: let username): - RegisterSetPasswordView(username: username) + ZStack { + // 背景毛玻璃 + VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow) + .ignoresSafeArea() + + Group { + switch registerModel.stage { + case .requestVerifyCode: + RegisterRequestVerifyCodeView() + case .submitVerifyCode(let username): + RegisterSubmitVerifyCodeView(username: username) + case .setPassword(let username): + RegisterSetPasswordView(username: username) + } } + .transition(.asymmetric(insertion: .move(edge: .trailing).combined(with: .opacity), + removal: .move(edge: .leading).combined(with: .opacity))) } .environment(registerModel) + .frame(width: 400, height: 450) } } -// 获取验证码 -struct RegisterRequestVerifyCodeView: View { - @Environment(RegisterModel.self) var registerModel: RegisterModel - - @State private var username: String = "" - @State private var showAlert = false - @State private var errorMessage = "" +// 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 { - VStack(spacing: 30) { + HStack(spacing: 12) { + Image(systemName: icon) + .foregroundColor(.secondary) + .frame(width: 20) - // 标题 - Text("注册 创建个人网络") - .font(.system(size: 18, weight: .medium)) - - VStack(alignment: .leading, spacing: 16) { - TextField("手机号/邮箱", text: $username) + if isSecure { + SecureField(placeholder, text: $text) .textFieldStyle(.plain) - .frame(width: 260, height: 28) - .overlay( - Rectangle() - .frame(height: 1) - .foregroundColor(.blue), - alignment: .bottom - ) + } else { + TextField(placeholder, text: $text) + .textFieldStyle(.plain) + .disabled(isDisabled) } - - Button { - Task { @MainActor in - //await self.sendVerifyCode() - withAnimation { - self.registerModel.stage = .submitVerifyCode(username: self.username) - } + } + .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 private 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() + }) { + if isProcessing { + ProgressView() + .controlSize(.small) + } else { + Text("获取验证码") + .fontWeight(.medium) + .frame(maxWidth: .infinity) } - } label: { - Text("获取验证码") - .font(.system(size: 14)) - .foregroundColor(.black) - .frame(width: 160, height: 36) - .background(Color(red: 74/255, green: 207/255, blue: 154/255)) } - .frame(width: 160, height: 36) - .cornerRadius(6) + .buttonStyle(.borderedProminent) + .controlSize(.large) + .frame(width: 280) + .disabled(username.isEmpty || isProcessing) Spacer() } - .padding(.top, 40) - .frame(width: 400, height: 400) + .padding(40) .alert(isPresented: $showAlert) { Alert(title: Text("提示"), message: Text(self.errorMessage)) } } - private func sendVerifyCode() async { - if username.isEmpty { - self.showAlert = true - self.errorMessage = "手机号/邮箱为空" - return + 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 { + let result = try await self.registerModel.requestVerifyCode(username: username) + print("send verify code result: \(result)") + withAnimation(.spring()) { + self.registerModel.stage = .submitVerifyCode(username: username) + } + } catch { + self.showAlert = true + self.errorMessage = error.localizedDescription + } + case .invalid: + self.showAlert = true + self.errorMessage = "手机号/邮箱格式错误" + } + self.isProcessing = false } + } + +} - switch SDLUtil.identifyContact(username) { - case .email, .phone: +// 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 = "" + + var body: some View { + VStack(spacing: 24) { + headerSection(title: "身份验证", subtitle: "验证码已发送至 \(username)") + + VStack(spacing: 16) { + PunchTextField(icon: "envelope.badge", placeholder: "输入 4 位验证码", text: $code) + + Button("没有收到?重新获取") { + // Resend Logic + } + .buttonStyle(.link) + .font(.caption) + } + .frame(width: 280) + + VStack(spacing: 12) { + Button(action: { + self.submitVerifyCode() + }) { + if isProcessing { + ProgressView() + .controlSize(.small) + } else { + Text("验证并设置密码") + .fontWeight(.medium) + .frame(maxWidth: .infinity) + } + } + .buttonStyle(.borderedProminent) + .controlSize(.large) + + Button("返回上一步") { + withAnimation { + registerModel.stage = .requestVerifyCode + } + } + .buttonStyle(.plain) + .foregroundColor(.secondary) + } + .frame(width: 280) + + Spacer() + } + .padding(40) + .alert(isPresented: $showAlert) { + Alert(title: Text("提示"), message: Text(errorMessage)) + } + } + + private func submitVerifyCode() { + self.isProcessing = true + Task { @MainActor in + if self.code.isEmpty { + self.showAlert = true + self.errorMessage = "请输入验证码" + self.isProcessing = false + return + } + + if self.code.count != 4 { + self.showAlert = true + self.errorMessage = "验证码错误" + self.isProcessing = false + return + } + do { - let result = try await self.registerModel.requestVerifyCode(username: username) + let result = try await self.registerModel.submitVerifyCode(username: username, verifyCode: self.code) + print("submit verify code result: \(result)") + withAnimation(.spring()) { + registerModel.stage = .setPassword(username: username) + } + } 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 showAlert: Bool = false + @State private var errorMessage: String = "" + + 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) + } + .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(password.isEmpty || password != confirm) + + Spacer() + } + .padding(40) + .alert(isPresented: $showAlert) { + Alert(title: Text("提示"), message: Text(errorMessage)) + } + } + + private func handleRegister() { + self.isProcessing = true + Task { @MainActor in + if password.isEmpty { + self.showAlert = true + self.errorMessage = "请输入新密码" + self.isProcessing = false + return + } + + if confirm.isEmpty || confirm != password { + self.showAlert = true + self.errorMessage = "两次输入的密码不一致" + self.isProcessing = false + return + } + + do { + let result = try await self.registerModel.register(username: username, password: self.password) print("send verify code result: \(result)") } catch { self.showAlert = true self.errorMessage = error.localizedDescription } - - case .invalid: - self.showAlert = true - self.errorMessage = "手机号/邮箱格式错误" - } - } -} - -// 输入验证码 -struct RegisterSubmitVerifyCodeView: View { - @Environment(RegisterModel.self) var registerModel: RegisterModel - - @State var username: String - @State private var verifiyCode: String = "" - - @State private var showAlert = false - @State private var errorMessage = "" - - var body: some View { - VStack(spacing: 30) { - - // 标题 - Text("注册 创建个人网络") - .font(.system(size: 18, weight: .medium)) - - VStack(alignment: .leading, spacing: 16) { - TextField("手机号/邮箱", text: $username) - .textFieldStyle(.plain) - .frame(width: 260, height: 28) - .disabled(true) - .overlay( - Rectangle() - .frame(height: 1) - .foregroundColor(.blue), - alignment: .bottom - ) - - HStack { - TextField("验证码", text: $verifiyCode) - .textFieldStyle(.plain) - .frame(width: 260, height: 28) - .overlay( - Rectangle() - .frame(height: 1) - .foregroundColor(.blue), - alignment: .bottom - ) - Spacer() - - Button { - Task { @MainActor in - //await self.sendVerifyCode() - } - } label: { - Text("再次获取") - .font(.system(size: 14)) - .foregroundColor(.black) - .frame(width: 160, height: 36) - .background(Color(red: 74/255, green: 207/255, blue: 154/255)) - } - .frame(width: 160, height: 36) - .cornerRadius(6) - } - } - - Button { - Task { @MainActor in - await self.submitVerifyCode() - withAnimation { - self.registerModel.stage = .setPassword(username: self.username) - } - } - } label: { - Text("设置密码") - .font(.system(size: 14)) - .foregroundColor(.black) - .frame(width: 160, height: 36) - .background(Color(red: 74/255, green: 207/255, blue: 154/255)) - } - .frame(width: 160, height: 36) - .cornerRadius(6) - - Spacer() - } - .padding(.top, 40) - .frame(width: 400, height: 400) - .alert(isPresented: $showAlert) { - Alert(title: Text("提示"), message: Text(self.errorMessage)) - } - } - - private func submitVerifyCode() async { - if verifiyCode.isEmpty { - self.showAlert = true - self.errorMessage = "请输入验证码" - return - } - - if verifiyCode.count != 4 { - self.showAlert = true - self.errorMessage = "验证码错误" - return - } - - do { - let result = try await self.registerModel.submitVerifyCode(username: username, verifyCode: verifiyCode) - print("submit verify code result: \(result)") - } catch { - self.showAlert = true - self.errorMessage = error.localizedDescription + self.isProcessing = false } } } -// 设置密码 -struct RegisterSetPasswordView: View { - @Environment(RegisterModel.self) var registerModel: RegisterModel - var username: String +// MARK: - 辅助视图 +extension View { - @State private var password: String = "" - @State private var confirmPassword: String = "" - - @State private var showAlert = false - @State private var errorMessage = "" - - var body: some View { - VStack(spacing: 30) { + func headerSection(title: String, subtitle: String) -> some View { + VStack(spacing: 8) { + Image(systemName: "shield.lefthalf.filled") + .font(.system(size: 42)) + .foregroundStyle(.blue.gradient) - // 标题 - Text("注册 创建个人网络") - .font(.system(size: 18, weight: .medium)) + Text(title) + .font(.title2.bold()) - VStack(alignment: .leading, spacing: 16) { - SecureField("新密码", text: $password) - .textFieldStyle(.plain) - .frame(width: 260, height: 28) - .overlay( - Rectangle() - .frame(height: 1) - .foregroundColor(.blue), - alignment: .bottom - ) - - SecureField("再次输入密码", text: $confirmPassword) - .textFieldStyle(.plain) - .frame(width: 260, height: 28) - .overlay( - Rectangle() - .frame(height: 1) - .foregroundColor(.blue), - alignment: .bottom - ) - } - - Button { - Task { @MainActor in - await self.resetPassword() - } - } label: { - Text("注册") - .font(.system(size: 14)) - .foregroundColor(.black) - .frame(width: 160, height: 36) - .background(Color(red: 74/255, green: 207/255, blue: 154/255)) - } - .frame(width: 160, height: 36) - .cornerRadius(6) - - Spacer() - } - .padding(.top, 40) - .frame(width: 400, height: 400) - .alert(isPresented: $showAlert) { - Alert(title: Text("提示"), message: Text(self.errorMessage)) - } - } - - private func resetPassword() async { - if password.isEmpty { - self.showAlert = true - self.errorMessage = "请输入新密码" - return - } - - if confirmPassword.isEmpty || confirmPassword != password { - self.showAlert = true - self.errorMessage = "两次输入的密码不一致" - return - } - - do { - let result = try await self.registerModel.register(username: username, password: self.password) - print("send verify code result: \(result)") - } catch { - self.showAlert = true - self.errorMessage = error.localizedDescription + Text(subtitle) + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) } } diff --git a/punchnet/punchnetApp.swift b/punchnet/punchnetApp.swift index b1462d8..dccd7db 100644 --- a/punchnet/punchnetApp.swift +++ b/punchnet/punchnetApp.swift @@ -47,27 +47,9 @@ struct punchnetApp: App { var body: some Scene { WindowGroup(id: "mainWindow") { - RootView() + // RootView() + RegisterRootView() .frame(width: 800, height: 500) - .onAppear { -// // 获取主屏幕的尺寸 -// guard let screenFrame = NSScreen.main?.frame else { -// return -// } -// -// // 获取当前应用的窗口(假设只有一个窗口) -// NSApplication.shared.windows.forEach { window in -// // 计算窗口的中心位置 -// let windowWidth = window.frame.width -// let windowHeight = window.frame.height -// let centerX = (screenFrame.width - windowWidth) / 2 -// let centerY = (screenFrame.height - windowHeight) / 2 -// -// // 设置窗口位置 -// window.setFrameOrigin(NSPoint(x: centerX, y: centerY)) -// } - } - //.toolbar(.hidden) .navigationTitle("") .environment(self.appContext) .environment(self.userContext)