Files
suixinkan_ios_new/suixinkan/Features/OperatingArea/ViewModels/OperatingAreaViewModel.swift
汉秋 26f4d0e671 Migrate from iOS 17 Observation to iOS 16-compatible Combine architecture.
Lower deployment target to iOS 16, replace @Observable with ObservableObject, add navigation and UI compatibility shims, and include login smoke UI tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 10:16:35 +08:00

154 lines
5.0 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 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 ?? "运营区域"
}
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 = []
}
}