feat: 使用旅拍相册批量删除接口
This commit is contained in:
@@ -41,8 +41,8 @@ protocol TravelAlbumServing {
|
|||||||
/// 删除旅拍相册。
|
/// 删除旅拍相册。
|
||||||
func deleteAlbum(id: Int) async throws
|
func deleteAlbum(id: Int) async throws
|
||||||
|
|
||||||
/// 删除旅拍相册素材。
|
/// 批量删除旅拍相册素材。
|
||||||
func deleteMaterial(id: Int) async throws
|
func batchDeleteMaterials(ids: [Int]) async throws
|
||||||
|
|
||||||
/// 拉取相册小程序码。
|
/// 拉取相册小程序码。
|
||||||
func mpCode(id: Int) async throws -> TravelAlbumMpCodeResponse
|
func mpCode(id: Int) async throws -> TravelAlbumMpCodeResponse
|
||||||
@@ -164,10 +164,14 @@ final class TravelAlbumAPI: TravelAlbumServing {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 删除旅拍相册素材。
|
/// 批量删除旅拍相册素材。
|
||||||
func deleteMaterial(id: Int) async throws {
|
func batchDeleteMaterials(ids: [Int]) async throws {
|
||||||
let _: EmptyPayload = try await client.send(
|
let _: EmptyPayload = try await client.send(
|
||||||
APIRequest(method: .post, path: "\(basePath)/delete-material", body: TravelAlbumIDRequest(id: id))
|
APIRequest(
|
||||||
|
method: .post,
|
||||||
|
path: "\(basePath)/batch-delete-material",
|
||||||
|
body: TravelAlbumBatchDeleteMaterialRequest(ids: ids)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,3 +216,8 @@ final class TravelAlbumAPI: TravelAlbumServing {
|
|||||||
private struct TravelAlbumIDRequest: Encodable, Sendable {
|
private struct TravelAlbumIDRequest: Encodable, Sendable {
|
||||||
let id: Int
|
let id: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 旅拍相册素材批量删除请求体。
|
||||||
|
private struct TravelAlbumBatchDeleteMaterialRequest: Encodable, Sendable {
|
||||||
|
let ids: [Int]
|
||||||
|
}
|
||||||
|
|||||||
@@ -267,15 +267,13 @@ final class TravelAlbumDetailViewModel {
|
|||||||
|
|
||||||
/// 删除已选素材。
|
/// 删除已选素材。
|
||||||
func deleteSelectedMaterials(api: any TravelAlbumServing) async {
|
func deleteSelectedMaterials(api: any TravelAlbumServing) async {
|
||||||
let ids = Array(selectedMaterialIds)
|
let ids = selectedMaterialIds.sorted()
|
||||||
guard !ids.isEmpty else {
|
guard !ids.isEmpty else {
|
||||||
onShowMessage?("请选择要删除的素材")
|
onShowMessage?("请选择要删除的素材")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
for id in ids {
|
try await api.batchDeleteMaterials(ids: ids)
|
||||||
try await api.deleteMaterial(id: id)
|
|
||||||
}
|
|
||||||
onShowMessage?("删除成功")
|
onShowMessage?("删除成功")
|
||||||
isSelectionMode = false
|
isSelectionMode = false
|
||||||
selectedMaterialIds = []
|
selectedMaterialIds = []
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ final class TravelAlbumPreviewActionHandler: TravelAlbumPreviewActionHandling {
|
|||||||
/// 调用素材删除接口,仅在服务端确认成功后返回成功结果。
|
/// 调用素材删除接口,仅在服务端确认成功后返回成功结果。
|
||||||
func deleteProject(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
|
func deleteProject(originalMaterialId: Int) async -> TravelAlbumPreviewActionResult {
|
||||||
do {
|
do {
|
||||||
try await api.deleteMaterial(id: originalMaterialId)
|
try await api.batchDeleteMaterials(ids: [originalMaterialId])
|
||||||
return .success("删除成功")
|
return .success("删除成功")
|
||||||
} catch is CancellationError {
|
} catch is CancellationError {
|
||||||
return .failure("删除已取消")
|
return .failure("删除已取消")
|
||||||
|
|||||||
@@ -104,6 +104,22 @@ final class TravelAlbumAPITests: XCTestCase {
|
|||||||
XCTAssertEqual(query?.first { $0.name == "material_id" }?.value, "6")
|
XCTAssertEqual(query?.first { $0.name == "material_id" }?.value, "6")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBatchDeleteMaterialsBuildsPathAndBody() async throws {
|
||||||
|
let session = MockURLSession(responses: [envelopeJSON("{}")])
|
||||||
|
let api = TravelAlbumAPI(client: APIClient(environment: .testing, session: session))
|
||||||
|
|
||||||
|
try await api.batchDeleteMaterials(ids: [2031, 2032, 2033])
|
||||||
|
|
||||||
|
let request = try XCTUnwrap(session.requests.first)
|
||||||
|
XCTAssertEqual(request.httpMethod, "POST")
|
||||||
|
XCTAssertEqual(
|
||||||
|
request.url?.path,
|
||||||
|
"/api/yf-handset-app/photog/travel-album/batch-delete-material"
|
||||||
|
)
|
||||||
|
let body = try JSONSerialization.jsonObject(with: try XCTUnwrap(request.httpBody)) as? [String: Any]
|
||||||
|
XCTAssertEqual(body?["ids"] as? [Int], [2031, 2032, 2033])
|
||||||
|
}
|
||||||
|
|
||||||
func testUploadMaterialBuildsPathAndBody() async throws {
|
func testUploadMaterialBuildsPathAndBody() async throws {
|
||||||
let material = envelopeJSON(#"{"id":6,"user_equity_travel_id":3,"status":1,"order_number":"","user_id":9,"file_name":"A.JPG","file_type":1,"file_url":"https://cdn/a.jpg","file_size":1024,"cover_url":"","is_purchased":false,"created_at":"2026-07-08 10:00:00","updated_at":"2026-07-08 10:00:00"}"#)
|
let material = envelopeJSON(#"{"id":6,"user_equity_travel_id":3,"status":1,"order_number":"","user_id":9,"file_name":"A.JPG","file_type":1,"file_url":"https://cdn/a.jpg","file_size":1024,"cover_url":"","is_purchased":false,"created_at":"2026-07-08 10:00:00","updated_at":"2026-07-08 10:00:00"}"#)
|
||||||
let session = MockURLSession(responses: [material])
|
let session = MockURLSession(responses: [material])
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
|||||||
|
|
||||||
let success = await handler.deleteProject(originalMaterialId: 8)
|
let success = await handler.deleteProject(originalMaterialId: 8)
|
||||||
|
|
||||||
XCTAssertEqual(api.deletedMaterialIds, [8])
|
XCTAssertEqual(api.deletedMaterialIDBatches, [[8]])
|
||||||
XCTAssertEqual(success, .success("删除成功"))
|
XCTAssertEqual(success, .success("删除成功"))
|
||||||
|
|
||||||
api.deleteMaterialError = APIError.serverCode(500, "服务暂不可用")
|
api.deleteMaterialError = APIError.serverCode(500, "服务暂不可用")
|
||||||
let failure = await handler.deleteProject(originalMaterialId: 9)
|
let failure = await handler.deleteProject(originalMaterialId: 9)
|
||||||
|
|
||||||
XCTAssertEqual(api.deletedMaterialIds, [8, 9])
|
XCTAssertEqual(api.deletedMaterialIDBatches, [[8], [9]])
|
||||||
XCTAssertEqual(failure, .failure("服务暂不可用"))
|
XCTAssertEqual(failure, .failure("服务暂不可用"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
|||||||
controller.view.allAccessibilityLabels().contains("第 1 张,共 2 张")
|
controller.view.allAccessibilityLabels().contains("第 1 张,共 2 张")
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual(api.deletedMaterialIds, [1])
|
XCTAssertEqual(api.deletedMaterialIDBatches, [[1]])
|
||||||
XCTAssertEqual(deletedIds, [1])
|
XCTAssertEqual(deletedIds, [1])
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
controller.view.allLabels().contains { $0.text == "第二张.jpg" }
|
controller.view.allLabels().contains { $0.text == "第二张.jpg" }
|
||||||
@@ -120,7 +120,7 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
|||||||
controller.view.allAccessibilityLabels().contains("第 2 张,共 2 张")
|
controller.view.allAccessibilityLabels().contains("第 2 张,共 2 张")
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual(api.deletedMaterialIds, [3])
|
XCTAssertEqual(api.deletedMaterialIDBatches, [[3]])
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
controller.view.allLabels().contains { $0.text == "第二张.jpg" }
|
controller.view.allLabels().contains { $0.text == "第二张.jpg" }
|
||||||
)
|
)
|
||||||
@@ -146,7 +146,7 @@ final class TravelAlbumDetailViewControllerTests: XCTestCase {
|
|||||||
controller.deleteCurrentProjectAfterConfirmation()
|
controller.deleteCurrentProjectAfterConfirmation()
|
||||||
await waitUntil { host.presentedViewController == nil }
|
await waitUntil { host.presentedViewController == nil }
|
||||||
|
|
||||||
XCTAssertEqual(api.deletedMaterialIds, [1])
|
XCTAssertEqual(api.deletedMaterialIDBatches, [[1]])
|
||||||
XCTAssertNil(host.presentedViewController)
|
XCTAssertNil(host.presentedViewController)
|
||||||
window.isHidden = true
|
window.isHidden = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,6 +242,26 @@ final class TravelAlbumDetailViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(api.deletedAlbumIds, [5])
|
XCTAssertEqual(api.deletedAlbumIds, [5])
|
||||||
XCTAssertEqual(deletedId, 5)
|
XCTAssertEqual(deletedId, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDeleteSelectedMaterialsUsesSingleBatchRequest() async {
|
||||||
|
let api = TravelAlbumMockAPI()
|
||||||
|
api.materialListResponses = [
|
||||||
|
TravelAlbumListResponse(total: 0, list: []),
|
||||||
|
TravelAlbumListResponse(total: 0, list: []),
|
||||||
|
TravelAlbumListResponse(total: 0, list: []),
|
||||||
|
]
|
||||||
|
let viewModel = TravelAlbumDetailViewModel(albumId: 5)
|
||||||
|
viewModel.toggleSelectionMode()
|
||||||
|
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 2033, status: 1))
|
||||||
|
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 2031, status: 1))
|
||||||
|
viewModel.toggleMaterialSelection(TravelAlbumMaterial(id: 2032, status: 1))
|
||||||
|
|
||||||
|
await viewModel.deleteSelectedMaterials(api: api)
|
||||||
|
|
||||||
|
XCTAssertEqual(api.deletedMaterialIDBatches, [[2031, 2032, 2033]])
|
||||||
|
XCTAssertFalse(viewModel.isSelectionMode)
|
||||||
|
XCTAssertTrue(viewModel.selectedMaterialIds.isEmpty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 有线传输 ViewModel 测试。
|
/// 有线传输 ViewModel 测试。
|
||||||
@@ -1097,7 +1117,7 @@ final class TravelAlbumMockAPI: TravelAlbumServing {
|
|||||||
private(set) var uploadMaterialRequests: [TravelAlbumUploadMaterialRequest] = []
|
private(set) var uploadMaterialRequests: [TravelAlbumUploadMaterialRequest] = []
|
||||||
private(set) var materialClientPhotoIdsCallCount = 0
|
private(set) var materialClientPhotoIdsCallCount = 0
|
||||||
private(set) var deletedAlbumIds: [Int] = []
|
private(set) var deletedAlbumIds: [Int] = []
|
||||||
private(set) var deletedMaterialIds: [Int] = []
|
private(set) var deletedMaterialIDBatches: [[Int]] = []
|
||||||
private(set) var aiRetouchTemplateScenicIds: [Int] = []
|
private(set) var aiRetouchTemplateScenicIds: [Int] = []
|
||||||
private(set) var aiRetouchRequests: [TravelAlbumAIRetouchRequest] = []
|
private(set) var aiRetouchRequests: [TravelAlbumAIRetouchRequest] = []
|
||||||
private(set) var aiReretouchRequests: [TravelAlbumAIReretouchRequest] = []
|
private(set) var aiReretouchRequests: [TravelAlbumAIReretouchRequest] = []
|
||||||
@@ -1167,8 +1187,8 @@ final class TravelAlbumMockAPI: TravelAlbumServing {
|
|||||||
deletedAlbumIds.append(id)
|
deletedAlbumIds.append(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteMaterial(id: Int) async throws {
|
func batchDeleteMaterials(ids: [Int]) async throws {
|
||||||
deletedMaterialIds.append(id)
|
deletedMaterialIDBatches.append(ids)
|
||||||
if deleteMaterialDelayNanoseconds > 0 {
|
if deleteMaterialDelayNanoseconds > 0 {
|
||||||
try await Task.sleep(nanoseconds: deleteMaterialDelayNanoseconds)
|
try await Task.sleep(nanoseconds: deleteMaterialDelayNanoseconds)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user