新增订单长尾流程,并迁移排队、消息、结算与审核模块
将定金订单、历史拍摄与多行程上传接入 Orders,并以真实页面替换首页排队管理、消息中心、景区结算与提现审核占位入口。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -197,10 +197,101 @@ struct WriteOffRequest: Encodable {
|
||||
}
|
||||
}
|
||||
|
||||
/// 押金订单列表实体,表示首页押金订单卡片的基础信息和操作状态。
|
||||
struct DepositOrderListItem: Decodable, Identifiable, Equatable, Hashable {
|
||||
let id: Int
|
||||
let orderNumber: String
|
||||
let userPhone: String
|
||||
let amount: String
|
||||
let status: Int
|
||||
let statusName: String
|
||||
let createdAt: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case orderNumber = "order_number"
|
||||
case userPhone = "user_phone"
|
||||
case amount
|
||||
case status
|
||||
case statusName = "status_name"
|
||||
case createdAt = "created_at"
|
||||
}
|
||||
|
||||
/// 宽松解码押金订单字段,兼容后端数字和字符串混用。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decodeLossyInt(forKey: .id) ?? 0
|
||||
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
|
||||
userPhone = try container.decodeLossyString(forKey: .userPhone)
|
||||
amount = try container.decodeLossyString(forKey: .amount)
|
||||
status = try container.decodeLossyInt(forKey: .status) ?? 0
|
||||
statusName = try container.decodeLossyString(forKey: .statusName)
|
||||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||||
}
|
||||
}
|
||||
|
||||
/// 押金订单核销请求实体。
|
||||
struct DepositOrderWriteOffRequest: Encodable, Equatable {
|
||||
let orderNumber: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case orderNumber = "order_number"
|
||||
}
|
||||
}
|
||||
|
||||
/// 押金订单退款请求实体。
|
||||
struct DepositOrderRefundRequest: Encodable, Equatable {
|
||||
let orderNumber: String
|
||||
let refundReason: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case orderNumber = "order_number"
|
||||
case refundReason = "refund_reason"
|
||||
}
|
||||
}
|
||||
|
||||
/// 普通订单退款模式,表示全额退款或部分退款。
|
||||
enum OrderRefundMode: Int, CaseIterable, Identifiable, Hashable {
|
||||
case full = 1
|
||||
case partial = 3
|
||||
|
||||
var id: Int { rawValue }
|
||||
|
||||
/// 退款模式展示文案。
|
||||
var title: String {
|
||||
switch self {
|
||||
case .full:
|
||||
return "全额退款"
|
||||
case .partial:
|
||||
return "部分退款"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 普通订单退款请求实体。
|
||||
struct OrderRefundRequest: Encodable, Equatable {
|
||||
let orderNumber: String
|
||||
let refundType: Int
|
||||
let refundAmount: String
|
||||
let refundReason: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case orderNumber = "order_number"
|
||||
case refundType = "refund_type"
|
||||
case refundAmount = "refund_amount"
|
||||
case refundReason = "refund_reason"
|
||||
}
|
||||
}
|
||||
|
||||
/// 订单详情路由实体,表示订单模块内部可 push 的二级页面。
|
||||
enum OrdersRoute: Hashable {
|
||||
case storeDetail(OrderEntity)
|
||||
case writeOffDetail(WriteOffOrderItem)
|
||||
case depositDetail(orderNumber: String)
|
||||
case depositShootingInfo(orderNumber: String, scenicSpotId: Int, photogUid: Int)
|
||||
case historicalShooting(orderNumber: String)
|
||||
case multiTravelTaskUpload(orderNumber: String)
|
||||
case orderTrailer(orderNumber: String, title: String)
|
||||
}
|
||||
|
||||
/// 订单详情接口响应实体,承载门店订单详情页的完整展示数据。
|
||||
@ -346,6 +437,226 @@ struct StoreOrderShootingListItem: Decodable, Identifiable, Equatable, Hashable
|
||||
}
|
||||
}
|
||||
|
||||
/// 订单拍摄信息响应实体,表示某个打卡点的评价、底片和成片。
|
||||
struct StoreOrderShootingDetailResponse: Decodable, Equatable, Hashable {
|
||||
let scenicSpotId: Int
|
||||
let scenicSpotName: String
|
||||
let orderType: Int
|
||||
let orderTypeName: String
|
||||
let orderComment: StoreOrderComment?
|
||||
let materialList: [OrderMediaFile]
|
||||
let completeList: [OrderMediaFile]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case scenicSpotId = "scenic_spot_id"
|
||||
case scenicSpotName = "scenic_spot_name"
|
||||
case orderType = "order_type"
|
||||
case orderTypeName = "order_type_name"
|
||||
case orderComment = "order_comment"
|
||||
case materialList = "material_list"
|
||||
case completeList = "complete_list"
|
||||
}
|
||||
|
||||
/// 宽松解码拍摄信息详情。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
|
||||
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
|
||||
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
|
||||
orderTypeName = try container.decodeLossyString(forKey: .orderTypeName)
|
||||
orderComment = try container.decodeIfPresent(StoreOrderComment.self, forKey: .orderComment)
|
||||
materialList = try container.decodeIfPresent([OrderMediaFile].self, forKey: .materialList) ?? []
|
||||
completeList = try container.decodeIfPresent([OrderMediaFile].self, forKey: .completeList) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
/// 订单评价实体,表示拍摄、修图、景区、服务和相机评分。
|
||||
struct StoreOrderComment: Decodable, Equatable, Hashable {
|
||||
let starShooting: Int
|
||||
let starRetouching: Int
|
||||
let starScenery: Int
|
||||
let starService: Int
|
||||
let starCamera: Int
|
||||
let content: String
|
||||
let createdAt: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case starShooting = "star_shooting"
|
||||
case starRetouching = "star_retouching"
|
||||
case starScenery = "star_scenery"
|
||||
case starService = "star_service"
|
||||
case starCamera = "star_camera"
|
||||
case content
|
||||
case createdAt = "created_at"
|
||||
}
|
||||
|
||||
/// 宽松解码评价字段。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
starShooting = try container.decodeLossyInt(forKey: .starShooting) ?? 0
|
||||
starRetouching = try container.decodeLossyInt(forKey: .starRetouching) ?? 0
|
||||
starScenery = try container.decodeLossyInt(forKey: .starScenery) ?? 0
|
||||
starService = try container.decodeLossyInt(forKey: .starService) ?? 0
|
||||
starCamera = try container.decodeLossyInt(forKey: .starCamera) ?? 0
|
||||
content = try container.decodeLossyString(forKey: .content)
|
||||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||||
}
|
||||
}
|
||||
|
||||
/// 订单媒体文件实体,表示底片、成片或历史拍摄媒体。
|
||||
struct OrderMediaFile: Decodable, Identifiable, Equatable, Hashable {
|
||||
var id: String { "\(fileUrl)-\(fileName)-\(uploadTime)" }
|
||||
|
||||
let fileName: String
|
||||
let fileUrl: String
|
||||
let fileType: Int
|
||||
let fileSize: Int64
|
||||
let coverUrl: String
|
||||
let uploadTime: String
|
||||
|
||||
var isVideo: Bool {
|
||||
fileType == 1
|
||||
}
|
||||
|
||||
var previewURLString: String {
|
||||
coverUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? fileUrl : coverUrl
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case fileName = "file_name"
|
||||
case fileUrl = "file_url"
|
||||
case fileType = "file_type"
|
||||
case fileSize = "file_size"
|
||||
case coverUrl = "cover_url"
|
||||
case uploadTime = "upload_time"
|
||||
}
|
||||
|
||||
/// 宽松解码媒体文件字段。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
fileName = try container.decodeLossyString(forKey: .fileName)
|
||||
fileUrl = try container.decodeLossyString(forKey: .fileUrl)
|
||||
fileType = try container.decodeLossyInt(forKey: .fileType) ?? 0
|
||||
fileSize = Int64(try container.decodeLossyInt(forKey: .fileSize) ?? 0)
|
||||
coverUrl = try container.decodeLossyString(forKey: .coverUrl)
|
||||
uploadTime = try container.decodeLossyString(forKey: .uploadTime)
|
||||
}
|
||||
}
|
||||
|
||||
/// 历史拍摄响应实体,表示订单多点位历史拍摄媒体。
|
||||
struct MultiTravelShootHistoryResponse: Decodable, Equatable, Hashable {
|
||||
let projectName: String
|
||||
let projectType: Int
|
||||
let projectTypeName: String
|
||||
let photogSpotList: [PhotogSpotItem]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case projectName = "project_name"
|
||||
case projectType = "project_type"
|
||||
case projectTypeName = "project_type_name"
|
||||
case photogSpotList = "photog_spot_list"
|
||||
}
|
||||
|
||||
/// 宽松解码历史拍摄响应。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
projectName = try container.decodeLossyString(forKey: .projectName)
|
||||
projectType = try container.decodeLossyInt(forKey: .projectType) ?? 0
|
||||
projectTypeName = try container.decodeLossyString(forKey: .projectTypeName)
|
||||
photogSpotList = try container.decodeIfPresent([PhotogSpotItem].self, forKey: .photogSpotList) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
/// 历史拍摄打卡点实体,表示某个摄影师在打卡点上传过的媒体。
|
||||
struct PhotogSpotItem: Decodable, Identifiable, Equatable, Hashable {
|
||||
var id: String { "\(scenicSpotId)-\(photogUid)" }
|
||||
|
||||
let scenicSpotId: Int
|
||||
let photogUid: Int
|
||||
let scenicSpotName: String
|
||||
let photogNickname: String
|
||||
let photogName: String
|
||||
let files: [OrderMediaFile]
|
||||
|
||||
var photographerDisplayName: String {
|
||||
let nickname = photogNickname.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return nickname.isEmpty ? photogName : nickname
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case scenicSpotId = "scenic_spot_id"
|
||||
case photogUid = "photog_uid"
|
||||
case scenicSpotName = "scenic_spot_name"
|
||||
case photogNickname = "photog_nickname"
|
||||
case photogName = "photog_name"
|
||||
case files
|
||||
}
|
||||
|
||||
/// 宽松解码历史拍摄打卡点。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
|
||||
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
|
||||
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
|
||||
photogNickname = try container.decodeLossyString(forKey: .photogNickname)
|
||||
photogName = try container.decodeLossyString(forKey: .photogName)
|
||||
files = try container.decodeIfPresent([OrderMediaFile].self, forKey: .files) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
/// 多点旅拍已核销打卡点实体,用于任务上传时选择目标打卡点。
|
||||
struct MultiTravelVerifiedScenicSpotItem: Decodable, Identifiable, Equatable, 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.decodeLossyInt(forKey: .id) ?? 0
|
||||
name = try container.decodeLossyString(forKey: .name)
|
||||
}
|
||||
}
|
||||
|
||||
/// 多点旅拍任务素材提交请求实体。
|
||||
struct MultiTravelUploadMaterialRequest: Encodable, Equatable {
|
||||
let orderNumber: String
|
||||
let scenicSpotId: Int
|
||||
let cloudFile: [MultiTravelCloudFileItem]
|
||||
let uploadFile: [MultiTravelUploadFileItem]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case orderNumber = "order_number"
|
||||
case scenicSpotId = "scenic_spot_id"
|
||||
case cloudFile = "cloud_file"
|
||||
case uploadFile = "upload_file"
|
||||
}
|
||||
}
|
||||
|
||||
/// 多点旅拍云盘素材提交项。
|
||||
struct MultiTravelCloudFileItem: Encodable, Equatable {
|
||||
let fileId: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case fileId = "file_id"
|
||||
}
|
||||
}
|
||||
|
||||
/// 多点旅拍本地上传素材提交项。
|
||||
struct MultiTravelUploadFileItem: Encodable, Equatable {
|
||||
let fileName: String
|
||||
let fileUrl: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case fileName = "file_name"
|
||||
case fileUrl = "file_url"
|
||||
}
|
||||
}
|
||||
|
||||
/// 订单详情展示适配实体,统一列表摘要和详情接口响应的读取方式。
|
||||
struct StoreOrderDetailDisplay: Equatable {
|
||||
let orderNumber: String
|
||||
|
||||
Reference in New Issue
Block a user