Files
suixinkan_ios_new/suixinkan/Features/Tasks/ViewModels/TaskManagementViewModel.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

166 lines
4.9 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.

//
// TaskManagementViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/23.
//
import Foundation
import Combine
///
enum TaskStatusFilter: Int, CaseIterable, Identifiable {
case all = 0
case unaccepted = 1
case accepted = 2
case completed = 3
case rejected = 4
var id: Int { rawValue }
///
var title: String {
switch self {
case .all:
"全部"
case .unaccepted:
"待接收"
case .accepted:
"处理中"
case .completed:
"已完成"
case .rejected:
"已驳回"
}
}
}
/// ViewModel
@MainActor
final class TaskManagementViewModel: ObservableObject {
@Published var isLoading = false
@Published var isLoadingMore = false
@Published var tasks: [PhotographerTaskItem] = []
@Published var total = 0
@Published var page = 1
@Published var selectedStatus: TaskStatusFilter = .all
@Published var searchText = ""
@Published var startDate: Date?
@Published var endDate: Date?
@Published var errorMessage: String?
private let pageSize = 10
///
var hasMore: Bool {
tasks.count < total
}
///
func reload(api: any TaskServing, scenicId: Int?) async throws {
guard let scenicId else {
resetList()
errorMessage = "请先选择景区"
return
}
isLoading = true
errorMessage = nil
defer { isLoading = false }
do {
let payload = try await requestList(api: api, scenicId: scenicId, page: 1)
page = 1
tasks = payload.list
total = payload.total
} catch {
resetList()
errorMessage = error.localizedDescription
throw error
}
}
///
func loadMore(api: any TaskServing, scenicId: Int?) async throws {
guard let scenicId, hasMore, !isLoadingMore else { return }
isLoadingMore = true
errorMessage = nil
defer { isLoadingMore = false }
let nextPage = page + 1
do {
let payload = try await requestList(api: api, scenicId: scenicId, page: nextPage)
page = nextPage
total = payload.total
tasks.append(contentsOf: payload.list)
} catch {
errorMessage = error.localizedDescription
throw error
}
}
///
func resetList() {
tasks = []
total = 0
page = 1
isLoading = false
isLoadingMore = false
}
///
private func requestList(api: any TaskServing, scenicId: Int, page: Int) async throws -> ListPayload<PhotographerTaskItem> {
try await api.taskList(
scenicId: scenicId,
page: page,
pageSize: pageSize,
taskStatus: selectedStatus.rawValue,
taskName: trimmedSearchText,
startTime: Self.requestDateString(from: startDate),
endTime: Self.requestDateString(from: endDate)
)
}
///
private var trimmedSearchText: String? {
let text = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
return text.isEmpty ? nil : text
}
///
private static let requestDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
///
private static func requestDateString(from date: Date?) -> String? {
guard let date else { return nil }
return requestDateFormatter.string(from: date)
}
}
/// ViewModel
@MainActor
final class TaskDetailViewModel: ObservableObject {
@Published var isLoading = false
@Published var detail: TaskDetailResponse?
@Published var errorMessage: String?
///
func load(api: any TaskServing, taskId: Int) async {
isLoading = true
errorMessage = nil
defer { isLoading = false }
do {
detail = try await api.taskDetail(id: taskId)
} catch {
errorMessage = error.localizedDescription
}
}
}