import SwiftUI /// 短信登录状态机(对齐安卓 SmsLoginViewModel)。一键登录首版不做。 @MainActor @Observable final class LoginViewModel { var phone: String = "" var code: String = "" var countdown: Int = 0 var isSending = false var isLoggingIn = false var isMock = false var errorMessage: String? private let auth = AuthService() private var timerTask: Task? var isValidPhone: Bool { phone.count == 11 && phone.hasPrefix("1") } var canSendCode: Bool { countdown == 0 && !isSending && isValidPhone } var canLogin: Bool { isValidPhone && code.count >= 4 && !isLoggingIn } func sendCode() async { guard canSendCode else { return } errorMessage = nil isSending = true defer { isSending = false } do { let resp = try await auth.smsSend(phone: phone) isMock = resp.mock startCountdown(resp.cooldownSec > 0 ? resp.cooldownSec : 60) } catch { errorMessage = (error as? LocalizedError)?.errorDescription ?? "验证码发送失败,请重试" } } /// 登录成功则已写入 session,返回 true(供 View 关闭页面)。 func login(into session: SessionStore) async -> Bool { guard canLogin else { return false } errorMessage = nil isLoggingIn = true defer { isLoggingIn = false } do { let resp = try await auth.smsLogin(phone: phone, code: code) await session.loginSucceeded(resp) return true } catch APIError.http(let status, _) where status == 400 { errorMessage = "验证码错误或已失效,请重新获取" return false } catch { errorMessage = (error as? LocalizedError)?.errorDescription ?? "登录失败,请重试" return false } } private func startCountdown(_ seconds: Int) { countdown = seconds timerTask?.cancel() timerTask = Task { @MainActor [weak self] in while true { do { try await Task.sleep(for: .seconds(1)) } catch { return } // 被 cancel(重发时)→ 立即退出,不再误减新一轮计时 guard let self, self.countdown > 0 else { return } self.countdown -= 1 } } } }