Add scenic selection and cooperation order flows aligned with Android.

Upgrade scenic picker with search, location sorting, permission apply/status, and blocking home dialog; add cooperation order module and order source picker improvements with unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 10:48:43 +08:00
parent 3c4ca7eefb
commit 2bea05b1d9
50 changed files with 5603 additions and 191 deletions

View File

@ -0,0 +1,213 @@
//
// CooperationAcquirerViewModel.swift
// suixinkan
//
import Foundation
/// ViewModel Android `CooperationAcquirerViewModel`
final class CooperationAcquirerViewModel {
private(set) var acquirers: [CooperativeSalerEntity] = []
private(set) var isRefreshing = false
private(set) var remarkEditingAcquirer: CooperativeSalerEntity?
private(set) var remarkDraft = ""
private(set) var commissionEditingAcquirer: CooperativeSalerEntity?
private(set) var commissionRateDraft = ""
private(set) var commissionSmsCodeDraft = ""
private(set) var commissionSmsCountdown = 0
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
var onNavigateBindAcquirer: ((Int) -> Void)?
private let appStore: AppStore
private var commissionSmsTimerTask: Task<Void, Never>?
init(appStore: AppStore = .shared) {
self.appStore = appStore
}
deinit {
commissionSmsTimerTask?.cancel()
}
func loadAcquirers(api: OrderAPI, initial: Bool = false) async {
if initial { isRefreshing = false }
let storeId = appStore.currentStoreId
let storeIdParam = storeId > 0 ? String(storeId) : nil
do {
let response = try await api.cooperativeSalers(storeId: storeIdParam, pageSize: "20")
acquirers = response.list
} catch {
onShowMessage?(error.localizedDescription)
}
isRefreshing = false
notifyStateChange()
}
func refresh(api: OrderAPI) async {
isRefreshing = true
notifyStateChange()
await loadAcquirers(api: api, initial: false)
}
func startEditRemark(_ acquirer: CooperativeSalerEntity) {
remarkEditingAcquirer = acquirer
remarkDraft = acquirer.photogRemarkName
notifyStateChange()
}
func updateRemarkDraft(_ text: String) {
remarkDraft = String(text.prefix(CooperationOrderFeature.remarkNameMaxLength))
notifyStateChange()
}
func dismissRemarkDialog() {
remarkEditingAcquirer = nil
remarkDraft = ""
notifyStateChange()
}
func saveRemark(api: OrderAPI) async {
guard let acquirer = remarkEditingAcquirer else { return }
let saleUserId = acquirer.saleUserId
guard saleUserId > 0 else { return }
let trimmedRemark = remarkDraft.trimmingCharacters(in: .whitespacesAndNewlines)
if let message = CooperationOrderFeature.remarkNameValidationMessage(remarkDraft) {
onShowMessage?(message)
return
}
dismissRemarkDialog()
do {
try await api.saleUserUpdateRemark(saleUserId: saleUserId, photogRemarkName: trimmedRemark)
acquirers = acquirers.map { item in
item.saleUserId == saleUserId ? item.withRemarkName(trimmedRemark) : item
}
onShowMessage?("备注已保存")
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)
}
}
func startEditCommissionRate(_ acquirer: CooperativeSalerEntity) {
stopCommissionSmsTimer()
commissionEditingAcquirer = acquirer
commissionRateDraft = String(acquirer.commissionRate)
commissionSmsCodeDraft = ""
notifyStateChange()
}
func updateCommissionRateDraft(_ text: String) {
commissionRateDraft = String(text.filter(\.isNumber).prefix(3))
notifyStateChange()
}
func updateCommissionSmsCodeDraft(_ text: String) {
commissionSmsCodeDraft = String(text.filter(\.isNumber).prefix(6))
notifyStateChange()
}
func dismissCommissionRateDialog() {
commissionEditingAcquirer = nil
commissionRateDraft = ""
commissionSmsCodeDraft = ""
stopCommissionSmsTimer()
notifyStateChange()
}
func requestCommissionSmsCode(api: OrderAPI) async {
guard let acquirer = commissionEditingAcquirer else { return }
if commissionSmsCountdown > 0 {
onShowMessage?("请在倒计时结束后重新获取")
return
}
let phone = acquirer.salerPhone.isEmpty ? acquirer.displayPhone : acquirer.salerPhone
let trimmedPhone = phone.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmedPhone.isEmpty else {
onShowMessage?("获客员手机号为空")
return
}
do {
try await api.saleUserCommissionRateSendSmsCode(phone: trimmedPhone)
startCommissionSmsTimer()
onShowMessage?("验证码已发送")
} catch {
onShowMessage?(error.localizedDescription)
}
}
func saveCommissionRate(api: OrderAPI) async {
guard let acquirer = commissionEditingAcquirer else { return }
let saleUserId = acquirer.saleUserId
guard saleUserId > 0 else { return }
guard let commissionRate = Int(commissionRateDraft.trimmingCharacters(in: .whitespacesAndNewlines)) else {
onShowMessage?("请输入分成比例")
return
}
guard (0...100).contains(commissionRate) else {
onShowMessage?("分成比例需在0到100之间")
return
}
let smsCode = commissionSmsCodeDraft.trimmingCharacters(in: .whitespacesAndNewlines)
guard !smsCode.isEmpty else {
onShowMessage?("请输入验证码")
return
}
do {
let updated = try await api.saleUserUpdateCommissionRate(
saleUserId: saleUserId,
commissionRate: commissionRate,
smsCode: smsCode
)
dismissCommissionRateDialog()
if updated.saleUserId > 0 {
acquirers = acquirers.map { item in
item.saleUserId == updated.saleUserId ? updated : item
}
} else {
await loadAcquirers(api: api, initial: false)
}
onShowMessage?("分成比例已修改")
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)
}
}
func processScanResult(_ scanResult: String) {
guard let saleUserId = SaleUserQrParser.parseSaleUserId(scanResult), saleUserId > 0 else {
onShowMessage?("请扫描正确的获客员二维码")
return
}
onNavigateBindAcquirer?(saleUserId)
}
private func startCommissionSmsTimer() {
commissionSmsTimerTask?.cancel()
commissionSmsTimerTask = Task { [weak self] in
guard let self else { return }
for second in stride(from: 60, through: 1, by: -1) {
if Task.isCancelled { return }
commissionSmsCountdown = second
notifyStateChange()
try? await Task.sleep(nanoseconds: 1_000_000_000)
}
if !Task.isCancelled {
commissionSmsCountdown = 0
notifyStateChange()
}
}
}
private func stopCommissionSmsTimer() {
commissionSmsTimerTask?.cancel()
commissionSmsTimerTask = nil
commissionSmsCountdown = 0
}
private func notifyStateChange() {
onStateChange?()
}
}