实现任务列表、详情与创建页面,对接 API,支持云端/本地附件,并添加单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
140 lines
4.0 KiB
Swift
140 lines
4.0 KiB
Swift
//
|
||
// TaskCloudFileSelectionViewModel.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/23.
|
||
//
|
||
|
||
import Foundation
|
||
import Observation
|
||
|
||
/// 云盘文件类型筛选实体,表示发布任务选择附件时的过滤方式。
|
||
enum TaskCloudFileFilter: Int, CaseIterable, Identifiable {
|
||
case all = 0
|
||
case video = 1
|
||
case image = 2
|
||
|
||
var id: Int { rawValue }
|
||
|
||
/// 返回筛选项展示文案。
|
||
var title: String {
|
||
switch self {
|
||
case .all:
|
||
"全部"
|
||
case .video:
|
||
"视频"
|
||
case .image:
|
||
"图片"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 云盘文件选择 ViewModel,负责目录浏览、筛选、分页和附件选择。
|
||
@MainActor
|
||
@Observable
|
||
final class TaskCloudFileSelectionViewModel {
|
||
var path: [CloudDriveFile] = [CloudDriveFile(id: 0, name: "云盘")]
|
||
var files: [CloudDriveFile] = []
|
||
var total = 0
|
||
var page = 1
|
||
var searchText = ""
|
||
var selectedFilter: TaskCloudFileFilter = .all
|
||
var selectedFiles: [CloudDriveFile] = []
|
||
var isLoading = false
|
||
var isLoadingMore = false
|
||
var errorMessage: String?
|
||
|
||
private let pageSize = 20
|
||
|
||
/// 当前目录 ID。
|
||
var currentFolderId: Int {
|
||
path.last?.id ?? 0
|
||
}
|
||
|
||
/// 判断当前目录是否还有下一页。
|
||
var hasMore: Bool {
|
||
files.count < total
|
||
}
|
||
|
||
/// 重新加载当前目录第一页。
|
||
func reload(api: any TaskServing) async throws {
|
||
isLoading = true
|
||
errorMessage = nil
|
||
defer { isLoading = false }
|
||
|
||
do {
|
||
let payload = try await requestFiles(api: api, page: 1)
|
||
page = 1
|
||
files = payload.list
|
||
total = payload.total
|
||
} catch {
|
||
files = []
|
||
total = 0
|
||
page = 1
|
||
errorMessage = error.localizedDescription
|
||
throw error
|
||
}
|
||
}
|
||
|
||
/// 加载当前目录下一页。
|
||
func loadMore(api: any TaskServing) async throws {
|
||
guard hasMore, !isLoadingMore else { return }
|
||
isLoadingMore = true
|
||
defer { isLoadingMore = false }
|
||
|
||
let nextPage = page + 1
|
||
let payload = try await requestFiles(api: api, page: nextPage)
|
||
page = nextPage
|
||
total = payload.total
|
||
files.append(contentsOf: payload.list)
|
||
}
|
||
|
||
/// 进入指定文件夹并刷新列表。
|
||
func enterFolder(_ folder: CloudDriveFile, api: any TaskServing) async throws {
|
||
guard folder.isFolder else { return }
|
||
path.append(folder)
|
||
try await reload(api: api)
|
||
}
|
||
|
||
/// 回到指定面包屑目录并刷新列表。
|
||
func popToFolder(at index: Int, api: any TaskServing) async throws {
|
||
guard path.indices.contains(index) else { return }
|
||
path = Array(path.prefix(index + 1))
|
||
try await reload(api: api)
|
||
}
|
||
|
||
/// 切换文件选中状态,文件夹不会被选为附件。
|
||
func toggleSelection(_ file: CloudDriveFile) {
|
||
guard !file.isFolder else { return }
|
||
if let index = selectedFiles.firstIndex(where: { $0.id == file.id }) {
|
||
selectedFiles.remove(at: index)
|
||
} else {
|
||
selectedFiles.append(file)
|
||
}
|
||
}
|
||
|
||
/// 判断指定云盘文件是否已选中。
|
||
func isSelected(_ file: CloudDriveFile) -> Bool {
|
||
selectedFiles.contains { $0.id == file.id }
|
||
}
|
||
|
||
/// 将当前选中的云盘文件转换为发布任务表单附件。
|
||
func makeSelectionItems() -> [TaskCloudSelectionItem] {
|
||
selectedFiles.map {
|
||
TaskCloudSelectionItem(id: $0.id, fileName: $0.name, fileType: $0.type, remark: "")
|
||
}
|
||
}
|
||
|
||
/// 组装并发送云盘文件列表请求。
|
||
private func requestFiles(api: any TaskServing, page: Int) async throws -> ListPayload<CloudDriveFile> {
|
||
try await api.cloudFileList(
|
||
parentFolderId: currentFolderId,
|
||
name: searchText.trimmingCharacters(in: .whitespacesAndNewlines),
|
||
type: selectedFilter.rawValue,
|
||
orderBy: 2,
|
||
page: page,
|
||
pageSize: pageSize
|
||
)
|
||
}
|
||
}
|