Sync app branding and fix order list decode for referral_order payloads.

Align display name, icon, and launch screen with suixinkan_ios_new, and tolerate nested sale_user and empty travel objects in listv2 responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 17:07:50 +08:00
parent 31d2b6094b
commit 75d0cb1f9a
8 changed files with 247 additions and 7 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24765" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24743"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -11,10 +13,52 @@
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" layoutMarginsRelativeArrangement="YES" axis="vertical" alignment="center" spacing="0" translatesAutoresizingMaskIntoConstraints="NO" id="Stk-Mn-001">
<rect key="frame" x="0.0" y="59" width="393" height="759"/>
<subviews>
<view contentMode="scaleToFill" verticalHuggingPriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="Spc-Tp-001">
<rect key="frame" x="0.0" y="0.0" width="393" height="200.5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SplashLogo" translatesAutoresizingMaskIntoConstraints="NO" id="Img-Lg-001">
<rect key="frame" x="106.5" y="200.5" width="180" height="180"/>
<constraints>
<constraint firstAttribute="width" constant="180" id="Img-Wd-001"/>
<constraint firstAttribute="height" constant="180" id="Img-Ht-001"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="随心瞰" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Lbl-Tt-001">
<rect key="frame" x="136.66666666666666" y="380.5" width="119.66666666666666" height="48"/>
<fontDescription key="fontDescription" type="system" weight="bold" pointSize="40"/>
<color key="textColor" red="0.0" green="0.45098039215686275" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" verticalHuggingPriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="Spc-Bt-001">
<rect key="frame" x="0.0" y="428.5" width="393" height="200.5"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="商家版" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Lbl-St-001">
<rect key="frame" x="161.66666666666666" y="629" width="69.666666666666686" height="24"/>
<fontDescription key="fontDescription" type="system" weight="bold" pointSize="20"/>
<color key="textColor" red="0.0" green="0.45098039215686275" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<edgeInsets key="layoutMargins" top="0.0" left="0.0" bottom="32" right="0.0"/>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Stk-Mn-001" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="Stk-Tp-001"/>
<constraint firstItem="Stk-Mn-001" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="Stk-Ld-001"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="Stk-Mn-001" secondAttribute="trailing" id="Stk-Tr-001"/>
<constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="Stk-Mn-001" secondAttribute="bottom" id="Stk-Bt-001"/>
<constraint firstItem="Spc-Tp-001" firstAttribute="height" secondItem="Spc-Bt-001" secondAttribute="height" id="Spc-Eq-001"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -22,4 +66,7 @@
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="SplashLogo" width="180" height="180"/>
</resources>
</document>

View File

@ -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<CodingKeys>
) 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<CodingKeys>
) 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) ?? []

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>随心瞰商家版</string>
<key>NSCameraUsageDescription</key>
<string>需要访问相机以扫描订单核销二维码</string>
<key>NSLocationWhenInUseUsageDescription</key>

View File

@ -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<OrderListResponse<OrderEntity>>.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)
}
}