import SwiftUI /// 短信登录页(sheet 形式)。登录成功后 dismiss,守卫页因 session 变化自动切到已登录内容。 struct LoginView: View { @Environment(SessionStore.self) private var session @Environment(\.dismiss) private var dismiss @State private var vm = LoginViewModel() var body: some View { NavigationStack { VStack(spacing: 24) { header VStack(spacing: 14) { phoneField codeRow } loginButton if vm.isMock { Text("体验模式:任意 6 位验证码即可登录") .font(.caption) .foregroundStyle(AppColors.warning) } if let err = vm.errorMessage { Text(err).font(.footnote).foregroundStyle(AppColors.error) } Spacer() } .padding(24) .navigationTitle("手机号登录") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("关闭") { dismiss() } } } } } private var header: some View { VStack(spacing: 10) { Image(systemName: "tag.fill") .font(.system(size: 48)) .foregroundStyle(AppColors.brand) Text("登录后查看你的省钱数据") .font(.subheadline) .foregroundStyle(.secondary) } .padding(.top, 12) } private var phoneField: some View { TextField("请输入手机号", text: $vm.phone) .keyboardType(.numberPad) .textContentType(.telephoneNumber) .padding() .background(AppColors.gray100) .clipShape(RoundedRectangle(cornerRadius: 12)) .onChange(of: vm.phone) { _, new in vm.phone = String(new.filter(\.isNumber).prefix(11)) } } private var codeRow: some View { HStack(spacing: 12) { TextField("验证码", text: $vm.code) .keyboardType(.numberPad) .textContentType(.oneTimeCode) .padding() .background(AppColors.gray100) .clipShape(RoundedRectangle(cornerRadius: 12)) .onChange(of: vm.code) { _, new in vm.code = String(new.filter(\.isNumber).prefix(6)) } Button { Task { await vm.sendCode() } } label: { Text(vm.countdown > 0 ? "\(vm.countdown)s" : "发送验证码") .font(.subheadline) .frame(width: 100) .padding(.vertical, 14) .background(vm.canSendCode ? AppColors.primary : AppColors.gray200) .foregroundStyle(vm.canSendCode ? .black : AppColors.gray500) .clipShape(RoundedRectangle(cornerRadius: 12)) } .disabled(!vm.canSendCode) } } private var loginButton: some View { Button { Task { if await vm.login(into: session) { dismiss() } } } label: { Text(vm.isLoggingIn ? "登录中…" : "登录") .font(.headline) .frame(maxWidth: .infinity) .padding(.vertical, 16) .background(vm.canLogin ? AppColors.primary : AppColors.gray200) .foregroundStyle(vm.canLogin ? .black : AppColors.gray500) .clipShape(RoundedRectangle(cornerRadius: 14)) } .disabled(!vm.canLogin) } }