Files
suixinkan_uikit/suixinkan/UI/Home/PermissionApplyViewController.swift
汉秋 77fec21563 Add scenic application flow aligned with Android.
Implement scenic apply models, API, upload, UI, and tests; wire entry from permission apply pages and include related permission/scenic selection UI fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 11:09:27 +08:00

224 lines
8.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// PermissionApplyViewController.swift
// suixinkan
//
import SnapKit
import UIKit
/// Android `PermissionApplyScreen`
final class PermissionApplyViewController: BaseViewController {
private let viewModel: PermissionApplyViewModel
private let homeAPI: HomeAPI
private let pendingData: RoleApplyPendingResponse?
private let scrollView = UIScrollView()
private let contentStack = UIStackView()
private let bannerView = PermissionApplyBannerView()
private let roleSelector = PermissionFormSelectorRow(
title: "角色信息",
tag: "单选",
description: "请选择您需要申请权限的角色",
placeholder: "请选择角色"
)
private let newRoleTags = PermissionSelectedTagsView()
private let scenicSelector = PermissionFormSelectorRow(
title: "选择景区",
tag: "可多选",
description: "请选择您需要申请权限的景区",
placeholder: "请选择景区"
)
private let selectedScenicTags = PermissionSelectedTagsView()
private let submitButton = PermissionSubmitButton()
init(
pendingData: RoleApplyPendingResponse? = nil,
viewModel: PermissionApplyViewModel = PermissionApplyViewModel(),
homeAPI: HomeAPI = NetworkServices.shared.homeAPI
) {
self.pendingData = pendingData
self.viewModel = viewModel
self.homeAPI = homeAPI
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "申请权限"
}
override func setupUI() {
contentStack.axis = .vertical
contentStack.spacing = 16
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 8
let cardStack = UIStackView(arrangedSubviews: [roleSelector, newRoleTags, scenicSelector, selectedScenicTags])
cardStack.axis = .vertical
cardStack.spacing = 20
card.addSubview(cardStack)
cardStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
contentStack.addArrangedSubview(bannerView)
contentStack.addArrangedSubview(card)
scrollView.addSubview(contentStack)
view.addSubview(scrollView)
view.addSubview(submitButton)
contentStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 16, right: 16))
make.width.equalTo(scrollView).offset(-32)
}
scrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
make.bottom.equalTo(submitButton.snp.top).offset(-12)
}
submitButton.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
viewModel.onSubmitSuccess = { [weak self] applyCode in
Task { @MainActor in self?.handleSubmitSuccess(applyCode: applyCode) }
}
bannerView.onApplyNewScenic = { [weak self] in
self?.navigationController?.pushViewController(ScenicApplicationViewController(), animated: true)
}
roleSelector.onTap = { [weak self] in self?.presentRolePicker() }
scenicSelector.onTap = { [weak self] in
guard let self else { return }
self.viewModel.showScenicPickerSheet()
if self.viewModel.availableScenics.isEmpty {
Task { await self.viewModel.loadAvailableScenics(api: self.homeAPI) }
}
self.presentScenicPickerIfNeeded()
}
submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside)
if let pendingData {
viewModel.initWithPendingData(pendingData)
Task { await viewModel.loadAvailableScenics(api: homeAPI) }
}
applyViewModel()
}
private func applyViewModel() {
if viewModel.selectedRoleName.isEmpty {
roleSelector.setValue("请选择角色", isPlaceholder: true)
newRoleTags.isHidden = true
} else {
roleSelector.setValue(viewModel.selectedRoleName, isPlaceholder: false)
newRoleTags.apply(title: "新增角色", names: [viewModel.selectedRoleName]) { [weak self] _ in
self?.viewModel.clearRoleSelection()
}
}
if viewModel.selectedScenicNames.isEmpty {
scenicSelector.setValue("请选择景区", isPlaceholder: true)
selectedScenicTags.isHidden = true
} else {
let displayText: String
if viewModel.selectedScenicNames.count == 1 {
displayText = viewModel.selectedScenicNames[0]
} else {
displayText = "已选中\(viewModel.selectedScenicNames.count)个景区"
}
scenicSelector.setValue(displayText, isPlaceholder: false)
selectedScenicTags.apply(
title: "新增景区",
names: viewModel.selectedScenicNames
) { [weak self] index in
guard let self else { return }
let id = self.viewModel.selectedScenicIds[index]
let name = self.viewModel.selectedScenicNames[index]
self.viewModel.removeSelectedScenic(id: id, name: name)
}
}
submitButton.setSubmitting(viewModel.isSubmitting)
presentScenicPickerIfNeeded()
presentRolePickerIfNeeded()
}
private func presentRolePickerIfNeeded() {
guard viewModel.showRolePicker else { return }
viewModel.hideRolePickerSheet()
presentRolePicker()
}
private func presentRolePicker() {
let alert = UIAlertController(title: "选择角色", message: nil, preferredStyle: .actionSheet)
viewModel.availableRoles.forEach { role in
alert.addAction(UIAlertAction(title: role.name, style: .default) { [weak self] _ in
self?.viewModel.selectRole(role)
Task { await self?.viewModel.loadAvailableScenics(api: self?.homeAPI) }
})
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
private func presentScenicPickerIfNeeded() {
guard viewModel.showScenicPicker else { return }
viewModel.hideScenicPickerSheet()
presentScenicPicker()
}
private func presentScenicPicker() {
guard viewModel.selectedRoleId != nil else {
showToast("请先选择角色")
return
}
if viewModel.availableScenics.isEmpty {
Task {
await viewModel.loadAvailableScenics(api: homeAPI)
await MainActor.run { self.presentScenicPicker() }
}
return
}
let alert = UIAlertController(title: "选择景区", message: nil, preferredStyle: .actionSheet)
viewModel.availableScenics.filter { !$0.isDisabled }.forEach { scenic in
let prefix = scenic.isSelected ? "" : ""
alert.addAction(UIAlertAction(title: "\(prefix)\(scenic.name)", style: .default) { [weak self] _ in
self?.viewModel.toggleScenic(scenic)
})
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
present(alert, animated: true)
}
@objc private func submitTapped() {
Task { await viewModel.submit(api: homeAPI) }
}
private func handleSubmitSuccess(applyCode: String) {
guard !applyCode.isEmpty else {
navigationController?.popViewController(animated: true)
return
}
var controllers = navigationController?.viewControllers ?? []
controllers.removeAll { $0 === self }
let statusVC = PermissionApplyStatusViewController(applyCode: applyCode)
controllers.append(statusVC)
navigationController?.setViewControllers(controllers, animated: true)
}
}