feat: 增强邮箱登录功能和密码恢复流程
- 更新邮箱登录相关功能,新增邮箱验证码获取和登录API端点。 - 添加AccountModel以管理用户认证信息,支持会话票据的存储和更新。 - 实现密码恢复功能,支持通过邮箱获取验证码和重置密码。 - 增加本地化支持,更新相关字符串以适应新功能。 - 引入ValidationHelper以验证邮箱和密码格式,确保用户输入的有效性。 - 更新视图以支持邮箱登录和密码恢复的用户交互。
This commit is contained in:
@@ -10,32 +10,29 @@ struct EMailLoginFeature {
|
||||
var isLoading: Bool = false
|
||||
var isCodeLoading: Bool = false
|
||||
var errorMessage: String? = nil
|
||||
var codeCountdown: Int = 0
|
||||
var isCodeButtonEnabled: Bool = true
|
||||
var isCodeSent: Bool = false
|
||||
|
||||
// Debug模式下的默认值
|
||||
#if DEBUG
|
||||
init() {
|
||||
self.email = "85494536@gmail.com"
|
||||
self.email = "exzero@126.com"
|
||||
self.verificationCode = ""
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
enum Action: Equatable {
|
||||
enum Action {
|
||||
case emailChanged(String)
|
||||
case verificationCodeChanged(String)
|
||||
case getVerificationCodeTapped
|
||||
case getCodeResponse(Result<EmailGetCodeResponse, Error>)
|
||||
case loginButtonTapped(email: String, verificationCode: String)
|
||||
case loginResponse(Result<AccountModel, Error>)
|
||||
case forgotPasswordTapped
|
||||
case codeCountdownTick
|
||||
case setLoading(Bool)
|
||||
case setCodeLoading(Bool)
|
||||
case setError(String?)
|
||||
case startCodeCountdown
|
||||
case resetCodeCountdown
|
||||
case resetState
|
||||
}
|
||||
|
||||
@Dependency(\.apiService) var apiService
|
||||
|
||||
var body: some ReducerOf<Self> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
@@ -55,28 +52,57 @@ struct EMailLoginFeature {
|
||||
return .none
|
||||
}
|
||||
|
||||
guard isValidEmail(state.email) else {
|
||||
guard ValidationHelper.isValidEmail(state.email) else {
|
||||
state.errorMessage = "email_login.invalid_email".localized
|
||||
return .none
|
||||
}
|
||||
|
||||
state.isCodeLoading = true
|
||||
state.isCodeSent = false // 重置状态确保触发变化
|
||||
state.errorMessage = nil
|
||||
|
||||
return .run { send in
|
||||
// 模拟获取验证码API调用
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000) // 1秒
|
||||
await send(.setCodeLoading(false))
|
||||
await send(.startCodeCountdown)
|
||||
return .run { [email = state.email] send in
|
||||
do {
|
||||
guard let request = LoginHelper.createEmailGetCodeRequest(email: email) else {
|
||||
await send(.getCodeResponse(.failure(APIError.encryptionFailed)))
|
||||
return
|
||||
}
|
||||
|
||||
let response = try await apiService.request(request)
|
||||
await send(.getCodeResponse(.success(response)))
|
||||
|
||||
} catch {
|
||||
await send(.getCodeResponse(.failure(error)))
|
||||
}
|
||||
}
|
||||
|
||||
case .getCodeResponse(.success(let response)):
|
||||
state.isCodeLoading = false
|
||||
|
||||
if response.isSuccess {
|
||||
state.isCodeSent = true
|
||||
return .none
|
||||
} else {
|
||||
state.errorMessage = response.errorMessage
|
||||
return .none
|
||||
}
|
||||
|
||||
case .getCodeResponse(.failure(let error)):
|
||||
state.isCodeLoading = false
|
||||
if let apiError = error as? APIError {
|
||||
state.errorMessage = apiError.localizedDescription
|
||||
} else {
|
||||
state.errorMessage = "验证码发送失败,请检查网络连接"
|
||||
}
|
||||
return .none
|
||||
|
||||
case .loginButtonTapped(let email, let verificationCode):
|
||||
guard !email.isEmpty && !verificationCode.isEmpty else {
|
||||
state.errorMessage = "email_login.fields_required".localized
|
||||
return .none
|
||||
}
|
||||
|
||||
guard isValidEmail(email) else {
|
||||
guard ValidationHelper.isValidEmail(email) else {
|
||||
state.errorMessage = "email_login.invalid_email".localized
|
||||
return .none
|
||||
}
|
||||
@@ -85,61 +111,78 @@ struct EMailLoginFeature {
|
||||
state.errorMessage = nil
|
||||
|
||||
return .run { send in
|
||||
// 模拟登录API调用
|
||||
try await Task.sleep(nanoseconds: 2_000_000_000) // 2秒
|
||||
await send(.setLoading(false))
|
||||
// 这里应该处理实际的登录逻辑
|
||||
print("🔐 邮箱登录尝试: \(email), 验证码: \(verificationCode)")
|
||||
do {
|
||||
guard let request = LoginHelper.createEmailLoginRequest(email: email, code: verificationCode) else {
|
||||
await send(.loginResponse(.failure(APIError.encryptionFailed)))
|
||||
return
|
||||
}
|
||||
|
||||
let response = try await apiService.request(request)
|
||||
|
||||
if response.isSuccess, let loginData = response.data {
|
||||
guard let accountModel = AccountModel.from(loginData: loginData) else {
|
||||
await send(.loginResponse(.failure(APIError.invalidResponse)))
|
||||
return
|
||||
}
|
||||
|
||||
// 第二阶段:获取Ticket
|
||||
let ticketRequest = TicketHelper.createTicketRequest(
|
||||
accessToken: accountModel.accessToken ?? "",
|
||||
uid: accountModel.uid.flatMap { Int($0) }
|
||||
)
|
||||
let ticketResponse = try await apiService.request(ticketRequest)
|
||||
|
||||
if ticketResponse.isSuccess, let ticket = ticketResponse.ticket {
|
||||
let completeAccount = accountModel.withTicket(ticket)
|
||||
await send(.loginResponse(.success(completeAccount)))
|
||||
} else {
|
||||
await send(.loginResponse(.failure(APIError.ticketFailed)))
|
||||
}
|
||||
} else {
|
||||
await send(.loginResponse(.failure(APIError.custom(response.errorMessage))))
|
||||
}
|
||||
|
||||
} catch {
|
||||
await send(.loginResponse(.failure(error)))
|
||||
}
|
||||
}
|
||||
|
||||
case .loginResponse(.success(let accountModel)):
|
||||
state.isLoading = false
|
||||
|
||||
// 保存AccountModel到本地存储
|
||||
UserInfoManager.saveAccountModel(accountModel)
|
||||
|
||||
// 发送成功通知,触发导航到主界面
|
||||
NotificationCenter.default.post(name: .ticketSuccess, object: nil)
|
||||
|
||||
return .none
|
||||
|
||||
case .loginResponse(.failure(let error)):
|
||||
state.isLoading = false
|
||||
if let apiError = error as? APIError {
|
||||
state.errorMessage = apiError.localizedDescription
|
||||
} else {
|
||||
state.errorMessage = "登录失败,请重试"
|
||||
}
|
||||
return .none
|
||||
|
||||
case .forgotPasswordTapped:
|
||||
// 处理忘记密码逻辑
|
||||
print("📧 忘记密码点击")
|
||||
return .none
|
||||
|
||||
case .codeCountdownTick:
|
||||
if state.codeCountdown > 0 {
|
||||
state.codeCountdown -= 1
|
||||
state.isCodeButtonEnabled = false
|
||||
|
||||
return .run { send in
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000) // 1秒
|
||||
await send(.codeCountdownTick)
|
||||
}
|
||||
} else {
|
||||
state.isCodeButtonEnabled = true
|
||||
return .none
|
||||
}
|
||||
|
||||
case .setLoading(let isLoading):
|
||||
state.isLoading = isLoading
|
||||
return .none
|
||||
|
||||
case .setCodeLoading(let isLoading):
|
||||
state.isCodeLoading = isLoading
|
||||
return .none
|
||||
|
||||
case .setError(let error):
|
||||
state.errorMessage = error
|
||||
return .none
|
||||
|
||||
case .startCodeCountdown:
|
||||
state.codeCountdown = 60
|
||||
state.isCodeButtonEnabled = false
|
||||
return .send(.codeCountdownTick)
|
||||
|
||||
case .resetCodeCountdown:
|
||||
state.codeCountdown = 0
|
||||
state.isCodeButtonEnabled = true
|
||||
case .resetState:
|
||||
state.email = ""
|
||||
state.verificationCode = ""
|
||||
state.isLoading = false
|
||||
state.isCodeLoading = false
|
||||
state.errorMessage = nil
|
||||
state.isCodeSent = false
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
private func isValidEmail(_ email: String) -> Bool {
|
||||
let emailRegex = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
|
||||
let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
|
||||
return emailPredicate.evaluate(with: email)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user