// // TravelAlbumDetailViewModel.swift // suixinkan // import Foundation /// 旅拍相册详情 ViewModel,负责相册信息、素材分页筛选和删除状态。 final class TravelAlbumDetailViewModel { enum Tab: Int, Sendable { case all = 0 case purchased = 1 } /// 排序方式。 enum SortOption: Int, CaseIterable, Sendable { case createdAsc = 1 case createdDesc = 2 case fileNameAsc = 3 case fileNameDesc = 4 var title: String { switch self { case .createdAsc: "创建时间正序" case .createdDesc: "创建时间倒序" case .fileNameAsc: "文件名正序" case .fileNameDesc: "文件名倒序" } } } private(set) var album: TravelAlbum? private(set) var materials: [TravelAlbumMaterial] = [] private(set) var allPhotoCount = 0 private(set) var selectedTab: Tab = .all private(set) var sortOption: SortOption = .createdDesc private(set) var isLoading = true private(set) var isRefreshing = false private(set) var isSelectionMode = false private(set) var selectedMaterialIds: Set = [] var onStateChange: (() -> Void)? var onShowMessage: ((String) -> Void)? var onDeletedAlbum: ((Int) -> Void)? let albumId: Int private var currentPage = 1 private var canLoadMore = false private var isLoadingMore = false private let pageSize = 30 init(albumId: Int) { self.albumId = albumId } /// 重新加载详情与素材。 func refreshAll(api: any TravelAlbumServing) async { guard albumId > 0 else { isLoading = false onShowMessage?("相册不存在") notifyStateChange() return } isRefreshing = true notifyStateChange() await loadAlbumInfo(api: api) await loadAllPhotoCount(api: api) await loadMaterials(reset: true, api: api) isRefreshing = false isLoading = false notifyStateChange() } /// 拉取相册详情。 func loadAlbumInfo(api: any TravelAlbumServing) async { do { album = try await api.info(id: albumId) notifyStateChange() } catch is CancellationError { return } catch { onShowMessage?(error.localizedDescription) } } /// 拉取全部照片数量。 func loadAllPhotoCount(api: any TravelAlbumServing) async { do { let response = try await api.materialList( userEquityTravelId: albumId, page: 1, pageSize: 1, orderBy: sortOption.rawValue, isPurchased: nil ) allPhotoCount = response.total notifyStateChange() } catch is CancellationError { return } catch { return } } /// 切换素材 tab。 func selectTab(_ tab: Tab, api: any TravelAlbumServing) async { guard selectedTab != tab else { return } selectedTab = tab isSelectionMode = false selectedMaterialIds = [] notifyStateChange() await loadMaterials(reset: true, api: api) } /// 切换排序。 func setSortOption(_ option: SortOption, api: any TravelAlbumServing) async { guard sortOption != option else { return } sortOption = option notifyStateChange() await loadMaterials(reset: true, api: api) } /// 拉取素材列表。 func loadMaterials(reset: Bool, api: any TravelAlbumServing) async { guard albumId > 0 else { return } if reset { currentPage = 1 canLoadMore = false } else { guard canLoadMore, !isLoadingMore else { return } isLoadingMore = true currentPage += 1 } do { let response = try await api.materialList( userEquityTravelId: albumId, page: currentPage, pageSize: pageSize, orderBy: sortOption.rawValue, isPurchased: selectedTab == .purchased ? 1 : nil ) materials = reset ? response.list : materials + response.list canLoadMore = materials.count < response.total if selectedTab == .all { allPhotoCount = response.total } isLoadingMore = false notifyStateChange() } catch is CancellationError { return } catch { if !reset && currentPage > 1 { currentPage -= 1 } isLoadingMore = false onShowMessage?(error.localizedDescription) } } /// 根据滚动位置触发加载更多。 func loadMoreIfNeeded(lastVisibleIndex: Int, api: any TravelAlbumServing) async { if lastVisibleIndex >= materials.count - 6 { await loadMaterials(reset: false, api: api) } } /// 切换选择模式。 func toggleSelectionMode() { guard selectedTab == .all else { return } isSelectionMode.toggle() if !isSelectionMode { selectedMaterialIds = [] } notifyStateChange() } /// 切换素材选中状态。 func toggleMaterialSelection(_ material: TravelAlbumMaterial) { guard isSelectionMode else { return } guard material.status == 1 else { onShowMessage?("仅未购买素材可删除") return } if selectedMaterialIds.contains(material.id) { selectedMaterialIds.remove(material.id) } else { selectedMaterialIds.insert(material.id) } notifyStateChange() } /// 删除已选素材。 func deleteSelectedMaterials(api: any TravelAlbumServing) async { let ids = Array(selectedMaterialIds) guard !ids.isEmpty else { onShowMessage?("请选择要删除的素材") return } do { for id in ids { try await api.deleteMaterial(id: id) } onShowMessage?("删除成功") isSelectionMode = false selectedMaterialIds = [] await refreshAll(api: api) } catch is CancellationError { return } catch { onShowMessage?(error.localizedDescription.isEmpty ? "删除失败" : error.localizedDescription) } } /// 删除当前相册。 func deleteAlbum(api: any TravelAlbumServing) async { do { try await api.deleteAlbum(id: albumId) onShowMessage?("相册已删除") onDeletedAlbum?(albumId) } catch is CancellationError { return } catch { onShowMessage?(error.localizedDescription.isEmpty ? "删除失败" : error.localizedDescription) } } private func notifyStateChange() { onStateChange?() } }