Migrate project management, schedule management, and photographer invitation flows from placeholders, including project image OSS upload and shared business models. Co-authored-by: Cursor <cursoragent@cursor.com>
167 lines
6.0 KiB
Swift
167 lines
6.0 KiB
Swift
//
|
||
// SharedBusinessModels.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/24.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 可关联订单实体,表示任务、排班等业务表单中可选择的订单。
|
||
struct AvailableOrderResponse: Decodable, Identifiable, Hashable {
|
||
var id: String { orderNumber }
|
||
let projectName: String
|
||
let orderNumber: String
|
||
let orderStatus: Int
|
||
let orderStatusLabel: String
|
||
let payTime: String
|
||
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 userPhone = "user_phone"
|
||
}
|
||
|
||
/// 宽松解码可关联订单字段。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
projectName = try container.decodeSharedLossyString(forKey: .projectName)
|
||
orderNumber = try container.decodeSharedLossyString(forKey: .orderNumber)
|
||
orderStatus = try container.decodeSharedLossyInt(forKey: .orderStatus) ?? 0
|
||
orderStatusLabel = try container.decodeSharedLossyString(forKey: .orderStatusLabel)
|
||
payTime = try container.decodeSharedLossyString(forKey: .payTime)
|
||
userPhone = try container.decodeSharedLossyString(forKey: .userPhone)
|
||
}
|
||
}
|
||
|
||
/// 摄影项目列表项实体,表示项目管理列表和样片上传时可关联的项目。
|
||
struct PhotographerProjectItem: Decodable, Identifiable, Hashable {
|
||
let id: Int
|
||
let type: Int
|
||
let typeName: String
|
||
let status: Int
|
||
let statusName: String
|
||
let name: String
|
||
let coverProject: String
|
||
let coverVideo: String
|
||
let price: String
|
||
let otPrice: String
|
||
let priceDeposit: String
|
||
let attrLabel: [String]
|
||
let label: String
|
||
|
||
/// 创建摄影项目列表项,主要用于详情页摘要和测试替身。
|
||
init(
|
||
id: Int,
|
||
type: Int = 0,
|
||
typeName: String = "",
|
||
status: Int = 0,
|
||
statusName: String = "",
|
||
name: String,
|
||
coverProject: String = "",
|
||
coverVideo: String = "",
|
||
price: String = "",
|
||
otPrice: String = "",
|
||
priceDeposit: String = "",
|
||
attrLabel: [String] = [],
|
||
label: String = ""
|
||
) {
|
||
self.id = id
|
||
self.type = type
|
||
self.typeName = typeName
|
||
self.status = status
|
||
self.statusName = statusName
|
||
self.name = name
|
||
self.coverProject = coverProject
|
||
self.coverVideo = coverVideo
|
||
self.price = price
|
||
self.otPrice = otPrice
|
||
self.priceDeposit = priceDeposit
|
||
self.attrLabel = attrLabel
|
||
self.label = label
|
||
}
|
||
|
||
/// 字段映射,兼容后端下划线命名。
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case type
|
||
case typeName = "type_name"
|
||
case status
|
||
case statusName = "status_name"
|
||
case name
|
||
case coverProject = "cover_project"
|
||
case coverVideo = "cover_video"
|
||
case price
|
||
case otPrice = "ot_price"
|
||
case priceDeposit = "price_deposit"
|
||
case attrLabel = "attr_label"
|
||
case label
|
||
}
|
||
|
||
/// 宽松解码项目列表项。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = try container.decodeSharedLossyInt(forKey: .id) ?? 0
|
||
type = try container.decodeSharedLossyInt(forKey: .type) ?? 0
|
||
typeName = try container.decodeSharedLossyString(forKey: .typeName)
|
||
status = try container.decodeSharedLossyInt(forKey: .status) ?? 0
|
||
statusName = try container.decodeSharedLossyString(forKey: .statusName)
|
||
name = try container.decodeSharedLossyString(forKey: .name)
|
||
coverProject = try container.decodeSharedLossyString(forKey: .coverProject)
|
||
coverVideo = try container.decodeSharedLossyString(forKey: .coverVideo)
|
||
price = try container.decodeSharedLossyString(forKey: .price)
|
||
otPrice = try container.decodeSharedLossyString(forKey: .otPrice)
|
||
priceDeposit = try container.decodeSharedLossyString(forKey: .priceDeposit)
|
||
label = try container.decodeSharedLossyString(forKey: .label)
|
||
if let labels = try? container.decodeIfPresent([String].self, forKey: .attrLabel) {
|
||
attrLabel = labels
|
||
} else if let text = try? container.decodeIfPresent(String.self, forKey: .attrLabel) {
|
||
attrLabel = text
|
||
.components(separatedBy: CharacterSet(charactersIn: ",,"))
|
||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||
.filter { !$0.isEmpty }
|
||
} else {
|
||
attrLabel = []
|
||
}
|
||
}
|
||
}
|
||
|
||
private extension KeyedDecodingContainer {
|
||
/// 将 String、数字和 Bool 宽松解码为字符串。
|
||
func decodeSharedLossyString(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 ? "1" : "0"
|
||
}
|
||
return ""
|
||
}
|
||
|
||
/// 将 String、Double 和 Int 宽松解码为 Int。
|
||
func decodeSharedLossyInt(forKey key: Key) throws -> Int? {
|
||
if let value = try? decodeIfPresent(Int.self, forKey: key) {
|
||
return value
|
||
}
|
||
if let value = try? decodeIfPresent(Double.self, forKey: key) {
|
||
return Int(value)
|
||
}
|
||
if let value = try? decodeIfPresent(String.self, forKey: key) {
|
||
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
return Int(text) ?? Double(text).map(Int.init)
|
||
}
|
||
return nil
|
||
}
|
||
}
|