Lower deployment target to iOS 16, replace @Observable with ObservableObject, add navigation and UI compatibility shims, and include login smoke UI tests. Co-authored-by: Cursor <cursoragent@cursor.com>
622 lines
23 KiB
Swift
622 lines
23 KiB
Swift
//
|
||
// ProjectViewModels.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/24.
|
||
//
|
||
|
||
import Foundation
|
||
import Combine
|
||
|
||
/// 项目表单模式,区分新建和编辑。
|
||
enum ProjectEditorMode: Equatable {
|
||
case create
|
||
case edit(PhotographerProjectDetailResponse)
|
||
}
|
||
|
||
/// 店铺项目表单模式,区分新建和编辑。
|
||
enum StoreProjectEditorMode: Equatable {
|
||
case create
|
||
case edit(PhotographerProjectDetailResponse)
|
||
}
|
||
|
||
/// 项目表单错误实体,表示校验、上传或提交失败。
|
||
enum ProjectEditorError: LocalizedError, Equatable {
|
||
case missingScenic
|
||
case missingUser
|
||
case missingName
|
||
case missingDescription
|
||
case missingPrice
|
||
case missingSpot
|
||
case missingImage
|
||
case missingStore
|
||
|
||
/// 错误文案。
|
||
var errorDescription: String? {
|
||
switch self {
|
||
case .missingScenic: return "当前缺少景区信息"
|
||
case .missingUser: return "当前缺少用户信息"
|
||
case .missingName: return "请输入项目名称"
|
||
case .missingDescription: return "请输入项目简介"
|
||
case .missingPrice: return "请填写有效价格"
|
||
case .missingSpot: return "请至少选择一个打卡点"
|
||
case .missingImage: return "请至少上传项目封面"
|
||
case .missingStore: return "请选择所属门店"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 摄影师项目管理 ViewModel,负责项目列表、分页、详情和删除。
|
||
@MainActor
|
||
final class ProjectManagementViewModel: ObservableObject {
|
||
@Published private(set) var items: [PhotographerProjectItem] = []
|
||
@Published private(set) var loading = false
|
||
@Published private(set) var loadingMore = false
|
||
@Published private(set) var hasMore = false
|
||
@Published private(set) var selectedDetail: PhotographerProjectDetailResponse?
|
||
@Published var searchText = ""
|
||
@Published var errorMessage: String?
|
||
|
||
@Published private var page = 1
|
||
@Published private var total = 0
|
||
private let pageSize = 10
|
||
|
||
/// 重新加载摄影师项目列表。
|
||
func reload(api: any ProjectServing, scenicId: Int?) async {
|
||
guard let scenicId else {
|
||
resetList()
|
||
return
|
||
}
|
||
loading = true
|
||
errorMessage = nil
|
||
defer { loading = false }
|
||
do {
|
||
page = 1
|
||
let response = try await api.projectList(
|
||
scenicId: scenicId,
|
||
name: normalizedSearch,
|
||
page: page,
|
||
pageSize: pageSize
|
||
)
|
||
total = response.total
|
||
items = sorted(response.list)
|
||
hasMore = items.count < total
|
||
page = 2
|
||
} catch {
|
||
resetList()
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
/// 加载下一页摄影师项目。
|
||
func loadMore(api: any ProjectServing, scenicId: Int?) async {
|
||
guard let scenicId, hasMore, !loadingMore else { return }
|
||
loadingMore = true
|
||
defer { loadingMore = false }
|
||
do {
|
||
let response = try await api.projectList(scenicId: scenicId, name: normalizedSearch, page: page, pageSize: pageSize)
|
||
total = response.total
|
||
items = sorted(deduplicated(items + response.list))
|
||
hasMore = items.count < total
|
||
page += 1
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
/// 加载项目详情。
|
||
func loadDetail(id: Int, api: any ProjectServing) async {
|
||
do {
|
||
selectedDetail = try await api.projectDetail(id: id)
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
/// 删除摄影师项目并从本地列表移除。
|
||
func delete(id: Int, api: any ProjectServing) async {
|
||
do {
|
||
try await api.deleteProject(id: id)
|
||
items.removeAll { $0.id == id }
|
||
total = max(total - 1, 0)
|
||
hasMore = items.count < total
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
private var normalizedSearch: String? {
|
||
let value = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
return value.isEmpty ? nil : value
|
||
}
|
||
|
||
private func resetList() {
|
||
items = []
|
||
loading = false
|
||
loadingMore = false
|
||
hasMore = false
|
||
page = 1
|
||
total = 0
|
||
}
|
||
|
||
private func sorted(_ values: [PhotographerProjectItem]) -> [PhotographerProjectItem] {
|
||
values.sorted { lhs, rhs in
|
||
if lhs.status != rhs.status { return lhs.status < rhs.status }
|
||
return lhs.id > rhs.id
|
||
}
|
||
}
|
||
|
||
private func deduplicated(_ values: [PhotographerProjectItem]) -> [PhotographerProjectItem] {
|
||
var result: [Int: PhotographerProjectItem] = [:]
|
||
values.forEach { result[$0.id] = $0 }
|
||
return Array(result.values)
|
||
}
|
||
}
|
||
|
||
/// 项目编辑 ViewModel,负责摄影师项目表单、图片上传和提交。
|
||
@MainActor
|
||
final class ProjectEditorViewModel: ObservableObject {
|
||
@Published var name = ""
|
||
@Published var descriptionText = ""
|
||
@Published var price = ""
|
||
@Published var otPrice = ""
|
||
@Published var deposit = ""
|
||
@Published var attrLabelText = ""
|
||
@Published var materialNum = "1"
|
||
@Published var photoNum = "1"
|
||
@Published var videoNum = "1"
|
||
@Published var selectedSpotIds: Set<Int> = []
|
||
@Published var coverImage: ProjectLocalImage?
|
||
@Published var carouselImages: [ProjectLocalImage] = []
|
||
@Published var existingCoverURL = ""
|
||
@Published var existingCarouselURLs: [String] = []
|
||
@Published private(set) var submitting = false
|
||
@Published private(set) var uploadProgress = 0
|
||
@Published var errorMessage: String?
|
||
|
||
private var mode: ProjectEditorMode
|
||
|
||
/// 初始化摄影师项目表单,并在编辑模式下回填详情。
|
||
init(mode: ProjectEditorMode) {
|
||
self.mode = mode
|
||
if case let .edit(detail) = mode {
|
||
apply(detail)
|
||
}
|
||
}
|
||
|
||
/// 回填编辑详情,保留同一个 ObservableObject 实例。
|
||
func apply(_ detail: PhotographerProjectDetailResponse) {
|
||
mode = .edit(detail)
|
||
name = detail.name
|
||
descriptionText = detail.description
|
||
price = detail.price
|
||
otPrice = detail.otPrice
|
||
deposit = detail.priceDeposit
|
||
attrLabelText = detail.attrLabel.joined(separator: ",")
|
||
materialNum = "\(max(detail.materialNum, 0))"
|
||
photoNum = "\(max(detail.photoNum, 0))"
|
||
videoNum = "\(max(detail.videoNum, 0))"
|
||
selectedSpotIds = Set(detail.scenicList.map(\.id))
|
||
existingCoverURL = detail.coverProject
|
||
existingCarouselURLs = detail.coverCarousel
|
||
coverImage = nil
|
||
carouselImages = []
|
||
}
|
||
|
||
/// 提交摄影师项目,必要时先上传封面和轮播图。
|
||
func submit(
|
||
scenicId: Int?,
|
||
userId: Int?,
|
||
api: any ProjectServing,
|
||
uploadService: any OSSUploadServing
|
||
) async -> Bool {
|
||
guard !submitting else { return false }
|
||
guard let scenicId else { return fail(.missingScenic) }
|
||
guard let userId, userId > 0 else { return fail(.missingUser) }
|
||
let normalizedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let normalizedDescription = descriptionText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
guard !normalizedName.isEmpty else { return fail(.missingName) }
|
||
guard !normalizedDescription.isEmpty else { return fail(.missingDescription) }
|
||
guard Double(price) ?? 0 > 0 else { return fail(.missingPrice) }
|
||
guard !selectedSpotIds.isEmpty else { return fail(.missingSpot) }
|
||
|
||
submitting = true
|
||
errorMessage = nil
|
||
uploadProgress = 0
|
||
defer { submitting = false }
|
||
|
||
do {
|
||
let coverURL = try await resolveCoverURL(scenicId: scenicId, uploadService: uploadService)
|
||
guard !coverURL.isEmpty else { throw ProjectEditorError.missingImage }
|
||
let carouselURLs = try await resolveCarouselURLs(scenicId: scenicId, uploadService: uploadService)
|
||
let request = ProjectCreateRequest(
|
||
id: editID,
|
||
type: 11,
|
||
scenicId: scenicId,
|
||
name: normalizedName,
|
||
price: normalizedMoney(price) ?? "0.00",
|
||
otPrice: normalizedMoney(otPrice),
|
||
coverProject: coverURL,
|
||
coverCarousel: carouselURLs.isEmpty ? nil : carouselURLs,
|
||
description: normalizedDescription,
|
||
attrLabel: normalizedLabels,
|
||
extra: ProjectCreateExtra(
|
||
priceDeposit: normalizedMoney(deposit) ?? "0.00",
|
||
materialNum: max(Int(materialNum) ?? 0, 0),
|
||
photoNum: max(Int(photoNum) ?? 0, 0),
|
||
videoNum: max(Int(videoNum) ?? 0, 0),
|
||
scenicSpotId: selectedSpotIds.sorted(),
|
||
photogUid: [userId]
|
||
)
|
||
)
|
||
if editID == nil {
|
||
try await api.createProject(request)
|
||
} else {
|
||
try await api.editProject(request)
|
||
}
|
||
uploadProgress = 100
|
||
return true
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
return false
|
||
}
|
||
}
|
||
|
||
private var editID: Int? {
|
||
if case let .edit(detail) = mode {
|
||
return detail.id
|
||
}
|
||
return nil
|
||
}
|
||
|
||
private var normalizedLabels: [String]? {
|
||
let labels = attrLabelText
|
||
.components(separatedBy: CharacterSet(charactersIn: ",,"))
|
||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||
.filter { !$0.isEmpty }
|
||
return labels.isEmpty ? nil : labels
|
||
}
|
||
|
||
private func resolveCoverURL(scenicId: Int, uploadService: any OSSUploadServing) async throws -> String {
|
||
if let coverImage {
|
||
return try await uploadService.uploadProjectImage(data: coverImage.data, fileName: coverImage.fileName, scenicId: scenicId) { [weak self] progress in
|
||
self?.uploadProgress = progress
|
||
}
|
||
}
|
||
return existingCoverURL
|
||
}
|
||
|
||
private func resolveCarouselURLs(scenicId: Int, uploadService: any OSSUploadServing) async throws -> [String] {
|
||
var urls = existingCarouselURLs
|
||
for image in carouselImages {
|
||
let url = try await uploadService.uploadProjectImage(data: image.data, fileName: image.fileName, scenicId: scenicId) { [weak self] progress in
|
||
self?.uploadProgress = progress
|
||
}
|
||
urls.append(url)
|
||
}
|
||
return urls
|
||
}
|
||
|
||
private func normalizedMoney(_ value: String) -> String? {
|
||
let amount = Double(value.trimmingCharacters(in: .whitespacesAndNewlines)) ?? 0
|
||
return amount > 0 ? String(format: "%.2f", amount) : nil
|
||
}
|
||
|
||
private func fail(_ error: ProjectEditorError) -> Bool {
|
||
errorMessage = error.localizedDescription
|
||
return false
|
||
}
|
||
}
|
||
|
||
/// 店铺项目管理 ViewModel,负责店铺项目列表、筛选、详情和删除。
|
||
@MainActor
|
||
final class StoreProjectManagementViewModel: ObservableObject {
|
||
@Published private(set) var items: [PhotographerProjectItem] = []
|
||
@Published private(set) var loading = false
|
||
@Published private(set) var selectedDetail: PhotographerProjectDetailResponse?
|
||
@Published var searchText = ""
|
||
@Published var selectedType = 0
|
||
@Published var errorMessage: String?
|
||
|
||
/// 根据本地筛选条件返回展示项目。
|
||
var filteredItems: [PhotographerProjectItem] {
|
||
let keyword = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
return items.filter { item in
|
||
let typeMatched = selectedType == 0 || item.type == selectedType
|
||
let keywordMatched = keyword.isEmpty || item.name.localizedCaseInsensitiveContains(keyword)
|
||
return typeMatched && keywordMatched
|
||
}
|
||
}
|
||
|
||
/// 重新加载店铺项目列表。
|
||
func reload(api: any ProjectServing, userId: String?) async {
|
||
let normalizedUserId = userId?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||
guard !normalizedUserId.isEmpty else {
|
||
items = []
|
||
return
|
||
}
|
||
loading = true
|
||
errorMessage = nil
|
||
defer { loading = false }
|
||
do {
|
||
let response = try await api.storeManagerProjectList(userId: normalizedUserId, page: 1, pageSize: 100)
|
||
items = response.list
|
||
} catch {
|
||
items = []
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
/// 加载店铺项目详情。
|
||
func loadDetail(id: Int, api: any ProjectServing) async {
|
||
do {
|
||
selectedDetail = try await api.storeManagerProjectDetail(id: id)
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
/// 删除店铺项目。
|
||
func delete(id: Int, api: any ProjectServing) async {
|
||
do {
|
||
try await api.storeManagerDeleteProject(id: id)
|
||
items.removeAll { $0.id == id }
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 店铺项目编辑 ViewModel,负责多点位和押金项目表单提交。
|
||
@MainActor
|
||
final class StoreProjectEditorViewModel: ObservableObject {
|
||
@Published var projectType = 19
|
||
@Published var scenicList: [StoreManagerScenicItem] = []
|
||
@Published var storeList: [StoreItem] = []
|
||
@Published var selectedScenicIds: Set<Int> = []
|
||
@Published var selectedStoreId: Int?
|
||
@Published var name = ""
|
||
@Published var descriptionText = ""
|
||
@Published var coverImage: ProjectLocalImage?
|
||
@Published var carouselImages: [ProjectLocalImage] = []
|
||
@Published var existingCoverURL = ""
|
||
@Published var existingCarouselURLs: [String] = []
|
||
@Published var settleSpotNum = "1"
|
||
@Published var projectPrice = ""
|
||
@Published var priceMaterial = ""
|
||
@Published var pricePhoto = ""
|
||
@Published var priceVideo = ""
|
||
@Published var priceDeposit = ""
|
||
@Published var materialNum = "1"
|
||
@Published var photoNum = "1"
|
||
@Published var videoNum = "1"
|
||
@Published private(set) var submitting = false
|
||
@Published var errorMessage: String?
|
||
|
||
private var mode: StoreProjectEditorMode
|
||
|
||
/// 初始化店铺项目表单,并在编辑模式下回填详情。
|
||
init(mode: StoreProjectEditorMode) {
|
||
self.mode = mode
|
||
if case let .edit(detail) = mode {
|
||
apply(detail)
|
||
}
|
||
}
|
||
|
||
/// 回填编辑详情,保留同一个 ObservableObject 实例。
|
||
func apply(_ detail: PhotographerProjectDetailResponse) {
|
||
mode = .edit(detail)
|
||
projectType = detail.type == 4 ? 4 : 19
|
||
name = detail.name
|
||
descriptionText = detail.description
|
||
existingCoverURL = detail.coverProject
|
||
existingCarouselURLs = detail.coverCarousel
|
||
selectedScenicIds = Set(detail.scenicList.map(\.id))
|
||
projectPrice = detail.price
|
||
priceDeposit = detail.priceDeposit
|
||
materialNum = "\(max(detail.materialNum, 0))"
|
||
photoNum = "\(max(detail.photoNum, 0))"
|
||
videoNum = "\(max(detail.videoNum, 0))"
|
||
coverImage = nil
|
||
carouselImages = []
|
||
}
|
||
|
||
/// 加载店铺项目可管理景区。
|
||
func loadScenicList(api: any ProjectServing, userId: Int) async {
|
||
guard scenicList.isEmpty, userId > 0 else { return }
|
||
do {
|
||
scenicList = try await api.storeManagerScenicList(userId: "\(userId)").list
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
/// 加载当前景区下门店列表。
|
||
func loadStoreList(api: any ProjectServing) async {
|
||
guard projectType == 4, let scenicId = selectedScenicIds.first else { return }
|
||
do {
|
||
storeList = try await api.storeAll().list.filter { $0.scenicId == scenicId }
|
||
if selectedStoreId == nil {
|
||
selectedStoreId = storeList.first?.id
|
||
}
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
}
|
||
}
|
||
|
||
/// 切换店铺项目景区选择。
|
||
func toggleScenic(_ id: Int) {
|
||
if projectType == 4 {
|
||
selectedScenicIds = [id]
|
||
} else if selectedScenicIds.contains(id) {
|
||
selectedScenicIds.remove(id)
|
||
} else {
|
||
selectedScenicIds.insert(id)
|
||
}
|
||
}
|
||
|
||
/// 提交店铺项目。
|
||
func submit(userId: Int, api: any ProjectServing, uploadService: any OSSUploadServing) async -> Bool {
|
||
guard !submitting else { return false }
|
||
guard userId > 0 else { return fail(.missingUser) }
|
||
let normalizedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
let normalizedDescription = descriptionText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
guard !normalizedName.isEmpty else { return fail(.missingName) }
|
||
guard !normalizedDescription.isEmpty else { return fail(.missingDescription) }
|
||
guard !selectedScenicIds.isEmpty else { return fail(.missingScenic) }
|
||
guard Double(projectType == 4 ? priceDeposit : projectPrice) ?? 0 > 0 else { return fail(.missingPrice) }
|
||
if projectType == 4, selectedStoreId == nil { return fail(.missingStore) }
|
||
|
||
submitting = true
|
||
errorMessage = nil
|
||
defer { submitting = false }
|
||
|
||
do {
|
||
let scenicIdForUpload = selectedScenicIds.first ?? 0
|
||
let coverURL = try await resolveCoverURL(scenicId: scenicIdForUpload, uploadService: uploadService)
|
||
guard !coverURL.isEmpty else { throw ProjectEditorError.missingImage }
|
||
let carouselURLs = try await resolveCarouselURLs(scenicId: scenicIdForUpload, uploadService: uploadService)
|
||
if projectType == 4 {
|
||
try await submitOfflineProject(
|
||
api: api,
|
||
name: normalizedName,
|
||
description: normalizedDescription,
|
||
coverURL: coverURL,
|
||
carouselURLs: carouselURLs
|
||
)
|
||
} else {
|
||
try await submitMultiPointProject(
|
||
userId: userId,
|
||
api: api,
|
||
name: normalizedName,
|
||
description: normalizedDescription,
|
||
coverURL: coverURL,
|
||
carouselURLs: carouselURLs
|
||
)
|
||
}
|
||
return true
|
||
} catch {
|
||
errorMessage = error.localizedDescription
|
||
return false
|
||
}
|
||
}
|
||
|
||
private var editID: Int? {
|
||
if case let .edit(detail) = mode {
|
||
return detail.id
|
||
}
|
||
return nil
|
||
}
|
||
|
||
private func resolveCoverURL(scenicId: Int, uploadService: any OSSUploadServing) async throws -> String {
|
||
if let coverImage {
|
||
return try await uploadService.uploadProjectImage(data: coverImage.data, fileName: coverImage.fileName, scenicId: scenicId) { _ in }
|
||
}
|
||
return existingCoverURL
|
||
}
|
||
|
||
private func resolveCarouselURLs(scenicId: Int, uploadService: any OSSUploadServing) async throws -> [String] {
|
||
var urls = existingCarouselURLs
|
||
for image in carouselImages {
|
||
let url = try await uploadService.uploadProjectImage(data: image.data, fileName: image.fileName, scenicId: scenicId) { _ in }
|
||
urls.append(url)
|
||
}
|
||
return urls
|
||
}
|
||
|
||
private func submitMultiPointProject(
|
||
userId: Int,
|
||
api: any ProjectServing,
|
||
name: String,
|
||
description: String,
|
||
coverURL: String,
|
||
carouselURLs: [String]
|
||
) async throws {
|
||
if let editID {
|
||
try await api.storeManagerUpdate(
|
||
StoreManagerUpdateRequest(
|
||
id: editID,
|
||
name: name,
|
||
type: 19,
|
||
description: description,
|
||
coverProject: coverURL,
|
||
coverCarousel: carouselURLs,
|
||
projectRule: nil,
|
||
scenicId: selectedScenicIds.sorted(),
|
||
settleSpotNum: max(Int(settleSpotNum) ?? 1, 1),
|
||
price: Double(projectPrice) ?? 0,
|
||
priceMaterial: Double(priceMaterial) ?? 0,
|
||
pricePhoto: Double(pricePhoto) ?? 0,
|
||
priceVideo: Double(priceVideo) ?? 0,
|
||
priceMaterialAll: nil,
|
||
packageList: nil,
|
||
userId: userId,
|
||
singleSpotMaterialNum: max(Int(materialNum) ?? 0, 0),
|
||
singleSpotPhotoNum: max(Int(photoNum) ?? 0, 0),
|
||
singleSpotVideoNum: max(Int(videoNum) ?? 0, 0)
|
||
)
|
||
)
|
||
} else {
|
||
try await api.storeManagerCreate(
|
||
StoreManagerCreateRequest(
|
||
name: name,
|
||
storeId: nil,
|
||
type: 19,
|
||
description: description,
|
||
coverProject: coverURL,
|
||
coverCarousel: carouselURLs,
|
||
projectRule: nil,
|
||
scenicId: selectedScenicIds.sorted(),
|
||
settleSpotNum: max(Int(settleSpotNum) ?? 1, 1),
|
||
price: Double(projectPrice) ?? 0,
|
||
priceMaterial: Double(priceMaterial) ?? 0,
|
||
pricePhoto: Double(pricePhoto) ?? 0,
|
||
priceVideo: Double(priceVideo) ?? 0,
|
||
priceMaterialAll: nil,
|
||
packageList: nil,
|
||
userId: userId,
|
||
singleSpotMaterialNum: max(Int(materialNum) ?? 0, 0),
|
||
singleSpotPhotoNum: max(Int(photoNum) ?? 0, 0),
|
||
singleSpotVideoNum: max(Int(videoNum) ?? 0, 0)
|
||
)
|
||
)
|
||
}
|
||
}
|
||
|
||
private func submitOfflineProject(api: any ProjectServing, name: String, description: String, coverURL: String, carouselURLs: [String]) async throws {
|
||
let scenicId = selectedScenicIds.first ?? 0
|
||
let storeId = selectedStoreId ?? 0
|
||
if let editID {
|
||
try await api.storeManagerOfflineUpdate(
|
||
StoreManagerOfflineUpdateRequest(
|
||
id: editID,
|
||
name: name,
|
||
scenicId: scenicId,
|
||
description: description,
|
||
price: Double(priceDeposit) ?? 0,
|
||
coverProject: coverURL,
|
||
coverCarousel: carouselURLs,
|
||
storeId: storeId
|
||
)
|
||
)
|
||
} else {
|
||
try await api.storeManagerOfflineCreate(
|
||
StoreManagerOfflineCreateRequest(
|
||
name: name,
|
||
scenicId: scenicId,
|
||
storeId: storeId,
|
||
description: description,
|
||
price: Double(priceDeposit) ?? 0,
|
||
coverProject: coverURL,
|
||
coverCarousel: carouselURLs
|
||
)
|
||
)
|
||
}
|
||
}
|
||
|
||
private func fail(_ error: ProjectEditorError) -> Bool {
|
||
errorMessage = error.localizedDescription
|
||
return false
|
||
}
|
||
}
|