feat: 手机号 + 验证码登录
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user