Files
suixinkan_ios_new/suixinkan/Features/OperatingArea/ViewModels/OperatingAreaViewModel.swift
汉秋 1970572edd 迁移合作订单完整能力,并统一 AppRoleCode 角色权限与首页菜单展示。
新增 CooperationOrder 模块(双 Tab 列表、获客员绑定、主 Tab 扫码)、订单来源/带客单绑定及配套 API 测试;同步引入 roleCode 权限匹配与相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 13:33:00 +08:00

151 lines
4.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// OperatingAreaViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/25.
//
import Foundation
import Combine
@MainActor
/// ViewModel
final class OperatingAreaViewModel: ObservableObject {
@Published private(set) var mode: OperatingAreaEntryMode?
@Published private(set) var loading = false
@Published private(set) var items: [OperatingAreaItem] = []
@Published private(set) var fenceRings: [OperatingFenceRing] = []
@Published private(set) var blockReason: OperatingAreaBlockReason?
@Published private(set) var errorMessage: String?
///
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<OperatingAreaItem>
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 appRole = permissionContext.currentAppRole
if appRole?.isStoreAdmin == true {
guard let storeId = accountContext.currentStore?.id, storeId > 0 else {
setBlocked(.missingStore)
return nil
}
mode = .storeAdmin(storeId: storeId)
blockReason = nil
return mode
}
if appRole?.isScenicAdmin == true {
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 ?? "运营区域"
}
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
}
private func setBlocked(_ reason: OperatingAreaBlockReason) {
resetLoadedData()
blockReason = reason
errorMessage = nil
loading = false
}
private func resetLoadedData() {
items = []
fenceRings = []
}
}