Add scenic selection and cooperation order flows aligned with Android.

Upgrade scenic picker with search, location sorting, permission apply/status, and blocking home dialog; add cooperation order module and order source picker improvements with unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 10:48:43 +08:00
parent 3c4ca7eefb
commit 2bea05b1d9
50 changed files with 5603 additions and 191 deletions

View File

@ -0,0 +1,194 @@
//
// PermissionApplyStatusViewController.swift
// suixinkan
//
import SnapKit
import UIKit
/// Android `PermissionApplyStatusScreen`
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 loadingIndicator = UIActivityIndicatorView(style: .medium)
init(
applyCode: String,
viewModel: PermissionApplyStatusViewModel? = nil,
homeAPI: HomeAPI = NetworkServices.shared.homeAPI
) {
self.viewModel = viewModel ?? PermissionApplyStatusViewModel(applyCode: applyCode)
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 = 12
scrollView.addSubview(contentStack)
view.addSubview(scrollView)
view.addSubview(loadingIndicator)
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.edges.equalTo(view.safeAreaLayoutGuide)
}
loadingIndicator.snp.makeConstraints { make in
make.center.equalToSuperview()
}
}
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?.showToast("功能开发中")
}
Task { await viewModel.loadPendingData(api: homeAPI) }
}
private func applyViewModel() {
if viewModel.isLoading {
loadingIndicator.startAnimating()
scrollView.isHidden = true
return
}
loadingIndicator.stopAnimating()
scrollView.isHidden = false
contentStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
contentStack.addArrangedSubview(bannerView)
navigationItem.rightBarButtonItem = nil
if let pending = viewModel.pendingData, pending.status == PermissionApplyPendingStatus.rejected {
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "去编辑",
style: .plain,
target: self,
action: #selector(editTapped)
)
navigationItem.rightBarButtonItem?.tintColor = AppColor.primary
}
guard let pending = viewModel.pendingData else { return }
contentStack.addArrangedSubview(makeStatusCard(pending: pending))
contentStack.addArrangedSubview(makeSectionCard(title: "新增角色", chips: [pending.roleName]))
if !pending.scenicList.isEmpty {
contentStack.addArrangedSubview(
makeSectionCard(title: "新增景区", chips: pending.scenicList.map(\.name))
)
}
}
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 = AppColor.warning
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 8
stack.addArrangedSubview(statusLabel)
[
("提交时间:", pending.createdAt),
("审核时间:", pending.auditedAt ?? "--"),
("审核人:", pending.auditedBy ?? "--"),
("审核备注:", pending.auditNote.isEmpty ? "--" : pending.auditNote),
].forEach { label, value in
stack.addArrangedSubview(makeInfoRow(label: label, value: value))
}
card.addSubview(stack)
stack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
}
return card
}
private func makeSectionCard(title: String, chips: [String]) -> UIView {
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 12
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 16, weight: .medium)
titleLabel.textColor = AppColor.textPrimary
let chipStack = UIStackView()
chipStack.axis = .horizontal
chipStack.spacing = 8
chips.forEach { name in
chipStack.addArrangedSubview(PermissionTagChip(title: name, showsRemove: false))
}
card.addSubview(titleLabel)
card.addSubview(chipStack)
titleLabel.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview().inset(16)
}
chipStack.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(12)
make.leading.trailing.bottom.equalToSuperview().inset(16)
}
return card
}
private func makeInfoRow(label: String, value: String) -> UIView {
let row = UIStackView()
row.axis = .horizontal
row.distribution = .equalSpacing
let left = UILabel()
left.text = label
left.font = .systemFont(ofSize: 14)
left.textColor = AppColor.textSecondary
let right = UILabel()
right.text = value.isEmpty ? "--" : value
right.font = .systemFont(ofSize: 14)
right.textColor = AppColor.textSecondary
right.textAlignment = .right
row.addArrangedSubview(left)
row.addArrangedSubview(right)
return row
}
@objc private func editTapped() {
viewModel.navigateToEditIfRejected()
}
private func navigateToEdit(pending: RoleApplyPendingResponse) {
var controllers = navigationController?.viewControllers ?? []
controllers.removeAll { $0 is PermissionApplyStatusViewController }
let applyVC = PermissionApplyViewController(pendingData: pending)
controllers.append(applyVC)
navigationController?.setViewControllers(controllers, animated: true)
}
}