Files
suixinkan_ios_uikit/suixinkan_ios/Features/Tasks/ViewModels/TaskManagementViewModel.swift
2026-06-26 14:33:31 +08:00

167 lines
5.1 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
///
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 {
var onChange: (() -> Void)?
var isLoading = false { didSet { onChange?() } }
var isLoadingMore = false { didSet { onChange?() } }
var tasks: [PhotographerTaskItem] = [] { didSet { onChange?() } }
var total = 0 { didSet { onChange?() } }
var page = 1 { didSet { onChange?() } }
var selectedStatus: TaskStatusFilter = .all { didSet { onChange?() } }
var searchText = "" { didSet { onChange?() } }
var startDate: Date? { didSet { onChange?() } }
var endDate: Date? { didSet { onChange?() } }
var errorMessage: String? { didSet { onChange?() } }
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 {
var onChange: (() -> Void)?
var isLoading = false { didSet { onChange?() } }
var detail: TaskDetailResponse? { didSet { onChange?() } }
var errorMessage: String? { didSet { onChange?() } }
///
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
}
}
}