添加提交任务流程并抽取资料编辑页

This commit is contained in:
2026-07-07 10:12:20 +08:00
parent af450fb4cc
commit ef1d3b4af5
23 changed files with 3186 additions and 170 deletions

View File

@ -0,0 +1,287 @@
//
// 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 {
urgentHour = 1
notifyStateChange()
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()
}
/// URL
func markUploadSucceeded(uploadTaskId: String, fileURL: String, coverURL: String? = nil, autoRemarkWhenSingle: Bool) {
selectedFiles = selectedFiles.map { item in
guard item.uploadTaskId == uploadTaskId else { return item }
var updated = item
updated.uploadFileUrl = fileURL
updated.uploadCoverUrl = coverURL
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.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?()
}
}