// // PermissionApplyViewModel.swift // suixinkan // import Foundation /// 权限申请 ViewModel,对齐 Android `PermissionApplyViewModel`。 final class PermissionApplyViewModel { private(set) var availableRoles: [RoleOption] = [] private(set) var selectedRoleId: Int? private(set) var selectedRoleName = "" private(set) var availableScenics: [ScenicOption] = [] private(set) var selectedScenicIds: [Int] = [] private(set) var selectedScenicNames: [String] = [] private(set) var isLoadingScenics = false private(set) var isSubmitting = false private(set) var showRolePicker = false private(set) var showScenicPicker = false private(set) var rolePermissionList: [RolePermissionResponse] = [] private(set) var scenicSearchQuery = "" private(set) var pendingDeletion: PermissionDeletionTarget? private(set) var showScenicListDialog = false private(set) var dialogRoleName = "" private(set) var dialogScenicList: [ScenicInfo] = [] var onStateChange: (() -> Void)? var onShowMessage: ((String) -> Void)? var onSubmitSuccess: (() -> Void)? private let appStore: AppStore init(appStore: AppStore = .shared) { self.appStore = appStore loadRolesFromLocal() } /// 当前表单是否满足 Android 的提交条件。 var canSubmit: Bool { selectedRoleId != nil && !selectedScenicIds.isEmpty && !isSubmitting } /// 按当前搜索词过滤景区,保持接口原始顺序。 var filteredScenics: [ScenicOption] { let query = scenicSearchQuery.trimmingCharacters(in: .whitespacesAndNewlines) guard !query.isEmpty else { return availableScenics } return availableScenics.filter { $0.name.localizedCaseInsensitiveContains(query) } } /// 从待审核数据预填表单(status=3 去编辑)。 func initWithPendingData(_ pending: RoleApplyPendingResponse) { selectedRoleId = pending.roleId selectedRoleName = pending.roleName selectedScenicIds = pending.scenicList.map(\.id) selectedScenicNames = pending.scenicList.map(\.name) availableRoles = availableRoles.map { role in var copy = role copy.isSelected = role.id == pending.roleId return copy } notifyStateChange() } func loadRolesFromLocal() { rolePermissionList = appStore.permissions.rolePermissionList() var seen = Set() availableRoles = rolePermissionList.compactMap { item in guard !seen.contains(item.role.id) else { return nil } seen.insert(item.role.id) return RoleOption( id: item.role.id, name: item.role.name, description: item.role.notes ?? "", isSelected: false ) } notifyStateChange() } func showRolePickerSheet() { showRolePicker = true notifyStateChange() } func hideRolePickerSheet() { showRolePicker = false notifyStateChange() } func selectRole(_ role: RoleOption) { selectedRoleId = role.id selectedRoleName = role.name availableRoles = availableRoles.map { item in var copy = item copy.isSelected = item.id == role.id return copy } selectedScenicIds = [] selectedScenicNames = [] availableScenics = [] showRolePicker = false notifyStateChange() } func showScenicPickerSheet() { guard selectedRoleId != nil else { onShowMessage?("请先选择角色") return } showScenicPicker = true notifyStateChange() } func hideScenicPickerSheet() { showScenicPicker = false notifyStateChange() } func loadAvailableScenics(api: HomeAPI?) async { guard let roleId = selectedRoleId else { return } guard let api else { return } guard !isLoadingScenics else { return } isLoadingScenics = true notifyStateChange() do { let response = try await api.scenicListAll() let existingIds = existingScenicIds(for: roleId) availableScenics = response.list.map { scenic in let isExisting = existingIds.contains(scenic.id) let isPreselected = selectedScenicIds.contains(scenic.id) return ScenicOption( id: scenic.id, name: scenic.name, isSelected: isExisting || isPreselected, isDisabled: isExisting ) } } catch { onShowMessage?("获取景区列表失败") } isLoadingScenics = false notifyStateChange() } func toggleScenic(_ scenic: ScenicOption) { guard !scenic.isDisabled else { return } if selectedScenicIds.contains(scenic.id) { selectedScenicIds.removeAll { $0 == scenic.id } selectedScenicNames.removeAll { $0 == scenic.name } } else { selectedScenicIds.append(scenic.id) selectedScenicNames.append(scenic.name) } availableScenics = availableScenics.map { item in var copy = item if !copy.isDisabled { copy.isSelected = selectedScenicIds.contains(copy.id) } return copy } notifyStateChange() } /// 更新景区下拉菜单的搜索词。 func updateScenicSearchQuery(_ query: String) { scenicSearchQuery = query notifyStateChange() } func removeSelectedScenic(id: Int, name: String) { pendingDeletion = .scenic(id: id, name: name) notifyStateChange() } /// 确认删除当前待删除的角色或景区。 func confirmDeletion() { guard let pendingDeletion else { return } switch pendingDeletion { case .role: clearRoleSelectionImmediately() case .scenic(let id, let name): removeSelectedScenicImmediately(id: id, name: name) } self.pendingDeletion = nil notifyStateChange() } /// 取消删除确认。 func cancelDeletion() { pendingDeletion = nil notifyStateChange() } private func removeSelectedScenicImmediately(id: Int, name: String) { selectedScenicIds.removeAll { $0 == id } selectedScenicNames.removeAll { $0 == name } availableScenics = availableScenics.map { item in var copy = item if !copy.isDisabled { copy.isSelected = selectedScenicIds.contains(copy.id) } return copy } } /// 已有角色下已开通的景区列表。 func existingScenics(for roleId: Int) -> [ScenicInfo] { appStore.permissions.rolePermissionList() .filter { $0.role.id == roleId } .flatMap(\.scenic) } func clearRoleSelection() { guard !selectedRoleName.isEmpty else { return } pendingDeletion = .role(name: selectedRoleName) notifyStateChange() } private func clearRoleSelectionImmediately() { selectedRoleId = nil selectedRoleName = "" selectedScenicIds = [] selectedScenicNames = [] availableScenics = [] availableRoles = availableRoles.map { role in var copy = role copy.isSelected = false return copy } } /// 展示已有角色下的全部景区。 func showScenicList(roleName: String, scenics: [ScenicInfo]) { dialogRoleName = roleName dialogScenicList = scenics showScenicListDialog = true notifyStateChange() } /// 关闭已有景区列表弹窗。 func hideScenicList() { dialogRoleName = "" dialogScenicList = [] showScenicListDialog = false notifyStateChange() } func submit(api: HomeAPI) async { guard canSubmit, let roleId = selectedRoleId else { onShowMessage?("请选择景区和角色") return } isSubmitting = true notifyStateChange() do { try await api.submitRoleApply(scenicIds: selectedScenicIds, roleId: roleId) isSubmitting = false notifyStateChange() onSubmitSuccess?() } catch { onShowMessage?("提交失败,请稍后重试") isSubmitting = false notifyStateChange() } } private func existingScenicIds(for roleId: Int) -> Set { Set(existingScenics(for: roleId).map(\.id)) } private func notifyStateChange() { onStateChange?() } }