Files
suixinkan_ios_new/suixinkan/Features/Orders/Views/ReferralOrderSelectView.swift
汉秋 1970572edd 迁移合作订单完整能力,并统一 AppRoleCode 角色权限与首页菜单展示。
新增 CooperationOrder 模块(双 Tab 列表、获客员绑定、主 Tab 扫码)、订单来源/带客单绑定及配套 API 测试;同步引入 roleCode 权限匹配与相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 13:33:00 +08:00

313 lines
11 KiB
Swift
Raw 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.

//
// ReferralOrderSelectView.swift
// suixinkan
//
// Created by Codex on 2026/6/29.
//
import SwiftUI
import Combine
/// ViewModel
@MainActor
final class ReferralOrderSelectViewModel: ObservableObject {
enum Step {
case selectAcquirer
case selectReferralOrder
}
@Published var step: Step = .selectAcquirer
@Published var acquirers: [CooperativeSaler] = []
@Published var selectedAcquirer: CooperativeSaler?
@Published var referralOrders: [ReferralOrder] = []
@Published var selectedReferralOrder: ReferralOrder?
@Published var isLoadingMore = false
@Published var canLoadMore = false
@Published private(set) var hasLoadedAcquirersOnce = false
@Published private(set) var hasLoadedReferralOrdersOnce = false
private var orderNumber = ""
private var currentPage = 1
private var totalCount = 0
private var isReloadingReferralOrders = false
private let pageSize = 10
///
func initOrder(orderNumber: String, api: CooperationOrderServing, storeId: Int?) async {
if self.orderNumber == orderNumber && !acquirers.isEmpty { return }
self.orderNumber = orderNumber
await loadAcquirers(api: api, storeId: storeId)
}
private func loadAcquirers(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: 50)
acquirers = data.list
if data.list.count == 1 {
await selectAcquirer(data.list[0], api: api)
} else {
step = .selectAcquirer
}
} catch {
if !hasLoadedAcquirersOnce {
acquirers = []
}
}
hasLoadedAcquirersOnce = true
}
///
func selectAcquirer(_ acquirer: CooperativeSaler, api: CooperationOrderServing) async {
selectedAcquirer = acquirer
selectedReferralOrder = nil
hasLoadedReferralOrdersOnce = false
step = .selectReferralOrder
await refreshReferralOrders(api: api)
}
///
func backToAcquirerStep() {
if acquirers.count <= 1 { return }
step = .selectAcquirer
selectedReferralOrder = nil
referralOrders = []
}
///
func selectReferralOrder(_ order: ReferralOrder) {
selectedReferralOrder = order
}
///
func refreshReferralOrders(api: CooperationOrderServing) async {
guard let acquirer = selectedAcquirer else { return }
guard !isReloadingReferralOrders else { return }
isReloadingReferralOrders = true
defer { isReloadingReferralOrders = false }
currentPage = 1
isLoadingMore = false
await loadReferralOrders(api: api, saleUserId: acquirer.displayId)
}
///
func loadMoreReferralOrders(api: CooperationOrderServing) async {
guard !isReloadingReferralOrders, !isLoadingMore, canLoadMore,
let acquirer = selectedAcquirer else { return }
isLoadingMore = true
currentPage += 1
await loadReferralOrders(api: api, saleUserId: acquirer.displayId)
}
private func loadReferralOrders(api: CooperationOrderServing, saleUserId: Int) async {
do {
let data = try await api.saleUserReferralOrders(
saleUserId: saleUserId,
page: currentPage,
pageSize: pageSize
)
totalCount = data.total
if currentPage == 1 {
referralOrders = data.list
} else {
referralOrders.append(contentsOf: data.list)
}
canLoadMore = referralOrders.count < totalCount
} catch {
if currentPage == 1, !hasLoadedReferralOrdersOnce {
referralOrders = []
}
if currentPage > 1 {
currentPage = max(currentPage - 1, 1)
}
}
if currentPage == 1 {
hasLoadedReferralOrdersOnce = true
}
isLoadingMore = false
}
///
func confirmBind(api: CooperationOrderServing) async throws {
guard let referralOrder = selectedReferralOrder else {
throw NSError(domain: "ReferralOrder", code: 1, userInfo: [NSLocalizedDescriptionKey: "请选择带客单"])
}
guard !orderNumber.isEmpty else {
throw NSError(domain: "ReferralOrder", code: 2, userInfo: [NSLocalizedDescriptionKey: "订单号无效"])
}
try await api.saleUserBindReferralOrder(
orderNumber: orderNumber,
referralOrderNo: referralOrder.displayReferralOrderNo
)
}
}
///
struct ReferralOrderSelectView: View {
@EnvironmentObject private var accountContext: AccountContext
@EnvironmentObject private var toastCenter: ToastCenter
@Environment(\.cooperationOrderAPI) private var cooperationOrderAPI
@Environment(\.globalLoading) private var globalLoading
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel = ReferralOrderSelectViewModel()
let orderNumber: String
let onBound: () -> Void
@State private var binding = false
var body: some View {
Group {
if viewModel.step == .selectAcquirer {
acquirerList
} else {
referralOrderList
}
}
.navigationTitle(viewModel.step == .selectAcquirer ? "选择获客员" : "选择带客单")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button {
if viewModel.step == .selectReferralOrder && viewModel.acquirers.count > 1 {
viewModel.backToAcquirerStep()
} else {
dismiss()
}
} label: {
Image(systemName: "chevron.backward")
}
}
}
.safeAreaInset(edge: .bottom) {
if viewModel.step == .selectReferralOrder {
Button {
Task { await confirmBind() }
} label: {
Text(binding ? "提交中..." : "确认绑定")
.font(.system(size: 16))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.frame(height: 48)
.background(
viewModel.selectedReferralOrder != nil
? OrderSourceColors.primary
: OrderSourceColors.primary.opacity(0.4),
in: RoundedRectangle(cornerRadius: 8)
)
}
.buttonStyle(.plain)
.disabled(viewModel.selectedReferralOrder == nil || binding)
.padding(.horizontal, 16)
.padding(.vertical, 10)
.background(.white)
}
}
.background(OrderSourceColors.pageBg.ignoresSafeArea())
.task {
await globalLoading.withLoading {
await viewModel.initOrder(
orderNumber: orderNumber,
api: cooperationOrderAPI,
storeId: accountContext.currentStore?.id
)
}
if viewModel.acquirers.isEmpty {
toastCenter.show("暂无合作获客员,请先绑定获客员")
dismiss()
}
}
}
private func selectAcquirer(_ acquirer: CooperativeSaler) async {
await globalLoading.withLoading {
await viewModel.selectAcquirer(acquirer, api: cooperationOrderAPI)
}
}
private var acquirerList: some View {
ScrollView {
LazyVStack(spacing: 12) {
ForEach(viewModel.acquirers, id: \.displayId) { acquirer in
AcquirerSelectCard(
name: acquirer.displayName,
phone: acquirer.displayPhone
) {
Task { await selectAcquirer(acquirer) }
}
}
}
.padding(.horizontal, 14)
.padding(.vertical, 12)
}
}
private var referralOrderList: some View {
ScrollView {
LazyVStack(spacing: 12) {
if let acquirer = viewModel.selectedAcquirer, !acquirer.displayName.isEmpty {
Text("获客员:\(acquirer.displayName)")
.font(.system(size: 13))
.foregroundStyle(OrderSourceColors.text666)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.bottom, 4)
}
if viewModel.hasLoadedReferralOrdersOnce && viewModel.referralOrders.isEmpty {
Text("暂无可绑定带客单")
.font(.system(size: 14))
.foregroundStyle(OrderSourceColors.text999)
.frame(maxWidth: .infinity)
.padding(.vertical, 32)
} else {
ForEach(Array(viewModel.referralOrders.enumerated()), id: \.element.id) { index, order in
let selected = viewModel.selectedReferralOrder?.id == order.id
ReferralOrderSelectCard(
order: order,
selected: selected
) {
viewModel.selectReferralOrder(order)
}
.onAppear {
guard index >= viewModel.referralOrders.count - 3 else { return }
guard viewModel.canLoadMore, !viewModel.isLoadingMore else { return }
Task { await viewModel.loadMoreReferralOrders(api: cooperationOrderAPI) }
}
}
if viewModel.isLoadingMore {
ProgressView()
.tint(OrderSourceColors.primary)
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
}
}
}
.padding(.horizontal, 14)
.padding(.vertical, 12)
}
.refreshable {
await globalLoading.withOptionalLoading(false) {
await viewModel.refreshReferralOrders(api: cooperationOrderAPI)
}
}
}
private func confirmBind() async {
binding = true
defer { binding = false }
do {
try await viewModel.confirmBind(api: cooperationOrderAPI)
onBound()
dismiss()
} catch {
toastCenter.show(error.localizedDescription)
}
}
}
/// Sheet
struct OrderNumberSheetItem: Identifiable {
let orderNumber: String
var id: String { orderNumber }
}