Files
shaguabijia-app-ios/App/Features/Login/LoginView.swift
T
marco 98b7600797 初始化傻瓜比价正式版 iOS 客户端
对标安卓正式版、对接 app-server(8770)的 SwiftUI 客户端。iOS 无障碍受限、无比价核心,做自成一体:美团 CPS 导购 + 福利(签到/任务/金币)+ 查看(比价记录/省钱战绩)。
功能完整 M0-M5:脚手架、短信登录(JWT+Keychain+401 自动刷新)、首页券 feed 导购、比价记录+省钱战绩、福利、我的(资料/反馈/设置/注销)。
单 target、0 三方依赖、XcodeGen 生成工程;Debug/Release 双 plist 分流 ATS。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:54:11 +08:00

111 lines
3.7 KiB
Swift

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)
}
}