Add ScenicPermission module and AMap CocoaPods with simulator support.
Integrate scenic selection and permission flows into home routing, add AMap SDK for device builds while keeping arm64 simulators compilable via Podfile post_install hooks. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,212 @@
|
||||
//
|
||||
// PermissionApplyViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Observation
|
||||
|
||||
/// 权限申请角色选项实体,表示可申请的一个业务角色。
|
||||
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
|
||||
@Observable
|
||||
/// 权限申请 ViewModel,负责角色/景区选择和提交审核。
|
||||
final class PermissionApplyViewModel {
|
||||
var roleOptions: [PermissionRoleOption] = []
|
||||
var selectedRoleId: Int?
|
||||
var scenicOptions: [PermissionScenicOption] = []
|
||||
var loadingScenics = false
|
||||
var scenicLoadFailed = false
|
||||
var scenicLoadFailureReason: String?
|
||||
var submitting = false
|
||||
var message: String?
|
||||
|
||||
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
|
||||
@Observable
|
||||
/// 权限申请状态 ViewModel,负责读取审核中的或指定编号的权限申请。
|
||||
final class PermissionApplyStatusViewModel {
|
||||
var loading = false
|
||||
var pending: RoleApplyPendingResponse?
|
||||
var loadFailed = false
|
||||
var loadFailureReason: String?
|
||||
|
||||
/// 加载权限申请状态;有申请编号时优先匹配编号,否则展示审核中或驳回记录。
|
||||
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