// // OperatingAreaViewModel.swift // suixinkan // // Created by Codex on 2026/6/25. // import Foundation @MainActor /// 运营区域 ViewModel,负责角色分流、围栏加载、解析和错误状态管理。 final class OperatingAreaViewModel { var onChange: (() -> Void)? private(set) var mode: OperatingAreaEntryMode? { didSet { onChange?() } } private(set) var loading = false { didSet { onChange?() } } private(set) var items: [OperatingAreaItem] = [] { didSet { onChange?() } } private(set) var fenceRings: [OperatingFenceRing] = [] { didSet { onChange?() } } private(set) var blockReason: OperatingAreaBlockReason? { didSet { onChange?() } } private(set) var errorMessage: String? { didSet { onChange?() } } /// 按当前账号和角色刷新运营区域。 func reload( api: any OperatingAreaServing, accountContext: AccountContext, permissionContext: PermissionContext ) async { guard !loading else { return } guard let nextMode = resolveMode(accountContext: accountContext, permissionContext: permissionContext) else { return } mode = nextMode loading = true errorMessage = nil blockReason = nil defer { loading = false } do { let response: ListPayload switch nextMode { case .storeAdmin(let storeId): response = try await api.storeBusinessArea(storeId: storeId) case .scenicAdmin(let scenicId): response = try await api.scenicAdminBusinessArea(scenicId: scenicId) } apply(items: response.list, mode: nextMode) } catch { resetLoadedData() let message = error.localizedDescription errorMessage = message blockReason = .backendMessage(message) } } /// 根据当前上下文解析运营区域入口模式。 @discardableResult func resolveMode(accountContext: AccountContext, permissionContext: PermissionContext) -> OperatingAreaEntryMode? { let roleName = permissionContext.currentRole?.name.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let lowercaseRoleName = roleName.lowercased() let isStoreRole = roleName.contains("店铺") || roleName.contains("门店") || lowercaseRoleName.contains("store") let isScenicRole = roleName.contains("景区") || lowercaseRoleName.contains("scenic") if isStoreRole { guard let storeId = accountContext.currentStore?.id, storeId > 0 else { setBlocked(.missingStore) return nil } mode = .storeAdmin(storeId: storeId) blockReason = nil return mode } if isScenicRole { guard let scenicId = accountContext.currentScenic?.id, scenicId > 0 else { setBlocked(.missingScenic) return nil } mode = .scenicAdmin(scenicId: scenicId) blockReason = nil return mode } if let storeId = accountContext.currentStore?.id, storeId > 0 { mode = .storeAdmin(storeId: storeId) blockReason = nil return mode } if let scenicId = accountContext.currentScenic?.id, scenicId > 0 { mode = .scenicAdmin(scenicId: scenicId) blockReason = nil return mode } setBlocked(.unsupportedRole) return nil } /// 根据状态生成统计展示。 var summary: (areaCount: Int, fenceCount: Int, currentStoreFenceCount: Int) { ( areaCount: items.count, fenceCount: fenceRings.count, currentStoreFenceCount: fenceRings.filter(\.isCurrentStore).count ) } /// 当前页面标题。 var title: String { mode?.title ?? "运营区域" } /// apply 业务逻辑。 private func apply(items: [OperatingAreaItem], mode: OperatingAreaEntryMode) { self.items = items if items.isEmpty { fenceRings = [] blockReason = .emptyAreaList return } let currentStoreId: Int? = { guard case .storeAdmin(let storeId) = mode else { return nil } return storeId }() let parsed = items.flatMap { item -> [OperatingFenceRing] in OperatingAreaParser.parseToRings(item.businessMapArea).enumerated().compactMap { index, points in guard points.count >= 3 else { return nil } return OperatingFenceRing( itemId: item.id, regionName: item.name, points: points, isCurrentStore: currentStoreId == item.id, ringIndex: index ) } } fenceRings = parsed blockReason = parsed.isEmpty ? .noParsableFenceData : nil } /// setBlocked相关逻辑。 private func setBlocked(_ reason: OperatingAreaBlockReason) { resetLoadedData() blockReason = reason errorMessage = nil loading = false } /// 重置LoadedData状态。 private func resetLoadedData() { items = [] fenceRings = [] } }