diff --git a/suixinkan/Assets.xcassets/AppIcon.appiconset/Contents.json b/suixinkan/Assets.xcassets/AppIcon.appiconset/Contents.json index 2305880..24cada9 100644 --- a/suixinkan/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/suixinkan/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "icon_1024.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" @@ -12,6 +13,7 @@ "value" : "dark" } ], + "filename" : "icon_1024.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" @@ -23,6 +25,7 @@ "value" : "tinted" } ], + "filename" : "icon_1024.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/suixinkan/Assets.xcassets/AppIcon.appiconset/icon_1024.png b/suixinkan/Assets.xcassets/AppIcon.appiconset/icon_1024.png new file mode 100644 index 0000000..508217d Binary files /dev/null and b/suixinkan/Assets.xcassets/AppIcon.appiconset/icon_1024.png differ diff --git a/suixinkan/Assets.xcassets/SplashLogo.imageset/Contents.json b/suixinkan/Assets.xcassets/SplashLogo.imageset/Contents.json new file mode 100644 index 0000000..644d848 --- /dev/null +++ b/suixinkan/Assets.xcassets/SplashLogo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "icon_splash.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/suixinkan/Assets.xcassets/SplashLogo.imageset/icon_splash.png b/suixinkan/Assets.xcassets/SplashLogo.imageset/icon_splash.png new file mode 100644 index 0000000..2cfbec3 Binary files /dev/null and b/suixinkan/Assets.xcassets/SplashLogo.imageset/icon_splash.png differ diff --git a/suixinkan/Base.lproj/LaunchScreen.storyboard b/suixinkan/Base.lproj/LaunchScreen.storyboard index 865e932..24ab4cb 100644 --- a/suixinkan/Base.lproj/LaunchScreen.storyboard +++ b/suixinkan/Base.lproj/LaunchScreen.storyboard @@ -1,7 +1,9 @@ - - + + + - + + @@ -11,10 +13,52 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -22,4 +66,7 @@ + + + diff --git a/suixinkan/Features/Orders/Models/OrderModels.swift b/suixinkan/Features/Orders/Models/OrderModels.swift index 389ef45..1b2d5a9 100644 --- a/suixinkan/Features/Orders/Models/OrderModels.swift +++ b/suixinkan/Features/Orders/Models/OrderModels.swift @@ -116,9 +116,9 @@ struct OrderEntity: Decodable, Equatable { orderTypeLabel = try container.decodeIfPresent(String.self, forKey: .orderTypeLabel) ?? "" depositPayTime = try container.decodeIfPresent(String.self, forKey: .depositPayTime) ?? "" createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? "" - photoTravel = try container.decodeIfPresent(PhotoTravelEntity.self, forKey: .photoTravel) + photoTravel = try Self.decodePhotoTravel(from: container) refundAmount = try Self.decodeFlexibleString(container: container, key: .refundAmount, defaultValue: "0") - multiTravel = try container.decodeIfPresent(MultiTravelEntity.self, forKey: .multiTravel) + multiTravel = try Self.decodeMultiTravel(from: container) isNeedEdit = try container.decodeIfPresent(Bool.self, forKey: .isNeedEdit) ?? true isRefined = try container.decodeIfPresent(Int.self, forKey: .isRefined) ?? 0 canBindReferral = try container.decodeIfPresent(Int.self, forKey: .canBindReferral) ?? 0 @@ -145,6 +145,26 @@ struct OrderEntity: Decodable, Equatable { } return defaultValue } + + private static func decodePhotoTravel( + from container: KeyedDecodingContainer + ) throws -> PhotoTravelEntity? { + guard container.contains(.photoTravel), try !container.decodeNil(forKey: .photoTravel) else { + return nil + } + let travel = try container.decode(PhotoTravelEntity.self, forKey: .photoTravel) + return travel.isEmpty ? nil : travel + } + + private static func decodeMultiTravel( + from container: KeyedDecodingContainer + ) throws -> MultiTravelEntity? { + guard container.contains(.multiTravel), try !container.decodeNil(forKey: .multiTravel) else { + return nil + } + let travel = try container.decode(MultiTravelEntity.self, forKey: .multiTravel) + return travel.isEmpty ? nil : travel + } } struct BoundReferralOrderEntity: Decodable, Equatable { @@ -184,6 +204,53 @@ struct BoundReferralOrderEntity: Decodable, Equatable { case status case statusLabel = "status_label" case createdAt = "created_at" + case saleUser = "sale_user" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0 + orderNo = try container.decodeIfPresent(String.self, forKey: .orderNo) ?? "" + saleUserId = try container.decodeIfPresent(Int.self, forKey: .saleUserId) ?? 0 + salerName = try container.decodeIfPresent(String.self, forKey: .salerName) ?? "" + salerPhone = try container.decodeIfPresent(String.self, forKey: .salerPhone) ?? "" + displayText = try container.decodeIfPresent(String.self, forKey: .displayText) ?? "" + userMobile = try container.decodeIfPresent(String.self, forKey: .userMobile) ?? "" + remark = try container.decodeIfPresent(String.self, forKey: .remark) ?? "" + bindOrderNumber = try container.decodeIfPresent(String.self, forKey: .bindOrderNumber) ?? "" + sourceType = try container.decodeIfPresent(Int.self, forKey: .sourceType) ?? 0 + sourceTypeLabel = try container.decodeIfPresent(String.self, forKey: .sourceTypeLabel) ?? "" + status = try container.decodeIfPresent(Int.self, forKey: .status) ?? 0 + statusLabel = try container.decodeIfPresent(String.self, forKey: .statusLabel) ?? "" + createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? "" + + let saleUser = try container.decodeIfPresent(ReferralSaleUserEntity.self, forKey: .saleUser) + photogRemarkName = try container.decodeIfPresent(String.self, forKey: .photogRemarkName) + ?? saleUser?.photogRemarkName + ?? saleUser?.displayName + ?? "" + } +} + +/// `referral_order.sale_user` 嵌套对象,接口字段与顶层可能重复。 +struct ReferralSaleUserEntity: Decodable, Equatable { + let photogRemarkName: String + let displayName: String + let salerName: String + + enum CodingKeys: String, CodingKey { + case photogRemarkName = "photog_remark_name" + case displayName = "display_name" + case salerName = "saler_name" + case name + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + photogRemarkName = try container.decodeIfPresent(String.self, forKey: .photogRemarkName) ?? "" + let name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" + salerName = try container.decodeIfPresent(String.self, forKey: .salerName) ?? name + displayName = try container.decodeIfPresent(String.self, forKey: .displayName) ?? salerName } } @@ -222,6 +289,18 @@ struct PhotoTravelEntity: Decodable, Equatable { retouchGiftPhotoNum = try container.decodeIfPresent(Int.self, forKey: .retouchGiftPhotoNum) ?? 0 retouchGiftVideoNum = try container.decodeIfPresent(Int.self, forKey: .retouchGiftVideoNum) ?? 0 } + + var isEmpty: Bool { + orderPhotoNum == 0 + && orderVideoNum == 0 + && !canCancelOrder + && !needCheckIn + && checkInTime.isEmpty + && !canGiftRetouch + && retouchGiftTime.isEmpty + && retouchGiftPhotoNum == 0 + && retouchGiftVideoNum == 0 + } } struct MultiTravelEntity: Decodable, Equatable { @@ -235,6 +314,10 @@ struct MultiTravelEntity: Decodable, Equatable { self.materialList = materialList } + var isEmpty: Bool { + materialList.isEmpty + } + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) materialList = try container.decodeIfPresent([MultiTravelMaterialEntity].self, forKey: .materialList) ?? [] diff --git a/suixinkan/Info.plist b/suixinkan/Info.plist index b685371..c529184 100644 --- a/suixinkan/Info.plist +++ b/suixinkan/Info.plist @@ -2,6 +2,8 @@ + CFBundleDisplayName + 随心瞰商家版 NSCameraUsageDescription 需要访问相机以扫描订单核销二维码 NSLocationWhenInUseUsageDescription diff --git a/suixinkanTests/OrderListViewModelTests.swift b/suixinkanTests/OrderListViewModelTests.swift index 72e7c11..e512b26 100644 --- a/suixinkanTests/OrderListViewModelTests.swift +++ b/suixinkanTests/OrderListViewModelTests.swift @@ -82,4 +82,88 @@ final class OrderListViewModelTests: XCTestCase { XCTAssertEqual(order.photoTravel?.orderPhotoNum, 10) XCTAssertEqual(order.photoTravel?.needCheckIn, true) } + + func testOrderListDecodesRealAPIResponseShape() throws { + let json = """ + { + "code": 100000, + "msg": "success", + "data": { + "total": 3, + "list": [ + { + "actual_pay_amount": "0.01", + "created_at": "2026-06-25 17:12:24", + "is_refined": 1, + "multi_travel": {}, + "order_number": "260625128009", + "order_status": 30, + "order_status_name": "已完成", + "order_type": 14, + "order_type_label": "摄影师跟拍线下扫码", + "phone": "13222319413", + "photo_travel": {}, + "project_name": "摄影师跟拍线下扫码", + "referral_order": { + "bind_order_number": "260625128009", + "created_at": "2026-06-25 17:13:51", + "display_text": "管铜彬1234567 19281871413", + "id": 53, + "order_no": "LD202606250008", + "sale_user": { + "display_name": "管铜彬1234567", + "name": "管铜彬1234567", + "photog_remark_name": "管铜彬1234567", + "sale_user_id": 6, + "saler_name": "管铜彬1234567" + }, + "sale_user_id": 6, + "saler_name": "管铜彬1234567", + "saler_phone": "19281871413", + "source_type": 2, + "source_type_label": "下级线索", + "status": 2, + "status_label": "已完成", + "user_mobile": "13333333331" + }, + "user_id": 141943 + }, + { + "actual_pay_amount": "0.01", + "created_at": "2026-06-17 14:08:37", + "is_refined": 2, + "multi_travel": {}, + "order_number": "260617128001", + "order_status": 50, + "order_status_name": "已退款", + "order_type": 11, + "phone": "18651857230", + "photo_travel": { + "can_cancel_order": false, + "can_gift_retouch": false, + "need_check_in": true, + "order_photo_num": 6, + "order_video_num": 0, + "retouch_gift_photo_num": 2, + "retouch_gift_time": "2026-06-23 11:29:33", + "retouch_gift_video_num": 0 + }, + "project_name": "旅拍 - 便装套餐", + "user_id": 143089 + } + ] + } + } + """.data(using: .utf8)! + + let envelope = try JSONDecoder().decode(APIEnvelope>.self, from: json) + let list = try XCTUnwrap(envelope.data?.list) + XCTAssertEqual(list.count, 2) + XCTAssertNil(list[0].photoTravel) + XCTAssertNil(list[0].multiTravel) + XCTAssertEqual(list[0].referralOrder?.orderNo, "LD202606250008") + XCTAssertEqual(list[0].referralOrder?.photogRemarkName, "管铜彬1234567") + XCTAssertEqual(list[1].photoTravel?.orderPhotoNum, 6) + XCTAssertEqual(list[1].photoTravel?.needCheckIn, true) + } }