Initial commit: suixinkan_ios UIKit rewrite project.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,138 @@
|
||||
//
|
||||
// TaskCloudFileSelectionViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 云盘文件类型筛选实体,表示发布任务选择附件时的过滤方式。
|
||||
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
|
||||
final class TaskCloudFileSelectionViewModel {
|
||||
var onChange: (() -> Void)?
|
||||
var path: [CloudDriveFile] = [CloudDriveFile(id: 0, name: "云盘")] { didSet { onChange?() } }
|
||||
var files: [CloudDriveFile] = [] { didSet { onChange?() } }
|
||||
var total = 0 { didSet { onChange?() } }
|
||||
var page = 1 { didSet { onChange?() } }
|
||||
var searchText = "" { didSet { onChange?() } }
|
||||
var selectedFilter: TaskCloudFileFilter = .all { didSet { onChange?() } }
|
||||
var selectedFiles: [CloudDriveFile] = [] { didSet { onChange?() } }
|
||||
var isLoading = false { didSet { onChange?() } }
|
||||
var isLoadingMore = false { didSet { onChange?() } }
|
||||
var errorMessage: String? { didSet { onChange?() } }
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user