// // PermissionApplyViewController.swift // suixinkan // import SnapKit import UIKit /// 权限申请页,严格对齐 Android `PermissionApplyScreen`。 @MainActor final class PermissionApplyViewController: BaseViewController { private let viewModel: PermissionApplyViewModel private let homeAPI: HomeAPI private let pendingData: RoleApplyPendingResponse? private let bannerView = PermissionApplyBannerView() private let scrollView = UIScrollView() private let cardView = UIView() 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 existingRoleList = PermissionExistingRoleListView() private let bottomBar = UIView() private let submitButton = PermissionSubmitButton() private weak var roleDropdown: PermissionDropdownViewController? private weak var scenicDropdown: PermissionDropdownViewController? /// 创建新增或驳回编辑状态的权限申请页。 init( pendingData: RoleApplyPendingResponse? = nil, viewModel: PermissionApplyViewModel = PermissionApplyViewModel(), homeAPI: HomeAPI? = nil ) { self.pendingData = pendingData self.viewModel = viewModel 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 scrollView.keyboardDismissMode = .onDrag cardView.backgroundColor = .white cardView.layer.cornerRadius = 8 bottomBar.backgroundColor = .white let roleSection = UIStackView(arrangedSubviews: [roleSelector, newRoleTags]) roleSection.axis = .vertical roleSection.spacing = 12 let scenicSection = UIStackView(arrangedSubviews: [scenicSelector, selectedScenicTags, existingRoleList]) scenicSection.axis = .vertical scenicSection.spacing = 12 let formStack = UIStackView(arrangedSubviews: [roleSection, scenicSection]) formStack.axis = .vertical formStack.spacing = 20 view.addSubview(bannerView) view.addSubview(scrollView) scrollView.addSubview(cardView) cardView.addSubview(formStack) view.addSubview(bottomBar) bottomBar.addSubview(submitButton) bannerView.snp.makeConstraints { make in make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) } bottomBar.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview() } submitButton.snp.makeConstraints { make in make.top.equalToSuperview().offset(16) make.leading.trailing.equalToSuperview().inset(16) make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-16) } scrollView.snp.makeConstraints { make in make.top.equalTo(bannerView.snp.bottom) make.leading.trailing.equalToSuperview() make.bottom.equalTo(bottomBar.snp.top) } cardView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(16) make.width.equalTo(scrollView).offset(-32) } formStack.snp.makeConstraints { make in make.edges.equalToSuperview().inset(16) } newRoleTags.isHidden = true selectedScenicTags.isHidden = true existingRoleList.isHidden = true } 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] in Task { @MainActor in self?.handleSubmitSuccess() } } bannerView.onApplyNewScenic = { [weak self] in self?.navigationController?.pushViewController(ScenicApplicationViewController(), animated: true) } roleSelector.onTap = { [weak self] in self?.viewModel.showRolePickerSheet() } scenicSelector.onTap = { [weak self] in guard let self else { return } self.viewModel.showScenicPickerSheet() if self.viewModel.showScenicPicker, self.viewModel.availableScenics.isEmpty { Task { await self.viewModel.loadAvailableScenics(api: self.homeAPI) } } } existingRoleList.onShowAllScenics = { [weak self] roleName, scenics in self?.viewModel.showScenicList(roleName: roleName, scenics: scenics) } submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside) if let pendingData { viewModel.initWithPendingData(pendingData) Task { await viewModel.loadAvailableScenics(api: homeAPI) } } applyViewModel() } @MainActor private func applyViewModel() { if viewModel.selectedRoleName.isEmpty { roleSelector.setValue("请选择角色", isPlaceholder: true) } else { roleSelector.setValue(viewModel.selectedRoleName, isPlaceholder: false) } newRoleTags.apply(title: "新增角色", names: viewModel.selectedRoleName.isEmpty ? [] : [viewModel.selectedRoleName]) { [weak self] _ in self?.viewModel.clearRoleSelection() } let scenicValue: String switch viewModel.selectedScenicNames.count { case 0: scenicValue = "请选择景区" case 1: scenicValue = viewModel.selectedScenicNames[0] default: scenicValue = "已选中\(viewModel.selectedScenicNames.count)个景区" } scenicSelector.setValue(scenicValue, isPlaceholder: viewModel.selectedScenicNames.isEmpty) selectedScenicTags.apply(title: "新增景区", names: viewModel.selectedScenicNames) { [weak self] index in guard let self, self.viewModel.selectedScenicIds.indices.contains(index), self.viewModel.selectedScenicNames.indices.contains(index) else { return } self.viewModel.removeSelectedScenic( id: self.viewModel.selectedScenicIds[index], name: self.viewModel.selectedScenicNames[index] ) } existingRoleList.apply(rolePermissions: viewModel.rolePermissionList) submitButton.applyState(canSubmit: viewModel.canSubmit, submitting: viewModel.isSubmitting) if viewModel.isSubmitting { showLoading() } else { hideLoading() } scenicDropdown?.updateScenics(viewModel.filteredScenics, isLoading: viewModel.isLoadingScenics) presentRolePickerIfNeeded() presentScenicPickerIfNeeded() presentDeletionIfNeeded() presentScenicListIfNeeded() } private func presentRolePickerIfNeeded() { guard viewModel.showRolePicker, roleDropdown == nil, presentedViewController == nil else { return } let dropdown = PermissionDropdownViewController( anchor: roleSelector.selectorControl, mode: .roles(viewModel.availableRoles) ) dropdown.onRoleSelect = { [weak self] role in self?.viewModel.selectRole(role) } dropdown.onDismiss = { [weak self, weak dropdown] in self?.viewModel.hideRolePickerSheet() if self?.roleDropdown === dropdown { self?.roleDropdown = nil } } roleDropdown = dropdown present(dropdown, animated: true) } private func presentScenicPickerIfNeeded() { guard viewModel.showScenicPicker, scenicDropdown == nil, presentedViewController == nil else { return } let dropdown = PermissionDropdownViewController( anchor: scenicSelector.selectorControl, mode: .scenics(viewModel.filteredScenics, isLoading: viewModel.isLoadingScenics) ) dropdown.onScenicToggle = { [weak self] scenic in self?.viewModel.toggleScenic(scenic) } dropdown.onSearchChange = { [weak self] query in self?.viewModel.updateScenicSearchQuery(query) } dropdown.onDismiss = { [weak self, weak dropdown] in self?.viewModel.hideScenicPickerSheet() if self?.scenicDropdown === dropdown { self?.scenicDropdown = nil } } scenicDropdown = dropdown present(dropdown, animated: true) } private func presentDeletionIfNeeded() { guard let target = viewModel.pendingDeletion, presentedViewController == nil else { return } present( PermissionDeleteDialogViewController( target: target, onCancel: { [weak self] in self?.viewModel.cancelDeletion() }, onConfirm: { [weak self] in self?.viewModel.confirmDeletion() } ), animated: true ) } 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 submitTapped() { Task { await viewModel.submit(api: homeAPI) } } private func handleSubmitSuccess() { hideLoading() guard let navigationController else { showToast("提交成功") return } navigationController.popViewController(animated: true) DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak navigationController] in (navigationController?.topViewController as? BaseViewController)?.showToast("提交成功") } } }