// // AccountContext.swift // suixinkan // // Created by Codex on 2026/6/18. // import Foundation /// 当前账号的基础资料实体,用于跨页面展示昵称、手机号和头像。 struct AccountProfile: Codable, Equatable { var userId: String var displayName: String var phone: String? var avatarURL: String? } /// 当前业务作用域实体,表示景区或门店维度的上下文。 struct BusinessScope: Codable, Equatable, Identifiable { /// 业务作用域类型,用于区分景区和门店。 enum Kind: Codable, Equatable { case scenic case store } var id: Int var name: String var kind: Kind var parentScenicId: Int? var status: Int? var address: String? var latitude: Double? var longitude: Double? var coverURLString: String? /// 创建业务作用域,门店可通过 parentScenicId 标记所属景区。 init( id: Int, name: String, kind: Kind, parentScenicId: Int? = nil, status: Int? = nil, address: String? = nil, latitude: Double? = nil, longitude: Double? = nil, coverURLString: String? = nil ) { self.id = id self.name = name self.kind = kind self.parentScenicId = parentScenicId self.status = status self.address = address self.latitude = latitude self.longitude = longitude self.coverURLString = coverURLString } } @MainActor /// 账号上下文状态中心,保存当前账号资料和景区/门店作用域。 final class AccountContext { var onChange: (() -> Void)? private(set) var profile: AccountProfile? { didSet { onChange?() } } private(set) var scenicScopes: [BusinessScope] = [] { didSet { onChange?() } } private(set) var storeScopes: [BusinessScope] = [] { didSet { onChange?() } } var currentScenic: BusinessScope? { didSet { onChange?() } } var currentStore: BusinessScope? { didSet { onChange?() } } /// 应用登录成功后写入账号资料。 func applyLogin(profile: AccountProfile? = nil) { self.profile = profile } /// 替换当前账号资料,通常用于用户信息刷新后同步全局展示。 func replaceProfile(_ profile: AccountProfile?) { self.profile = profile } /// 替换景区和门店作用域,并尽量保持当前选择不变。 func replaceScopes( scenic: [BusinessScope], stores: [BusinessScope], currentScenicId: Int? = nil, currentStoreId: Int? = nil ) { scenicScopes = scenic storeScopes = stores currentScenic = currentScenicId.flatMap { id in scenic.first { $0.id == id } } ?? currentScenic.flatMap { current in scenic.first { $0.id == current.id } } ?? scenic.first currentStore = resolvedStore( in: stores, currentScenic: currentScenic, preferredStoreId: currentStoreId ?? currentStore?.id ) } /// 切换当前景区,并按景区重新校准当前门店。 func selectScenic(id scenicId: Int) { guard let scenic = scenicScopes.first(where: { $0.id == scenicId }) else { return } currentScenic = scenic currentStore = resolvedStore( in: storeScopes, currentScenic: scenic, preferredStoreId: currentStore?.id ) } /// 切换当前门店,并在门店有关联景区时同步当前景区。 func selectStore(id storeId: Int) { guard let store = storeScopes.first(where: { $0.id == storeId }) else { return } currentStore = store if let scenicId = store.parentScenicId, currentScenic?.id != scenicId, let scenic = scenicScopes.first(where: { $0.id == scenicId }) { currentScenic = scenic } } /// 清空账号上下文,通常在退出登录时调用。 func reset() { profile = nil scenicScopes = [] storeScopes = [] currentScenic = nil currentStore = nil } /// 按当前景区和期望门店 ID 选择有效门店。 private func resolvedStore( in stores: [BusinessScope], currentScenic: BusinessScope?, preferredStoreId: Int? ) -> BusinessScope? { let scenicId = currentScenic?.id let storesForScenic = stores.filter { store in guard let scenicId else { return true } return store.parentScenicId == nil || store.parentScenicId == scenicId } if let preferredStoreId, let store = storesForScenic.first(where: { $0.id == preferredStoreId }) { return store } return storesForScenic.first } }