Initial commit: suixinkan_ios UIKit rewrite project.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,263 @@
|
||||
//
|
||||
// AccountSwitchViewController.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 账号切换页面,展示当前登录主体下可进入的景区账号和门店账号。
|
||||
final class AccountSwitchViewController: UIViewController {
|
||||
|
||||
private let viewModel = AccountSwitchViewModel()
|
||||
|
||||
private lazy var tableView: UITableView = {
|
||||
let table = UITableView(frame: .zero, style: .plain)
|
||||
table.backgroundColor = UIColor(hex: 0xF5F7FB)
|
||||
table.separatorStyle = .none
|
||||
table.dataSource = self
|
||||
table.delegate = self
|
||||
table.register(AccountSwitchCell.self, forCellReuseIdentifier: AccountSwitchCell.reuseID)
|
||||
return table
|
||||
}()
|
||||
|
||||
private lazy var confirmButton: UIButton = {
|
||||
let button = makePrimaryButton(title: "确认切换")
|
||||
button.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
title = "账号切换"
|
||||
view.backgroundColor = UIColor(hex: 0xF5F7FB)
|
||||
|
||||
view.addSubview(tableView)
|
||||
view.addSubview(confirmButton)
|
||||
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)
|
||||
make.height.equalTo(50)
|
||||
}
|
||||
|
||||
viewModel.onChange = { [weak self] in
|
||||
self?.tableView.reloadData()
|
||||
self?.updateConfirmButton()
|
||||
}
|
||||
Task { await loadAccounts() }
|
||||
}
|
||||
|
||||
private func updateConfirmButton() {
|
||||
let enabled = viewModel.selectedAccount != nil && !viewModel.loading && !viewModel.switching
|
||||
confirmButton.isEnabled = enabled
|
||||
confirmButton.alpha = enabled ? 1 : 0.5
|
||||
}
|
||||
|
||||
private func loadAccounts(force: Bool = false) async {
|
||||
do {
|
||||
try await appServices.globalLoading.withOptionalLoading(!force && viewModel.accounts.isEmpty, message: "加载中...") {
|
||||
try await self.viewModel.load(
|
||||
api: self.appServices.profileAPI,
|
||||
force: force,
|
||||
currentAccountId: self.currentAccountId
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
showToast(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private var currentAccountId: String? {
|
||||
guard let current = appServices.accountContext.profile else { return nil }
|
||||
if let store = appServices.accountContext.currentStore {
|
||||
return "\(V9StoreUser.accountTypeValue)_\(store.id)"
|
||||
}
|
||||
if let scenic = appServices.accountContext.currentScenic {
|
||||
return "\(V9ScenicUser.accountTypeValue)_\(scenic.id)"
|
||||
}
|
||||
return current.userId.isEmpty ? nil : current.userId
|
||||
}
|
||||
|
||||
private func isCurrentAccount(_ account: AccountSwitchAccount) -> Bool {
|
||||
viewModel.isCurrent(account, currentAccountId: currentAccountId)
|
||||
}
|
||||
|
||||
@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 {
|
||||
do {
|
||||
let response = try await appServices.globalLoading.withLoading(message: "切换中...") {
|
||||
try await self.viewModel.switchAccount(account, api: self.appServices.authAPI)
|
||||
}
|
||||
let username = nonEmpty(appServices.accountContext.profile?.phone)
|
||||
?? nonEmpty(account.phone)
|
||||
?? ""
|
||||
try await appServices.authSessionCoordinator.completeLogin(
|
||||
with: response,
|
||||
username: username,
|
||||
privacyAgreementAccepted: appServices.authSessionCoordinator.loginPreferences().privacyAgreementAccepted,
|
||||
appSession: appServices.appSession,
|
||||
accountContext: appServices.accountContext,
|
||||
permissionContext: appServices.permissionContext,
|
||||
profileAPI: appServices.profileAPI,
|
||||
accountContextAPI: appServices.accountContextAPI
|
||||
)
|
||||
appServices.appRouter.reset()
|
||||
showToast("账号已切换")
|
||||
navigationController?.popToRootViewController(animated: true)
|
||||
} catch {
|
||||
showToast(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func nonEmpty(_ value: String?) -> String? {
|
||||
let text = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
return text.isEmpty ? nil : text
|
||||
}
|
||||
}
|
||||
|
||||
extension AccountSwitchViewController: UITableViewDataSource, UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
viewModel.accounts.isEmpty && !viewModel.loading ? 1 : viewModel.accounts.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard !viewModel.accounts.isEmpty else {
|
||||
let cell = UITableViewCell()
|
||||
cell.selectionStyle = .none
|
||||
cell.backgroundColor = .clear
|
||||
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
|
||||
let empty = makeEmptyStateView(
|
||||
title: "暂无可切换账号",
|
||||
message: "当前登录账号下没有其他可切换账号。",
|
||||
systemImage: "person.crop.circle.badge.exclamationmark"
|
||||
)
|
||||
cell.contentView.addSubview(empty)
|
||||
empty.snp.makeConstraints { make in make.edges.equalToSuperview(); make.height.equalTo(280) }
|
||||
return cell
|
||||
}
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: AccountSwitchCell.reuseID, for: indexPath) as! AccountSwitchCell
|
||||
let account = viewModel.accounts[indexPath.row]
|
||||
cell.configure(
|
||||
account: account,
|
||||
selected: viewModel.selectedAccountId == account.id,
|
||||
isCurrent: account.isCurrent || isCurrentAccount(account)
|
||||
)
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
guard indexPath.row < viewModel.accounts.count else { return }
|
||||
viewModel.select(viewModel.accounts[indexPath.row])
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
viewModel.accounts.isEmpty ? 280 : 92
|
||||
}
|
||||
}
|
||||
|
||||
private final class AccountSwitchCell: UITableViewCell {
|
||||
static let reuseID = "AccountSwitchCell"
|
||||
|
||||
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
|
||||
|
||||
let card = UIView()
|
||||
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 = AppDesignUIKit.textSecondary
|
||||
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.equalToSuperview().inset(14)
|
||||
make.bottom.equalToSuperview().inset(14)
|
||||
make.width.height.equalTo(22)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
func configure(account: AccountSwitchAccount, selected: Bool, isCurrent: Bool) {
|
||||
let title = account.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
titleLabel.text = title.isEmpty ? account.accountTypeLabel : 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 ? AppDesignUIKit.primary : UIColor(hex: 0xB6BECA)
|
||||
avatarView.loadRemoteAvatar(urlString: account.avatar)
|
||||
if isCurrent {
|
||||
titleLabel.text = (titleLabel.text ?? "") + " (当前)"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user