迁移合作订单完整能力,并统一 AppRoleCode 角色权限与首页菜单展示。
新增 CooperationOrder 模块(双 Tab 列表、获客员绑定、主 Tab 扫码)、订单来源/带客单绑定及配套 API 测试;同步引入 roleCode 权限匹配与相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -23,7 +23,8 @@ struct AccountContextLoader {
|
||||
permissionContext: PermissionContext,
|
||||
profileAPI: UserProfileServing,
|
||||
accountContextAPI: AccountContextServing,
|
||||
cachedCurrentRoleId: Int? = nil,
|
||||
cachedCurrentRoleCode: String? = nil,
|
||||
cachedLegacyRoleId: Int? = nil,
|
||||
cachedCurrentScenicId: Int? = nil,
|
||||
cachedCurrentStoreId: Int? = nil
|
||||
) async throws {
|
||||
@ -31,7 +32,11 @@ struct AccountContextLoader {
|
||||
async let roleData = accountContextAPI.rolePermissions()
|
||||
let (userInfo, rolePermissions) = try await (userData, roleData)
|
||||
|
||||
permissionContext.replaceRolePermissions(rolePermissions, currentRoleId: cachedCurrentRoleId)
|
||||
permissionContext.replaceRolePermissions(
|
||||
rolePermissions,
|
||||
currentRoleCode: cachedCurrentRoleCode,
|
||||
cachedLegacyRoleId: cachedLegacyRoleId
|
||||
)
|
||||
|
||||
let scenicResponse = await loadScenicList(accountContextAPI: accountContextAPI, rolePermissions: rolePermissions)
|
||||
let storesResponse = await loadStores(accountContextAPI: accountContextAPI)
|
||||
|
||||
@ -73,6 +73,7 @@ final class AuthSessionCoordinator {
|
||||
permissionContext: permissionContext,
|
||||
profileAPI: profileAPI,
|
||||
accountContextAPI: accountContextAPI,
|
||||
cachedCurrentRoleCode: response.currentAccountRoleCode,
|
||||
cachedCurrentScenicId: accountContext.currentScenic?.id,
|
||||
cachedCurrentStoreId: accountContext.currentStore?.id
|
||||
)
|
||||
@ -114,7 +115,7 @@ final class AuthSessionCoordinator {
|
||||
profile: profile,
|
||||
accountType: snapshotStore.load()?.accountType,
|
||||
businessUserId: snapshotStore.load()?.businessUserId,
|
||||
currentRoleId: snapshotStore.load()?.currentRoleId,
|
||||
currentRoleCode: snapshotStore.load()?.currentRoleCode,
|
||||
accountContext: accountContext
|
||||
)
|
||||
}
|
||||
@ -144,7 +145,7 @@ final class AuthSessionCoordinator {
|
||||
accountType: String?,
|
||||
businessUserId: Int?,
|
||||
permissionContext: PermissionContext? = nil,
|
||||
currentRoleId: Int? = nil,
|
||||
currentRoleCode: String? = nil,
|
||||
accountContext: AccountContext
|
||||
) {
|
||||
snapshotStore.save(
|
||||
@ -152,7 +153,7 @@ final class AuthSessionCoordinator {
|
||||
profile: profile,
|
||||
accountType: accountType,
|
||||
businessUserId: businessUserId,
|
||||
currentRoleId: permissionContext?.currentRole?.id ?? currentRoleId,
|
||||
currentRoleCode: permissionContext?.currentAppRole?.rawValue ?? currentRoleCode,
|
||||
scenicScopes: accountContext.scenicScopes,
|
||||
storeScopes: accountContext.storeScopes,
|
||||
currentScenicId: accountContext.currentScenic?.id,
|
||||
|
||||
@ -15,20 +15,94 @@ final class PermissionContext: ObservableObject {
|
||||
@Published private(set) var permissionURIs: Set<String> = []
|
||||
@Published var currentRole: RoleInfo?
|
||||
|
||||
/// 替换角色权限列表,并尽量按缓存角色 ID 保持当前角色。
|
||||
func replaceRolePermissions(_ rolePermissions: [RolePermissionResponse], currentRoleId: Int? = nil) {
|
||||
/// 当前 App 业务角色,优先 role_code,兼容 legacy id 与角色名。
|
||||
var currentAppRole: AppRoleCode? {
|
||||
if let code = AppRoleCode.fromCode(currentRole?.roleCode) { return code }
|
||||
if let roleId = currentRole?.id, let code = AppRoleCode.fromLegacyRoleId(roleId) { return code }
|
||||
return AppRoleCode.fromRoleName(currentRole?.name)
|
||||
}
|
||||
|
||||
/// 替换角色权限列表,并按 role_code 优先恢复当前角色。
|
||||
func replaceRolePermissions(
|
||||
_ rolePermissions: [RolePermissionResponse],
|
||||
currentRoleCode: String? = nil,
|
||||
cachedLegacyRoleId: Int? = nil,
|
||||
roleName: String? = 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
|
||||
let resolvedCode = RolePermissionMatching.resolveRoleCode(
|
||||
roleCode: currentRoleCode,
|
||||
roleId: cachedLegacyRoleId,
|
||||
roleName: roleName,
|
||||
permissions: rolePermissions
|
||||
)?.rawValue ?? currentRoleCode
|
||||
|
||||
if let matched = RolePermissionMatching.match(
|
||||
in: rolePermissions,
|
||||
roleCode: resolvedCode,
|
||||
roleId: cachedLegacyRoleId,
|
||||
roleName: roleName ?? currentRole?.name
|
||||
) {
|
||||
currentRole = matched.role
|
||||
} else if let currentRole,
|
||||
let matched = rolePermissions.first(where: { $0.role.id == currentRole.id }) {
|
||||
self.currentRole = matched.role
|
||||
} else {
|
||||
currentRole = rolePermissions.first?.role
|
||||
}
|
||||
permissionURIs = Set(Self.flattenPermissions(currentRole?.permission ?? []))
|
||||
}
|
||||
|
||||
/// 切换当前角色,并同步账号上下文中的景区和门店选择。
|
||||
func selectRole(code: AppRoleCode, accountContext: AccountContext) {
|
||||
guard let rolePermission = rolePermissions.first(where: {
|
||||
AppRoleCode.fromCode($0.role.roleCode) == code
|
||||
}) else { return }
|
||||
applyRoleSelection(rolePermission, accountContext: accountContext)
|
||||
}
|
||||
|
||||
/// 切换当前角色(legacy id),供权限申请等仍使用 API role id 的场景。
|
||||
func selectRole(id roleId: Int, accountContext: AccountContext) {
|
||||
guard let rolePermission = rolePermissions.first(where: { $0.role.id == roleId }) else { return }
|
||||
applyRoleSelection(rolePermission, accountContext: accountContext)
|
||||
}
|
||||
|
||||
/// 判断当前角色是否拥有指定 URI 权限。
|
||||
func canAccess(_ uri: String) -> Bool {
|
||||
permissionURIs.contains(uri.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
}
|
||||
|
||||
/// 返回指定角色的顶层权限 URI 列表,顺序与 API 返回一致。
|
||||
func topLevelPermissionURIs(for roleCode: String?) -> [String] {
|
||||
guard let roleCode,
|
||||
let rolePermission = matchedRolePermission(roleCode: roleCode) else {
|
||||
return []
|
||||
}
|
||||
return rolePermission.role.permission.compactMap { item in
|
||||
let uri = item.uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return uri.isEmpty ? nil : uri
|
||||
}
|
||||
}
|
||||
|
||||
/// 返回当前角色关联的景区作用域。
|
||||
func currentRoleScenicScopes() -> [BusinessScope] {
|
||||
guard let currentRole else { return [] }
|
||||
guard let rolePermission = matchedRolePermission(roleCode: currentRole.roleCode)
|
||||
?? rolePermissions.first(where: { $0.role.id == currentRole.id }) else {
|
||||
return []
|
||||
}
|
||||
return Self.uniqueScenicScopes(from: rolePermission.scenic)
|
||||
}
|
||||
|
||||
/// 清空权限上下文,通常在退出登录时调用。
|
||||
func reset() {
|
||||
rolePermissions = []
|
||||
permissionURIs = []
|
||||
currentRole = nil
|
||||
}
|
||||
|
||||
/// 应用角色切换并同步景区作用域。
|
||||
private func applyRoleSelection(_ rolePermission: RolePermissionResponse, accountContext: AccountContext) {
|
||||
currentRole = rolePermission.role
|
||||
permissionURIs = Set(Self.flattenPermissions(rolePermission.role.permission))
|
||||
|
||||
@ -41,25 +115,14 @@ final class PermissionContext: ObservableObject {
|
||||
)
|
||||
}
|
||||
|
||||
/// 判断当前角色是否拥有指定 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
|
||||
/// 按 role_code 在权限列表中查找角色权限项。
|
||||
private func matchedRolePermission(roleCode: String) -> RolePermissionResponse? {
|
||||
RolePermissionMatching.match(
|
||||
in: rolePermissions,
|
||||
roleCode: roleCode,
|
||||
roleId: nil,
|
||||
roleName: nil
|
||||
)
|
||||
}
|
||||
|
||||
/// 递归提取权限树里的非空 URI。
|
||||
|
||||
@ -58,7 +58,8 @@ final class SessionBootstrapper {
|
||||
permissionContext: permissionContext,
|
||||
profileAPI: profileAPI,
|
||||
accountContextAPI: accountContextAPI,
|
||||
cachedCurrentRoleId: cachedSnapshot?.currentRoleId,
|
||||
cachedCurrentRoleCode: cachedSnapshot?.currentRoleCode,
|
||||
cachedLegacyRoleId: cachedSnapshot?.currentRoleId,
|
||||
cachedCurrentScenicId: cachedSnapshot?.currentScenicId,
|
||||
cachedCurrentStoreId: cachedSnapshot?.currentStoreId
|
||||
)
|
||||
@ -100,7 +101,7 @@ final class SessionBootstrapper {
|
||||
profile: accountContext.profile,
|
||||
accountType: existing?.accountType,
|
||||
businessUserId: existing?.businessUserId,
|
||||
currentRoleId: permissionContext.currentRole?.id ?? existing?.currentRoleId,
|
||||
currentRoleCode: permissionContext.currentAppRole?.rawValue ?? existing?.currentRoleCode,
|
||||
scenicScopes: accountContext.scenicScopes,
|
||||
storeScopes: accountContext.storeScopes,
|
||||
currentScenicId: accountContext.currentScenic?.id,
|
||||
|
||||
Reference in New Issue
Block a user