Files
suixinkan_ios_new/suixinkan/Features/CooperationOrder/Views/CooperationAcquirerView.swift
汉秋 49e997ddba 修复合作获客扫码绑定崩溃与 Toast 层级,并接入分成比例修改。
将扫码页提升到列表层弹出并补齐 AppDelegate.window,避免扫码时 UIKit 取主窗口崩溃;Toast 改用独立 UIWindow 显示在 sheet 之上;合作获客员支持短信验证修改分成比例,同时优化冷启动根视图创建时机并补充相关测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 20:37:24 +08:00

184 lines
7.0 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// CooperationAcquirerView.swift
// suixinkan
//
// Created by Codex on 2026/6/29.
//
import SwiftUI
import Combine
///
struct CooperationAcquirerView: View {
@EnvironmentObject private var accountContext: AccountContext
@EnvironmentObject private var toastCenter: ToastCenter
@Environment(\.cooperationOrderAPI) private var cooperationOrderAPI
@Environment(\.globalLoading) private var globalLoading
@Environment(\.openURL) private var openURL
@StateObject private var viewModel = CooperationAcquirerViewModel()
@State private var showRemarkAlert = false
@State private var remarkEditingAcquirer: CooperativeSaler?
@State private var remarkDraft = ""
@State private var sendingCommissionSms = false
@State private var submittingCommissionRate = false
let onScanBind: () -> Void
var body: some View {
ScrollView {
LazyVStack(spacing: 12) {
if viewModel.hasLoadedOnce && viewModel.acquirers.isEmpty {
Text("暂无合作获客员")
.font(.system(size: 14))
.foregroundStyle(CooperationOrderColors.text999)
.frame(maxWidth: .infinity)
.padding(.vertical, 48)
} else {
ForEach(viewModel.acquirers, id: \.displayId) { acquirer in
CooperativeSalerCard(acquirer: acquirer) { phone in
dialPhone(phone)
} onEditRemark: {
remarkEditingAcquirer = acquirer
remarkDraft = acquirer.photogRemarkName
showRemarkAlert = true
} onEditCommission: {
viewModel.startEditCommissionRate(acquirer)
}
}
}
}
.padding(.horizontal, 14)
.padding(.vertical, 12)
}
.navigationTitle("合作获客员")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("扫码绑定") {
onScanBind()
}
.font(.system(size: 14))
.foregroundStyle(CooperationOrderColors.primary)
}
}
.background(CooperationOrderColors.pageBg.ignoresSafeArea())
.refreshable {
await reload(showLoading: false)
}
.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) {
remarkEditingAcquirer = nil
}
Button("保存") {
// alert dismiss state
guard let acquirer = remarkEditingAcquirer else { return }
if let message = CooperationOrderRemarkName.validationMessage(for: remarkDraft) {
toastCenter.show(message)
return
}
let saleUserId = acquirer.saleUserId
let draft = remarkDraft
showRemarkAlert = false
remarkEditingAcquirer = nil
Task {
await saveRemark(saleUserId: saleUserId, draft: draft)
}
}
} message: {
if let acquirer = remarkEditingAcquirer {
Text("\(acquirer.salerRealName.cooperationNonEmptyOrDefault("")) 设置备注名")
}
}
.onReceive(Timer.publish(every: 1, on: .main, in: .common).autoconnect()) { _ in
viewModel.tickCommissionSmsCountdown()
}
}
///
private func saveRemark(saleUserId: Int, draft: String) async {
guard saleUserId > 0 else { return }
await globalLoading.withOptionalLoading(true) {
do {
try await viewModel.updateRemark(
saleUserId: saleUserId,
remark: draft,
api: cooperationOrderAPI
)
toastCenter.show("备注已保存")
} catch {
toastCenter.show(error.localizedDescription)
}
}
}
private func reload(showLoading: Bool) async {
await globalLoading.withOptionalLoading(showLoading) {
await viewModel.reload(api: cooperationOrderAPI, storeId: accountContext.currentStore?.id)
}
}
///
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 }
openURL(url)
}
}