import SwiftUI import DesignSystem public struct AuthScreen: View { private let viewModel: AuthViewModel private let onLoggedIn: () -> Void @State private var phone = "" @State private var code = "" @State private var privacyAgreed = false @Environment(\.dismiss) private var dismiss public init(viewModel: AuthViewModel, onLoggedIn: @escaping () -> Void) { self.viewModel = viewModel self.onLoggedIn = onLoggedIn } public var body: some View { NavigationStack { ScrollView { VStack(alignment: .leading, spacing: DS.Spacing.md) { Text("手机号登录") .font(.title2.bold()) .foregroundStyle(DS.Color.brandDark) Text("手机号仅用于账号绑定,不会公开展示") .font(.footnote) .foregroundStyle(DS.Color.faintText) TextField("手机号", text: Binding(get: { phone }, set: { phone = digits($0, max: 11) })) .textFieldStyle(.roundedBorder) HStack(spacing: DS.Spacing.sm) { TextField("验证码", text: Binding(get: { code }, set: { code = digits($0, max: 6) })) .textFieldStyle(.roundedBorder) Button(getCodeTitle) { viewModel.requestSmsCode(phone: phone) } .buttonStyle(.bordered) .disabled(!canSendCode) } Button { privacyAgreed.toggle() } label: { HStack(spacing: DS.Spacing.xs) { Image(systemName: privacyAgreed ? "checkmark.square.fill" : "square") .foregroundStyle(privacyAgreed ? DS.Color.brandGold : DS.Color.brandMuted) Text("我已阅读并同意《用户协议》和《隐私政策》") .font(.caption) .foregroundStyle(DS.Color.faintText) } } .buttonStyle(.plain) if let errorMessage = viewModel.errorMessage { Text(errorMessage) .font(.footnote) .foregroundStyle(DS.Color.danger) } Button(viewModel.isSubmitting ? "登录中..." : "登录") { viewModel.login(phone: phone, code: code) } .buttonStyle(.borderedProminent) .frame(maxWidth: .infinity) .disabled(!canSubmit) } .padding(DS.Spacing.lg) } .navigationTitle("登录") .toolbar { ToolbarItem(placement: .cancellationAction) { Button("取消") { dismiss() } } } } .onChange(of: viewModel.didLogin) { _, loggedIn in if loggedIn { onLoggedIn() } } } private var phoneValid: Bool { phone.range(of: #"^1\d{10}$"#, options: .regularExpression) != nil } private var canSendCode: Bool { phoneValid && viewModel.countdownSeconds == 0 && !viewModel.isSendingCode } private var canSubmit: Bool { phoneValid && !code.isEmpty && privacyAgreed && !viewModel.isSubmitting } private var getCodeTitle: String { if viewModel.countdownSeconds > 0 { return "\(viewModel.countdownSeconds)s 后重发" } if viewModel.isSendingCode { return "发送中" } return "获取验证码" } private func digits(_ string: String, max: Int) -> String { String(string.filter(\.isNumber).prefix(max)) } }