// // CooperationAcquirerViewModel.swift // suixinkan // // Created by Codex on 2026/6/29. // import Foundation import Combine /// 合作获客员列表 ViewModel。 @MainActor final class CooperationAcquirerViewModel: ObservableObject { @Published var acquirers: [CooperativeSaler] = [] @Published private(set) var hasLoadedOnce = false @Published var commissionEditingAcquirer: CooperativeSaler? @Published var commissionRateDraft = "" @Published var commissionSmsCodeDraft = "" @Published var commissionSmsCountdown = 0 /// 重新加载当前门店下的合作获客员。 func reload(api: CooperationOrderServing, storeId: Int?) async { do { let storeParam = storeId.flatMap { $0 > 0 ? $0 : nil } let data = try await api.cooperativeSalers(storeId: storeParam, page: 1, pageSize: 20) acquirers = data.list } catch { if !hasLoadedOnce { acquirers = [] } } hasLoadedOnce = true } /// 更新指定获客员的备注名,成功后刷新本地列表项。 func updateRemark( saleUserId: Int, remark: String, api: CooperationOrderServing ) async throws { guard saleUserId > 0 else { return } if let message = CooperationOrderRemarkName.validationMessage(for: remark) { throw CooperationOrderRemarkValidationError(message: message) } let trimmedRemark = CooperationOrderRemarkName.trimmed(remark) try await api.saleUserUpdateRemark(saleUserId: saleUserId, photogRemarkName: trimmedRemark) acquirers = acquirers.map { acquirer in guard acquirer.saleUserId == saleUserId else { return acquirer } return acquirer.withPhotogRemarkName(trimmedRemark) } } /// 打开分成比例编辑弹窗。 func startEditCommissionRate(_ acquirer: CooperativeSaler) { commissionEditingAcquirer = acquirer commissionRateDraft = "\(acquirer.commissionRate)" commissionSmsCodeDraft = "" commissionSmsCountdown = 0 } /// 更新分成比例输入,只保留数字且最多 3 位。 func updateCommissionRateDraft(_ text: String) { commissionRateDraft = String(text.filter(\.isNumber).prefix(3)) } /// 更新短信验证码输入,只保留数字且最多 6 位。 func updateCommissionSmsCodeDraft(_ text: String) { commissionSmsCodeDraft = String(text.filter(\.isNumber).prefix(6)) } /// 关闭分成比例编辑弹窗并清理状态。 func dismissCommissionRateDialog() { commissionEditingAcquirer = nil commissionRateDraft = "" commissionSmsCodeDraft = "" commissionSmsCountdown = 0 } /// 发送分成比例修改验证码,成功后启动本地 60 秒倒计时。 func sendCommissionRateSms(api: CooperationOrderServing) async throws { guard commissionSmsCountdown == 0 else { throw CooperationOrderCommissionRateValidationError(message: "请在倒计时结束后重新获取") } guard let acquirer = commissionEditingAcquirer else { return } let phone = acquirer.salerPhone.cooperationNonEmptyOrDefault(acquirer.displayPhone) .trimmingCharacters(in: .whitespacesAndNewlines) guard !phone.isEmpty else { throw CooperationOrderCommissionRateValidationError(message: "获客员手机号为空") } try await api.saleUserCommissionRateSendSmsCode(phone: phone) commissionSmsCountdown = 60 } /// 保存分成比例,成功后用接口返回数据刷新本地列表。 func saveCommissionRate(api: CooperationOrderServing) async throws { guard let acquirer = commissionEditingAcquirer else { return } guard acquirer.saleUserId > 0 else { return } guard let commissionRate = Int(commissionRateDraft.trimmingCharacters(in: .whitespacesAndNewlines)) else { throw CooperationOrderCommissionRateValidationError(message: "请输入分成比例") } guard (0...100).contains(commissionRate) else { throw CooperationOrderCommissionRateValidationError(message: "分成比例需在0到100之间") } let smsCode = commissionSmsCodeDraft.trimmingCharacters(in: .whitespacesAndNewlines) guard !smsCode.isEmpty else { throw CooperationOrderCommissionRateValidationError(message: "请输入验证码") } let updated = try await api.saleUserUpdateCommissionRate( saleUserId: acquirer.saleUserId, commissionRate: commissionRate, smsCode: smsCode ) if updated.saleUserId > 0 { acquirers = acquirers.map { item in item.saleUserId == updated.saleUserId ? updated : item } } dismissCommissionRateDialog() } /// 倒计时递减,由页面定时器驱动。 func tickCommissionSmsCountdown() { if commissionSmsCountdown > 0 { commissionSmsCountdown -= 1 } } } /// 合作获客员备注名校验失败错误。 struct CooperationOrderRemarkValidationError: LocalizedError { let message: String var errorDescription: String? { message } } /// 合作获客员分成比例校验失败错误。 struct CooperationOrderCommissionRateValidationError: LocalizedError { let message: String var errorDescription: String? { message } }