fix: 优化相册素材状态与删除保护
This commit is contained in:
@@ -224,7 +224,7 @@ struct TravelAlbumMaterial: Decodable, Sendable, Equatable, Hashable, Identifiab
|
||||
}
|
||||
}
|
||||
|
||||
/// 相册素材网格角标类别,用于稳定映射文案优先级和语义颜色。
|
||||
/// 相册素材网格角标类别,用于稳定映射独立业务状态和语义颜色。
|
||||
enum TravelAlbumMaterialBadgeKind: Sendable, Equatable {
|
||||
case purchased
|
||||
case pending
|
||||
@@ -241,14 +241,15 @@ struct TravelAlbumMaterialBadgePresentation: Sendable, Equatable {
|
||||
}
|
||||
|
||||
extension TravelAlbumMaterial {
|
||||
/// 按 AI 修图状态和购买状态生成网格角标;返回 nil 时隐藏角标。
|
||||
var badgePresentation: TravelAlbumMaterialBadgePresentation? {
|
||||
if aiRetouchStatus == 0 {
|
||||
return isPurchased
|
||||
? TravelAlbumMaterialBadgePresentation(kind: .purchased, text: "已购")
|
||||
: nil
|
||||
}
|
||||
/// 生成购买状态角标;购买状态与修图状态互不覆盖。
|
||||
var purchaseBadgePresentation: TravelAlbumMaterialBadgePresentation? {
|
||||
isPurchased
|
||||
? TravelAlbumMaterialBadgePresentation(kind: .purchased, text: "已购")
|
||||
: nil
|
||||
}
|
||||
|
||||
/// 生成 AI 修图状态角标;未进入修图流程时返回 nil。
|
||||
var aiRetouchBadgePresentation: TravelAlbumMaterialBadgePresentation? {
|
||||
let statusName = aiRetouchStatusName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
switch aiRetouchStatus {
|
||||
case 1:
|
||||
|
||||
@@ -48,6 +48,7 @@ final class TravelAlbumDetailViewModel {
|
||||
private(set) var isRefreshing = false
|
||||
private(set) var isSelectionMode = false
|
||||
private(set) var selectedMaterialIds: Set<Int> = []
|
||||
private var selectedMaterialPurchaseStates: [Int: Bool] = [:]
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
@@ -142,12 +143,29 @@ final class TravelAlbumDetailViewModel {
|
||||
selectedTab == .all ? allPhotoCount : purchasedPhotoCount
|
||||
}
|
||||
|
||||
/// 当前选中素材中的已购买数量。
|
||||
var selectedPurchasedMaterialCount: Int {
|
||||
selectedMaterialIds.count - selectedDeletableMaterialIds.count
|
||||
}
|
||||
|
||||
/// 删除确认弹窗文案;选中已购素材时明确说明保护规则与实际删除数量。
|
||||
var deleteSelectedConfirmationMessage: String {
|
||||
let selectedCount = selectedMaterialIds.count
|
||||
let purchasedCount = selectedPurchasedMaterialCount
|
||||
guard purchasedCount > 0 else {
|
||||
return "确定删除选中的 \(selectedCount) 张素材吗?"
|
||||
}
|
||||
let deletableCount = selectedCount - purchasedCount
|
||||
return "已选择 \(selectedCount) 张素材,其中 \(purchasedCount) 张已购买。"
|
||||
+ "不会删除已购买的项目,只会删除 \(deletableCount) 张未购买的项目。"
|
||||
}
|
||||
|
||||
/// 切换素材 tab。
|
||||
func selectTab(_ tab: Tab, api: any TravelAlbumServing) async {
|
||||
guard selectedTab != tab else { return }
|
||||
selectedTab = tab
|
||||
isSelectionMode = false
|
||||
selectedMaterialIds = []
|
||||
clearMaterialSelection()
|
||||
notifyStateChange()
|
||||
await loadMaterials(reset: true, api: api)
|
||||
}
|
||||
@@ -223,10 +241,9 @@ final class TravelAlbumDetailViewModel {
|
||||
|
||||
/// 切换选择模式。
|
||||
func toggleSelectionMode() {
|
||||
guard selectedTab == .all else { return }
|
||||
isSelectionMode.toggle()
|
||||
if !isSelectionMode {
|
||||
selectedMaterialIds = []
|
||||
clearMaterialSelection()
|
||||
}
|
||||
notifyStateChange()
|
||||
}
|
||||
@@ -234,14 +251,12 @@ final class TravelAlbumDetailViewModel {
|
||||
/// 切换素材选中状态。
|
||||
func toggleMaterialSelection(_ material: TravelAlbumMaterial) {
|
||||
guard isSelectionMode else { return }
|
||||
guard material.status == 1 else {
|
||||
onShowMessage?("仅未购买素材可删除")
|
||||
return
|
||||
}
|
||||
if selectedMaterialIds.contains(material.id) {
|
||||
selectedMaterialIds.remove(material.id)
|
||||
selectedMaterialPurchaseStates.removeValue(forKey: material.id)
|
||||
} else {
|
||||
selectedMaterialIds.insert(material.id)
|
||||
selectedMaterialPurchaseStates[material.id] = material.isPurchased
|
||||
}
|
||||
notifyStateChange()
|
||||
}
|
||||
@@ -249,7 +264,7 @@ final class TravelAlbumDetailViewModel {
|
||||
/// AI 修图任务提交成功后退出选择模式并清空当前选择。
|
||||
func completeAIRetouchSubmission() {
|
||||
isSelectionMode = false
|
||||
selectedMaterialIds = []
|
||||
clearMaterialSelection()
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
@@ -264,6 +279,7 @@ final class TravelAlbumDetailViewModel {
|
||||
guard let index = materials.firstIndex(where: { $0.id == id }) else { return }
|
||||
let material = materials.remove(at: index)
|
||||
selectedMaterialIds.remove(id)
|
||||
selectedMaterialPurchaseStates.removeValue(forKey: id)
|
||||
allPhotoCount = max(0, allPhotoCount - 1)
|
||||
if material.isPurchased {
|
||||
purchasedPhotoCount = max(0, purchasedPhotoCount - 1)
|
||||
@@ -273,16 +289,20 @@ final class TravelAlbumDetailViewModel {
|
||||
|
||||
/// 删除已选素材。
|
||||
func deleteSelectedMaterials(api: any TravelAlbumServing) async {
|
||||
let ids = selectedMaterialIds.sorted()
|
||||
guard !ids.isEmpty else {
|
||||
guard !selectedMaterialIds.isEmpty else {
|
||||
onShowMessage?("请选择要删除的素材")
|
||||
return
|
||||
}
|
||||
let ids = selectedDeletableMaterialIds
|
||||
guard !ids.isEmpty else {
|
||||
onShowMessage?("选中的素材均已购买,未删除任何项目")
|
||||
return
|
||||
}
|
||||
do {
|
||||
try await api.batchDeleteMaterials(ids: ids)
|
||||
onShowMessage?("删除成功")
|
||||
isSelectionMode = false
|
||||
selectedMaterialIds = []
|
||||
clearMaterialSelection()
|
||||
await refreshAll(api: api)
|
||||
} catch is CancellationError {
|
||||
return
|
||||
@@ -307,4 +327,18 @@ final class TravelAlbumDetailViewModel {
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
|
||||
private func clearMaterialSelection() {
|
||||
selectedMaterialIds = []
|
||||
selectedMaterialPurchaseStates = [:]
|
||||
}
|
||||
|
||||
private var selectedDeletableMaterialIds: [Int] {
|
||||
selectedMaterialIds
|
||||
.filter { id in
|
||||
guard selectedMaterialPurchaseStates[id] == false else { return false }
|
||||
return materials.first(where: { $0.id == id })?.isPurchased != true
|
||||
}
|
||||
.sorted()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user