新增 CooperationOrder 模块(双 Tab 列表、获客员绑定、主 Tab 扫码)、订单来源/带客单绑定及配套 API 测试;同步引入 roleCode 权限匹配与相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
131 lines
5.1 KiB
Swift
131 lines
5.1 KiB
Swift
//
|
||
// HomeCommonMenuStore.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/22.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 首页常用应用存储服务,负责按当前角色权限过滤和持久化常用 URI。
|
||
struct HomeCommonMenuStore {
|
||
static let storageKeyPrefix = "home.common.menu.uris.role."
|
||
|
||
private let defaults: UserDefaults
|
||
|
||
/// 初始化常用应用存储服务,并允许测试注入独立 UserDefaults。
|
||
init(defaults: UserDefaults = .standard) {
|
||
self.defaults = defaults
|
||
}
|
||
|
||
/// 按 Android 规则生成默认常用 URI:顶层权限顺序、可用权限过滤、别名去重,超过 4 个取前 4 个。
|
||
static func defaultCommonURIs(
|
||
orderedTopLevelURIs: [String],
|
||
availableURIs: Set<String>
|
||
) -> [String] {
|
||
let resolved = orderedTopLevelURIs.compactMap { uri -> String? in
|
||
let trimmed = uri.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
guard !trimmed.isEmpty else { return nil }
|
||
let canonical = HomeMenuRouter.canonicalURI(for: trimmed, availableURIs: availableURIs)
|
||
return availableURIs.contains(canonical) ? canonical : nil
|
||
}
|
||
let deduplicated = unique(resolved)
|
||
if deduplicated.count > 4 {
|
||
return Array(deduplicated.prefix(4))
|
||
}
|
||
return deduplicated
|
||
}
|
||
|
||
/// 读取常用 URI;无角色时返回空,无保存或过滤后为空时写入 Android 对齐默认值。
|
||
func load(
|
||
menuItems: [HomeMenuItem],
|
||
roleCode: String?,
|
||
legacyRoleId: Int? = nil,
|
||
topLevelPermissionURIs: [String]
|
||
) -> [String] {
|
||
guard let roleCode, !roleCode.isEmpty else { return [] }
|
||
migrateLegacyStorageIfNeeded(roleCode: roleCode, legacyRoleId: legacyRoleId)
|
||
|
||
let availableURIs = Set(menuItems.map(\.uri))
|
||
let key = storageKey(for: roleCode)
|
||
let saved = defaults.stringArray(forKey: key)
|
||
|
||
if let saved, !saved.isEmpty {
|
||
let filtered = Self.unique(saved.compactMap { uri -> String? in
|
||
let resolved = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
|
||
return availableURIs.contains(resolved) ? resolved : nil
|
||
})
|
||
if !filtered.isEmpty {
|
||
save(filtered, roleCode: roleCode)
|
||
return filtered
|
||
}
|
||
}
|
||
|
||
let defaultURIs = Self.defaultCommonURIs(
|
||
orderedTopLevelURIs: topLevelPermissionURIs,
|
||
availableURIs: availableURIs
|
||
)
|
||
save(defaultURIs, roleCode: roleCode)
|
||
return defaultURIs
|
||
}
|
||
|
||
/// 将指定 URI 加入常用应用,按别名避免重复添加。
|
||
func add(_ uri: String, current: [String], menuItems: [HomeMenuItem], roleCode: String?) -> [String] {
|
||
guard let roleCode, !roleCode.isEmpty else { return current }
|
||
let availableURIs = Set(menuItems.map(\.uri))
|
||
let resolved = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
|
||
guard availableURIs.contains(resolved) else { return current }
|
||
guard !current.contains(where: { HomeMenuRouter.menuAliasKey(for: $0) == HomeMenuRouter.menuAliasKey(for: resolved) }) else {
|
||
return current
|
||
}
|
||
let next = current + [resolved]
|
||
save(next, roleCode: roleCode)
|
||
return next
|
||
}
|
||
|
||
/// 从常用应用中移除指定 URI,同义 URI 会一起移除。
|
||
func remove(_ uri: String, current: [String], roleCode: String?) -> [String] {
|
||
guard let roleCode, !roleCode.isEmpty else { return current }
|
||
let aliasKey = HomeMenuRouter.menuAliasKey(for: uri)
|
||
let next = current.filter { HomeMenuRouter.menuAliasKey(for: $0) != aliasKey }
|
||
save(next, roleCode: roleCode)
|
||
return next
|
||
}
|
||
|
||
/// 保存指定角色的常用 URI。
|
||
func save(_ uris: [String], roleCode: String) {
|
||
defaults.set(Self.unique(uris), forKey: storageKey(for: roleCode))
|
||
}
|
||
|
||
/// 若新 key 无数据,则从 legacy role id key 一次性迁移常用菜单。
|
||
func migrateLegacyStorageIfNeeded(roleCode: String, legacyRoleId: Int?) {
|
||
let newKey = storageKey(for: roleCode)
|
||
guard defaults.stringArray(forKey: newKey) == nil,
|
||
let legacyRoleId,
|
||
let legacyData = defaults.stringArray(forKey: legacyStorageKey(for: legacyRoleId)),
|
||
!legacyData.isEmpty else {
|
||
return
|
||
}
|
||
defaults.set(legacyData, forKey: newKey)
|
||
defaults.removeObject(forKey: legacyStorageKey(for: legacyRoleId))
|
||
}
|
||
|
||
/// 生成按 role_code 隔离的 UserDefaults key。
|
||
private func storageKey(for roleCode: String) -> String {
|
||
Self.storageKeyPrefix + roleCode
|
||
}
|
||
|
||
/// 旧版按 numeric role id 存储的 key。
|
||
private func legacyStorageKey(for roleId: Int) -> String {
|
||
Self.storageKeyPrefix + String(roleId)
|
||
}
|
||
|
||
/// 按 URI 别名去重,保留首次出现的 URI。
|
||
private static func unique(_ uris: [String]) -> [String] {
|
||
var seen = Set<String>()
|
||
return uris.filter { uri in
|
||
seen.insert(HomeMenuRouter.menuAliasKey(for: uri)).inserted
|
||
}
|
||
}
|
||
}
|