Initial commit: suixinkan_ios UIKit rewrite project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 14:33:31 +08:00
commit 9edf993432
297 changed files with 47151 additions and 0 deletions

View File

@ -0,0 +1,232 @@
//
// LocationReportViewModels.swift
// suixinkan
//
// Created by Codex on 2026/6/24.
//
import Foundation
/// ViewModel线
@MainActor
final class LocationReportViewModel {
var onChange: (() -> Void)?
var isOnline = false { didSet { onChange?() } }
var reminderMinutes = 30 { didSet { onChange?() } }
var secondsUntilReport = 0 { didSet { onChange?() } }
var currentCoordinate: LocationCoordinate? { didSet { onChange?() } }
var markedCoordinate: LocationCoordinate? { didSet { onChange?() } }
var currentAddress = "" { didSet { onChange?() } }
var markedAddress = "" { didSet { onChange?() } }
var lastReportText = "" { didSet { onChange?() } }
var errorMessage: String? { didSet { onChange?() } }
var isSubmitting = false { didSet { onChange?() } }
///
var countdownText: String {
guard secondsUntilReport > 0 else { return "可立即上报" }
let hours = secondsUntilReport / 3600
let minutes = (secondsUntilReport % 3600) / 60
return "\(hours)小时\(minutes)分钟后可再次提醒"
}
///
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 setOnline(
_ value: Bool,
staffId: Int?,
scenicId: Int?,
api: any LocationReportServing
) async -> Bool {
isOnline = value
guard value else { return true }
return await submit(type: .onlineStatus, staffId: staffId, scenicId: scenicId, api: api)
}
///
func submit(
type: LocationReportType,
staffId: Int?,
scenicId: Int?,
api: any LocationReportServing
) 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 }
do {
let response = try await api.reportLocation(
staffId: staffId,
latitude: coordinate.latitude,
longitude: coordinate.longitude,
address: address,
type: type,
scenicId: scenicId
)
secondsUntilReport = response.expired > 0 ? response.expired : 7200
lastReportText = LocationReportViewModel.displayFormatter.string(from: Date())
return true
} catch {
errorMessage = error.localizedDescription
return false
}
}
///
func tick() {
guard secondsUntilReport > 0 else { return }
secondsUntilReport -= 1
}
///
private func reportSource(for type: LocationReportType) -> (coordinate: LocationCoordinate?, address: String) {
switch type {
case .marked:
(markedCoordinate ?? currentCoordinate, markedAddress.isEmpty ? currentAddress : markedAddress)
case .all, .immediate, .onlineStatus:
(currentCoordinate, currentAddress)
}
}
private static let displayFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter
}()
}
/// ViewModel
@MainActor
final class LocationReportHistoryViewModel {
var onChange: (() -> Void)?
var selectedType: LocationReportType = .all { didSet { onChange?() } }
var startDate: Date? { didSet { onChange?() } }
var endDate: Date? { didSet { onChange?() } }
var items: [LocationReportHistoryItem] = [] { didSet { onChange?() } }
var errorMessage: String? { didSet { onChange?() } }
var isLoading = false { didSet { onChange?() } }
var isLoadingMore = false { didSet { onChange?() } }
var total = 0 { didSet { onChange?() } }
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
}()
}