// // LocationReportViewModels.swift // suixinkan // // Created by Codex on 2026/6/24. // import Foundation import Combine /// 位置上报 ViewModel,负责页面内坐标、标记点和提交表单。 @MainActor final class LocationReportViewModel: ObservableObject { @Published var currentCoordinate: LocationCoordinate? @Published var markedCoordinate: LocationCoordinate? @Published var currentAddress = "" @Published var markedAddress = "" @Published var errorMessage: String? @Published var isSubmitting = false /// 设置当前位置。 func applyCurrentLocation(latitude: Double, longitude: Double, address: String) { currentCoordinate = LocationCoordinate(latitude: latitude, longitude: longitude) currentAddress = address } /// 设置标记点位置。 func applyMarkedLocation(latitude: Double, longitude: Double, address: String) { markedCoordinate = LocationCoordinate(latitude: latitude, longitude: longitude) markedAddress = address } /// 提交指定类型的位置上报。 func submit( type: LocationReportType, staffId: Int?, scenicId: Int?, homeLocationViewModel: HomeLocationViewModel, toast: ToastCenter ) async -> Bool { guard !isSubmitting else { return false } guard let staffId, staffId > 0 else { errorMessage = "缺少上报人员" return false } guard let scenicId, scenicId > 0 else { errorMessage = "缺少当前景区" return false } let source = reportSource(for: type) guard let coordinate = source.coordinate else { errorMessage = "请先获取或选择位置" return false } let address = source.address.trimmingCharacters(in: .whitespacesAndNewlines) guard !address.isEmpty else { errorMessage = "请填写位置地址" return false } isSubmitting = true errorMessage = nil defer { isSubmitting = false } let success = await homeLocationViewModel.reportCoordinate( latitude: coordinate.latitude, longitude: coordinate.longitude, address: address, type: type, staffId: staffId, scenicId: scenicId, toast: toast ) if !success, errorMessage == nil { errorMessage = "上报失败" } return success } /// 根据上报类型选择当前坐标或标记点坐标。 private func reportSource(for type: LocationReportType) -> (coordinate: LocationCoordinate?, address: String) { switch type { case .marked: (markedCoordinate ?? currentCoordinate, markedAddress.isEmpty ? currentAddress : markedAddress) case .all, .immediate, .offlineReport: (currentCoordinate, currentAddress) } } } /// 位置上报历史 ViewModel,负责筛选、日期和分页加载。 @MainActor final class LocationReportHistoryViewModel: ObservableObject { @Published var selectedType: LocationReportType = .all @Published var startDate: Date? @Published var endDate: Date? @Published var items: [LocationReportHistoryItem] = [] @Published var errorMessage: String? private var isLoading = false @Published var isLoadingMore = false @Published var total = 0 @Published private var page = 1 private let pageSize = 20 /// 是否还有下一页历史记录。 var hasMore: Bool { items.count < total } /// 重新加载历史记录。 func reload(staffId: Int?, api: any LocationReportServing) async { guard let staffId, staffId > 0 else { reset() errorMessage = "缺少上报人员" return } page = 1 isLoading = true errorMessage = nil defer { isLoading = false } do { let payload = try await api.locationReportList( staffId: staffId, page: page, pageSize: pageSize, type: selectedType, startDate: dateString(startDate), endDate: dateString(endDate) ) items = payload.list total = payload.total } catch { items = [] total = 0 errorMessage = error.localizedDescription } } /// 加载下一页历史记录。 func loadMore(staffId: Int?, api: any LocationReportServing) async { guard let staffId, staffId > 0, hasMore, !isLoading, !isLoadingMore else { return } isLoadingMore = true let nextPage = page + 1 defer { isLoadingMore = false } do { let payload = try await api.locationReportList( staffId: staffId, page: nextPage, pageSize: pageSize, type: selectedType, startDate: dateString(startDate), endDate: dateString(endDate) ) page = nextPage items.append(contentsOf: payload.list) total = payload.total } catch { errorMessage = error.localizedDescription } } /// 切换历史类型筛选并刷新。 func selectType(_ type: LocationReportType, staffId: Int?, api: any LocationReportServing) async { guard selectedType != type else { return } selectedType = type await reload(staffId: staffId, api: api) } /// 清空历史状态。 func reset() { items = [] total = 0 page = 1 isLoading = false isLoadingMore = false } /// 将日期转为接口需要的 yyyy-MM-dd 字符串。 private func dateString(_ date: Date?) -> String? { guard let date else { return nil } return Self.dateFormatter.string(from: date) } private static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" return formatter }() }