// // TaskManagementViewModel.swift // suixinkan // // Created by Codex on 2026/6/23. // import Foundation import Observation /// 任务状态筛选实体,表示任务管理页支持的任务状态。 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 @Observable final class TaskManagementViewModel { var isLoading = false var isLoadingMore = false var tasks: [PhotographerTaskItem] = [] var total = 0 var page = 1 var selectedStatus: TaskStatusFilter = .all var searchText = "" var startDate: Date? var endDate: Date? 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 { 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 @Observable final class TaskDetailViewModel { var isLoading = false var detail: TaskDetailResponse? 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 } } }