import SwiftUI import ComposableArchitecture import Perception struct IDLoginView: View { let store: StoreOf let onBack: () -> Void @Binding var showIDLogin: Bool // 新增:绑定父视图的显示状态 // 使用本地@State管理UI状态 @State private var userID: String = "" @State private var password: String = "" @State private var isPasswordVisible: Bool = false // 导航状态管理 - 与 LoginView 保持一致 @State private var showRecoverPassword: Bool = false // 计算登录按钮是否可用 private var isLoginButtonEnabled: Bool { return !store.isLoading && !userID.isEmpty && !password.isEmpty } var body: some View { WithViewStore(store, observe: { $0.loginStep }) { viewStore in GeometryReader { geometry in WithPerceptionTracking { ZStack { // 背景图片 - 使用与登录页面相同的"bg" Image("bg") .resizable() .aspectRatio(contentMode: .fill) .ignoresSafeArea(.all) VStack(spacing: 0) { // 顶部导航栏 HStack { Button(action: { onBack() }) { Image(systemName: "chevron.left") .font(.system(size: 24, weight: .medium)) .foregroundColor(.white) .frame(width: 44, height: 44) } Spacer() } .padding(.horizontal, 16) .padding(.top, 8) Spacer() .frame(height: 60) // 标题 Text(NSLocalizedString("id_login.title", comment: "")) .font(.system(size: 28, weight: .medium)) .foregroundColor(.white) .padding(.bottom, 80) // 输入框区域 VStack(spacing: 24) { // ID 输入框 ZStack { RoundedRectangle(cornerRadius: 25) .fill(Color.white.opacity(0.1)) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.white.opacity(0.3), lineWidth: 1) ) .frame(height: 56) TextField("", text: $userID) // 使用SwiftUI的绑定 .placeholder(when: userID.isEmpty) { Text(NSLocalizedString("placeholder.enter_id", comment: "")) .foregroundColor(.white.opacity(0.6)) } .foregroundColor(.white) .font(.system(size: 16)) .padding(.horizontal, 24) .keyboardType(.numberPad) } // 密码输入框 ZStack { RoundedRectangle(cornerRadius: 25) .fill(Color.white.opacity(0.1)) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.white.opacity(0.3), lineWidth: 1) ) .frame(height: 56) HStack { if isPasswordVisible { TextField("", text: $password) // 使用SwiftUI的绑定 .placeholder(when: password.isEmpty) { Text(NSLocalizedString("placeholder.enter_password", comment: "")) .foregroundColor(.white.opacity(0.6)) } .foregroundColor(.white) .font(.system(size: 16)) } else { SecureField("", text: $password) // 使用SwiftUI的绑定 .placeholder(when: password.isEmpty) { Text(NSLocalizedString("placeholder.enter_password", comment: "")) .foregroundColor(.white.opacity(0.6)) } .foregroundColor(.white) .font(.system(size: 16)) } Button(action: { isPasswordVisible.toggle() }) { Image(systemName: isPasswordVisible ? "eye.slash" : "eye") .foregroundColor(.white.opacity(0.7)) .font(.system(size: 18)) } } .padding(.horizontal, 24) } } .padding(.horizontal, 32) // Forgot Password 链接 HStack { Spacer() Button(action: { showRecoverPassword = true }) { Text(NSLocalizedString("id_login.forgot_password", comment: "")) .font(.system(size: 14)) .foregroundColor(.white.opacity(0.8)) } } .padding(.horizontal, 32) .padding(.top, 16) Spacer() .frame(height: 60) // 登录按钮 Button(action: { // 发送登录action时传递本地状态 store.send(.loginButtonTapped(userID: userID, password: password)) }) { ZStack { // 渐变背景 LinearGradient( colors: [ Color(red: 0.85, green: 0.37, blue: 1.0), // #D85EFF Color(red: 0.54, green: 0.31, blue: 1.0) // #8A4FFF ], startPoint: .leading, endPoint: .trailing ) .clipShape(RoundedRectangle(cornerRadius: 28)) HStack { if store.isLoading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .scaleEffect(0.8) } Text(store.isLoading ? NSLocalizedString("id_login.logging_in", comment: "") : NSLocalizedString("id_login.login_button", comment: "")) .font(.system(size: 18, weight: .semibold)) .foregroundColor(.white) } } .frame(height: 56) } .disabled(store.isLoading || userID.isEmpty || password.isEmpty) .opacity(isLoginButtonEnabled ? 1.0 : 0.5) // 透明度50%当条件不满足时 .padding(.horizontal, 32) // 错误信息 if let errorMessage = store.errorMessage { Text(errorMessage) .font(.system(size: 14)) .foregroundColor(.red) .padding(.top, 16) .padding(.horizontal, 32) } Spacer() } } } } .navigationBarHidden(true) // 使用与 LoginView 一致的 navigationDestination 方式 .navigationDestination(isPresented: $showRecoverPassword) { WithPerceptionTracking { RecoverPasswordView( store: Store( initialState: RecoverPasswordFeature.State() ) { RecoverPasswordFeature() }, onBack: { showRecoverPassword = false } ) .navigationBarHidden(true) } } .onAppear { let _ = WithPerceptionTracking { // 初始化时同步TCA状态到本地状态 userID = store.userID password = store.password isPasswordVisible = store.isPasswordVisible #if DEBUG // 移除测试用的硬编码凭据 debugInfoSync("🐛 Debug模式: 已移除硬编码测试凭据") #endif } } // 新增:监听登录状态,成功后自动关闭自身 .onChange(of: viewStore.state) { newStep in debugInfoSync("🔄 IDLoginView: loginStep 变化为 \(newStep)") if newStep == .completed { debugInfoSync("✅ IDLoginView: 登录成功,准备关闭自身") showIDLogin = false } } } } } //#Preview { // IDLoginView( // store: Store( // initialState: IDLoginFeature.State() // ) { // IDLoginFeature() // }, // onBack: {}, // showIDLogin: .constant(true) // ) //}