Initial commit: suixinkan_ios UIKit rewrite project.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,211 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user