Files
suixinkan_ios_new/suixinkan/Features/Tasks/Models/TaskModels.swift
汉秋 311a70d610 新增项目、排期与邀请模块,并接入首页路由
将项目管理、排期管理与摄影师邀请流程从占位页迁移为完整实现,包含项目图片 OSS 上传与共享业务模型。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 15:57:15 +08:00

292 lines
11 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// TaskModels.swift
// suixinkan
//
// Created by Codex on 2026/6/23.
//
import Foundation
///
struct PhotographerTaskItem: Decodable, Identifiable, Hashable {
let id: Int
let name: String
let type: Int
let orderNumber: String
let taskStatus: Int
let statusName: String
let createdAt: String
let updatedAt: String
let photogRemark: String
let editorName: String
let operateTime: String
let orderUserNickname: String
let orderUserPhone: String
let orderUserId: String
let media: [TaskMediaItem]
/// 线
enum CodingKeys: String, CodingKey {
case id
case name
case type
case orderNumber = "order_number"
case taskStatus = "task_status"
case statusName = "status_name"
case createdAt = "created_at"
case updatedAt = "updated_at"
case photogRemark = "photog_remark"
case editorName = "editor_name"
case operateTime = "operate_time"
case orderUserNickname = "order_user_nickname"
case orderUserPhone = "order_user_phone"
case orderUserId = "order_user_id"
case media
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
name = try container.decodeLossyString(forKey: .name)
type = try container.decodeLossyInt(forKey: .type) ?? 0
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
taskStatus = try container.decodeLossyInt(forKey: .taskStatus) ?? 0
statusName = try container.decodeLossyString(forKey: .statusName)
createdAt = try container.decodeLossyString(forKey: .createdAt)
updatedAt = try container.decodeLossyString(forKey: .updatedAt)
photogRemark = try container.decodeLossyString(forKey: .photogRemark)
editorName = try container.decodeLossyString(forKey: .editorName)
operateTime = try container.decodeLossyString(forKey: .operateTime)
orderUserNickname = try container.decodeLossyString(forKey: .orderUserNickname)
orderUserPhone = try container.decodeLossyString(forKey: .orderUserPhone)
orderUserId = try container.decodeLossyString(forKey: .orderUserId)
media = (try? container.decode([TaskMediaItem].self, forKey: .media)) ?? []
}
}
///
struct TaskMediaItem: Decodable, Identifiable, Hashable {
let id: Int
let photogTaskId: Int
let userId: Int
let fileName: String
let fileType: Int
let fileUrl: String
let fileSize: Int64
let coverUrl: String
let duration: Int
let remark: String
let createdAt: String
let updatedAt: String
///
var isVideo: Bool {
fileType == 1 || Self.hasVideoExtension(fileName) || Self.hasVideoExtension(fileUrl)
}
/// URL使
var displayURL: String {
coverUrl.isEmpty ? fileUrl : coverUrl
}
///
private static func hasVideoExtension(_ value: String) -> Bool {
["mp4", "mov", "m4v", "avi"].contains(URL(string: value)?.pathExtension.lowercased() ?? URL(fileURLWithPath: value).pathExtension.lowercased())
}
/// 线
enum CodingKeys: String, CodingKey {
case id
case photogTaskId = "photog_task_id"
case userId = "user_id"
case fileName = "file_name"
case fileType = "file_type"
case fileUrl = "file_url"
case fileSize = "file_size"
case coverUrl = "cover_url"
case duration
case remark
case createdAt = "created_at"
case updatedAt = "updated_at"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
photogTaskId = try container.decodeLossyInt(forKey: .photogTaskId) ?? 0
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
fileName = try container.decodeLossyString(forKey: .fileName)
fileType = try container.decodeLossyInt(forKey: .fileType) ?? 0
fileUrl = try container.decodeLossyString(forKey: .fileUrl)
fileSize = Int64(try container.decodeLossyInt(forKey: .fileSize) ?? 0)
coverUrl = try container.decodeLossyString(forKey: .coverUrl)
duration = try container.decodeLossyInt(forKey: .duration) ?? 0
remark = try container.decodeLossyString(forKey: .remark)
createdAt = try container.decodeLossyString(forKey: .createdAt)
updatedAt = try container.decodeLossyString(forKey: .updatedAt)
}
}
///
struct TaskDetailResponse: Decodable, Identifiable, Hashable {
let id: Int
let name: String
let createdAt: String
let taskStatus: Int
let taskStatusLabel: String
let acceptedAt: String
let editorName: String
let editorPhone: String
let urgentHour: Int
let photogRemark: String
let order: TaskDetailOrder?
let taskResult: [TaskMediaItem]
/// 线
enum CodingKeys: String, CodingKey {
case id
case name
case createdAt = "created_at"
case taskStatus = "task_status"
case taskStatusLabel = "task_status_label"
case acceptedAt = "accepted_at"
case editorName = "editor_name"
case editorPhone = "editor_phone"
case urgentHour = "urgent_hour"
case photogRemark = "photog_remark"
case order
case taskResult = "task_result"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
name = try container.decodeLossyString(forKey: .name)
createdAt = try container.decodeLossyString(forKey: .createdAt)
taskStatus = try container.decodeLossyInt(forKey: .taskStatus) ?? 0
taskStatusLabel = try container.decodeLossyString(forKey: .taskStatusLabel)
acceptedAt = try container.decodeLossyString(forKey: .acceptedAt)
editorName = try container.decodeLossyString(forKey: .editorName)
editorPhone = try container.decodeLossyString(forKey: .editorPhone)
urgentHour = try container.decodeLossyInt(forKey: .urgentHour) ?? 0
photogRemark = try container.decodeLossyString(forKey: .photogRemark)
order = try? container.decode(TaskDetailOrder.self, forKey: .order)
taskResult = (try? container.decode([TaskMediaItem].self, forKey: .taskResult)) ?? []
}
}
///
struct TaskDetailOrder: Decodable, Hashable {
let projectName: String
let orderNumber: String
let payTime: String
let userPhone: String
/// 线
enum CodingKeys: String, CodingKey {
case projectName = "project_name"
case orderNumber = "order_number"
case payTime = "pay_time"
case userPhone = "user_phone"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
projectName = try container.decodeLossyString(forKey: .projectName)
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
payTime = try container.decodeLossyString(forKey: .payTime)
userPhone = try container.decodeLossyString(forKey: .userPhone)
}
}
///
struct AddTaskRequest: Encodable, Equatable {
let scenicId: String
let name: String
let orderNumber: String?
let remark: String
let urgentHour: Int
let cloudFile: [CloudFileItem]
let uploadFile: [UploadFileItem]
/// 线
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"
}
}
///
struct CloudFileItem: Encodable, Equatable {
let fileId: Int
let remark: String
/// 线
enum CodingKeys: String, CodingKey {
case fileId = "file_id"
case remark
}
}
/// OSS
struct UploadFileItem: Encodable, Equatable {
let fileUrl: String
let fileName: String
let remark: String
/// 线
enum CodingKeys: String, CodingKey {
case fileUrl = "file_url"
case fileName = "file_name"
case remark
}
}
private extension KeyedDecodingContainer {
/// String Bool
func decodeLossyString(forKey key: Key) throws -> String {
if let value = try? decodeIfPresent(String.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? "true" : "false"
}
return ""
}
/// IntDouble
func decodeLossyInt(forKey key: Key) throws -> Int? {
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
if let intValue = Int(text) {
return intValue
}
if let doubleValue = Double(text) {
return Int(doubleValue)
}
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return Int(value)
}
return nil
}
}