Lower deployment target to iOS 16, replace @Observable with ObservableObject, add navigation and UI compatibility shims, and include login smoke UI tests. Co-authored-by: Cursor <cursoragent@cursor.com>
139 lines
4.2 KiB
Swift
139 lines
4.2 KiB
Swift
//
|
||
// NavigationRouter.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/18.
|
||
//
|
||
|
||
import Combine
|
||
import SwiftUI
|
||
|
||
/// 应用内可导航目标,承载各 Tab 内部的路由目的地。
|
||
enum AppRoute: Hashable {
|
||
case placeholder(title: String)
|
||
case home(HomeRoute)
|
||
case profile(ProfileRoute)
|
||
case orders(OrdersRoute)
|
||
|
||
/// 返回该路由通过 NavigationStack push 后是否隐藏底部 TabBar。
|
||
var hidesTabBarWhenPushed: Bool {
|
||
true
|
||
}
|
||
|
||
/// 构建当前路由对应的目标页面。
|
||
@ViewBuilder
|
||
var destinationView: some View {
|
||
switch self {
|
||
case .placeholder(let title):
|
||
PlaceholderDetailView(title: title)
|
||
case .home(let route):
|
||
route.destinationView
|
||
case .profile(let route):
|
||
route.destinationView
|
||
case .orders(let route):
|
||
route.destinationView
|
||
}
|
||
}
|
||
}
|
||
|
||
extension OrdersRoute {
|
||
/// 构建订单模块二级路由对应的目标页面。
|
||
@ViewBuilder
|
||
var destinationView: some View {
|
||
switch self {
|
||
case .storeDetail(let item):
|
||
StoreOrderDetailView(item: item)
|
||
case .writeOffDetail(let item):
|
||
WriteOffOrderDetailView(item: item)
|
||
case .depositDetail(let orderNumber):
|
||
DepositOrderDetailView(orderNumber: orderNumber)
|
||
case .depositShootingInfo(let orderNumber, let scenicSpotId, let photogUid):
|
||
DepositOrderShootingInfoView(orderNumber: orderNumber, scenicSpotId: scenicSpotId, photogUid: photogUid)
|
||
case .historicalShooting(let orderNumber):
|
||
HistoricalShootingInfoView(orderNumber: orderNumber)
|
||
case .multiTravelTaskUpload(let orderNumber):
|
||
MultiTravelTaskUploadView(initialOrderNumber: orderNumber)
|
||
case .orderTrailer(let orderNumber, let title):
|
||
OrderTrailerView(orderNumber: orderNumber, title: title)
|
||
}
|
||
}
|
||
}
|
||
|
||
@MainActor
|
||
/// 单个 NavigationStack 的路径容器,用于保存某个 Tab 的导航历史。
|
||
final class RouterPath: ObservableObject {
|
||
@Published var path: [AppRoute] = []
|
||
|
||
/// 将指定路由压入当前 Tab 的导航栈。
|
||
func navigate(to route: AppRoute) {
|
||
path.append(route)
|
||
}
|
||
|
||
/// 清空当前 Tab 的导航栈。
|
||
func reset() {
|
||
path = []
|
||
}
|
||
}
|
||
|
||
@MainActor
|
||
/// 主导航状态中心,管理当前 Tab 和每个 Tab 独立的 NavigationStack 路径。
|
||
final class AppRouter: ObservableObject {
|
||
@Published var selectedTab: AppTab = .home
|
||
@Published var selectedOrdersEntry: OrdersEntry = .storeOrders
|
||
@Published private(set) var pendingOrderScanCode: String?
|
||
@Published private var routers: [AppTab: RouterPath] = [:]
|
||
|
||
/// 获取指定 Tab 对应的路由路径容器,不存在时自动创建。
|
||
func router(for tab: AppTab) -> RouterPath {
|
||
if let router = routers[tab] {
|
||
return router
|
||
}
|
||
|
||
let router = RouterPath()
|
||
routers[tab] = router
|
||
return router
|
||
}
|
||
|
||
/// 生成 SwiftUI NavigationStack 可使用的路径绑定。
|
||
func binding(for tab: AppTab) -> Binding<[AppRoute]> {
|
||
let router = router(for: tab)
|
||
|
||
return Binding(
|
||
get: { router.path },
|
||
set: { router.path = $0 }
|
||
)
|
||
}
|
||
|
||
/// 切换到指定主 Tab。
|
||
func select(_ tab: AppTab) {
|
||
selectedTab = tab
|
||
}
|
||
|
||
/// 切换到订单 Tab,并指定订单内部子入口。
|
||
func selectOrders(entry: OrdersEntry) {
|
||
selectedOrdersEntry = entry
|
||
selectedTab = .orders
|
||
}
|
||
|
||
/// 切换到核销订单入口,并暂存全局扫码结果等待订单页消费。
|
||
func routeToOrderVerification(scannedCode: String) {
|
||
pendingOrderScanCode = scannedCode
|
||
selectOrders(entry: .verificationOrders)
|
||
}
|
||
|
||
/// 读取并清空待处理的全局扫码结果,确保同一次扫码只被处理一次。
|
||
func consumePendingOrderScanCode() -> String? {
|
||
let code = pendingOrderScanCode
|
||
pendingOrderScanCode = nil
|
||
return code
|
||
}
|
||
|
||
/// 重置主 Tab 选择和所有 Tab 的导航历史。
|
||
func reset() {
|
||
selectedTab = .home
|
||
selectedOrdersEntry = .storeOrders
|
||
pendingOrderScanCode = nil
|
||
routers.values.forEach { $0.reset() }
|
||
}
|
||
}
|