367 lines
14 KiB
Swift
367 lines
14 KiB
Swift
//
|
|
// AccountSelectionViewController.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import SnapKit
|
|
import UIKit
|
|
|
|
private typealias AccountSelectionSection = Int
|
|
private typealias AccountSelectionItem = String
|
|
|
|
/// 多账号登录时的账号选择页,展示景区/门店账号列表并提交选择。
|
|
final class AccountSelectionViewController: UIViewController, UITableViewDelegate {
|
|
|
|
private let payload: AccountSelectionPayload
|
|
private var isLoading: Bool
|
|
private let onCancel: () -> Void
|
|
private let onConfirm: (AccountSwitchAccount) -> Void
|
|
|
|
private var selectedAccountId: String?
|
|
private let tableView = UITableView(frame: .zero, style: .plain)
|
|
private var tableDataSource: UITableViewDiffableDataSource<AccountSelectionSection, AccountSelectionItem>!
|
|
private let confirmButton = UIButton(type: .system)
|
|
private let bottomBar = UIView()
|
|
|
|
init(
|
|
payload: AccountSelectionPayload,
|
|
isLoading: Bool,
|
|
onCancel: @escaping () -> Void,
|
|
onConfirm: @escaping (AccountSwitchAccount) -> Void
|
|
) {
|
|
self.payload = payload
|
|
self.isLoading = isLoading
|
|
self.onCancel = onCancel
|
|
self.onConfirm = onConfirm
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
title = "选择账号"
|
|
view.backgroundColor = UIColor(hex: 0xF5F7FB)
|
|
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
|
title: "取消",
|
|
style: .plain,
|
|
target: self,
|
|
action: #selector(cancelTapped)
|
|
)
|
|
isModalInPresentation = isLoading
|
|
|
|
selectedAccountId = payload.accounts.first?.id
|
|
configureTableView()
|
|
configureTableDataSource()
|
|
configureBottomBar()
|
|
}
|
|
|
|
/// 更新 Loading 状态。
|
|
func updateLoading(_ loading: Bool) {
|
|
isLoading = loading
|
|
isModalInPresentation = loading
|
|
confirmButton.isEnabled = canConfirm
|
|
confirmButton.backgroundColor = canConfirm ? AppColor.primary : AppColor.buttonDisabled
|
|
}
|
|
|
|
private var selectedAccount: AccountSwitchAccount? {
|
|
payload.accounts.first { $0.id == selectedAccountId }
|
|
}
|
|
|
|
private var canConfirm: Bool {
|
|
selectedAccount != nil && !isLoading
|
|
}
|
|
|
|
private func configureTableDataSource() {
|
|
tableDataSource = UITableViewDiffableDataSource<AccountSelectionSection, AccountSelectionItem>(
|
|
tableView: tableView
|
|
) { [weak self] tableView, indexPath, item in
|
|
guard let self,
|
|
let cell = tableView.dequeueReusableCell(
|
|
withIdentifier: AccountSelectionCell.reuseIdentifier,
|
|
for: indexPath
|
|
) as? AccountSelectionCell,
|
|
let account = self.payload.accounts.first(where: { $0.id == item }) else {
|
|
return UITableViewCell()
|
|
}
|
|
cell.configure(account: account, selected: account.id == self.selectedAccountId)
|
|
return cell
|
|
}
|
|
applyTableSnapshot(animated: false)
|
|
}
|
|
|
|
private func buildTableSnapshot() -> NSDiffableDataSourceSnapshot<AccountSelectionSection, AccountSelectionItem> {
|
|
var snapshot = NSDiffableDataSourceSnapshot<AccountSelectionSection, AccountSelectionItem>()
|
|
snapshot.appendSections([0])
|
|
snapshot.appendItems(payload.accounts.map(\.id), toSection: 0)
|
|
return snapshot
|
|
}
|
|
|
|
private func applyTableSnapshot(animated: Bool = true, reconfigure: Bool = false) {
|
|
var snapshot = buildTableSnapshot()
|
|
if reconfigure, !snapshot.itemIdentifiers.isEmpty {
|
|
snapshot.reconfigureItems(snapshot.itemIdentifiers)
|
|
}
|
|
tableDataSource.apply(snapshot, animatingDifferences: animated)
|
|
}
|
|
|
|
private func configureTableView() {
|
|
tableView.backgroundColor = .clear
|
|
tableView.separatorStyle = .none
|
|
tableView.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
|
|
tableView.rowHeight = UITableView.automaticDimension
|
|
tableView.estimatedRowHeight = 92
|
|
tableView.delegate = self
|
|
tableView.register(AccountSelectionCell.self, forCellReuseIdentifier: AccountSelectionCell.reuseIdentifier)
|
|
view.addSubview(tableView)
|
|
tableView.snp.makeConstraints { make in
|
|
make.top.leading.trailing.equalToSuperview()
|
|
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(90)
|
|
}
|
|
}
|
|
|
|
private func configureBottomBar() {
|
|
bottomBar.backgroundColor = .white
|
|
|
|
let divider = UIView()
|
|
divider.backgroundColor = UIColor.separator
|
|
|
|
confirmButton.setTitle("进入系统", for: .normal)
|
|
confirmButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
|
|
confirmButton.setTitleColor(.white, for: .normal)
|
|
confirmButton.layer.cornerRadius = 8
|
|
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
|
|
updateLoading(isLoading)
|
|
|
|
view.addSubview(bottomBar)
|
|
bottomBar.addSubview(divider)
|
|
bottomBar.addSubview(confirmButton)
|
|
|
|
bottomBar.snp.makeConstraints { make in
|
|
make.leading.trailing.bottom.equalToSuperview()
|
|
}
|
|
|
|
divider.snp.makeConstraints { make in
|
|
make.top.leading.trailing.equalToSuperview()
|
|
make.height.equalTo(0.5)
|
|
}
|
|
|
|
confirmButton.snp.makeConstraints { make in
|
|
make.top.equalTo(divider.snp.bottom).offset(12)
|
|
make.leading.trailing.equalToSuperview().inset(16)
|
|
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(14)
|
|
make.height.equalTo(50)
|
|
}
|
|
}
|
|
|
|
@objc private func cancelTapped() {
|
|
onCancel()
|
|
dismiss(animated: true)
|
|
}
|
|
|
|
@objc private func confirmTapped() {
|
|
guard canConfirm, let selectedAccount else { return }
|
|
if selectedAccount.requiresDeregistrationConfirmation {
|
|
present(
|
|
makeDeregistrationLoginAlert(
|
|
account: selectedAccount,
|
|
onConfirm: { [weak self] in self?.onConfirm(selectedAccount) }
|
|
),
|
|
animated: true
|
|
)
|
|
return
|
|
}
|
|
onConfirm(selectedAccount)
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
guard let item = tableDataSource.itemIdentifier(for: indexPath) else { return }
|
|
selectedAccountId = item
|
|
applyTableSnapshot(animated: false, reconfigure: true)
|
|
confirmButton.isEnabled = canConfirm
|
|
confirmButton.backgroundColor = canConfirm ? AppColor.primary : AppColor.buttonDisabled
|
|
}
|
|
}
|
|
|
|
/// 创建冷静期账号登录确认弹窗,确认后 `set-user` 会由后端自动撤销原注销申请。
|
|
func makeDeregistrationLoginAlert(
|
|
account: AccountSwitchAccount,
|
|
onConfirm: @escaping () -> Void
|
|
) -> UIAlertController {
|
|
let accountName = account.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let displayName = accountName.isEmpty ? "该门店账号" : "“\(accountName)”"
|
|
let alert = UIAlertController(
|
|
title: "该账号正在注销",
|
|
message: "\(displayName)已提交注销申请,目前处于冷静期。确认登录后,将自动撤销之前的注销申请。",
|
|
preferredStyle: .alert
|
|
)
|
|
alert.addAction(UIAlertAction(title: "暂不登录", style: .cancel))
|
|
alert.addAction(UIAlertAction(title: "确认登录", style: .default) { _ in onConfirm() })
|
|
return alert
|
|
}
|
|
|
|
/// 账号选择列表 Cell。
|
|
private final class AccountSelectionCell: UITableViewCell {
|
|
static let reuseIdentifier = "AccountSelectionCell"
|
|
|
|
private let card = UIView()
|
|
private let avatarView = UIImageView()
|
|
private let avatarPlaceholderLabel = UILabel()
|
|
private let titleLabel = UILabel()
|
|
private let subtitleLabel = UILabel()
|
|
private let nameLabel = UILabel()
|
|
private let typeTag = UILabel()
|
|
private let currentTag = UILabel()
|
|
private let checkmark = UIImageView()
|
|
|
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
selectionStyle = .none
|
|
backgroundColor = .clear
|
|
contentView.backgroundColor = .clear
|
|
|
|
card.backgroundColor = .white
|
|
card.layer.cornerRadius = 10
|
|
card.layer.masksToBounds = true
|
|
|
|
avatarView.clipsToBounds = true
|
|
avatarView.layer.cornerRadius = 25
|
|
avatarView.contentMode = .scaleAspectFill
|
|
|
|
avatarPlaceholderLabel.font = .systemFont(ofSize: 14, weight: .semibold)
|
|
avatarPlaceholderLabel.textAlignment = .center
|
|
|
|
titleLabel.font = .systemFont(ofSize: 16, weight: .semibold)
|
|
titleLabel.textColor = AppColor.text333
|
|
|
|
subtitleLabel.font = .systemFont(ofSize: 13)
|
|
subtitleLabel.textColor = AppColor.text666
|
|
|
|
nameLabel.font = .systemFont(ofSize: 12)
|
|
nameLabel.textColor = UIColor(hex: 0x9AA1AA)
|
|
|
|
typeTag.font = .systemFont(ofSize: 11, weight: .semibold)
|
|
typeTag.textAlignment = .center
|
|
typeTag.layer.cornerRadius = 4
|
|
typeTag.clipsToBounds = true
|
|
typeTag.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
|
|
currentTag.font = .systemFont(ofSize: 11, weight: .semibold)
|
|
currentTag.textAlignment = .center
|
|
currentTag.text = "当前"
|
|
currentTag.textColor = AppColor.primary
|
|
currentTag.backgroundColor = AppColor.secondaryButtonBackground
|
|
currentTag.layer.cornerRadius = 4
|
|
currentTag.clipsToBounds = true
|
|
currentTag.isHidden = true
|
|
|
|
checkmark.contentMode = .scaleAspectFit
|
|
|
|
contentView.addSubview(card)
|
|
card.addSubview(avatarView)
|
|
avatarView.addSubview(avatarPlaceholderLabel)
|
|
card.addSubview(titleLabel)
|
|
card.addSubview(subtitleLabel)
|
|
card.addSubview(nameLabel)
|
|
card.addSubview(typeTag)
|
|
card.addSubview(currentTag)
|
|
card.addSubview(checkmark)
|
|
|
|
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.equalToSuperview().inset(16)
|
|
make.centerY.equalToSuperview()
|
|
make.width.height.equalTo(50)
|
|
}
|
|
|
|
avatarPlaceholderLabel.snp.makeConstraints { make in
|
|
make.center.equalToSuperview()
|
|
}
|
|
|
|
titleLabel.snp.makeConstraints { make in
|
|
make.top.equalToSuperview().inset(16)
|
|
make.leading.equalTo(avatarView.snp.trailing).offset(12)
|
|
make.trailing.lessThanOrEqualTo(typeTag.snp.leading).offset(-8)
|
|
}
|
|
|
|
currentTag.snp.makeConstraints { make in
|
|
make.leading.equalTo(titleLabel.snp.trailing).offset(6)
|
|
make.trailing.lessThanOrEqualTo(typeTag.snp.leading).offset(-8)
|
|
make.centerY.equalTo(titleLabel)
|
|
make.height.equalTo(18)
|
|
}
|
|
|
|
subtitleLabel.snp.makeConstraints { make in
|
|
make.top.equalTo(titleLabel.snp.bottom).offset(4)
|
|
make.leading.equalTo(titleLabel)
|
|
make.trailing.equalTo(checkmark.snp.leading).offset(-8)
|
|
}
|
|
|
|
nameLabel.snp.makeConstraints { make in
|
|
make.top.equalTo(subtitleLabel.snp.bottom).offset(4)
|
|
make.leading.equalTo(titleLabel)
|
|
make.bottom.equalToSuperview().inset(16)
|
|
}
|
|
|
|
typeTag.snp.makeConstraints { make in
|
|
make.trailing.equalTo(checkmark.snp.leading).offset(-8)
|
|
make.top.equalTo(titleLabel)
|
|
make.height.equalTo(18)
|
|
}
|
|
|
|
checkmark.snp.makeConstraints { make in
|
|
make.trailing.equalToSuperview().inset(16)
|
|
make.centerY.equalToSuperview()
|
|
make.width.height.equalTo(22)
|
|
}
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
nil
|
|
}
|
|
|
|
func configure(account: AccountSwitchAccount, selected: Bool) {
|
|
titleLabel.text = account.title.isEmpty ? account.accountTypeLabel : account.title
|
|
subtitleLabel.text = account.subtitle
|
|
subtitleLabel.isHidden = account.subtitle.isEmpty
|
|
nameLabel.text = account.realName
|
|
nameLabel.isHidden = account.realName.isEmpty
|
|
currentTag.isHidden = !account.isCurrent
|
|
|
|
let avatarPlaceholderColor: UIColor
|
|
let avatarTextColor: UIColor
|
|
if account.isStoreUser {
|
|
avatarPlaceholderColor = UIColor(hex: 0xE8F8F1)
|
|
avatarTextColor = UIColor(hex: 0x0F9F6E)
|
|
avatarPlaceholderLabel.text = "店"
|
|
typeTag.text = " 门店 "
|
|
typeTag.textColor = UIColor(hex: 0x0F9F6E)
|
|
typeTag.backgroundColor = UIColor(hex: 0xE8F8F1)
|
|
} else {
|
|
avatarPlaceholderColor = UIColor(hex: 0xF3ECFF)
|
|
avatarTextColor = UIColor(hex: 0x7C3AED)
|
|
avatarPlaceholderLabel.text = "景"
|
|
typeTag.text = " 景区 "
|
|
typeTag.textColor = UIColor(hex: 0x7C3AED)
|
|
typeTag.backgroundColor = UIColor(hex: 0xF3ECFF)
|
|
}
|
|
avatarPlaceholderLabel.textColor = avatarTextColor
|
|
avatarPlaceholderLabel.isHidden = !account.avatar.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
avatarView.loadRemoteImage(urlString: account.avatar, placeholderColor: avatarPlaceholderColor)
|
|
|
|
let symbolName = selected ? "checkmark.circle.fill" : "circle"
|
|
checkmark.image = UIImage(systemName: symbolName)
|
|
checkmark.tintColor = selected ? AppColor.primary : UIColor(hex: 0xB6BECA)
|
|
|
|
card.layer.borderWidth = selected ? 1.4 : 1
|
|
card.layer.borderColor = (selected ? AppColor.primary : UIColor(hex: 0xE6ECF4)).cgColor
|
|
}
|
|
}
|