新增项目、排期与邀请模块,并接入首页路由

将项目管理、排期管理与摄影师邀请流程从占位页迁移为完整实现,包含项目图片 OSS 上传与共享业务模型。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-24 15:57:15 +08:00
parent abcac9bfdf
commit 311a70d610
40 changed files with 4496 additions and 108 deletions

View File

@ -0,0 +1,166 @@
//
// 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 ""
}
/// StringDouble 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
}
}