修复合作获客扫码绑定崩溃与 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

@ -6,6 +6,7 @@
//
import SwiftUI
import Combine
///
struct CooperationAcquirerView: View {
@ -15,11 +16,12 @@ struct CooperationAcquirerView: View {
@Environment(\.globalLoading) private var globalLoading
@Environment(\.openURL) private var openURL
@StateObject private var viewModel = CooperationAcquirerViewModel()
@State private var showScanner = false
@State private var showRemarkAlert = false
@State private var remarkEditingAcquirer: CooperativeSaler?
@State private var remarkDraft = ""
let onBind: (Int) -> Void
@State private var sendingCommissionSms = false
@State private var submittingCommissionRate = false
let onScanBind: () -> Void
var body: some View {
ScrollView {
@ -38,6 +40,8 @@ struct CooperationAcquirerView: View {
remarkEditingAcquirer = acquirer
remarkDraft = acquirer.photogRemarkName
showRemarkAlert = true
} onEditCommission: {
viewModel.startEditCommissionRate(acquirer)
}
}
}
@ -50,7 +54,7 @@ struct CooperationAcquirerView: View {
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("扫码绑定") {
showScanner = true
onScanBind()
}
.font(.system(size: 14))
.foregroundStyle(CooperationOrderColors.primary)
@ -60,26 +64,38 @@ struct CooperationAcquirerView: View {
.refreshable {
await reload(showLoading: false)
}
.fullScreenCover(isPresented: $showScanner) {
OrderScannerPage(
onClose: { showScanner = false },
onSuccess: { raw in
showScanner = false
if let saleUserId = SaleUserQrParser.parseSaleUserId(raw) {
onBind(saleUserId)
} else {
toastCenter.show("请扫描正确的获客员二维码")
}
},
onFailure: { error in
showScanner = false
toastCenter.show(error.localizedDescription)
}
)
}
.task {
await reload(showLoading: true)
}
.sheet(item: $viewModel.commissionEditingAcquirer, onDismiss: {
viewModel.dismissCommissionRateDialog()
}) { acquirer in
CommissionRateEditSheet(
acquirer: acquirer,
commissionRate: Binding(
get: { viewModel.commissionRateDraft },
set: { viewModel.updateCommissionRateDraft($0) }
),
smsCode: Binding(
get: { viewModel.commissionSmsCodeDraft },
set: { viewModel.updateCommissionSmsCodeDraft($0) }
),
smsCountdown: viewModel.commissionSmsCountdown,
sending: sendingCommissionSms,
submitting: submittingCommissionRate,
onSendSmsCode: {
Task { await sendCommissionSms() }
},
onConfirm: {
Task { await saveCommissionRate() }
},
onCancel: {
viewModel.dismissCommissionRateDialog()
}
)
.presentationDetents([.height(430)])
.presentationDragIndicator(.hidden)
}
.alert("修改备注", isPresented: $showRemarkAlert) {
TextField("备注名", text: $remarkDraft)
Button("取消", role: .cancel) {
@ -105,6 +121,9 @@ struct CooperationAcquirerView: View {
Text("\(acquirer.salerRealName.cooperationNonEmptyOrDefault("")) 设置备注名")
}
}
.onReceive(Timer.publish(every: 1, on: .main, in: .common).autoconnect()) { _ in
viewModel.tickCommissionSmsCountdown()
}
}
///
@ -130,6 +149,32 @@ struct CooperationAcquirerView: View {
}
}
///
private func sendCommissionSms() async {
guard !sendingCommissionSms else { return }
sendingCommissionSms = true
defer { sendingCommissionSms = false }
do {
try await viewModel.sendCommissionRateSms(api: cooperationOrderAPI)
toastCenter.show("验证码已发送")
} catch {
toastCenter.show(error.localizedDescription)
}
}
///
private func saveCommissionRate() async {
guard !submittingCommissionRate else { return }
submittingCommissionRate = true
defer { submittingCommissionRate = false }
do {
try await viewModel.saveCommissionRate(api: cooperationOrderAPI)
toastCenter.show("分成比例已修改")
} catch {
toastCenter.show(error.localizedDescription)
}
}
private func dialPhone(_ phone: String) {
let trimmed = phone.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty, let url = URL(string: "tel:\(trimmed)") else { return }