feat: 手机号 + 验证码登录

This commit is contained in:
AndyChen1024
2026-09-15 14:35:52 +08:00
parent 7f6cbb3190
commit affe326e90
2 changed files with 158 additions and 3 deletions

View File

@@ -1,10 +1,100 @@
import SwiftUI
import DesignSystem
/// FeatureAuth 占位(后续实现登录 / 注册)。
public struct AuthScreen: View {
public init() {}
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 {
ContentUnavailableView("登录", systemImage: "person.crop.circle")
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))
}
}