迁移合作订单完整能力,并统一 AppRoleCode 角色权限与首页菜单展示。

新增 CooperationOrder 模块(双 Tab 列表、获客员绑定、主 Tab 扫码)、订单来源/带客单绑定及配套 API 测试;同步引入 roleCode 权限匹配与相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 13:33:00 +08:00
parent d310d26293
commit 1970572edd
66 changed files with 4247 additions and 201 deletions

View File

@ -0,0 +1,70 @@
//
// BindAcquirerViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/29.
//
import Foundation
import Combine
/// ViewModel
@MainActor
final class BindAcquirerViewModel: ObservableObject {
@Published var inviteInfo: SaleUserInviteInfo?
@Published var isLoading = false
@Published var isAlreadyBound = false
@Published var errorMessage: String?
private var saleUserId = 0
///
func load(api: CooperationOrderServing, saleUserId: Int) async {
guard saleUserId > 0 else {
errorMessage = "获客员信息无效"
return
}
if self.saleUserId == saleUserId && (inviteInfo != nil || isAlreadyBound) { return }
self.saleUserId = saleUserId
isAlreadyBound = false
isLoading = true
errorMessage = nil
defer { isLoading = false }
do {
var info = try await api.saleUserInviteInfo(saleUserId: saleUserId)
if info.saleUserId <= 0 {
info = SaleUserInviteInfo(
saleUserId: saleUserId,
isBound: info.isBound,
photographerName: info.photographerName,
photographerPhone: info.photographerPhone,
salerName: info.salerName,
salerPhone: info.salerPhone,
shareCode: info.shareCode,
subtitle: info.subtitle,
title: info.title
)
}
if info.isBound {
isAlreadyBound = true
}
inviteInfo = info
} catch {
handleBindError(error.localizedDescription.isEmpty ? "获取邀请信息失败" : error.localizedDescription)
}
}
///
func acceptBind(api: CooperationOrderServing) async throws {
guard saleUserId > 0, !isAlreadyBound else { return }
try await api.saleUserAcceptBind(saleUserId: saleUserId)
}
///
func handleBindError(_ message: String) {
errorMessage = message
if message.contains("已绑定") {
isAlreadyBound = true
}
}
}

View File

@ -0,0 +1,30 @@
//
// 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
///
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
}
}

View File

@ -0,0 +1,130 @@
//
// CooperationOrderListViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/29.
//
import Foundation
import Combine
/// ViewModel线 Tab
@MainActor
final class CooperationOrderListViewModel: ObservableObject {
@Published var selectedTab = 0
@Published var searchText = ""
@Published var leads: [ReferralLead] = []
@Published var orders: [AcquisitionOrder] = []
@Published var isLoadingMore = false
@Published var canLoadMore = false
@Published private(set) var hasLoadedLeadsOnce = false
@Published private(set) var hasLoadedOrdersOnce = false
private var leadsPage = 1
private var ordersPage = 1
private var leadsTotal = 0
private var ordersTotal = 0
private var isReloading = false
private let pageSize = 10
/// Tab
func onTabSelected(_ index: Int) {
guard selectedTab != index else { return }
selectedTab = index
}
/// Tab
func refreshCurrentTab(api: CooperationOrderServing) async {
if selectedTab == 0 {
await refreshLeads(api: api)
} else {
await refreshOrders(api: api)
}
}
/// 线
func refreshLeads(api: CooperationOrderServing) async {
guard !isReloading else { return }
isReloading = true
defer { isReloading = false }
leadsPage = 1
isLoadingMore = false
await loadLeads(api: api)
}
///
func refreshOrders(api: CooperationOrderServing) async {
guard !isReloading else { return }
isReloading = true
defer { isReloading = false }
ordersPage = 1
isLoadingMore = false
await loadOrders(api: api)
}
/// Tab
func loadMore(api: CooperationOrderServing) async {
guard !isReloading, !isLoadingMore, canLoadMore else { return }
isLoadingMore = true
if selectedTab == 0 {
leadsPage += 1
await loadLeads(api: api)
} else {
ordersPage += 1
await loadOrders(api: api)
}
}
///
func searchOrders(api: CooperationOrderServing) async {
guard selectedTab == 1 else { return }
await refreshOrders(api: api)
}
private func loadLeads(api: CooperationOrderServing) async {
do {
let data = try await api.saleUserReferralLeads(page: leadsPage, pageSize: pageSize)
leadsTotal = data.total
if leadsPage == 1 {
leads = data.list
} else {
leads.append(contentsOf: data.list)
}
canLoadMore = leads.count < leadsTotal
} catch {
if leadsPage == 1, !hasLoadedLeadsOnce {
leads = []
}
}
if leadsPage == 1 {
hasLoadedLeadsOnce = true
}
isLoadingMore = false
}
private func loadOrders(api: CooperationOrderServing) async {
do {
let keyword = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
let data = try await api.saleUserAcquisitionOrders(
keyword: keyword.isEmpty ? nil : keyword,
page: ordersPage,
pageSize: pageSize
)
ordersTotal = data.total
if ordersPage == 1 {
orders = data.list
} else {
orders.append(contentsOf: data.list)
}
canLoadMore = orders.count < ordersTotal
} catch {
if ordersPage == 1, !hasLoadedOrdersOnce {
orders = []
}
}
if ordersPage == 1 {
hasLoadedOrdersOnce = true
}
isLoadingMore = false
}
}