完善样片管理列表、详情与上传页 UI 交互。
对齐 Android 样片模块布局与加载状态,补充共享 UI 组件与图片资源,并增强列表解析容错与单元测试覆盖。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -20,7 +20,7 @@ enum HomeMenuCatalog {
|
||||
HomeMenuItem(uri: "system_settings", title: "设置中心", iconName: "gearshape.fill"),
|
||||
HomeMenuItem(uri: "message_center", title: "消息中心", iconName: "bell.fill"),
|
||||
HomeMenuItem(uri: "checkin_points", title: "打卡点管理", iconName: "mappin.and.ellipse"),
|
||||
HomeMenuItem(uri: "sample_management", title: "样片管理", iconName: "point.3.connected.trianglepath.dotted"),
|
||||
HomeMenuItem(uri: "sample_management", title: "样片管理", iconName: "sample_ic_home_menu_route"),
|
||||
HomeMenuItem(uri: "live_stream_management", title: "直播管理", iconName: "dot.radiowaves.left.and.right"),
|
||||
HomeMenuItem(uri: "verification_order", title: "核销订单", iconName: "checkmark.seal.fill"),
|
||||
HomeMenuItem(uri: "travel_album", title: "新增相册", iconName: "photo.badge.plus"),
|
||||
|
||||
@ -335,6 +335,11 @@ struct SampleDetailMediaItem: Decodable, Sendable, Equatable, Hashable, Identifi
|
||||
?? (showUrl?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty)
|
||||
?? ""
|
||||
}
|
||||
|
||||
/// 返回可展示的媒体类型;服务端媒体项为 0 时按 Android 规则回退到详情顶层类型。
|
||||
func resolvedType(fallback: Int) -> Int {
|
||||
type == 0 ? fallback : type
|
||||
}
|
||||
}
|
||||
|
||||
/// 样片关联项目项,对齐 Android `ProjectItem`。
|
||||
|
||||
@ -26,6 +26,7 @@ final class SampleListViewModel {
|
||||
private(set) var canLoadMore = false
|
||||
private(set) var filterStatus: SampleFilterStatus = .all
|
||||
private(set) var searchText = ""
|
||||
private(set) var operatingSampleIDs: Set<Int> = []
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
@ -33,6 +34,11 @@ final class SampleListViewModel {
|
||||
private var currentPage = 1
|
||||
private var totalCount = 0
|
||||
private let pageSize = 10
|
||||
private var isRequestingList = false
|
||||
private var isLoadingMore = false
|
||||
|
||||
/// 是否正在执行样片上下架操作。
|
||||
var isOperating: Bool { !operatingSampleIDs.isEmpty }
|
||||
|
||||
/// 首次加载样片列表。
|
||||
func loadInitial(api: any SampleManagementServing) async {
|
||||
@ -67,15 +73,26 @@ final class SampleListViewModel {
|
||||
}
|
||||
|
||||
/// 切换样片上下架状态。
|
||||
func toggleListing(sampleId: Int, checked: Bool, api: any SampleManagementServing) async {
|
||||
guard let currentItem = items.first(where: { $0.id == sampleId }) else { return }
|
||||
@discardableResult
|
||||
func toggleListing(sampleId: Int, checked: Bool, api: any SampleManagementServing) async -> Bool {
|
||||
guard let currentItem = items.first(where: { $0.id == sampleId }) else { return !checked }
|
||||
let previousState = currentItem.isListed
|
||||
guard currentItem.status == 1 else {
|
||||
onShowMessage?("审核通过才可以操作上下架")
|
||||
return
|
||||
notifyStateChange()
|
||||
return previousState
|
||||
}
|
||||
|
||||
let targetStatus = checked ? 1 : 0
|
||||
guard currentItem.listingStatus != targetStatus else { return }
|
||||
guard currentItem.listingStatus != targetStatus else { return previousState }
|
||||
|
||||
guard !operatingSampleIDs.contains(sampleId) else { return previousState }
|
||||
operatingSampleIDs.insert(sampleId)
|
||||
notifyStateChange()
|
||||
defer {
|
||||
operatingSampleIDs.remove(sampleId)
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
do {
|
||||
try await api.setListingStatus(id: sampleId, listingStatus: targetStatus)
|
||||
@ -97,25 +114,38 @@ final class SampleListViewModel {
|
||||
)
|
||||
}
|
||||
notifyStateChange()
|
||||
return checked
|
||||
} catch is CancellationError {
|
||||
return
|
||||
return previousState
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription.isEmpty ? "操作失败" : error.localizedDescription)
|
||||
return previousState
|
||||
}
|
||||
}
|
||||
|
||||
private func load(reset: Bool, showLoading: Bool, api: any SampleManagementServing) async {
|
||||
guard !isRequestingList else {
|
||||
if isRefreshing {
|
||||
isRefreshing = false
|
||||
notifyStateChange()
|
||||
}
|
||||
return
|
||||
}
|
||||
if reset {
|
||||
currentPage = 1
|
||||
canLoadMore = false
|
||||
} else {
|
||||
guard canLoadMore, !isLoading else { return }
|
||||
guard canLoadMore, !isLoading, !isLoadingMore else { return }
|
||||
isLoadingMore = true
|
||||
currentPage += 1
|
||||
}
|
||||
|
||||
isRequestingList = true
|
||||
isLoading = showLoading
|
||||
notifyStateChange()
|
||||
defer {
|
||||
isRequestingList = false
|
||||
if !reset { isLoadingMore = false }
|
||||
isLoading = false
|
||||
isRefreshing = false
|
||||
notifyStateChange()
|
||||
@ -276,7 +306,7 @@ final class UploadSampleViewModel {
|
||||
onShowMessage?("请先选择景区")
|
||||
return
|
||||
}
|
||||
if projectIsLoading && !refresh { return }
|
||||
guard !projectIsLoading else { return }
|
||||
if refresh {
|
||||
projectCurrentPage = 1
|
||||
projectList = []
|
||||
|
||||
Reference in New Issue
Block a user