持久化服务端 expired 过期时间戳以正确恢复倒计时,切换在线/离线与上报时展示定位 Loading,移除重复的成功 Alert,并将 Toast 调整为居中半透明样式。 Co-authored-by: Cursor <cursoragent@cursor.com>
194 lines
6.0 KiB
Swift
194 lines
6.0 KiB
Swift
//
|
||
// 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
|
||
}()
|
||
}
|