// // LoginViewController.swift // suixinkan // import SnapKit import UIKit /// 登录页,UI 严格对齐 Android `LoginScreen`。 final class LoginViewController: BaseViewController { private let viewModel = LoginViewModel() private let backgroundImageView = UIImageView() private let welcomeLabel = UILabel() private let formCardView = UIView() private let topFormStack = UIStackView() private let accountField = LoginTextField(placeholder: "请输入账号") private let passwordField = PasswordInputField() private let agreementRow = AgreementRowView() private let loginButton = AppButton(title: "登录", style: .primary) private let loginByCodeButton = AppButton(title: "验证码登录", style: .secondary) private let registerContainer = UIStackView() private let registerPrefixLabel = UILabel() private let registerButton = UIButton(type: .system) private var hasPlayedEntranceAnimation = false override var preferredStatusBarStyle: UIStatusBarStyle { .lightContent } override func setupNavigationBar() { navigationController?.setNavigationBarHidden(true, animated: false) } override func setupUI() { view.backgroundColor = .black backgroundImageView.image = UIImage(named: "img_login_bg") backgroundImageView.contentMode = .scaleAspectFill backgroundImageView.clipsToBounds = true welcomeLabel.text = "欢迎使用\n随心瞰商家版" welcomeLabel.numberOfLines = 2 welcomeLabel.font = .systemFont(ofSize: 28, weight: .semibold) welcomeLabel.textColor = .white formCardView.backgroundColor = .white formCardView.layer.cornerRadius = 16 formCardView.clipsToBounds = true topFormStack.axis = .vertical topFormStack.spacing = 0 topFormStack.alignment = .fill loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside) loginByCodeButton.addTarget(self, action: #selector(loginByCodeButtonTapped), for: .touchUpInside) registerContainer.axis = .horizontal registerContainer.alignment = .center registerContainer.spacing = 0 registerPrefixLabel.text = "没有账号?" registerPrefixLabel.font = .systemFont(ofSize: 14, weight: .medium) registerPrefixLabel.textColor = AppColor.text333 registerButton.setTitle("去注册", for: .normal) registerButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium) registerButton.setTitleColor(AppColor.primary, for: .normal) registerButton.addTarget(self, action: #selector(registerButtonTapped), for: .touchUpInside) registerContainer.addArrangedSubview(registerPrefixLabel) registerContainer.addArrangedSubview(registerButton) view.addSubview(backgroundImageView) view.addSubview(welcomeLabel) view.addSubview(formCardView) formCardView.addSubview(topFormStack) formCardView.addSubview(registerContainer) topFormStack.addArrangedSubview(accountField) topFormStack.setCustomSpacing(8, after: accountField) topFormStack.addArrangedSubview(passwordField) topFormStack.setCustomSpacing(24, after: passwordField) topFormStack.addArrangedSubview(agreementRow) topFormStack.setCustomSpacing(16, after: agreementRow) topFormStack.addArrangedSubview(loginButton) topFormStack.setCustomSpacing(8, after: loginButton) topFormStack.addArrangedSubview(loginByCodeButton) bindViewModel() updateUI() } override func setupConstraints() { backgroundImageView.snp.makeConstraints { make in make.edges.equalToSuperview() } welcomeLabel.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).offset(48) make.leading.trailing.equalToSuperview().inset(24) } formCardView.snp.makeConstraints { make in make.top.equalTo(welcomeLabel.snp.bottom).offset(48) make.leading.trailing.equalToSuperview().inset(24) make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-24) } topFormStack.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview().inset(16) make.bottom.equalToSuperview().inset(16) } registerContainer.snp.makeConstraints { make in make.centerX.equalToSuperview() make.bottom.equalToSuperview().inset(16) } } override func bindActions() { accountField.onTextChange = { [weak self] text in self?.viewModel.updateAccount(text) } accountField.onReturnKey = { [weak self] in self?.passwordField.becomeFirstResponder() } passwordField.onTextChange = { [weak self] text in self?.viewModel.updatePassword(text) } passwordField.onReturnKey = { [weak self] in self?.view.endEditing(true) self?.viewModel.login() } agreementRow.onCheckedChange = { [weak self] in self?.viewModel.togglePrivacyChecked() } agreementRow.onUserAgreementTap = { [weak self] in self?.viewModel.onUserAgreement?() } agreementRow.onPrivacyPolicyTap = { [weak self] in self?.viewModel.onPrivacyPolicy?() } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) playEntranceAnimationIfNeeded() } private func bindViewModel() { viewModel.onStateChange = { [weak self] in self?.updateUI() } viewModel.onShowMessage = { [weak self] message in self?.showToast(message) } viewModel.onLogin = { [weak self] in self?.showToast("登录功能待接入") } viewModel.onLoginByCode = { [weak self] in self?.showToast("验证码登录待接入") } viewModel.onRegister = { [weak self] in self?.showToast("注册待接入") } viewModel.onUserAgreement = { [weak self] in self?.showToast("用户协议待接入") } viewModel.onPrivacyPolicy = { [weak self] in self?.showToast("隐私政策待接入") } } private func updateUI() { agreementRow.isChecked = viewModel.isPrivacyChecked loginButton.isEnabled = viewModel.isLoginEnabled let showRegisterSection = viewModel.enableRegister loginByCodeButton.isHidden = !showRegisterSection registerContainer.isHidden = !showRegisterSection topFormStack.snp.remakeConstraints { make in make.top.leading.trailing.equalToSuperview().inset(16) if showRegisterSection { make.bottom.lessThanOrEqualTo(registerContainer.snp.top).offset(-16) } else { make.bottom.equalToSuperview().inset(16) } } } private func playEntranceAnimationIfNeeded() { guard !hasPlayedEntranceAnimation else { return } hasPlayedEntranceAnimation = true formCardView.transform = CGAffineTransform(translationX: 0, y: view.bounds.height) welcomeLabel.alpha = 0 UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseOut) { self.formCardView.transform = .identity self.welcomeLabel.alpha = 1 } } @objc private func loginButtonTapped() { view.endEditing(true) viewModel.login() } @objc private func loginByCodeButtonTapped() { view.endEditing(true) viewModel.onLoginByCode?() } @objc private func registerButtonTapped() { view.endEditing(true) viewModel.onRegister?() } }