修复合作获客扫码绑定崩溃与 Toast 层级,并接入分成比例修改。

将扫码页提升到列表层弹出并补齐 AppDelegate.window,避免扫码时 UIKit 取主窗口崩溃;Toast 改用独立 UIWindow 显示在 sheet 之上;合作获客员支持短信验证修改分成比例,同时优化冷启动根视图创建时机并补充相关测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-03 20:37:24 +08:00
parent 00c3cd0a93
commit 49e997ddba
20 changed files with 905 additions and 70 deletions

View File

@ -36,6 +36,24 @@ struct SaleUserUpdateRemarkRequest: Encodable {
}
}
///
struct SaleUserCommissionRateSmsRequest: Encodable {
let phone: String
}
///
struct SaleUserCommissionRateUpdateRequest: Encodable {
let saleUserId: Int
let commissionRate: Int
let smsCode: String
enum CodingKeys: String, CodingKey {
case saleUserId = "sale_user_id"
case commissionRate = "commission_rate"
case smsCode = "sms_code"
}
}
///
struct CooperativeSalerListPayload: Decodable {
let total: Int
@ -73,6 +91,9 @@ struct CooperativeSaler: Decodable, Identifiable {
let salerPhone: String
let shareCode: String
let storeId: Int
let photogCommissionRate: Int
let commissionRate: Int
let commissionRateLabel: String
let bindTime: String
let boundAt: String
@ -88,10 +109,18 @@ struct CooperativeSaler: Decodable, Identifiable {
salerPhone.cooperationNonEmptyOrDefault(phone)
}
var displayMaskedPhone: String {
phoneMasked.cooperationNonEmptyOrDefault(displayPhone)
}
var displayBindTime: String {
bindTime.cooperationNonEmptyOrDefault(boundAt)
}
var displayCommissionRate: String {
commissionRateLabel.cooperationNonEmptyOrDefault("\(commissionRate)%")
}
enum CodingKeys: String, CodingKey {
case saleUserId = "sale_user_id"
case bindingId = "binding_id"
@ -105,6 +134,9 @@ struct CooperativeSaler: Decodable, Identifiable {
case salerPhone = "saler_phone"
case shareCode = "share_code"
case storeId = "store_id"
case photogCommissionRate = "photog_commission_rate"
case commissionRate = "commission_rate"
case commissionRateLabel = "commission_rate_label"
case bindTime = "bind_time"
case boundAt = "bound_at"
}
@ -123,6 +155,9 @@ struct CooperativeSaler: Decodable, Identifiable {
salerPhone = try container.decodeLossyString(forKey: .salerPhone)
shareCode = try container.decodeLossyString(forKey: .shareCode)
storeId = try container.decodeLossyInt(forKey: .storeId) ?? 0
photogCommissionRate = try container.decodeLossyInt(forKey: .photogCommissionRate) ?? 0
commissionRate = try container.decodeLossyInt(forKey: .commissionRate) ?? 0
commissionRateLabel = try container.decodeLossyString(forKey: .commissionRateLabel)
bindTime = try container.decodeLossyString(forKey: .bindTime)
boundAt = try container.decodeLossyString(forKey: .boundAt)
}
@ -142,6 +177,9 @@ struct CooperativeSaler: Decodable, Identifiable {
salerPhone: salerPhone,
shareCode: shareCode,
storeId: storeId,
photogCommissionRate: photogCommissionRate,
commissionRate: commissionRate,
commissionRateLabel: commissionRateLabel,
bindTime: bindTime,
boundAt: boundAt
)
@ -160,6 +198,9 @@ struct CooperativeSaler: Decodable, Identifiable {
salerPhone: String,
shareCode: String,
storeId: Int,
photogCommissionRate: Int = 0,
commissionRate: Int = 0,
commissionRateLabel: String = "",
bindTime: String,
boundAt: String
) {
@ -175,6 +216,9 @@ struct CooperativeSaler: Decodable, Identifiable {
self.salerPhone = salerPhone
self.shareCode = shareCode
self.storeId = storeId
self.photogCommissionRate = photogCommissionRate
self.commissionRate = commissionRate
self.commissionRateLabel = commissionRateLabel
self.bindTime = bindTime
self.boundAt = boundAt
}
@ -244,6 +288,10 @@ struct SaleUserInviteInfo: Decodable {
struct ReferralLead: Decodable, Identifiable {
let id: Int
let leadId: Int
let displayName: String
let photogRemarkName: String
let name: String
let salerName: String
let userPhone: String
let userMobileMasked: String
let phone: String
@ -261,6 +309,10 @@ struct ReferralLead: Decodable, Identifiable {
userMobileMasked.cooperationNonEmptyOrDefault(userPhone.cooperationNonEmptyOrDefault(phone))
}
var displaySalerName: String {
displayName.cooperationNonEmptyOrDefault(photogRemarkName.saleUserDisplayName(primaryName: name, fallbackName: salerName))
}
var displayImages: [String] {
imageUrls.isEmpty ? images : imageUrls
}
@ -268,6 +320,10 @@ struct ReferralLead: Decodable, Identifiable {
enum CodingKeys: String, CodingKey {
case id
case leadId = "lead_id"
case displayName = "display_name"
case photogRemarkName = "photog_remark_name"
case name
case salerName = "saler_name"
case userPhone = "user_phone"
case userMobileMasked = "user_mobile_masked"
case phone
@ -281,6 +337,10 @@ struct ReferralLead: Decodable, Identifiable {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
leadId = try container.decodeLossyInt(forKey: .leadId) ?? 0
displayName = try container.decodeLossyString(forKey: .displayName)
photogRemarkName = try container.decodeLossyString(forKey: .photogRemarkName)
name = try container.decodeLossyString(forKey: .name)
salerName = try container.decodeLossyString(forKey: .salerName)
userPhone = try container.decodeLossyString(forKey: .userPhone)
userMobileMasked = try container.decodeLossyString(forKey: .userMobileMasked)
phone = try container.decodeLossyString(forKey: .phone)