完善样片管理列表、详情与上传页 UI 交互。

对齐 Android 样片模块布局与加载状态,补充共享 UI 组件与图片资源,并增强列表解析容错与单元测试覆盖。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 15:00:18 +08:00
parent cbf2db649e
commit afcd412f11
16 changed files with 862 additions and 134 deletions

View File

@ -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"),

View File

@ -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`

View File

@ -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 = []