Files
suixinkan_ios_new/suixinkan/Features/Projects/Models/ProjectModels.swift
汉秋 fb8430889b Add Projects, Schedule, and Invite modules with home routing.
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>
2026-06-24 15:57:15 +08:00

441 lines
15 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.

//
// ProjectModels.swift
// suixinkan
//
// Created by Codex on 2026/6/24.
//
import Foundation
///
struct PhotographerProjectDetailResponse: Decodable, Identifiable, Hashable {
let id: Int
let scenicId: Int
let cover: String
let coverProject: String
let coverCarousel: [String]
let name: String
let type: Int
let typeName: String
let status: Int
let statusName: String
let price: String
let otPrice: String
let priceDeposit: String
let description: String
let attrLabel: [String]
let projectContent: String
let sold: Int
let unitName: String
let materialNum: Int
let photoNum: Int
let videoNum: Int
let createdAt: String
let updatedAt: String
let creatorName: String
let creatorPhone: String
let creatorRoleName: String
let auditRemark: String
let photogList: [ProjectPhotographerBrief]
let scenicList: [ProjectScenicSpotBrief]
/// 线
enum CodingKeys: String, CodingKey {
case id
case scenicId = "scenic_id"
case cover
case coverProject = "cover_project"
case coverCarousel = "cover_carousel"
case name
case type
case typeName = "type_name"
case status
case statusName = "status_name"
case price
case otPrice = "ot_price"
case priceDeposit = "price_deposit"
case description
case attrLabel = "attr_label"
case projectContent = "project_content"
case sold
case unitName = "unit_name"
case materialNum = "material_num"
case photoNum = "photo_num"
case videoNum = "video_num"
case createdAt = "created_at"
case updatedAt = "updated_at"
case creatorName = "creator_name"
case creatorPhone = "creator_phone"
case creatorRoleName = "creator_role_name"
case auditRemark = "audit_remark"
case photogList = "photog_list"
case scenicList = "scenic_list"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeProjectLossyInt(forKey: .id) ?? 0
scenicId = try container.decodeProjectLossyInt(forKey: .scenicId) ?? 0
cover = try container.decodeProjectLossyString(forKey: .cover)
coverProject = try container.decodeProjectLossyString(forKey: .coverProject)
coverCarousel = try container.decodeProjectLossyStringArray(forKey: .coverCarousel)
name = try container.decodeProjectLossyString(forKey: .name)
type = try container.decodeProjectLossyInt(forKey: .type) ?? 0
typeName = try container.decodeProjectLossyString(forKey: .typeName)
status = try container.decodeProjectLossyInt(forKey: .status) ?? 0
statusName = try container.decodeProjectLossyString(forKey: .statusName)
price = try container.decodeProjectLossyString(forKey: .price)
otPrice = try container.decodeProjectLossyString(forKey: .otPrice)
priceDeposit = try container.decodeProjectLossyString(forKey: .priceDeposit)
description = try container.decodeProjectLossyString(forKey: .description)
attrLabel = try container.decodeProjectLossyStringArray(forKey: .attrLabel)
projectContent = try container.decodeProjectLossyString(forKey: .projectContent)
sold = try container.decodeProjectLossyInt(forKey: .sold) ?? 0
unitName = try container.decodeProjectLossyString(forKey: .unitName)
materialNum = try container.decodeProjectLossyInt(forKey: .materialNum) ?? 0
photoNum = try container.decodeProjectLossyInt(forKey: .photoNum) ?? 0
videoNum = try container.decodeProjectLossyInt(forKey: .videoNum) ?? 0
createdAt = try container.decodeProjectLossyString(forKey: .createdAt)
updatedAt = try container.decodeProjectLossyString(forKey: .updatedAt)
creatorName = try container.decodeProjectLossyString(forKey: .creatorName)
creatorPhone = try container.decodeProjectLossyString(forKey: .creatorPhone)
creatorRoleName = try container.decodeProjectLossyString(forKey: .creatorRoleName)
auditRemark = try container.decodeProjectLossyString(forKey: .auditRemark)
photogList = (try? container.decode([ProjectPhotographerBrief].self, forKey: .photogList)) ?? []
scenicList = (try? container.decode([ProjectScenicSpotBrief].self, forKey: .scenicList)) ?? []
}
}
///
struct ProjectPhotographerBrief: Decodable, Identifiable, Hashable {
let id: Int
let photogUid: Int
let nickname: String
let avatar: String
let orderNum: Int
let completedOrderCount: Int
/// 线
enum CodingKeys: String, CodingKey {
case id
case photogUid = "photog_uid"
case nickname
case avatar
case orderNum = "order_num"
case completedOrderCount = "completed_order_count"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeProjectLossyInt(forKey: .id) ?? 0
photogUid = try container.decodeProjectLossyInt(forKey: .photogUid) ?? 0
nickname = try container.decodeProjectLossyString(forKey: .nickname)
avatar = try container.decodeProjectLossyString(forKey: .avatar)
orderNum = try container.decodeProjectLossyInt(forKey: .orderNum) ?? 0
completedOrderCount = try container.decodeProjectLossyInt(forKey: .completedOrderCount) ?? 0
}
}
///
struct ProjectScenicSpotBrief: Decodable, Identifiable, Hashable {
let id: Int
let name: String
///
enum CodingKeys: String, CodingKey {
case id
case name
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeProjectLossyInt(forKey: .id) ?? 0
name = try container.decodeProjectLossyString(forKey: .name)
}
}
///
struct ProjectCreateRequest: Encodable, Equatable {
let id: Int?
let type: Int
let scenicId: Int
let name: String
let price: String
let otPrice: String?
let coverProject: String
let coverCarousel: [String]?
let description: String
let attrLabel: [String]?
let extra: ProjectCreateExtra
let allowNFC: Int = 1
/// 线
enum CodingKeys: String, CodingKey {
case id
case type
case scenicId = "scenic_id"
case name
case price
case otPrice = "ot_price"
case coverProject = "cover_project"
case coverCarousel = "cover_carousel"
case description
case attrLabel = "attr_label"
case extra
case allowNFC = "allow_nfc"
}
}
/// /
struct ProjectCreateExtra: Encodable, Equatable {
let priceDeposit: String
let materialNum: Int
let photoNum: Int
let videoNum: Int
let scenicSpotId: [Int]
let photogUid: [Int]
/// 线
enum CodingKeys: String, CodingKey {
case priceDeposit = "price_deposit"
case materialNum = "material_num"
case photoNum = "photo_num"
case videoNum = "video_num"
case scenicSpotId = "scenic_spot_id"
case photogUid = "photog_uid"
}
}
///
struct ProjectDeleteRequest: Encodable, Equatable {
let id: Int
}
///
struct StoreManagerScenicItem: Decodable, Identifiable, Hashable {
let id: Int
let name: String
///
enum CodingKeys: String, CodingKey {
case id
case name
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeProjectLossyInt(forKey: .id) ?? 0
name = try container.decodeProjectLossyString(forKey: .name)
}
}
///
struct StoreManagerPackageItem: Codable, Hashable {
let materialNum: Int
let price: String
/// 线
enum CodingKeys: String, CodingKey {
case materialNum = "material_num"
case price
}
}
///
struct StoreManagerCreateRequest: Encodable, Equatable {
let name: String
let storeId: Int?
let type: Int
let description: String
let coverProject: String?
let coverCarousel: [String]?
let projectRule: String?
let scenicId: [Int]
let settleSpotNum: Int
let price: Double
let priceMaterial: Double
let pricePhoto: Double
let priceVideo: Double
let priceMaterialAll: Double?
let packageList: [StoreManagerPackageItem]?
let userId: Int
let singleSpotMaterialNum: Int
let singleSpotPhotoNum: Int
let singleSpotVideoNum: Int
/// 线
enum CodingKeys: String, CodingKey {
case name
case storeId = "store_id"
case type
case description
case coverProject = "cover_project"
case coverCarousel = "cover_carousel"
case projectRule = "project_rule"
case scenicId = "scenic_id"
case settleSpotNum = "settle_spot_num"
case price
case priceMaterial = "price_material"
case pricePhoto = "price_photo"
case priceVideo = "price_video"
case priceMaterialAll = "price_material_all"
case packageList = "package"
case userId = "user_id"
case singleSpotMaterialNum = "single_spot_material_num"
case singleSpotPhotoNum = "single_spot_photo_num"
case singleSpotVideoNum = "single_spot_video_num"
}
}
///
struct StoreManagerUpdateRequest: Encodable, Equatable {
let id: Int
let name: String
let type: Int
let description: String
let coverProject: String?
let coverCarousel: [String]?
let projectRule: String?
let scenicId: [Int]
let settleSpotNum: Int
let price: Double
let priceMaterial: Double
let pricePhoto: Double
let priceVideo: Double
let priceMaterialAll: Double?
let packageList: [StoreManagerPackageItem]?
let userId: Int
let singleSpotMaterialNum: Int
let singleSpotPhotoNum: Int
let singleSpotVideoNum: Int
/// 线
enum CodingKeys: String, CodingKey {
case id
case name
case type
case description
case coverProject = "cover_project"
case coverCarousel = "cover_carousel"
case projectRule = "project_rule"
case scenicId = "scenic_id"
case settleSpotNum = "settle_spot_num"
case price
case priceMaterial = "price_material"
case pricePhoto = "price_photo"
case priceVideo = "price_video"
case priceMaterialAll = "price_material_all"
case packageList = "package"
case userId = "user_id"
case singleSpotMaterialNum = "single_spot_material_num"
case singleSpotPhotoNum = "single_spot_photo_num"
case singleSpotVideoNum = "single_spot_video_num"
}
}
///
struct StoreManagerOfflineCreateRequest: Encodable, Equatable {
let name: String
let scenicId: Int
let storeId: Int
let description: String
let price: Double
let coverProject: String
let coverCarousel: [String]
/// 线
enum CodingKeys: String, CodingKey {
case name
case scenicId = "scenic_id"
case storeId = "store_id"
case description
case price
case coverProject = "cover_project"
case coverCarousel = "cover_carousel"
}
}
///
struct StoreManagerOfflineUpdateRequest: Encodable, Equatable {
let id: Int
let name: String
let scenicId: Int
let description: String
let price: Double
let coverProject: String?
let coverCarousel: [String]?
let storeId: Int?
/// 线
enum CodingKeys: String, CodingKey {
case id
case name
case scenicId = "scenic_id"
case description
case price
case coverProject = "cover_project"
case coverCarousel = "cover_carousel"
case storeId = "store_id"
}
}
/// PhotosPicker
struct ProjectLocalImage: Identifiable, Equatable {
let id = UUID()
let data: Data
let fileName: String
let previewURL: String?
}
private extension KeyedDecodingContainer {
/// String Bool
func decodeProjectLossyString(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 decodeProjectLossyInt(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
}
///
func decodeProjectLossyStringArray(forKey key: Key) throws -> [String] {
if let values = try? decodeIfPresent([String].self, forKey: key) {
return values
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
return value
.components(separatedBy: CharacterSet(charactersIn: ",\n"))
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
}
return []
}
}