将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。 Co-authored-by: Cursor <cursoragent@cursor.com>
309 lines
9.8 KiB
Swift
309 lines
9.8 KiB
Swift
//
|
|
// TaskAddViewModel.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// 添加任务 ViewModel,对齐 Android `TaskAddViewModel` 表单与提交逻辑。
|
|
final class TaskAddViewModel {
|
|
|
|
private(set) var taskName = ""
|
|
private(set) var taskDetails = ""
|
|
private(set) var selectedOrderNumber: String?
|
|
private(set) var urgentHour = 0
|
|
private(set) var selectedFiles: [TaskFileItem] = []
|
|
private(set) var isUploading = false
|
|
private(set) var showSuccessDialog = false
|
|
private(set) var showRemarkDialog = false
|
|
private(set) var remarkDialogText = ""
|
|
private(set) var remarkTargetFileID: String?
|
|
|
|
var onStateChange: (() -> Void)?
|
|
var onShowMessage: ((String) -> Void)?
|
|
|
|
private let appStore: AppStore
|
|
private var pendingRemarkFileID: String?
|
|
|
|
init(appStore: AppStore = .shared) {
|
|
self.appStore = appStore
|
|
}
|
|
|
|
/// 更新任务名称。
|
|
func updateTaskName(_ value: String) {
|
|
taskName = String(value.prefix(20))
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 更新任务描述。
|
|
func updateTaskDetails(_ value: String) {
|
|
taskDetails = String(value.prefix(200))
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 设置关联订单号。
|
|
func setSelectedOrderNumber(_ orderNumber: String?) {
|
|
let trimmed = orderNumber?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
selectedOrderNumber = trimmed.isEmpty ? nil : trimmed
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 设置优先级小时数。
|
|
func setUrgentHour(_ hour: Int) {
|
|
urgentHour = max(0, hour)
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 更新自定义优先级小时文本。
|
|
func updateCustomUrgentHourText(_ text: String) {
|
|
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard let hour = Int(trimmed), hour > 0 else { return }
|
|
urgentHour = hour
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 合并云盘文件,去重后追加。
|
|
func addCloudFiles(_ files: [CloudFile]) {
|
|
let existingIDs = Set(selectedFiles.compactMap { $0.cloudFile?.id })
|
|
let newItems = files
|
|
.filter { !existingIDs.contains($0.id) }
|
|
.map {
|
|
TaskFileItem(source: .cloud, cloudFile: $0)
|
|
}
|
|
guard !newItems.isEmpty else { return }
|
|
selectedFiles.append(contentsOf: newItems)
|
|
if newItems.count == 1 {
|
|
pendingRemarkFileID = newItems[0].id
|
|
}
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 若云盘单选后需要自动弹备注,返回目标文件 ID 并清空 pending。
|
|
func consumePendingRemarkFileID() -> String? {
|
|
defer { pendingRemarkFileID = nil }
|
|
return pendingRemarkFileID
|
|
}
|
|
|
|
/// 添加本地上传占位项。
|
|
@discardableResult
|
|
func addUploadPlaceholder(fileName: String, fileType: Int, uploadTaskId: String) -> TaskFileItem {
|
|
let item = TaskFileItem(
|
|
source: .upload,
|
|
uploadFileName: fileName,
|
|
uploadFileType: fileType,
|
|
uploadTaskId: uploadTaskId,
|
|
isUploading: true,
|
|
uploadProgress: 0
|
|
)
|
|
selectedFiles.append(item)
|
|
isUploading = true
|
|
notifyStateChange()
|
|
return item
|
|
}
|
|
|
|
/// 更新上传进度。
|
|
func updateUploadProgress(uploadTaskId: String, progress: Int) {
|
|
selectedFiles = selectedFiles.map { item in
|
|
guard item.uploadTaskId == uploadTaskId else { return item }
|
|
var updated = item
|
|
updated.uploadProgress = progress
|
|
updated.isUploading = progress < 100
|
|
return updated
|
|
}
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 本地文件读取完成后同步真实文件名与预览图。
|
|
func prepareUpload(uploadTaskId: String, fileName: String, localPreviewData: Data?) {
|
|
selectedFiles = selectedFiles.map { item in
|
|
guard item.uploadTaskId == uploadTaskId else { return item }
|
|
var updated = item
|
|
updated.uploadFileName = fileName
|
|
updated.localPreviewData = localPreviewData
|
|
return updated
|
|
}
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 上传成功后更新文件 URL。
|
|
func markUploadSucceeded(
|
|
uploadTaskId: String,
|
|
fileURL: String,
|
|
fileName: String? = nil,
|
|
coverURL: String? = nil,
|
|
localPreviewData: Data? = nil,
|
|
autoRemarkWhenSingle: Bool
|
|
) {
|
|
selectedFiles = selectedFiles.map { item in
|
|
guard item.uploadTaskId == uploadTaskId else { return item }
|
|
var updated = item
|
|
updated.uploadFileUrl = fileURL
|
|
if let fileName, !fileName.isEmpty {
|
|
updated.uploadFileName = fileName
|
|
}
|
|
updated.uploadCoverUrl = coverURL
|
|
if let localPreviewData {
|
|
updated.localPreviewData = localPreviewData
|
|
}
|
|
updated.isUploading = false
|
|
updated.uploadProgress = 100
|
|
return updated
|
|
}
|
|
isUploading = selectedFiles.contains(where: \.isUploading)
|
|
if autoRemarkWhenSingle, let item = selectedFiles.first(where: { $0.uploadTaskId == uploadTaskId }) {
|
|
pendingRemarkFileID = item.id
|
|
}
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 上传失败时移除占位项。
|
|
func markUploadFailed(uploadTaskId: String) {
|
|
selectedFiles.removeAll { $0.uploadTaskId == uploadTaskId }
|
|
isUploading = selectedFiles.contains(where: \.isUploading)
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 删除素材。
|
|
func removeTaskFile(id: String) {
|
|
selectedFiles.removeAll { $0.id == id }
|
|
isUploading = selectedFiles.contains(where: \.isUploading)
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 打开备注弹窗。
|
|
func presentRemarkDialog(for fileID: String) {
|
|
guard let file = selectedFiles.first(where: { $0.id == fileID }) else { return }
|
|
remarkTargetFileID = fileID
|
|
remarkDialogText = file.remark
|
|
showRemarkDialog = true
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 更新备注弹窗文本。
|
|
func updateRemarkDialogText(_ text: String) {
|
|
remarkDialogText = String(text.prefix(50))
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 保存备注。
|
|
func saveRemarkDialog() {
|
|
guard let fileID = remarkTargetFileID else { return }
|
|
selectedFiles = selectedFiles.map { item in
|
|
guard item.id == fileID else { return item }
|
|
var updated = item
|
|
updated.remark = remarkDialogText
|
|
return updated
|
|
}
|
|
hideRemarkDialog()
|
|
}
|
|
|
|
/// 关闭备注弹窗。
|
|
func hideRemarkDialog() {
|
|
showRemarkDialog = false
|
|
remarkTargetFileID = nil
|
|
remarkDialogText = ""
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 当前备注弹窗对应文件。
|
|
var remarkTargetFile: TaskFileItem? {
|
|
guard let fileID = remarkTargetFileID else { return nil }
|
|
return selectedFiles.first { $0.id == fileID }
|
|
}
|
|
|
|
/// 校验并提交任务。
|
|
func validateAndSubmit(api: TaskAPI) async {
|
|
if isUploading {
|
|
onShowMessage?("文件正在上传中,请稍后添加!")
|
|
return
|
|
}
|
|
let trimmedName = taskName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if trimmedName.isEmpty {
|
|
onShowMessage?("请填写任务名称!")
|
|
return
|
|
}
|
|
if trimmedName.count > 20 {
|
|
onShowMessage?("任务名称不能超过20字!")
|
|
return
|
|
}
|
|
if taskDetails.count > 200 {
|
|
onShowMessage?("备注信息不能超过200字!")
|
|
return
|
|
}
|
|
|
|
let request = buildAddTaskRequest(name: trimmedName)
|
|
do {
|
|
try await api.createTask(request)
|
|
showSuccessDialog = true
|
|
notifyStateChange()
|
|
} catch is CancellationError {
|
|
return
|
|
} catch {
|
|
onShowMessage?(error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
/// 构建创建任务请求体。
|
|
func buildAddTaskRequest(name: String? = nil) -> AddTaskRequest {
|
|
let cloudItems = selectedFiles
|
|
.filter { $0.source == .cloud }
|
|
.compactMap { item -> TaskCloudFileItem? in
|
|
guard let fileId = item.cloudFile?.id else { return nil }
|
|
return TaskCloudFileItem(fileId: fileId, remark: item.remark)
|
|
}
|
|
let uploadItems = selectedFiles
|
|
.filter { $0.source == .upload }
|
|
.compactMap { item -> TaskUploadFileItem? in
|
|
guard let url = item.uploadFileUrl, !url.isEmpty else { return nil }
|
|
return TaskUploadFileItem(
|
|
fileUrl: url,
|
|
fileName: item.uploadFileName ?? "",
|
|
remark: item.remark
|
|
)
|
|
}
|
|
return AddTaskRequest(
|
|
scenicId: appStore.session.currentScenicId,
|
|
name: name ?? taskName,
|
|
orderNumber: selectedOrderNumber,
|
|
remark: taskDetails,
|
|
urgentHour: urgentHour,
|
|
cloudFile: cloudItems,
|
|
uploadFile: uploadItems
|
|
)
|
|
}
|
|
|
|
/// 成功弹窗选择继续添加。
|
|
func handleContinueAdding() {
|
|
resetAllState()
|
|
}
|
|
|
|
/// 关闭成功弹窗。
|
|
func dismissSuccessDialog() {
|
|
showSuccessDialog = false
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 重置全部表单状态。
|
|
func resetAllState() {
|
|
taskName = ""
|
|
taskDetails = ""
|
|
selectedOrderNumber = nil
|
|
urgentHour = 0
|
|
selectedFiles = []
|
|
isUploading = false
|
|
showSuccessDialog = false
|
|
hideRemarkDialog()
|
|
pendingRemarkFileID = nil
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 已导入云盘文件 ID,供云盘页禁用重复选择。
|
|
var importedCloudFileIDs: Set<Int> {
|
|
Set(selectedFiles.compactMap { $0.cloudFile?.id })
|
|
}
|
|
|
|
private func notifyStateChange() {
|
|
onStateChange?()
|
|
}
|
|
}
|