完善注册流程

This commit is contained in:
anlicheng 2026-03-23 23:29:29 +08:00
parent c9c507974e
commit 9fcb902090
3 changed files with 94 additions and 72 deletions

View File

@ -176,6 +176,9 @@ struct LoginAccountView: View {
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.appScene = .logined self.appContext.appScene = .logined
} }
} catch let err as SDLAPIError {
self.showAlert = true
self.errorMessage = err.message
} catch let err { } catch let err {
self.showAlert = true self.showAlert = true
self.errorMessage = err.localizedDescription self.errorMessage = err.localizedDescription

View File

@ -13,15 +13,19 @@ import SwiftUI
class ResetPasswordModel { class ResetPasswordModel {
enum Stage: Equatable { enum Stage: Equatable {
case requestVerifyCode(username: String?) case requestVerifyCode
case submitVerifyCode(username: String, sessionId: Int) case submitVerifyCode
case resetPassword(username: String, sessionId: Int) case resetPassword
case success case success
} }
var stage: Stage = .requestVerifyCode(username: nil) var stage: Stage = .requestVerifyCode
var transitionEdge: Edge = .trailing // var transitionEdge: Edge = .trailing //
//
var username: String = ""
var sessionId: Int = 0
// //
struct ResetPasswordSession: Codable { struct ResetPasswordSession: Codable {
let sessionId: Int let sessionId: Int

View File

@ -20,12 +20,12 @@ struct ResetPasswordRootView: View {
Group { Group {
switch resetPasswordModel.stage { switch resetPasswordModel.stage {
case .requestVerifyCode(let username): case .requestVerifyCode:
GetVerifyCodeView(username: username ?? "") GetVerifyCodeView()
case .submitVerifyCode(let username, let sessionId): case .submitVerifyCode:
SubmitVerifyCodeView(username: username, sessionId: sessionId) SubmitVerifyCodeView()
case .resetPassword(let username, let sessionId): case .resetPassword:
ResetPasswordView(username: username, sessionId: sessionId) ResetPasswordView()
case .success: case .success:
ResetPasswordSuccessView() ResetPasswordSuccessView()
} }
@ -67,20 +67,23 @@ struct ResetPasswordRootView: View {
// MARK: - 2. // MARK: - 2.
struct GetVerifyCodeView: View { struct GetVerifyCodeView: View {
@Environment(ResetPasswordModel.self) var resetPasswordModel @Environment(ResetPasswordModel.self) var resetPasswordModel
@State var username: String = ""
@State private var isProcessing = false @State private var isProcessing = false
@State private var showAlert = false @State private var showAlert = false
@State private var errorMessage = "" @State private var errorMessage = ""
var body: some View { var body: some View {
@Bindable var model = resetPasswordModel
VStack(spacing: 24) { VStack(spacing: 24) {
headerSection(title: "重置密码", subtitle: "请输入关联的邮箱来验证身份") headerSection(title: "重置密码", subtitle: "请输入关联的邮箱来验证身份")
PunchTextField(icon: "person.crop.circle", placeholder: "邮箱", text: $username) PunchTextField(icon: "person.crop.circle", placeholder: "邮箱", text: $model.username)
.frame(width: 280) .frame(width: 280)
Button { Button {
self.sendVerifyCode() Task { @MainActor in
await self.sendVerifyCode(username: model.username)
}
} label: { } label: {
Text("获取验证码") Text("获取验证码")
.fontWeight(.medium) .fontWeight(.medium)
@ -89,7 +92,7 @@ struct GetVerifyCodeView: View {
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.controlSize(.large) .controlSize(.large)
.frame(width: 280) .frame(width: 280)
.disabled(!SDLUtil.isValidIdentifyContact(username) || isProcessing) .disabled(!SDLUtil.isValidIdentifyContact(model.username) || isProcessing)
Spacer() Spacer()
} }
@ -100,30 +103,31 @@ struct GetVerifyCodeView: View {
} }
// //
private func sendVerifyCode() { private func sendVerifyCode(username: String) async {
self.isProcessing = true self.isProcessing = true
Task { @MainActor in defer {
do {
let resetSession = try await resetPasswordModel.requestVerifyCode(username: username)
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
resetPasswordModel.stage = .submitVerifyCode(username: username, sessionId: resetSession.sessionId)
resetPasswordModel.transitionEdge = .trailing
}
} catch {
self.errorMessage = error.localizedDescription
self.showAlert = true
}
self.isProcessing = false self.isProcessing = false
} }
do {
let resetSession = try await resetPasswordModel.requestVerifyCode(username: username)
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.resetPasswordModel.stage = .submitVerifyCode
self.resetPasswordModel.sessionId = resetSession.sessionId
self.resetPasswordModel.transitionEdge = .trailing
}
} catch {
self.errorMessage = error.localizedDescription
self.showAlert = true
}
} }
} }
// MARK: - 3. // MARK: - 3.
struct SubmitVerifyCodeView: View { struct SubmitVerifyCodeView: View {
@Environment(ResetPasswordModel.self) var resetPasswordModel @Environment(ResetPasswordModel.self) var resetPasswordModel: ResetPasswordModel
let username: String
let sessionId: Int
@State private var code: String = "" @State private var code: String = ""
@State private var isProcessing = false @State private var isProcessing = false
@ -142,13 +146,15 @@ struct SubmitVerifyCodeView: View {
var body: some View { var body: some View {
VStack(spacing: 24) { VStack(spacing: 24) {
headerSection(title: "身份验证", subtitle: "验证码已发送至 \(username)") headerSection(title: "身份验证", subtitle: "验证码已发送至 \(self.resetPasswordModel.username)")
VStack(alignment: .trailing, spacing: 16) { VStack(alignment: .trailing, spacing: 16) {
PunchTextField(icon: "envelope.badge", placeholder: "输入 6 位验证码", text: $code) PunchTextField(icon: "envelope.badge", placeholder: "输入 6 位验证码", text: $code)
Button(isResendEnabled ? "重新获取" : "重新获取 (\(remainingSeconds)s)") { Button(isResendEnabled ? "重新获取" : "重新获取 (\(remainingSeconds)s)") {
self.resendAction() Task { @MainActor in
await self.resendAction(username: self.resetPasswordModel.username)
}
} }
.buttonStyle(.link) .buttonStyle(.link)
.font(.caption) .font(.caption)
@ -158,7 +164,9 @@ struct SubmitVerifyCodeView: View {
VStack(spacing: 12) { VStack(spacing: 12) {
Button { Button {
self.submitAction() Task { @MainActor in
await self.submitAction(sessionId: self.resetPasswordModel.sessionId)
}
} label: { } label: {
Text("验证并继续") Text("验证并继续")
.fontWeight(.medium) .fontWeight(.medium)
@ -170,7 +178,7 @@ struct SubmitVerifyCodeView: View {
Button("返回上一步") { Button("返回上一步") {
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.resetPasswordModel.stage = .requestVerifyCode(username: self.username) self.resetPasswordModel.stage = .requestVerifyCode
self.resetPasswordModel.transitionEdge = .leading self.resetPasswordModel.transitionEdge = .leading
} }
} }
@ -190,11 +198,9 @@ struct SubmitVerifyCodeView: View {
} }
} }
private func resendAction() { private func resendAction(username: String) async {
Task { _ = try? await resetPasswordModel.requestVerifyCode(username: username)
_ = try? await resetPasswordModel.requestVerifyCode(username: username) await startCountdown()
await startCountdown()
}
} }
private func startCountdown() async { private func startCountdown() async {
@ -207,31 +213,34 @@ struct SubmitVerifyCodeView: View {
self.isResendEnabled = true self.isResendEnabled = true
} }
private func submitAction() { private func submitAction(sessionId: Int) async {
self.isProcessing = true self.isProcessing = true
Task { @MainActor in defer {
do {
let result = try await resetPasswordModel.submitVerifyCode(sessionId: sessionId, verifyCode: code)
NSLog("reset password submit verify code result: \(result)")
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.resetPasswordModel.stage = .resetPassword(username: username, sessionId: sessionId)
self.resetPasswordModel.transitionEdge = .trailing
}
} catch let err as SDLAPIError {
self.errorMessage = err.message
self.showAlert = true
}
self.isProcessing = false self.isProcessing = false
} }
do {
let result = try await resetPasswordModel.submitVerifyCode(sessionId: sessionId, verifyCode: code)
NSLog("reset password submit verify code result: \(result)")
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.resetPasswordModel.stage = .resetPassword
self.resetPasswordModel.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: - 4. // MARK: - 4.
struct ResetPasswordView: View { struct ResetPasswordView: View {
@Environment(ResetPasswordModel.self) var resetPasswordModel @Environment(ResetPasswordModel.self) var resetPasswordModel: ResetPasswordModel
let username: String
let sessionId: Int
@State private var password = "" @State private var password = ""
@State private var confirm = "" @State private var confirm = ""
@State private var isProcessing = false @State private var isProcessing = false
@ -264,7 +273,7 @@ struct ResetPasswordView: View {
var body: some View { var body: some View {
VStack(spacing: 24) { VStack(spacing: 24) {
headerSection(title: "设置新密码", subtitle: "请为账号 \(username) 设置一个强密码") headerSection(title: "设置新密码", subtitle: "请为账号 \(self.resetPasswordModel.username) 设置一个强密码")
VStack(spacing: 12) { VStack(spacing: 12) {
PunchTextField(icon: "lock.shield", placeholder: "新密码 (至少8位)", text: $password, isSecure: true) PunchTextField(icon: "lock.shield", placeholder: "新密码 (至少8位)", text: $password, isSecure: true)
@ -280,7 +289,9 @@ struct ResetPasswordView: View {
.frame(width: 280) .frame(width: 280)
Button { Button {
self.handleReset() Task { @MainActor in
await self.handleReset(sessionId: self.resetPasswordModel.sessionId)
}
} label: { } label: {
Text("重置密码并登录") Text("重置密码并登录")
.fontWeight(.medium) .fontWeight(.medium)
@ -299,29 +310,31 @@ struct ResetPasswordView: View {
} }
} }
private func handleReset() { private func handleReset(sessionId: Int) async {
self.isProcessing = true self.isProcessing = true
Task { @MainActor in defer {
do {
let result = try await resetPasswordModel.resetPassword(sessionId: sessionId, newPassword: password)
print("密码重置成功: \(result)")
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.resetPasswordModel.stage = .success
self.resetPasswordModel.transitionEdge = .trailing
}
} catch {
self.showAlert = true
self.errorMessage = "重置失败, 请稍后重试"
}
self.isProcessing = false self.isProcessing = false
} }
do {
let result = try await resetPasswordModel.resetPassword(sessionId: sessionId, newPassword: password)
print("密码重置成功: \(result)")
withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.resetPasswordModel.stage = .success
self.resetPasswordModel.transitionEdge = .trailing
}
} catch {
self.showAlert = true
self.errorMessage = "重置失败, 请稍后重试"
}
} }
} }
struct ResetPasswordSuccessView: View { struct ResetPasswordSuccessView: View {
@Environment(\.dismiss) private var dismiss @Environment(AppContext.self) var appContext: AppContext
@Environment(ResetPasswordModel.self) var resetPasswordModel: ResetPasswordModel
// //
@State private var animateIcon = false @State private var animateIcon = false
@State private var animateText = false @State private var animateText = false
@ -357,7 +370,9 @@ struct ResetPasswordSuccessView: View {
} }
Button(action: { Button(action: {
dismiss() // withAnimation(.spring(duration: 0.6, bounce: 0.2)) {
self.appContext.appScene = .login(username: self.resetPasswordModel.username)
}
}) { }) {
Text("返回登录") Text("返回登录")
.fontWeight(.bold) .fontWeight(.bold)