// // PermissionContext.swift // suixinkan // // Created by Codex on 2026/6/22. // import Foundation @MainActor /// 权限上下文状态中心,保存角色权限、当前角色和扁平化权限 URI。 final class PermissionContext { var onChange: (() -> Void)? private(set) var rolePermissions: [RolePermissionResponse] = [] { didSet { onChange?() } } private(set) var permissionURIs: Set = [] { didSet { onChange?() } } var currentRole: RoleInfo? { didSet { onChange?() } } /// 替换角色权限列表,并尽量按缓存角色 ID 保持当前角色。 func replaceRolePermissions(_ rolePermissions: [RolePermissionResponse], currentRoleId: Int? = nil) { self.rolePermissions = rolePermissions currentRole = currentRoleId.flatMap { id in rolePermissions.first { $0.role.id == id }?.role } ?? currentRole.flatMap { role in rolePermissions.first { $0.role.id == role.id }?.role } ?? rolePermissions.first?.role permissionURIs = Set(Self.flattenPermissions(currentRole?.permission ?? [])) } /// 切换当前角色,并同步账号上下文中的景区和门店选择。 func selectRole(id roleId: Int, accountContext: AccountContext) { guard let rolePermission = rolePermissions.first(where: { $0.role.id == roleId }) else { return } currentRole = rolePermission.role permissionURIs = Set(Self.flattenPermissions(rolePermission.role.permission)) let scenicScopes = Self.uniqueScenicScopes(from: rolePermission.scenic) accountContext.replaceScopes( scenic: scenicScopes, stores: accountContext.storeScopes, currentScenicId: accountContext.currentScenic?.id, currentStoreId: accountContext.currentStore?.id ) } /// 判断当前角色是否拥有指定 URI 权限。 func canAccess(_ uri: String) -> Bool { permissionURIs.contains(uri.trimmingCharacters(in: .whitespacesAndNewlines)) } /// 返回当前角色关联的景区作用域。 func currentRoleScenicScopes() -> [BusinessScope] { guard let currentRole else { return [] } guard let rolePermission = rolePermissions.first(where: { $0.role.id == currentRole.id }) else { return [] } return Self.uniqueScenicScopes(from: rolePermission.scenic) } /// 清空权限上下文,通常在退出登录时调用。 func reset() { rolePermissions = [] permissionURIs = [] currentRole = nil } /// 递归提取权限树里的非空 URI。 private static func flattenPermissions(_ permissions: [PermissionItem]) -> [String] { permissions.flatMap { item -> [String] in let current = item.uri.trimmingCharacters(in: .whitespacesAndNewlines) let children = flattenPermissions(item.children) return current.isEmpty ? children : [current] + children } } /// 将角色关联景区去重并转换为业务作用域。 private static func uniqueScenicScopes(from scenics: [ScenicInfo]) -> [BusinessScope] { var seen = Set() return scenics.compactMap { scenic in guard seen.insert(scenic.id).inserted else { return nil } return BusinessScope( id: scenic.id, name: scenic.name, kind: .scenic, status: scenic.status, address: scenic.location?.address, latitude: scenic.location?.lat, longitude: scenic.location?.lng, coverURLString: scenic.coverImg ) } } }