feat: 新增相册自动修图与OTG状态预览
支持相册修图配置、模板选择和传输模式选择;上传后自动提交修图并展示状态角标及精修预览。补充接口文档与相关测试。
This commit is contained in:
@@ -5,6 +5,36 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 相册级自动 AI 修图配置,由服务端同步并在每次上传开始时固定快照。
|
||||
struct TravelAlbumAutoRetouchConfiguration: Codable, Sendable, Equatable, Hashable {
|
||||
let enabled: Bool
|
||||
let refinedTemplateId: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case enabled
|
||||
case refinedTemplateId = "refined_template_id"
|
||||
}
|
||||
|
||||
/// 默认关闭自动修图,用于兼容尚未返回配置字段的旧接口。
|
||||
static let disabled = TravelAlbumAutoRetouchConfiguration(
|
||||
enabled: false,
|
||||
refinedTemplateId: nil
|
||||
)
|
||||
|
||||
/// 创建经过规范化的自动修图配置。
|
||||
init(enabled: Bool, refinedTemplateId: Int?) {
|
||||
let validTemplateId = refinedTemplateId.flatMap { $0 > 0 ? $0 : nil }
|
||||
self.enabled = enabled
|
||||
self.refinedTemplateId = enabled ? validTemplateId : nil
|
||||
}
|
||||
|
||||
/// 上传页紧凑设置项的展示文案。
|
||||
var displayTitle: String { enabled ? "AI修图" : "不修图" }
|
||||
|
||||
/// 当前配置是否可以用于提交自动修图任务。
|
||||
var isValid: Bool { !enabled || refinedTemplateId != nil }
|
||||
}
|
||||
|
||||
/// 旅拍相册用户信息,对齐 Android `TravelAlbumUserEntity`。
|
||||
struct TravelAlbumUser: Decodable, Sendable, Equatable, Hashable {
|
||||
let id: Int
|
||||
@@ -33,6 +63,7 @@ struct TravelAlbum: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
||||
let createdAt: String
|
||||
let updatedAt: String
|
||||
let user: TravelAlbumUser?
|
||||
let autoRetouchConfiguration: TravelAlbumAutoRetouchConfiguration
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
@@ -50,6 +81,31 @@ struct TravelAlbum: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
||||
case createdAt = "created_at"
|
||||
case updatedAt = "updated_at"
|
||||
case user
|
||||
case autoRetouchConfiguration = "auto_retouch_config"
|
||||
}
|
||||
|
||||
/// 解码相册详情;旧响应缺少自动修图配置时按关闭状态兼容。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decode(Int.self, forKey: .id)
|
||||
storeUserId = try container.decode(Int.self, forKey: .storeUserId)
|
||||
name = try container.decode(String.self, forKey: .name)
|
||||
type = try container.decode(Int.self, forKey: .type)
|
||||
orderNumber = try container.decode(String.self, forKey: .orderNumber)
|
||||
materialNum = try container.decode(Int.self, forKey: .materialNum)
|
||||
materialPrice = try container.decode(Int.self, forKey: .materialPrice)
|
||||
materialPackagePrice = try container.decode(Int.self, forKey: .materialPackagePrice)
|
||||
photoPrice = try container.decode(Int.self, forKey: .photoPrice)
|
||||
coverUrl = try container.decode(String.self, forKey: .coverUrl)
|
||||
userId = try container.decode(Int.self, forKey: .userId)
|
||||
status = try container.decode(Int.self, forKey: .status)
|
||||
createdAt = try container.decode(String.self, forKey: .createdAt)
|
||||
updatedAt = try container.decode(String.self, forKey: .updatedAt)
|
||||
user = try container.decodeIfPresent(TravelAlbumUser.self, forKey: .user)
|
||||
autoRetouchConfiguration = try container.decodeIfPresent(
|
||||
TravelAlbumAutoRetouchConfiguration.self,
|
||||
forKey: .autoRetouchConfiguration
|
||||
) ?? .disabled
|
||||
}
|
||||
|
||||
init(
|
||||
@@ -67,7 +123,8 @@ struct TravelAlbum: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
||||
status: Int = 0,
|
||||
createdAt: String = "",
|
||||
updatedAt: String = "",
|
||||
user: TravelAlbumUser? = nil
|
||||
user: TravelAlbumUser? = nil,
|
||||
autoRetouchConfiguration: TravelAlbumAutoRetouchConfiguration = .disabled
|
||||
) {
|
||||
self.id = id
|
||||
self.storeUserId = storeUserId
|
||||
@@ -84,6 +141,7 @@ struct TravelAlbum: Decodable, Sendable, Equatable, Hashable, Identifiable {
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
self.user = user
|
||||
self.autoRetouchConfiguration = autoRetouchConfiguration
|
||||
}
|
||||
|
||||
/// 展示用手机号。
|
||||
@@ -287,6 +345,7 @@ struct TravelAlbumCreateRequest: Encodable, Sendable, Equatable {
|
||||
let materialPrice: Double?
|
||||
let materialPackagePrice: Double?
|
||||
let photoPrice: Double?
|
||||
let autoRetouchConfiguration: TravelAlbumAutoRetouchConfiguration
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
@@ -296,6 +355,67 @@ struct TravelAlbumCreateRequest: Encodable, Sendable, Equatable {
|
||||
case materialPrice = "material_price"
|
||||
case materialPackagePrice = "material_package_price"
|
||||
case photoPrice = "photo_price"
|
||||
case autoRetouchConfiguration = "auto_retouch_config"
|
||||
}
|
||||
|
||||
private enum AutoRetouchConfigurationCodingKeys: String, CodingKey {
|
||||
case enabled
|
||||
case refinedTemplateId = "refined_template_id"
|
||||
}
|
||||
|
||||
/// 创建相册请求;自动修图配置默认关闭以兼容现有调用方。
|
||||
init(
|
||||
name: String,
|
||||
type: Int,
|
||||
orderNumber: String?,
|
||||
materialNum: Int?,
|
||||
materialPrice: Double?,
|
||||
materialPackagePrice: Double?,
|
||||
photoPrice: Double?,
|
||||
autoRetouchConfiguration: TravelAlbumAutoRetouchConfiguration = .disabled
|
||||
) {
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.orderNumber = orderNumber
|
||||
self.materialNum = materialNum
|
||||
self.materialPrice = materialPrice
|
||||
self.materialPackagePrice = materialPackagePrice
|
||||
self.photoPrice = photoPrice
|
||||
self.autoRetouchConfiguration = autoRetouchConfiguration
|
||||
}
|
||||
|
||||
/// 编码创建参数;相册配置版本由服务端生成,不随创建请求上送。
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(type, forKey: .type)
|
||||
try container.encodeIfPresent(orderNumber, forKey: .orderNumber)
|
||||
try container.encodeIfPresent(materialNum, forKey: .materialNum)
|
||||
try container.encodeIfPresent(materialPrice, forKey: .materialPrice)
|
||||
try container.encodeIfPresent(materialPackagePrice, forKey: .materialPackagePrice)
|
||||
try container.encodeIfPresent(photoPrice, forKey: .photoPrice)
|
||||
var configuration = container.nestedContainer(
|
||||
keyedBy: AutoRetouchConfigurationCodingKeys.self,
|
||||
forKey: .autoRetouchConfiguration
|
||||
)
|
||||
try configuration.encode(autoRetouchConfiguration.enabled, forKey: .enabled)
|
||||
try configuration.encodeIfPresent(
|
||||
autoRetouchConfiguration.refinedTemplateId,
|
||||
forKey: .refinedTemplateId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新相册自动修图配置的请求参数。
|
||||
struct TravelAlbumAutoRetouchConfigurationRequest: Encodable, Sendable, Equatable {
|
||||
let userEquityTravelId: Int
|
||||
let enabled: Bool
|
||||
let refinedTemplateId: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case userEquityTravelId = "user_equity_travel_id"
|
||||
case enabled
|
||||
case refinedTemplateId = "refined_template_id"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +457,28 @@ struct TravelAlbumListResponse<Item: Decodable & Sendable & Equatable>: Decodabl
|
||||
/// 旅拍相册创建响应。
|
||||
struct TravelAlbumCreateResponse: Decodable, Sendable, Equatable {
|
||||
let id: Int
|
||||
let autoRetouchConfiguration: TravelAlbumAutoRetouchConfiguration
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case autoRetouchConfiguration = "auto_retouch_config"
|
||||
}
|
||||
|
||||
/// 创建相册响应;后端暂未回传配置时按关闭状态兼容。
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
id = try container.decode(Int.self, forKey: .id)
|
||||
autoRetouchConfiguration = try container.decodeIfPresent(
|
||||
TravelAlbumAutoRetouchConfiguration.self,
|
||||
forKey: .autoRetouchConfiguration
|
||||
) ?? .disabled
|
||||
}
|
||||
|
||||
/// 创建相册响应测试数据。
|
||||
init(id: Int, autoRetouchConfiguration: TravelAlbumAutoRetouchConfiguration = .disabled) {
|
||||
self.id = id
|
||||
self.autoRetouchConfiguration = autoRetouchConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
/// 旅拍相册小程序码响应。
|
||||
@@ -496,6 +638,7 @@ struct TravelAlbumAIRetouchRequest: Encodable, Sendable, Equatable {
|
||||
let refinedTemplateId: Int
|
||||
let atmosphereTemplateId: Int?
|
||||
let coverTemplateId: Int?
|
||||
let clientRequestId: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case userEquityTravelId = "user_equity_travel_id"
|
||||
@@ -503,6 +646,24 @@ struct TravelAlbumAIRetouchRequest: Encodable, Sendable, Equatable {
|
||||
case refinedTemplateId = "refined_template_id"
|
||||
case atmosphereTemplateId = "atmosphere_template_id"
|
||||
case coverTemplateId = "cover_template_id"
|
||||
case clientRequestId = "client_request_id"
|
||||
}
|
||||
|
||||
/// 创建首次 AI 修图请求;手动修图可不传幂等标识,自动修图必须传稳定标识。
|
||||
init(
|
||||
userEquityTravelId: Int,
|
||||
materialIds: [Int],
|
||||
refinedTemplateId: Int,
|
||||
atmosphereTemplateId: Int?,
|
||||
coverTemplateId: Int?,
|
||||
clientRequestId: String? = nil
|
||||
) {
|
||||
self.userEquityTravelId = userEquityTravelId
|
||||
self.materialIds = materialIds
|
||||
self.refinedTemplateId = refinedTemplateId
|
||||
self.atmosphereTemplateId = atmosphereTemplateId
|
||||
self.coverTemplateId = coverTemplateId
|
||||
self.clientRequestId = clientRequestId
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user