Files
suixinkan_ios_uikit/suixinkan_ios/Features/ScenicPermission/ViewModels/PermissionApplyViewModel.swift
2026-06-26 14:33:31 +08:00

212 lines
7.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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
}
}
}