Add submit-task flow aligned with Android and extract profile edit page.

Implement task creation with cloud/local media, order linking, and OSS upload; wire home entry and move profile nickname/avatar editing to ProfileEditViewController.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 10:12:20 +08:00
parent 715393e78c
commit 3c4ca7eefb
23 changed files with 3186 additions and 170 deletions

View File

@ -0,0 +1,260 @@
//
// TaskModels.swift
// suixinkan
//
import Foundation
/// Android `AddTaskRequest`
struct AddTaskRequest: Encodable, Sendable, Equatable {
let scenicId: Int
let name: String
let orderNumber: String?
let remark: String
let urgentHour: Int
let cloudFile: [TaskCloudFileItem]
let uploadFile: [TaskUploadFileItem]
enum CodingKeys: String, CodingKey {
case scenicId = "scenic_id"
case name
case orderNumber = "order_number"
case remark = "photog_remark"
case urgentHour = "urgent_hour"
case cloudFile = "cloud_file"
case uploadFile = "upload_file"
}
}
/// Android `CloudFileItem`
struct TaskCloudFileItem: Encodable, Sendable, Equatable {
let fileId: Int
let remark: String
enum CodingKeys: String, CodingKey {
case fileId = "file_id"
case remark
}
}
/// Android `UploadFileItem`
struct TaskUploadFileItem: Encodable, Sendable, Equatable {
let fileUrl: String
let fileName: String
let remark: String
enum CodingKeys: String, CodingKey {
case fileUrl = "file_url"
case fileName = "file_name"
case remark
}
}
/// Android `AvailableOrderResponse`
struct AvailableOrder: Decodable, Sendable, Equatable, Hashable {
let projectName: String
let orderNumber: String
let orderStatus: Int
let orderStatusLabel: String
let payTime: String
let userId: Int
let userPhone: String
enum CodingKeys: String, CodingKey {
case projectName = "project_name"
case orderNumber = "order_number"
case orderStatus = "order_status"
case orderStatusLabel = "order_status_label"
case payTime = "pay_time"
case userId = "user_id"
case userPhone = "user_phone"
}
init(
projectName: String = "",
orderNumber: String = "",
orderStatus: Int = 0,
orderStatusLabel: String = "",
payTime: String = "",
userId: Int = 0,
userPhone: String = ""
) {
self.projectName = projectName
self.orderNumber = orderNumber
self.orderStatus = orderStatus
self.orderStatusLabel = orderStatusLabel
self.payTime = payTime
self.userId = userId
self.userPhone = userPhone
}
}
/// / Android `CloudFileEntity`
struct CloudFile: Decodable, Sendable, Equatable, Hashable {
let id: Int
let parentFolderId: Int
let fileUrl: String
let coverUrl: String
let updatedAt: String
let name: String
let createdAt: String
let childNum: Int
let type: Int
let fileSize: Int64
enum CodingKeys: String, CodingKey {
case id
case parentFolderId = "parent_folder_id"
case fileUrl = "file_url"
case coverUrl = "cover_url"
case updatedAt = "updated_at"
case name
case createdAt = "created_at"
case childNum = "child_num"
case type
case fileSize = "file_size"
}
init(
id: Int = 0,
parentFolderId: Int = 0,
fileUrl: String = "",
coverUrl: String = "",
updatedAt: String = "",
name: String = "",
createdAt: String = "",
childNum: Int = 0,
type: Int = 0,
fileSize: Int64 = 0
) {
self.id = id
self.parentFolderId = parentFolderId
self.fileUrl = fileUrl
self.coverUrl = coverUrl
self.updatedAt = updatedAt
self.name = name
self.createdAt = createdAt
self.childNum = childNum
self.type = type
self.fileSize = fileSize
}
///
var isFolder: Bool { type == 99 }
///
var isImage: Bool { type == 2 }
///
var isVideo: Bool { type == 1 }
///
var isSelectableMedia: Bool { isImage || isVideo }
}
///
struct CloudFileListResponse: Decodable, Sendable, Equatable {
let total: Int
let list: [CloudFile]
init(total: Int = 0, list: [CloudFile] = []) {
self.total = total
self.list = list
}
}
///
struct CloudPathItem: Sendable, Equatable, Hashable {
let id: Int
let name: String
}
///
enum TaskFileSource: String, Sendable, Equatable {
case cloud
case upload
}
/// Android `TaskFileItem`
struct TaskFileItem: Sendable, Equatable, Hashable, Identifiable {
let id: String
let source: TaskFileSource
let cloudFile: CloudFile?
let uploadFileName: String?
let uploadFileType: Int
let uploadTaskId: String?
var uploadFileUrl: String?
var uploadCoverUrl: String?
var remark: String
var isUploading: Bool
var uploadProgress: Int
init(
id: String? = nil,
source: TaskFileSource,
cloudFile: CloudFile? = nil,
uploadFileUrl: String? = nil,
uploadFileName: String? = nil,
uploadFileType: Int = 0,
uploadCoverUrl: String? = nil,
remark: String = "",
uploadTaskId: String? = nil,
isUploading: Bool = false,
uploadProgress: Int = 0
) {
if let id {
self.id = id
} else if source == .cloud, let cloudFile {
self.id = "cloud_\(cloudFile.id)"
} else if let uploadTaskId {
self.id = "upload_\(uploadTaskId)"
} else {
self.id = UUID().uuidString
}
self.source = source
self.cloudFile = cloudFile
self.uploadFileUrl = uploadFileUrl
self.uploadFileName = uploadFileName
self.uploadFileType = uploadFileType
self.uploadCoverUrl = uploadCoverUrl
self.remark = remark
self.uploadTaskId = uploadTaskId
self.isUploading = isUploading
self.uploadProgress = uploadProgress
}
/// URL
var previewURLString: String {
switch source {
case .cloud:
let cover = cloudFile?.coverUrl ?? ""
if !cover.isEmpty { return cover }
return cloudFile?.fileUrl ?? ""
case .upload:
if uploadFileType == 1 {
return uploadCoverUrl ?? uploadFileUrl ?? ""
}
return uploadFileUrl ?? ""
}
}
/// 1=2=
var fileType: Int {
switch source {
case .cloud:
return cloudFile?.type ?? 0
case .upload:
return uploadFileType
}
}
///
var displayName: String {
switch source {
case .cloud:
return cloudFile?.name ?? ""
case .upload:
return uploadFileName ?? ""
}
}
}