迁移合作订单完整能力,并统一 AppRoleCode 角色权限与首页菜单展示。
新增 CooperationOrder 模块(双 Tab 列表、获客员绑定、主 Tab 扫码)、订单来源/带客单绑定及配套 API 测试;同步引入 roleCode 权限匹配与相关单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
312
suixinkan/Features/Orders/Views/ReferralOrderSelectView.swift
Normal file
312
suixinkan/Features/Orders/Views/ReferralOrderSelectView.swift
Normal file
@ -0,0 +1,312 @@
|
||||
//
|
||||
// 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user