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

@@ -10,14 +10,13 @@ struct LoginFeature {
var isLoading = false
var error: String?
var idLoginState = IDLoginFeature.State()
var emailLoginState = EMailLoginFeature.State() //
// Ticket
var accessToken: String?
var ticket: String?
// Account Model Ticket
var accountModel: AccountModel?
var isTicketLoading = false
var ticketError: String?
var loginStep: LoginStep = .initial
var uid: Int? // uidInt
enum LoginStep: Equatable {
case initial //
@@ -36,12 +35,13 @@ struct LoginFeature {
#endif
}
enum Action: Equatable {
enum Action {
case updateAccount(String)
case updatePassword(String)
case login
case loginResponse(TaskResult<IDLoginResponse>)
case idLogin(IDLoginFeature.Action)
case emailLogin(EMailLoginFeature.Action) // action
// Ticket actions
case requestTicket(accessToken: String)
@@ -57,6 +57,10 @@ struct LoginFeature {
IDLoginFeature()
}
Scope(state: \.emailLoginState, action: \.emailLogin) {
EMailLoginFeature()
}
Reduce { state, action in
switch action {
case let .updateAccount(account):
@@ -99,20 +103,21 @@ struct LoginFeature {
if response.isSuccess {
// OAuth
state.error = nil
state.accessToken = response.data?.accessToken
state.uid = response.data?.uid // uid
print("✅ OAuth 认证成功")
if let accessToken = response.data?.accessToken {
print("🔑 Access Token: \(accessToken)")
// ticket uid
return .send(.requestTicket(accessToken: accessToken))
}
if let userInfo = response.data?.userInfo {
print("👤 用户信息: \(userInfo)")
}
if let uid = response.data?.uid {
print("🆔 用户 UID: \(uid)")
// AccountModel
if let loginData = response.data,
let accountModel = AccountModel.from(loginData: loginData) {
state.accountModel = accountModel
print("✅ OAuth 认证成功")
print("🔑 Access Token: \(accountModel.accessToken ?? "nil")")
print("🆔 用户 UID: \(accountModel.uid ?? "nil")")
// ticket
return .send(.requestTicket(accessToken: accountModel.accessToken!))
} else {
state.error = "登录数据格式错误"
state.loginStep = .failed
}
} else {
state.error = response.errorMessage
@@ -131,9 +136,10 @@ struct LoginFeature {
state.ticketError = nil
state.loginStep = .gettingTicket
return .run { [uid = state.uid] send in
return .run { [accountModel = state.accountModel] send in
do {
// 使 TicketHelper uid
// AccountModel uid Int
let uid = accountModel?.uid != nil ? Int(accountModel!.uid!) : nil
let ticketRequest = TicketHelper.createTicketRequest(accessToken: accessToken, uid: uid)
let response = try await apiService.request(ticketRequest)
await send(.ticketResponse(.success(response)))
@@ -147,25 +153,32 @@ struct LoginFeature {
state.isTicketLoading = false
if response.isSuccess {
state.ticketError = nil
state.ticket = response.ticket
state.loginStep = .completed
print("✅ 完整登录流程成功")
print("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
//
if let accessToken = state.accessToken,
let ticket = response.ticket {
UserInfoManager.saveCompleteAuthenticationData(
accessToken: accessToken,
ticket: ticket,
uid: state.uid,
userInfo: nil // LoginFeature
)
// AccountModel ticket
if let ticket = response.ticket {
if var accountModel = state.accountModel {
accountModel.ticket = ticket
state.accountModel = accountModel
// AccountModel
UserInfoManager.saveAccountModel(accountModel)
// Ticket
NotificationCenter.default.post(name: .ticketSuccess, object: nil)
} else {
print("❌ AccountModel 不存在,无法保存 ticket")
state.ticketError = "内部错误:账户信息丢失"
state.loginStep = .failed
}
} else {
state.ticketError = "Ticket 为空"
state.loginStep = .failed
}
// TODO:
} else {
state.ticketError = response.errorMessage
state.loginStep = .failed
@@ -188,9 +201,7 @@ struct LoginFeature {
state.isTicketLoading = false
state.error = nil
state.ticketError = nil
state.accessToken = nil
state.ticket = nil
state.uid = nil // uid
state.accountModel = nil // AccountModel
state.loginStep = .initial
//
@@ -201,6 +212,10 @@ struct LoginFeature {
case .idLogin:
// IDLoginfeature
return .none
case .emailLogin:
// EmailLoginfeature
return .none
}
}
}