对齐 Android 无权限强制弹窗、角色下拉与申请页交互,补充 UIButton configuration 适配,并增强景区排队 TTS 与相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
204 lines
7.9 KiB
Swift
204 lines
7.9 KiB
Swift
//
|
|
// PermissionApplyStatusViewController.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import SnapKit
|
|
import UIKit
|
|
|
|
/// 权限申请审核状态页,严格对齐 Android `PermissionApplyStatusScreen`。
|
|
@MainActor
|
|
final class PermissionApplyStatusViewController: BaseViewController {
|
|
|
|
private let viewModel: PermissionApplyStatusViewModel
|
|
private let homeAPI: HomeAPI
|
|
private let scrollView = UIScrollView()
|
|
private let contentStack = UIStackView()
|
|
private let bannerView = PermissionApplyBannerView()
|
|
private let existingRoleList = PermissionExistingRoleListView()
|
|
private let loadingIndicator = UIActivityIndicatorView(style: .medium)
|
|
|
|
/// 使用申请编号创建审核状态页。
|
|
init(
|
|
applyCode: String,
|
|
viewModel: PermissionApplyStatusViewModel? = nil,
|
|
homeAPI: HomeAPI? = nil
|
|
) {
|
|
self.viewModel = viewModel ?? PermissionApplyStatusViewModel(applyCode: applyCode)
|
|
self.homeAPI = homeAPI ?? NetworkServices.shared.homeAPI
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
@available(*, unavailable)
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func setupNavigationBar() {
|
|
title = "权限申请"
|
|
navigationItem.backButtonDisplayMode = .minimal
|
|
}
|
|
|
|
override func setupUI() {
|
|
view.backgroundColor = PermissionApplyUITokens.pageBackground
|
|
contentStack.axis = .vertical
|
|
contentStack.spacing = 0
|
|
scrollView.addSubview(contentStack)
|
|
view.addSubview(scrollView)
|
|
view.addSubview(loadingIndicator)
|
|
contentStack.snp.makeConstraints { make in
|
|
make.edges.equalToSuperview()
|
|
make.width.equalTo(scrollView)
|
|
}
|
|
scrollView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) }
|
|
loadingIndicator.color = PermissionApplyUITokens.selectedBlue
|
|
loadingIndicator.snp.makeConstraints { make in make.center.equalTo(view.safeAreaLayoutGuide) }
|
|
}
|
|
|
|
override func bindActions() {
|
|
viewModel.onStateChange = { [weak self] in
|
|
Task { @MainActor in self?.applyViewModel() }
|
|
}
|
|
viewModel.onNavigateToEdit = { [weak self] pending in
|
|
Task { @MainActor in self?.navigateToEdit(pending: pending) }
|
|
}
|
|
bannerView.onApplyNewScenic = { [weak self] in
|
|
self?.navigationController?.pushViewController(ScenicApplicationViewController(), animated: true)
|
|
}
|
|
existingRoleList.onShowAllScenics = { [weak self] roleName, scenics in
|
|
self?.viewModel.showScenicList(roleName: roleName, scenics: scenics)
|
|
}
|
|
Task { await viewModel.loadPendingData(api: homeAPI) }
|
|
}
|
|
|
|
@MainActor
|
|
private func applyViewModel() {
|
|
if viewModel.isLoading {
|
|
loadingIndicator.startAnimating()
|
|
scrollView.isHidden = true
|
|
return
|
|
}
|
|
loadingIndicator.stopAnimating()
|
|
scrollView.isHidden = false
|
|
navigationItem.rightBarButtonItem = nil
|
|
contentStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
|
|
contentStack.addArrangedSubview(bannerView)
|
|
guard let pending = viewModel.pendingData else { return }
|
|
|
|
if pending.status == PermissionApplyPendingStatus.rejected {
|
|
let editItem = UIBarButtonItem(title: "去编辑", style: .plain, target: self, action: #selector(editTapped))
|
|
editItem.tintColor = AppColor.primary
|
|
editItem.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 14, weight: .medium)], for: .normal)
|
|
navigationItem.rightBarButtonItem = editItem
|
|
}
|
|
|
|
contentStack.addArrangedSubview(
|
|
insetContainer(makeStatusCard(pending: pending), insets: UIEdgeInsets(top: 16, left: 16, bottom: 8, right: 16))
|
|
)
|
|
|
|
let roleSection = PermissionStaticSectionView()
|
|
roleSection.apply(title: "新增角色", names: [pending.roleName])
|
|
contentStack.addArrangedSubview(
|
|
insetContainer(roleSection, insets: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16))
|
|
)
|
|
|
|
if !pending.scenicList.isEmpty {
|
|
let scenicSection = PermissionStaticSectionView()
|
|
scenicSection.apply(title: "新增景区", names: pending.scenicList.map(\.name))
|
|
contentStack.addArrangedSubview(
|
|
insetContainer(scenicSection, insets: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16))
|
|
)
|
|
}
|
|
|
|
if !viewModel.rolePermissionList.isEmpty {
|
|
existingRoleList.apply(rolePermissions: viewModel.rolePermissionList)
|
|
contentStack.addArrangedSubview(
|
|
insetContainer(existingRoleList, insets: UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16))
|
|
)
|
|
}
|
|
presentScenicListIfNeeded()
|
|
}
|
|
|
|
private func makeStatusCard(pending: RoleApplyPendingResponse) -> UIView {
|
|
let card = UIView()
|
|
card.backgroundColor = AppColor.warningBackground
|
|
card.layer.cornerRadius = 12
|
|
card.layer.borderWidth = 1
|
|
card.layer.borderColor = AppColor.warning.cgColor
|
|
|
|
let statusLabel = UILabel()
|
|
statusLabel.text = pending.statusLabel
|
|
statusLabel.font = .systemFont(ofSize: 18, weight: .medium)
|
|
statusLabel.textColor = UIColor(hex: 0xFF6B00)
|
|
|
|
let stack = UIStackView()
|
|
stack.axis = .vertical
|
|
stack.spacing = 8
|
|
stack.addArrangedSubview(statusLabel)
|
|
stack.setCustomSpacing(12, after: statusLabel)
|
|
[
|
|
("提交时间:", pending.createdAt),
|
|
("审核时间:", pending.auditedAt ?? "--"),
|
|
("审核人:", pending.auditedBy ?? "--"),
|
|
("审核备注:", pending.auditNote.isEmpty ? "--" : pending.auditNote),
|
|
].forEach { stack.addArrangedSubview(makeInfoRow(label: $0.0, value: $0.1)) }
|
|
|
|
card.addSubview(stack)
|
|
stack.snp.makeConstraints { make in make.edges.equalToSuperview().inset(16) }
|
|
return card
|
|
}
|
|
|
|
private func makeInfoRow(label: String, value: String) -> UIView {
|
|
let row = UIStackView()
|
|
row.axis = .horizontal
|
|
row.alignment = .top
|
|
row.spacing = 8
|
|
let left = UILabel()
|
|
left.text = label
|
|
left.font = .systemFont(ofSize: 14)
|
|
left.textColor = UIColor(hex: 0x4B5563)
|
|
left.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
let right = UILabel()
|
|
right.text = value.isEmpty ? "--" : value
|
|
right.font = .systemFont(ofSize: 14)
|
|
right.textColor = UIColor(hex: 0x4B5563)
|
|
right.textAlignment = .right
|
|
right.numberOfLines = 0
|
|
row.addArrangedSubview(left)
|
|
row.addArrangedSubview(right)
|
|
right.snp.makeConstraints { make in make.width.lessThanOrEqualTo(220) }
|
|
return row
|
|
}
|
|
|
|
private func insetContainer(_ content: UIView, insets: UIEdgeInsets) -> UIView {
|
|
let container = UIView()
|
|
container.addSubview(content)
|
|
content.snp.makeConstraints { make in make.edges.equalToSuperview().inset(insets) }
|
|
return container
|
|
}
|
|
|
|
private func presentScenicListIfNeeded() {
|
|
guard viewModel.showScenicListDialog, presentedViewController == nil else { return }
|
|
present(
|
|
PermissionScenicListDialogViewController(
|
|
roleName: viewModel.dialogRoleName,
|
|
scenics: viewModel.dialogScenicList,
|
|
onDismiss: { [weak self] in self?.viewModel.hideScenicList() }
|
|
),
|
|
animated: true
|
|
)
|
|
}
|
|
|
|
@objc private func editTapped() {
|
|
viewModel.navigateToEditIfRejected()
|
|
}
|
|
|
|
private func navigateToEdit(pending: RoleApplyPendingResponse) {
|
|
var controllers = navigationController?.viewControllers ?? []
|
|
controllers.removeAll { $0 is PermissionApplyStatusViewController }
|
|
controllers.append(PermissionApplyViewController(pendingData: pending))
|
|
navigationController?.setViewControllers(controllers, animated: true)
|
|
}
|
|
}
|