Implement permission-driven home tab aligned with Android.
Load role-permission on first visit to drive menus, role-based layout, store card, and location reporting with account-scoped persistence and unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -26,6 +26,13 @@ final class AppStore {
|
||||
static let currentScenicId = "key_in_current_scenic_id"
|
||||
static let currentScenicName = "key_in_current_scenic_name"
|
||||
static let currentStoreId = "key_in_current_store_id"
|
||||
static let legacyRoleId = "key_in_role_id"
|
||||
static let rolePermissionList = "key_role_permission_list"
|
||||
static let permissionItems = "key_in_permission"
|
||||
static let roleScenicList = "key_current_role_scenic_list"
|
||||
static let onlineStatus = "key_online_status"
|
||||
static let lastLocationReportTime = "key_last_location_report_time"
|
||||
static let locationReminderMinutes = "key_location_reminder_minutes"
|
||||
}
|
||||
|
||||
private let defaults: UserDefaults
|
||||
@ -123,6 +130,39 @@ final class AppStore {
|
||||
set { defaults.set(newValue, forKey: Key.currentStoreId) }
|
||||
}
|
||||
|
||||
/// 旧版 role_id,用于权限匹配兜底。
|
||||
var legacyRoleId: Int {
|
||||
get { defaults.integer(forKey: accountScopedKey(Key.legacyRoleId)) }
|
||||
set { defaults.set(newValue, forKey: accountScopedKey(Key.legacyRoleId)) }
|
||||
}
|
||||
|
||||
/// 是否在线(位置上报会话)。
|
||||
var onlineStatus: Bool {
|
||||
get { defaults.bool(forKey: accountScopedKey(Key.onlineStatus)) }
|
||||
set { defaults.set(newValue, forKey: accountScopedKey(Key.onlineStatus)) }
|
||||
}
|
||||
|
||||
/// 上次位置上报时间戳(毫秒)。
|
||||
var lastLocationReportTime: Int64 {
|
||||
get { Int64(defaults.integer(forKey: accountScopedKey(Key.lastLocationReportTime))) }
|
||||
set { defaults.set(Int(newValue), forKey: accountScopedKey(Key.lastLocationReportTime)) }
|
||||
}
|
||||
|
||||
/// 位置超时提前提醒分钟数,0 表示不提醒。
|
||||
var locationReminderMinutes: Int {
|
||||
get { defaults.integer(forKey: accountScopedKey(Key.locationReminderMinutes)) }
|
||||
set { defaults.set(newValue, forKey: accountScopedKey(Key.locationReminderMinutes)) }
|
||||
}
|
||||
|
||||
/// 当前账号缓存前缀,对齐 Android `getAccountCachePrefix`。
|
||||
var accountCachePrefix: String {
|
||||
let uid = userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let type = accountType.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if uid.isEmpty { return "guest" }
|
||||
if type.isEmpty { return uid }
|
||||
return "\(uid)_\(type)"
|
||||
}
|
||||
|
||||
/// 当前解析后的业务角色。
|
||||
var currentAppRole: AppRoleCode? {
|
||||
AppRoleCode.fromCode(roleCode) ?? AppRoleCode.fromRoleName(roleName)
|
||||
@ -183,6 +223,7 @@ final class AppStore {
|
||||
|
||||
/// 清除登录态与会话快照。
|
||||
func logout() {
|
||||
clearPermissionSnapshot()
|
||||
token = ""
|
||||
userId = ""
|
||||
userName = ""
|
||||
@ -197,4 +238,82 @@ final class AppStore {
|
||||
currentScenicName = ""
|
||||
currentStoreId = 0
|
||||
}
|
||||
|
||||
/// 保存完整 role-permission 列表。
|
||||
func saveRolePermissionList(_ list: [RolePermissionResponse]) {
|
||||
guard let data = try? JSONEncoder().encode(list) else { return }
|
||||
defaults.set(data, forKey: accountScopedKey(Key.rolePermissionList))
|
||||
}
|
||||
|
||||
/// 读取完整 role-permission 列表。
|
||||
func rolePermissionList() -> [RolePermissionResponse] {
|
||||
guard let data = defaults.data(forKey: accountScopedKey(Key.rolePermissionList)),
|
||||
let list = try? JSONDecoder().decode([RolePermissionResponse].self, from: data) else {
|
||||
return []
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/// 保存当前角色扁平权限。
|
||||
func savePermissionItems(_ items: [HomePermissionItem]) {
|
||||
guard let data = try? JSONEncoder().encode(items) else { return }
|
||||
defaults.set(data, forKey: accountScopedKey(Key.permissionItems))
|
||||
}
|
||||
|
||||
/// 读取当前角色扁平权限。
|
||||
func permissionItems() -> [HomePermissionItem] {
|
||||
guard let data = defaults.data(forKey: accountScopedKey(Key.permissionItems)),
|
||||
let items = try? JSONDecoder().decode([HomePermissionItem].self, from: data) else {
|
||||
return []
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
/// 保存当前角色可选景区列表。
|
||||
func saveRoleScenicList(_ scenicList: [ScenicInfo]) {
|
||||
guard let data = try? JSONEncoder().encode(scenicList) else { return }
|
||||
defaults.set(data, forKey: accountScopedKey(Key.roleScenicList))
|
||||
}
|
||||
|
||||
/// 读取当前角色可选景区列表。
|
||||
func roleScenicList() -> [ScenicInfo] {
|
||||
guard let data = defaults.data(forKey: accountScopedKey(Key.roleScenicList)),
|
||||
let list = try? JSONDecoder().decode([ScenicInfo].self, from: data) else {
|
||||
return []
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
/// 在 role-permission 列表中匹配当前账号角色。
|
||||
func matchRolePermissionItem(in list: [RolePermissionResponse]) -> RolePermissionResponse? {
|
||||
RolePermissionMatcher.match(
|
||||
in: list,
|
||||
roleCode: roleCode,
|
||||
roleName: roleName,
|
||||
legacyRoleId: legacyRoleId
|
||||
)
|
||||
}
|
||||
|
||||
/// 清除上次位置上报时间。
|
||||
func clearLastLocationReportTime() {
|
||||
defaults.removeObject(forKey: accountScopedKey(Key.lastLocationReportTime))
|
||||
}
|
||||
|
||||
/// 清除权限与位置会话快照。
|
||||
func clearPermissionSnapshot() {
|
||||
let prefix = accountScopedKey("")
|
||||
let keys = [
|
||||
Key.rolePermissionList,
|
||||
Key.permissionItems,
|
||||
Key.roleScenicList,
|
||||
Key.onlineStatus,
|
||||
Key.lastLocationReportTime,
|
||||
Key.locationReminderMinutes,
|
||||
]
|
||||
keys.forEach { defaults.removeObject(forKey: prefix + $0) }
|
||||
}
|
||||
|
||||
private func accountScopedKey(_ key: String) -> String {
|
||||
"\(accountCachePrefix)_\(key)"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user