212 lines
7.7 KiB
Swift
212 lines
7.7 KiB
Swift
//
|
||
// PermissionApplyViewModel.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/23.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 权限申请角色选项实体,表示可申请的一个业务角色。
|
||
struct PermissionRoleOption: Equatable, Identifiable {
|
||
let id: Int
|
||
let name: String
|
||
let notes: String
|
||
}
|
||
|
||
/// 权限申请景区选项实体,表示可申请或已拥有权限的一个景区。
|
||
struct PermissionScenicOption: Equatable, Identifiable {
|
||
let id: Int
|
||
let name: String
|
||
var selected: Bool
|
||
var disabled: Bool
|
||
}
|
||
|
||
/// 已有角色权限展示实体,表示一个角色及其已拥有景区。
|
||
struct ExistingRolePermissionInfo: Equatable, Identifiable {
|
||
let id: Int
|
||
let name: String
|
||
let scenics: [ScenicInfo]
|
||
}
|
||
|
||
@MainActor
|
||
/// 权限申请 ViewModel,负责角色/景区选择和提交审核。
|
||
final class PermissionApplyViewModel {
|
||
var onChange: (() -> Void)?
|
||
var roleOptions: [PermissionRoleOption] = [] { didSet { onChange?() } }
|
||
var selectedRoleId: Int? { didSet { onChange?() } }
|
||
var scenicOptions: [PermissionScenicOption] = [] { didSet { onChange?() } }
|
||
var loadingScenics = false { didSet { onChange?() } }
|
||
var scenicLoadFailed = false { didSet { onChange?() } }
|
||
var scenicLoadFailureReason: String? { didSet { onChange?() } }
|
||
var submitting = false { didSet { onChange?() } }
|
||
var message: String? { didSet { onChange?() } }
|
||
|
||
private let initialPending: RoleApplyPendingResponse?
|
||
private var rolePermissions: [RolePermissionResponse] = []
|
||
private var selectedIds: Set<Int> = []
|
||
|
||
/// 初始化权限申请 ViewModel,可传入驳回申请用于编辑回填。
|
||
init(initialPending: RoleApplyPendingResponse? = nil) {
|
||
self.initialPending = initialPending
|
||
}
|
||
|
||
/// 返回是否允许提交审核。
|
||
var canSubmit: Bool {
|
||
!submitting && selectedRoleId != nil && !selectedIds.isEmpty
|
||
}
|
||
|
||
/// 返回已选择景区数量。
|
||
var selectedCount: Int {
|
||
selectedIds.count
|
||
}
|
||
|
||
/// 返回已选择景区 ID 集合,供测试和提交校验使用。
|
||
var selectedScenicIds: Set<Int> {
|
||
selectedIds
|
||
}
|
||
|
||
/// 返回景区选择摘要文案。
|
||
var selectedScenicSummary: String {
|
||
let names = scenicOptions
|
||
.filter { !$0.disabled && selectedIds.contains($0.id) }
|
||
.map(\.name)
|
||
if names.isEmpty { return "请选择景区" }
|
||
if names.count == 1 { return names[0] }
|
||
return "已选择 \(names.count) 个景区"
|
||
}
|
||
|
||
/// 返回当前选择角色的说明。
|
||
var selectedRoleNotes: String? {
|
||
guard let selectedRoleId else { return nil }
|
||
return roleOptions.first(where: { $0.id == selectedRoleId })?.notes
|
||
}
|
||
|
||
/// 返回当前选择角色名称。
|
||
var selectedRoleName: String? {
|
||
guard let selectedRoleId else { return nil }
|
||
return roleOptions.first(where: { $0.id == selectedRoleId })?.name
|
||
}
|
||
|
||
/// 返回已有角色权限展示列表。
|
||
var existingRoleInfos: [ExistingRolePermissionInfo] {
|
||
rolePermissions.map {
|
||
ExistingRolePermissionInfo(id: $0.role.id, name: $0.role.name, scenics: $0.scenic)
|
||
}
|
||
}
|
||
|
||
/// 从权限上下文启动表单数据。
|
||
func bootstrap(rolePermissions: [RolePermissionResponse]) {
|
||
self.rolePermissions = rolePermissions
|
||
roleOptions = uniqueRoles(from: rolePermissions)
|
||
if let pending = initialPending {
|
||
selectedRoleId = pending.roleId
|
||
selectedIds = Set(pending.scenicList.map(\.id))
|
||
}
|
||
}
|
||
|
||
/// 选择角色,并清空当前景区选择等待重新加载。
|
||
func selectRole(id: Int) {
|
||
selectedRoleId = id
|
||
selectedIds.removeAll()
|
||
scenicOptions = []
|
||
scenicLoadFailed = false
|
||
scenicLoadFailureReason = nil
|
||
}
|
||
|
||
/// 按当前角色加载可申请景区列表。
|
||
func loadScenicListIfNeeded(api: any ScenicPermissionServing, force: Bool = false) async {
|
||
guard selectedRoleId != nil else { return }
|
||
if !force && !scenicOptions.isEmpty { return }
|
||
loadingScenics = true
|
||
scenicLoadFailed = false
|
||
scenicLoadFailureReason = nil
|
||
defer { loadingScenics = false }
|
||
do {
|
||
let list = try await api.scenicListAll().list
|
||
let disabledIds = Set(currentRolePermission()?.scenic.map(\.id) ?? [])
|
||
scenicOptions = list.map { scenic in
|
||
let selected = disabledIds.contains(scenic.id) || selectedIds.contains(scenic.id)
|
||
return PermissionScenicOption(id: scenic.id, name: scenic.name, selected: selected, disabled: disabledIds.contains(scenic.id))
|
||
}
|
||
selectedIds = Set(scenicOptions.filter { $0.selected && !$0.disabled }.map(\.id))
|
||
} catch {
|
||
scenicOptions = []
|
||
selectedIds.removeAll()
|
||
scenicLoadFailed = true
|
||
scenicLoadFailureReason = error.localizedDescription
|
||
message = "景区列表加载失败,请重试"
|
||
}
|
||
}
|
||
|
||
/// 切换一个景区的选择状态,已有权限的景区不可切换。
|
||
func toggleScenic(id: Int) {
|
||
guard let index = scenicOptions.firstIndex(where: { $0.id == id }), !scenicOptions[index].disabled else { return }
|
||
scenicOptions[index].selected.toggle()
|
||
if scenicOptions[index].selected {
|
||
selectedIds.insert(id)
|
||
} else {
|
||
selectedIds.remove(id)
|
||
}
|
||
}
|
||
|
||
/// 提交角色权限申请。
|
||
func submit(api: any ScenicPermissionServing) async {
|
||
guard let roleId = selectedRoleId, !selectedIds.isEmpty, !submitting else { return }
|
||
submitting = true
|
||
defer { submitting = false }
|
||
do {
|
||
try await api.roleApplySubmit(roleId: roleId, scenicIds: Array(selectedIds).sorted())
|
||
message = "提交成功,等待审核"
|
||
} catch {
|
||
message = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
/// 生成去重后的角色选项。
|
||
private func uniqueRoles(from list: [RolePermissionResponse]) -> [PermissionRoleOption] {
|
||
var seen = Set<Int>()
|
||
return list.compactMap { item in
|
||
guard seen.insert(item.role.id).inserted else { return nil }
|
||
return PermissionRoleOption(id: item.role.id, name: item.role.name, notes: item.role.notes ?? "")
|
||
}
|
||
}
|
||
|
||
/// 返回当前选择角色对应的权限实体。
|
||
private func currentRolePermission() -> RolePermissionResponse? {
|
||
guard let selectedRoleId else { return nil }
|
||
return rolePermissions.first { $0.role.id == selectedRoleId }
|
||
}
|
||
}
|
||
|
||
@MainActor
|
||
/// 权限申请状态 ViewModel,负责读取审核中的或指定编号的权限申请。
|
||
final class PermissionApplyStatusViewModel {
|
||
var onChange: (() -> Void)?
|
||
var loading = false { didSet { onChange?() } }
|
||
var pending: RoleApplyPendingResponse? { didSet { onChange?() } }
|
||
var loadFailed = false { didSet { onChange?() } }
|
||
var loadFailureReason: String? { didSet { onChange?() } }
|
||
|
||
/// 加载权限申请状态;有申请编号时优先匹配编号,否则展示审核中或驳回记录。
|
||
func load(api: any ScenicPermissionServing, applyCode: String?) async {
|
||
loading = true
|
||
loadFailed = false
|
||
loadFailureReason = nil
|
||
defer { loading = false }
|
||
do {
|
||
let all = try await api.roleApplyAll()
|
||
let normalizedCode = applyCode?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||
if !normalizedCode.isEmpty {
|
||
pending = all.first { $0.code == normalizedCode }
|
||
} else {
|
||
pending = all.first { $0.status == 1 || $0.status == 3 }
|
||
}
|
||
} catch {
|
||
pending = nil
|
||
loadFailed = true
|
||
loadFailureReason = error.localizedDescription
|
||
}
|
||
}
|
||
}
|