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>
166 lines
4.9 KiB
Swift
166 lines
4.9 KiB
Swift
//
|
||
// 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
|
||
}
|
||
}
|
||
}
|