// // MainTabBarController.swift // suixinkan // import UIKit @MainActor /// 主 Tab 容器,使用系统 TabBar 展示四个一级入口及中间扫码按钮。 final class MainTabBarController: UITabBarController { private let services: AppServices private let badgeViewModel = MainTabBadgeViewModel() private var tabNavigationControllers: [AppTab: TabNavigationController] = [:] private var isSyncingTabSelection = false /// 测试时可注入扫码点击处理,生产环境为空时执行真实扫码展示。 var onScanTabSelected: (() -> Void)? /// 主 TabBar 固定槽位,扫码槽位只负责触发全局扫码,不参与业务路由。 private enum TabSlot: CaseIterable { case home case orders case scan case statistics case profile /// 返回槽位对应的业务 Tab,扫码槽位无业务根页面。 var appTab: AppTab? { switch self { case .home: return .home case .orders: return .orders case .scan: return nil case .statistics: return .statistics case .profile: return .profile } } /// 返回指定业务 Tab 在系统 TabBar 中的槽位索引。 static func index(for tab: AppTab) -> Int? { allCases.firstIndex { $0.appTab == tab } } /// 返回扫码槽位在系统 TabBar 中的索引。 static var scanIndex: Int? { allCases.firstIndex(of: .scan) } } /// 初始化实例。 init(services: AppServices) { self.services = services super.init(nibName: nil, bundle: nil) delegate = self } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// 视图加载完成后的 UI 初始化与数据绑定。 override func viewDidLoad() { super.viewDidLoad() configureTabBarAppearance() configureTabs() services.appNavigator.attach(host: self) bindBadgeViewModel() bindAccountContext() Task { await refreshOrderBadge() } #if DEBUG Task { await AppUITestRouteDriver.applyIfNeeded(services: services) } #endif } /// 配置 TabBar 外观,对齐参考工程 CustomMainTabBar 样式。 private func configureTabBarAppearance() { tabBar.tintColor = AppDesign.primary tabBar.backgroundColor = .white tabBar.shadowImage = UIImage() let normalAttributes: [NSAttributedString.Key: Any] = [ .foregroundColor: UIColor(hex: 0x7D8DA3), .font: UIFont.systemFont(ofSize: 12, weight: .regular) ] let selectedAttributes: [NSAttributedString.Key: Any] = [ .foregroundColor: AppDesign.primary, .font: UIFont.systemFont(ofSize: 12, weight: .medium) ] let appearance = UITabBarAppearance() appearance.configureWithOpaqueBackground() appearance.backgroundColor = .white appearance.shadowColor = .clear appearance.stackedLayoutAppearance.normal.titleTextAttributes = normalAttributes appearance.stackedLayoutAppearance.selected.titleTextAttributes = selectedAttributes tabBar.standardAppearance = appearance tabBar.scrollEdgeAppearance = appearance } /// 为每个业务 Tab 创建独立导航栈,并在中间插入系统扫码槽位。 private func configureTabs() { tabNavigationControllers.removeAll() viewControllers = TabSlot.allCases.map { slot in if let tab = slot.appTab { let navigationController = TabNavigationController(tab: tab, services: services) navigationController.tabBarItem = tab.makeTabBarItem() tabNavigationControllers[tab] = navigationController return navigationController } let scannerPlaceholder = UIViewController() scannerPlaceholder.tabBarItem = MainScanTabItem.makeTabBarItem() return scannerPlaceholder } } /// 绑定角标 ViewModel 回调。 private func bindBadgeViewModel() { badgeViewModel.onChange = { [weak self] in self?.updateOrderBadge() } } /// 绑定账号上下文,景区或门店变化时刷新角标。 private func bindAccountContext() { services.accountContext.onChange = { [weak self] in Task { await self?.refreshOrderBadge() } } } /// 更新订单 Tab 待核销角标。 private func updateOrderBadge() { let badgeValue: String? if let count = badgeViewModel.pendingWriteOffCount, count > 0 { badgeValue = "\(count)" } else { badgeValue = nil } tabNavigationControllers[.orders]?.tabBarItem.badgeValue = badgeValue } /// 刷新订单 Tab 待核销角标数据。 private func refreshOrderBadge() async { await badgeViewModel.refreshPendingWriteOffCount( api: services.ordersAPI, scenicId: services.accountContext.currentScenic?.id, storeId: services.accountContext.currentStore?.id ) } /// 从 TabBar 中间按钮打开全局扫码页,成功后路由至订单核销入口。 private func presentGlobalScanner() { guard presentedViewController == nil else { return } let scanner = OrderCodeScannerViewController() scanner.onScanResult = { [weak self, weak scanner] result in scanner?.dismiss(animated: true) guard let self else { return } switch result { case .success(let code): self.services.appNavigator.openOrdersEntry(.verificationOrders, scannedCode: code) case .failure(let error): self.services.toastCenter.show(error.localizedDescription) } } let navigationController = UINavigationController(rootViewController: scanner) navigationController.modalPresentationStyle = .fullScreen present(navigationController, animated: true) } } extension MainTabBarController: UITabBarControllerDelegate { /// 用户选择 TabBar 槽位前拦截扫码入口,使关闭扫码页后保留原页面。 func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { guard viewControllers?.firstIndex(of: viewController) == TabSlot.scanIndex else { return true } if let onScanTabSelected { onScanTabSelected() } else { presentGlobalScanner() } return false } /// 用户点击 TabBar 时刷新订单角标。 func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { guard !isSyncingTabSelection, let navigationController = viewController as? TabNavigationController else { return } if navigationController.appTab == .orders { Task { await refreshOrderBadge() } } } } extension MainTabBarController: AppNavigationHost { /// 选中指定主 Tab。 func selectTab(_ tab: AppTab) { guard let index = TabSlot.index(for: tab) else { return } guard selectedIndex != index else { return } isSyncingTabSelection = true selectedIndex = index isSyncingTabSelection = false if tab == .orders { Task { await refreshOrderBadge() } } } /// 返回指定 Tab 的导航控制器。 func navigationController(for tab: AppTab) -> TabNavigationController? { tabNavigationControllers[tab] } /// 返回当前选中 Tab 的导航控制器。 func selectedNavigationController() -> TabNavigationController? { selectedViewController as? TabNavigationController } /// 重置全部 Tab 的导航栈。 func resetAllStacks() { for navigationController in tabNavigationControllers.values { navigationController.popToRootViewController(animated: false) } selectTab(.home) } /// 将订单入口和可选扫码结果应用到订单根页面。 func applyOrdersEntry(_ entry: OrdersEntry, scannedCode: String?) { guard let ordersController = tabNavigationControllers[.orders]?.viewControllers.first as? OrdersViewController else { return } ordersController.applyOrdersEntry(entry, scannedCode: scannedCode) } }