// // AccountSwitchViewController.swift // suixinkan // import SnapKit import UIKit private typealias AccountSwitchSection = Int private typealias AccountSwitchItem = String /// 账号切换页面,展示当前登录主体下可进入的景区账号和门店账号。 final class AccountSwitchViewController: BaseViewController, UITableViewDelegate { private let viewModel = AccountSwitchViewModel() private let profileAPI = NetworkServices.shared.profileAPI private let authAPI = NetworkServices.shared.authAPI private lazy var tableView: UITableView = { let table = UITableView(frame: .zero, style: .plain) table.backgroundColor = UIColor(hex: 0xF5F7FB) table.separatorStyle = .none table.delegate = self table.register(AccountSwitchCell.self, forCellReuseIdentifier: AccountSwitchCell.reuseID) return table }() private let confirmButton = AppButton(title: "确认切换") private var tableDataSource: UITableViewDiffableDataSource! override func setupNavigationBar() { title = "账号切换" } override func setupUI() { view.backgroundColor = UIColor(hex: 0xF5F7FB) configureTableDataSource() view.addSubview(tableView) view.addSubview(confirmButton) } override func setupConstraints() { tableView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() make.bottom.equalTo(confirmButton.snp.top).offset(-12) } confirmButton.snp.makeConstraints { make in make.leading.trailing.equalToSuperview().inset(16) make.bottom.equalTo(view.safeAreaLayoutGuide).inset(14) } } override func bindActions() { viewModel.onStateChange = { [weak self] in self?.applyTableSnapshot(reconfigure: true) self?.updateConfirmButton() } confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside) Task { await loadAccounts() } } private func configureTableDataSource() { tableDataSource = UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, item in guard let self else { return UITableViewCell() } let cell = tableView.dequeueReusableCell(withIdentifier: AccountSwitchCell.reuseID, for: indexPath) as! AccountSwitchCell guard let account = self.viewModel.accounts.first(where: { $0.id == item }) else { return cell } cell.configure( account: account, selected: account.id == self.viewModel.selectedAccountId, isCurrent: account.isCurrent || self.isCurrentAccount(account) ) return cell } applyTableSnapshot(animated: false) } private func buildSnapshot() -> NSDiffableDataSourceSnapshot { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) snapshot.appendItems(viewModel.accounts.map(\.id), toSection: 0) return snapshot } private func applyTableSnapshot(animated: Bool = true, reconfigure: Bool = false) { var snapshot = buildSnapshot() if reconfigure, !snapshot.itemIdentifiers.isEmpty { snapshot.reconfigureItems(snapshot.itemIdentifiers) } tableDataSource.apply(snapshot, animatingDifferences: animated) } private func updateConfirmButton() { let enabled = viewModel.selectedAccount != nil && !viewModel.loading && !viewModel.switching confirmButton.isEnabled = enabled } private var currentAccountId: String? { let store = AppStore.shared if store.currentStoreId > 0 { return "\(V9StoreUser.accountTypeValue)_\(store.currentStoreId)" } if store.currentScenicId > 0 { return "\(V9ScenicUser.accountTypeValue)_\(store.userId)" } return store.userId.isEmpty ? nil : store.userId } private func isCurrentAccount(_ account: AccountSwitchAccount) -> Bool { viewModel.isCurrent(account, currentAccountId: currentAccountId) } private func loadAccounts(force: Bool = false) async { showLoading() defer { hideLoading() } do { try await viewModel.load(api: profileAPI, force: force, currentAccountId: currentAccountId) } catch { showToast(error.localizedDescription) } } @objc private func confirmTapped() { guard let account = viewModel.selectedAccount else { return } if account.isCurrent || isCurrentAccount(account) { navigationController?.popViewController(animated: true) return } Task { await switchAccount(account) } } private func switchAccount(_ account: AccountSwitchAccount) async { showLoading() defer { hideLoading() } do { let response = try await viewModel.switchAccount(account, api: authAPI) AuthSessionHelper.applyAccountSwitch(with: response, account: account) showToast("账号已切换") navigationController?.popViewController(animated: true) } catch { showToast(error.localizedDescription) } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) guard let item = tableDataSource.itemIdentifier(for: indexPath), let account = viewModel.accounts.first(where: { $0.id == item }) else { return } viewModel.select(account) } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 92 } } private final class AccountSwitchCell: UITableViewCell { static let reuseID = "AccountSwitchCell" private let card = UIView() private let avatarView = UIImageView() private let titleLabel = UILabel() private let subtitleLabel = UILabel() private let phoneLabel = UILabel() private let tagLabel = UILabel() private let checkView = UIImageView() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .clear card.backgroundColor = .white card.layer.cornerRadius = 12 contentView.addSubview(card) avatarView.layer.cornerRadius = 25 avatarView.clipsToBounds = true avatarView.contentMode = .scaleAspectFill titleLabel.font = .systemFont(ofSize: 16, weight: .semibold) subtitleLabel.font = .systemFont(ofSize: 13) subtitleLabel.textColor = AppColor.text666 phoneLabel.font = .systemFont(ofSize: 12) phoneLabel.textColor = UIColor(hex: 0x9AA1AA) tagLabel.font = .systemFont(ofSize: 11, weight: .semibold) tagLabel.textAlignment = .center tagLabel.layer.cornerRadius = 4 tagLabel.clipsToBounds = true card.addSubview(avatarView) card.addSubview(titleLabel) card.addSubview(subtitleLabel) card.addSubview(phoneLabel) card.addSubview(tagLabel) card.addSubview(checkView) card.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 6, left: 16, bottom: 6, right: 16)) } avatarView.snp.makeConstraints { make in make.leading.centerY.equalToSuperview().inset(14) make.width.height.equalTo(50) } titleLabel.snp.makeConstraints { make in make.leading.equalTo(avatarView.snp.trailing).offset(13) make.top.equalTo(avatarView).offset(2) make.trailing.lessThanOrEqualTo(tagLabel.snp.leading).offset(-8) } subtitleLabel.snp.makeConstraints { make in make.leading.equalTo(titleLabel) make.top.equalTo(titleLabel.snp.bottom).offset(4) } phoneLabel.snp.makeConstraints { make in make.leading.equalTo(titleLabel) make.top.equalTo(subtitleLabel.snp.bottom).offset(4) } tagLabel.snp.makeConstraints { make in make.trailing.equalToSuperview().inset(14) make.top.equalToSuperview().offset(14) make.height.equalTo(22) make.width.greaterThanOrEqualTo(40) } checkView.snp.makeConstraints { make in make.trailing.bottom.equalToSuperview().inset(14) make.width.height.equalTo(22) } } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } func configure(account: AccountSwitchAccount, selected: Bool, isCurrent: Bool) { titleLabel.text = account.title if isCurrent { titleLabel.text = (account.title) + " (当前)" } subtitleLabel.text = account.subtitle phoneLabel.text = account.phone tagLabel.text = account.isStoreUser ? "门店" : "景区" tagLabel.textColor = account.isStoreUser ? UIColor(hex: 0x0F9F6E) : UIColor(hex: 0x7C3AED) tagLabel.backgroundColor = account.isStoreUser ? UIColor(hex: 0xE8F8F1) : UIColor(hex: 0xF3ECFF) checkView.image = UIImage(systemName: selected ? "checkmark.circle.fill" : "circle") checkView.tintColor = selected ? AppColor.primary : UIColor(hex: 0xB6BECA) avatarView.loadRemoteImage(urlString: account.avatar) } }