feat: 增强邮箱登录功能和密码恢复流程

- 更新邮箱登录相关功能,新增邮箱验证码获取和登录API端点。
- 添加AccountModel以管理用户认证信息,支持会话票据的存储和更新。
- 实现密码恢复功能,支持通过邮箱获取验证码和重置密码。
- 增加本地化支持,更新相关字符串以适应新功能。
- 引入ValidationHelper以验证邮箱和密码格式,确保用户输入的有效性。
- 更新视图以支持邮箱登录和密码恢复的用户交互。
This commit is contained in:
edwinQQQ
2025-07-10 14:00:58 +08:00
parent c470dba79c
commit e45ad3bad5
23 changed files with 2054 additions and 164 deletions

View File

@@ -8,6 +8,16 @@ struct EMailLoginView: View {
// 使@StateUI
@State private var email: String = ""
@State private var verificationCode: String = ""
@State private var codeCountdown: Int = 0
@State private var timer: Timer?
//
@FocusState private var focusedField: Field?
enum Field {
case email
case verificationCode
}
//
private var isLoginButtonEnabled: Bool {
@@ -18,17 +28,22 @@ struct EMailLoginView: View {
private var getCodeButtonText: String {
if store.isCodeLoading {
return ""
} else if store.codeCountdown > 0 {
return "\(store.codeCountdown)S"
} else if codeCountdown > 0 {
return "\(codeCountdown)S"
} else {
return "email_login.get_code".localized
}
}
//
private var isCodeButtonEnabled: Bool {
return !store.isCodeLoading && codeCountdown == 0
}
var body: some View {
GeometryReader { geometry in
ZStack {
// - 使"bg"
//
Image("bg")
.resizable()
.aspectRatio(contentMode: .fill)
@@ -83,6 +98,7 @@ struct EMailLoginView: View {
.keyboardType(.emailAddress)
.autocapitalization(.none)
.disableAutocorrection(true)
.focused($focusedField, equals: .email)
}
//
@@ -104,9 +120,13 @@ struct EMailLoginView: View {
.foregroundColor(.white)
.font(.system(size: 16))
.keyboardType(.numberPad)
.focused($focusedField, equals: .verificationCode)
//
Button(action: {
//
startCountdown()
// API
store.send(.getVerificationCodeTapped)
}) {
ZStack {
@@ -123,10 +143,10 @@ struct EMailLoginView: View {
.frame(width: 60, height: 36)
.background(
RoundedRectangle(cornerRadius: 18)
.fill(Color.white.opacity(store.isCodeButtonEnabled && !email.isEmpty ? 0.2 : 0.1))
.fill(Color.white.opacity(isCodeButtonEnabled && !email.isEmpty ? 0.2 : 0.1))
)
}
.disabled(!store.isCodeButtonEnabled || email.isEmpty || store.isCodeLoading)
.disabled(!isCodeButtonEnabled || email.isEmpty || store.isCodeLoading)
}
.padding(.horizontal, 24)
}
@@ -138,7 +158,6 @@ struct EMailLoginView: View {
//
Button(action: {
// action
store.send(.loginButtonTapped(email: email, verificationCode: verificationCode))
}) {
ZStack {
@@ -184,21 +203,59 @@ struct EMailLoginView: View {
}
}
.onAppear {
// TCA
email = store.email
verificationCode = store.verificationCode
//
store.send(.resetState)
email = ""
verificationCode = ""
codeCountdown = 0
stopCountdown()
#if DEBUG
// Debug
if email.isEmpty {
email = "85494536@gmail.com"
}
if verificationCode.isEmpty {
verificationCode = "784544"
}
print("🐛 Debug模式: 默认邮箱=\(email), 默认验证码=\(verificationCode)")
email = "exzero@126.com"
store.send(.emailChanged(email))
#endif
}
.onDisappear {
stopCountdown()
}
.onChange(of: email) { newEmail in
store.send(.emailChanged(newEmail))
}
.onChange(of: verificationCode) { newCode in
store.send(.verificationCodeChanged(newCode))
}
.onChange(of: store.isCodeLoading) { isCodeLoading in
// API
if !isCodeLoading && store.errorMessage == nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
focusedField = .verificationCode
}
}
}
}
// MARK: -
private func startCountdown() {
stopCountdown()
//
codeCountdown = 60
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
DispatchQueue.main.async {
if codeCountdown > 0 {
codeCountdown -= 1
} else {
stopCountdown()
}
}
}
}
private func stopCountdown() {
timer?.invalidate()
timer = nil
}
}
@@ -211,4 +268,4 @@ struct EMailLoginView: View {
},
onBack: {}
)
}
}