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

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

208 lines
7.2 KiB
Swift
Raw Permalink 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.

//
// MainTabsView.swift
// suixinkan
//
// Created by Codex on 2026/6/18.
//
import SwiftUI
/// Tab 使 TabView
struct MainTabsView: View {
private static let leadingTabs: [AppTab] = [.home, .orders]
private static let trailingTabs: [AppTab] = [.statistics, .profile]
@EnvironmentObject private var accountContext: AccountContext
@EnvironmentObject private var appRouter: AppRouter
@Environment(\.ordersAPI) private var ordersAPI
@EnvironmentObject private var toastCenter: ToastCenter
@StateObject private var badgeViewModel = MainTabBadgeViewModel()
@State private var showScanner = false
@State private var bindAcquirerSaleUserId: Int?
@State private var tabSelection: MainTabSelection = .tab(.home)
@State private var selectedBusinessTab: AppTab = .home
var body: some View {
TabView(selection: $tabSelection) {
ForEach(Self.leadingTabs) { tab in
TabNavigationStackHost(tab: tab)
.tabItem { tabItem(for: tab) }
.tag(MainTabSelection.tab(tab))
.badge(tab == .orders ? badgeViewModel.pendingWriteOffCount ?? 0 : 0)
}
ScannerTabPlaceholder()
.accessibilityIdentifier("main.scan")
.tabItem {
Image("icon_scan")
.accessibilityIdentifier("main.scan")
.accessibilityLabel("扫码核销")
}
.tag(MainTabSelection.scanner)
ForEach(Self.trailingTabs) { tab in
TabNavigationStackHost(tab: tab)
.tabItem { tabItem(for: tab) }
.tag(MainTabSelection.tab(tab))
.badge(tab == .orders ? badgeViewModel.pendingWriteOffCount ?? 0 : 0)
}
}
.fullScreenCover(isPresented: $showScanner) {
OrderScannerPage(
onClose: { showScanner = false },
onSuccess: { rawCode in
showScanner = false
handleGlobalScanResult(rawCode)
},
onFailure: { error in
showScanner = false
toastCenter.show(error.localizedDescription)
}
)
}
.fullScreenCover(item: bindAcquirerBinding) { item in
NavigationStack {
BindAcquirerView(saleUserId: item.saleUserId, onSuccess: {
bindAcquirerSaleUserId = nil
})
}
}
.onAppear {
syncSelection(with: appRouter.selectedTab)
}
.onChange(of: tabSelection) { selection in
handleTabSelection(selection)
}
.task {
await refreshOrderBadge()
}
.onChange(of: appRouter.selectedTab) { selectedTab in
syncSelection(with: selectedTab)
guard selectedTab == .orders else { return }
Task { await refreshOrderBadge() }
}
.onChange(of: accountContext.currentScenic?.id) { _ in
Task { await refreshOrderBadge() }
}
.onChange(of: accountContext.currentStore?.id) { _ in
Task { await refreshOrderBadge() }
}
}
/// Tab
private func refreshOrderBadge() async {
await badgeViewModel.refreshPendingWriteOffCount(
api: ordersAPI,
scenicId: accountContext.currentScenic?.id,
storeId: accountContext.currentStore?.id
)
}
/// Tab
private func handleGlobalScanResult(_ rawCode: String) {
if SaleUserQrParser.isSaleUserBindQR(rawCode) {
guard let saleUserId = SaleUserQrParser.parseSaleUserId(rawCode), saleUserId > 0 else {
toastCenter.show("请扫描正确的获客员二维码")
return
}
bindAcquirerSaleUserId = saleUserId
return
}
appRouter.routeToOrderVerification(scannedCode: rawCode)
}
private var bindAcquirerBinding: Binding<BindAcquirerSheetItem?> {
Binding(
get: { bindAcquirerSaleUserId.map(BindAcquirerSheetItem.init(saleUserId:)) },
set: { bindAcquirerSaleUserId = $0?.saleUserId }
)
}
/// TabBar Tab
private func handleTabSelection(_ selection: MainTabSelection) {
switch selection {
case .tab(let tab):
selectedBusinessTab = tab
guard appRouter.selectedTab != tab else { return }
appRouter.select(tab)
case .scanner:
showScanner = true
restoreBusinessTabSelection()
}
}
/// TabView
private func syncSelection(with tab: AppTab) {
selectedBusinessTab = tab
guard tabSelection != .tab(tab) else { return }
tabSelection = .tab(tab)
}
/// 线 TabBar
private func restoreBusinessTabSelection() {
let tab = selectedBusinessTab
DispatchQueue.main.async {
tabSelection = .tab(tab)
}
}
/// Tab
@ViewBuilder
private func tabItem(for tab: AppTab) -> some View {
Image(appRouter.selectedTab == tab ? tab.selectedImageName : tab.unselectedImageName)
.renderingMode(.original)
Text(tab.title)
}
}
/// TabView AppTab
private enum MainTabSelection: Hashable {
case tab(AppTab)
case scanner
}
/// Tab
private struct TabNavigationStackHost: View {
@EnvironmentObject private var appRouter: AppRouter
let tab: AppTab
var body: some View {
TabNavigationStackContent(tab: tab, router: appRouter.router(for: tab))
}
}
/// Tab RouterPath
private struct TabNavigationStackContent: View {
let tab: AppTab
@ObservedObject var router: RouterPath
var body: some View {
NavigationStack(path: $router.path) {
tab.rootView
.navigationTitle(tab.title)
.navigationDestination(for: AppRoute.self) { route in
route.destinationView
.toolbar(route.hidesTabBarWhenPushed ? .hidden : .visible, for: .tabBar)
}
}
.environmentObject(router)
}
}
/// Tab TabView selection
private struct ScannerTabPlaceholder: View {
var body: some View {
Color.clear
}
}
#Preview {
MainTabsView()
.environmentObject(AppRouter())
.environmentObject(AccountContext())
.environment(\.ordersAPI, OrdersAPI(client: APIClient()))
.environmentObject(ToastCenter())
}