Initial commit: suixinkan_ios UIKit rewrite project.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
181
suixinkan_ios/App/Navigation/AppRouteViewControllerFactory.swift
Normal file
181
suixinkan_ios/App/Navigation/AppRouteViewControllerFactory.swift
Normal file
@ -0,0 +1,181 @@
|
||||
//
|
||||
// AppRouteViewControllerFactory.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
/// 应用路由 ViewController 工厂,将 `AppRoute` 及各模块路由映射为 UIKit 页面。
|
||||
enum AppRouteViewControllerFactory {
|
||||
|
||||
/// 根据路由创建目标 ViewController;未迁移页面使用占位页。
|
||||
static func makeViewController(for route: AppRoute, services: AppServices) -> UIViewController {
|
||||
switch route {
|
||||
case .placeholder(let title):
|
||||
return PlaceholderViewController(title: title)
|
||||
case .home(let homeRoute):
|
||||
return makeViewController(for: homeRoute, services: services)
|
||||
case .profile(let profileRoute):
|
||||
return makeViewController(for: profileRoute, services: services)
|
||||
case .orders(let ordersRoute):
|
||||
return makeViewController(for: ordersRoute, services: services)
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据首页二级路由创建 ViewController。
|
||||
static func makeViewController(for route: HomeRoute, services: AppServices) -> UIViewController {
|
||||
switch route {
|
||||
case .profileSpace:
|
||||
return ProfileViewController()
|
||||
case .scenicSelection:
|
||||
return ScenicSelectionViewController()
|
||||
case .permissionApply:
|
||||
return PermissionApplyViewController()
|
||||
case .permissionApplyStatus:
|
||||
return PermissionApplyStatusViewController()
|
||||
case .scenicApplication:
|
||||
return ScenicApplicationViewController()
|
||||
case .moreFunctions:
|
||||
return HomeMoreFunctionsViewController()
|
||||
case .settings:
|
||||
return SettingsViewController()
|
||||
case .paymentCollection:
|
||||
return PaymentCollectionViewController()
|
||||
case .wallet:
|
||||
return WalletViewController()
|
||||
case .taskManagement:
|
||||
return TaskManagementViewController()
|
||||
case .taskCreate:
|
||||
return TaskCreateViewController()
|
||||
case .taskDetail(let id, let summary):
|
||||
return TaskDetailViewController(taskId: id, summary: summary)
|
||||
case .projectManagement:
|
||||
return ProjectManagementViewController()
|
||||
case .pmProjectManagement:
|
||||
return StoreProjectManagementViewController()
|
||||
case .projectDetail(let id, let storeMode):
|
||||
return ProjectDetailViewController(projectId: id, storeMode: storeMode)
|
||||
case .projectEditor(let id, let storeMode):
|
||||
if storeMode {
|
||||
return StoreProjectEditorViewController(projectId: id)
|
||||
}
|
||||
return ProjectEditorViewController(projectId: id)
|
||||
case .scheduleManagement:
|
||||
return ScheduleManagementViewController()
|
||||
case .scheduleAdd:
|
||||
return ScheduleAddViewController()
|
||||
case .photographerInvite:
|
||||
return PhotographerInviteViewController()
|
||||
case .inviteRecord:
|
||||
return InviteRecordViewController()
|
||||
case .cloudStorage:
|
||||
return CloudStorageViewController()
|
||||
case .cloudStorageTransit:
|
||||
return CloudStorageTransitViewController()
|
||||
case .materialLibrary:
|
||||
return MediaLibraryViewController(kind: .material)
|
||||
case .materialUpload:
|
||||
return MediaLibraryUploadViewController(kind: .material)
|
||||
case .sampleLibrary:
|
||||
return MediaLibraryViewController(kind: .sample)
|
||||
case .sampleUpload:
|
||||
return MediaLibraryUploadViewController(kind: .sample)
|
||||
case .albumList:
|
||||
return AlbumListViewController()
|
||||
case .albumTrailer:
|
||||
return AlbumTrailerViewController()
|
||||
case .punchPointList:
|
||||
return PunchPointListViewController()
|
||||
case .punchPointDetail(let id, let summary):
|
||||
return PunchPointDetailViewController(punchPointId: id, summary: summary)
|
||||
case .punchPointEditor(let id):
|
||||
return PunchPointEditorViewController(punchPointId: id)
|
||||
case .punchPointQR(_, let title, let qrURL):
|
||||
return PunchPointQRViewController(title: title, qrURL: qrURL)
|
||||
case .locationReport:
|
||||
return LocationReportViewController()
|
||||
case .locationReportHistory:
|
||||
return LocationReportHistoryViewController()
|
||||
case .depositOrders:
|
||||
return DepositOrderListViewController()
|
||||
case .withdrawalAudit:
|
||||
return WithdrawalAuditViewController()
|
||||
case .scenicSettlement:
|
||||
return ScenicSettlementViewController()
|
||||
case .scenicSettlementReview:
|
||||
return ScenicSettlementReviewViewController()
|
||||
case .messageCenter:
|
||||
return MessageCenterViewController()
|
||||
case .queueManagement:
|
||||
return QueueManagementViewController()
|
||||
case .liveManagement:
|
||||
return LiveManagementViewController()
|
||||
case .liveAlbum:
|
||||
return LiveAlbumViewController()
|
||||
case .operatingArea:
|
||||
return OperatingAreaViewController()
|
||||
case .pilotCertification:
|
||||
return PilotCertificationViewController()
|
||||
case .modulePlaceholder(_, let title):
|
||||
return FeaturePlaceholderViewController(title: title, uri: homeRouteURI(route))
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据个人中心二级路由创建 ViewController。
|
||||
static func makeViewController(for route: ProfileRoute, services: AppServices) -> UIViewController {
|
||||
switch route {
|
||||
case .accountSwitch:
|
||||
return AccountSwitchViewController()
|
||||
case .realNameAuth:
|
||||
return RealNameAuthViewController()
|
||||
case .settings:
|
||||
return SettingsViewController()
|
||||
case .agreement(let page):
|
||||
return AgreementViewController(page: page)
|
||||
#if DEBUG
|
||||
case .debugHomeMenus:
|
||||
return HomeMoreFunctionsViewController()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据订单模块二级路由创建 ViewController。
|
||||
static func makeViewController(for route: OrdersRoute, services: AppServices) -> UIViewController {
|
||||
switch route {
|
||||
case .storeDetail(let item):
|
||||
return StoreOrderDetailViewController(item: item)
|
||||
case .writeOffDetail(let item):
|
||||
return WriteOffOrderDetailViewController(item: item)
|
||||
case .depositDetail(let orderNumber):
|
||||
return DepositOrderDetailViewController(orderNumber: orderNumber)
|
||||
case .depositShootingInfo(let orderNumber, let scenicSpotId, let photogUid):
|
||||
return DepositOrderShootingInfoViewController(
|
||||
orderNumber: orderNumber,
|
||||
scenicSpotId: scenicSpotId,
|
||||
photogUid: photogUid
|
||||
)
|
||||
case .historicalShooting(let orderNumber):
|
||||
return HistoricalShootingInfoViewController(orderNumber: orderNumber)
|
||||
case .multiTravelTaskUpload(let orderNumber):
|
||||
return MultiTravelTaskUploadViewController(initialOrderNumber: orderNumber)
|
||||
case .orderTrailer(let orderNumber, let title):
|
||||
return FeaturePlaceholderViewController(title: title, uri: "order_trailer:\(orderNumber)")
|
||||
}
|
||||
}
|
||||
|
||||
private static func homeRouteURI(_ route: HomeRoute) -> String {
|
||||
if case let .modulePlaceholder(uri, _) = route {
|
||||
return uri
|
||||
}
|
||||
return String(describing: route)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 首页路由 ViewController 工厂,供 `UIKitAppNavigation` 复用。
|
||||
enum HomeRouteViewControllerFactory {
|
||||
static func make(for route: HomeRoute) -> UIViewController {
|
||||
AppRouteViewControllerFactory.makeViewController(for: route, services: AppServices.shared)
|
||||
}
|
||||
}
|
||||
58
suixinkan_ios/App/Navigation/AppTab.swift
Normal file
58
suixinkan_ios/App/Navigation/AppTab.swift
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// AppTab.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// 主 Tab 实体,描述底部导航中的每一个一级入口。
|
||||
enum AppTab: String, CaseIterable, Identifiable, Hashable {
|
||||
case home
|
||||
case orders
|
||||
case statistics
|
||||
case profile
|
||||
|
||||
var id: Self { self }
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .home:
|
||||
"首页"
|
||||
case .orders:
|
||||
"订单"
|
||||
case .statistics:
|
||||
"数据"
|
||||
case .profile:
|
||||
"我的"
|
||||
}
|
||||
}
|
||||
|
||||
var systemImage: String {
|
||||
switch self {
|
||||
case .home:
|
||||
"house"
|
||||
case .orders:
|
||||
"doc.text"
|
||||
case .statistics:
|
||||
"chart.bar"
|
||||
case .profile:
|
||||
"person"
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建 Tab 根页面 ViewController。
|
||||
func makeRootViewController(services: AppServices) -> UIViewController {
|
||||
switch self {
|
||||
case .home:
|
||||
return HomeViewController()
|
||||
case .orders:
|
||||
return OrdersViewController()
|
||||
case .statistics:
|
||||
return StatisticsViewController()
|
||||
case .profile:
|
||||
return ProfileViewController()
|
||||
}
|
||||
}
|
||||
}
|
||||
109
suixinkan_ios/App/Navigation/NavigationRouter.swift
Normal file
109
suixinkan_ios/App/Navigation/NavigationRouter.swift
Normal file
@ -0,0 +1,109 @@
|
||||
//
|
||||
// NavigationRouter.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 应用内可导航目标,承载各 Tab 内部的路由目的地。
|
||||
enum AppRoute: Hashable {
|
||||
case placeholder(title: String)
|
||||
case home(HomeRoute)
|
||||
case profile(ProfileRoute)
|
||||
case orders(OrdersRoute)
|
||||
|
||||
/// 返回该路由通过 push 后是否隐藏底部 TabBar。
|
||||
var hidesTabBarWhenPushed: Bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 单个导航栈的路径容器,用于保存某个 Tab 的导航历史。
|
||||
final class RouterPath {
|
||||
var onChange: (() -> Void)?
|
||||
var path: [AppRoute] = [] { didSet { onChange?() } }
|
||||
|
||||
/// 将指定路由压入当前 Tab 的导航栈。
|
||||
func navigate(to route: AppRoute) {
|
||||
path.append(route)
|
||||
}
|
||||
|
||||
/// 从当前 Tab 的导航栈弹出最后一级路由。
|
||||
func navigateBack() {
|
||||
guard !path.isEmpty else { return }
|
||||
path.removeLast()
|
||||
}
|
||||
|
||||
/// 清空当前 Tab 的导航栈。
|
||||
func reset() {
|
||||
path = []
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
/// 主导航状态中心,管理当前 Tab 和每个 Tab 独立的导航路径。
|
||||
final class AppRouter {
|
||||
var onChange: (() -> Void)?
|
||||
var selectedTab: AppTab = .home { didSet { onChange?() } }
|
||||
var selectedOrdersEntry: OrdersEntry = .storeOrders { didSet { onChange?() } }
|
||||
private(set) var pendingOrderScanCode: String? { didSet { onChange?() } }
|
||||
|
||||
private let routers: [AppTab: RouterPath]
|
||||
|
||||
init() {
|
||||
var builtRouters: [AppTab: RouterPath] = [:]
|
||||
for tab in AppTab.allCases {
|
||||
builtRouters[tab] = RouterPath()
|
||||
}
|
||||
routers = builtRouters
|
||||
routers.values.forEach { router in
|
||||
router.onChange = { [weak self] in
|
||||
self?.onChange?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定 Tab 对应的路由路径容器。
|
||||
func router(for tab: AppTab) -> RouterPath {
|
||||
guard let router = routers[tab] else {
|
||||
assertionFailure("Missing router path for tab: \(tab)")
|
||||
return RouterPath()
|
||||
}
|
||||
return router
|
||||
}
|
||||
|
||||
/// 切换到指定主 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() }
|
||||
}
|
||||
}
|
||||
111
suixinkan_ios/App/Navigation/TabNavigationController.swift
Normal file
111
suixinkan_ios/App/Navigation/TabNavigationController.swift
Normal file
@ -0,0 +1,111 @@
|
||||
//
|
||||
// TabNavigationController.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
/// 单个 Tab 的导航栈,与 `AppRouter` 路径同步并在 push 时隐藏底部 TabBar。
|
||||
final class TabNavigationController: UINavigationController, UINavigationControllerDelegate {
|
||||
|
||||
let appTab: AppTab
|
||||
private let services: AppServices
|
||||
private var isSyncingStack = false
|
||||
|
||||
weak var tabBarHost: MainTabBarController?
|
||||
|
||||
init(tab: AppTab, services: AppServices) {
|
||||
self.appTab = tab
|
||||
self.services = services
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
delegate = self
|
||||
navigationBar.prefersLargeTitles = false
|
||||
setViewControllers([tab.makeRootViewController(services: services)], animated: false)
|
||||
registerNavigationBridge()
|
||||
services.appRouter.router(for: appTab).onChange = { [weak self] in
|
||||
self?.syncFromRouterPath()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// Push 指定应用路由,并同步写入 RouterPath。
|
||||
func pushRoute(_ route: AppRoute, animated: Bool = true) {
|
||||
let viewController = AppRouteViewControllerFactory.makeViewController(for: route, services: services)
|
||||
viewController.hidesBottomBarWhenPushed = route.hidesTabBarWhenPushed
|
||||
isSyncingStack = true
|
||||
services.appRouter.router(for: appTab).navigate(to: route)
|
||||
isSyncingStack = false
|
||||
pushViewController(viewController, animated: animated)
|
||||
}
|
||||
|
||||
func navigationController(
|
||||
_ navigationController: UINavigationController,
|
||||
willShow viewController: UIViewController,
|
||||
animated: Bool
|
||||
) {
|
||||
let isRoot = viewController === viewControllers.first
|
||||
tabBarHost?.setCustomTabBarHidden(!isRoot, animated: animated)
|
||||
}
|
||||
|
||||
func navigationController(
|
||||
_ navigationController: UINavigationController,
|
||||
didShow viewController: UIViewController,
|
||||
animated: Bool
|
||||
) {
|
||||
syncRouterPathFromStack()
|
||||
}
|
||||
|
||||
private func registerNavigationBridge() {
|
||||
switch appTab {
|
||||
case .home:
|
||||
UIKitAppNavigation.homeNavigationController = self
|
||||
case .profile:
|
||||
UIKitAppNavigation.profileNavigationController = self
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func syncFromRouterPath() {
|
||||
guard !isSyncingStack else { return }
|
||||
|
||||
let routerPath = services.appRouter.router(for: appTab)
|
||||
let targetDepth = routerPath.path.count
|
||||
let currentDepth = max(viewControllers.count - 1, 0)
|
||||
|
||||
if targetDepth > currentDepth {
|
||||
for index in currentDepth..<targetDepth {
|
||||
let route = routerPath.path[index]
|
||||
let viewController = AppRouteViewControllerFactory.makeViewController(for: route, services: services)
|
||||
viewController.hidesBottomBarWhenPushed = route.hidesTabBarWhenPushed
|
||||
pushViewController(viewController, animated: true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if targetDepth < currentDepth {
|
||||
if targetDepth == 0 {
|
||||
popToRootViewController(animated: true)
|
||||
} else if targetDepth < viewControllers.count {
|
||||
popToViewController(viewControllers[targetDepth], animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func syncRouterPathFromStack() {
|
||||
guard !isSyncingStack else { return }
|
||||
|
||||
let routerPath = services.appRouter.router(for: appTab)
|
||||
let depth = max(viewControllers.count - 1, 0)
|
||||
if depth < routerPath.path.count {
|
||||
isSyncingStack = true
|
||||
routerPath.path = Array(routerPath.path.prefix(depth))
|
||||
isSyncingStack = false
|
||||
}
|
||||
}
|
||||
}
|
||||
46
suixinkan_ios/App/Navigation/UIKitAppNavigation.swift
Normal file
46
suixinkan_ios/App/Navigation/UIKitAppNavigation.swift
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// UIKitAppNavigation.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/26.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
/// UIKit 导航协调器,供 AppRouter、推送和 UI Test 复用。
|
||||
enum UIKitAppNavigation {
|
||||
weak static var homeNavigationController: UINavigationController?
|
||||
weak static var profileNavigationController: UINavigationController?
|
||||
|
||||
/// Push 首页子模块页面。
|
||||
static func pushHomeRoute(_ route: HomeRoute, animated: Bool = true) {
|
||||
let viewController = AppRouteViewControllerFactory.makeViewController(for: route, services: AppServices.shared)
|
||||
homeNavigationController?.pushViewController(viewController, animated: animated)
|
||||
}
|
||||
|
||||
/// Push 个人中心子页面。
|
||||
static func pushProfileRoute(_ route: ProfileRoute, animated: Bool = true) {
|
||||
let viewController = AppRouteViewControllerFactory.makeViewController(
|
||||
for: .profile(route),
|
||||
services: AppServices.shared
|
||||
)
|
||||
profileNavigationController?.pushViewController(viewController, animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
extension AppRouter {
|
||||
/// 同步路由栈并执行 UIKit push。
|
||||
func navigateHome(_ route: HomeRoute, animated: Bool = true) {
|
||||
select(.home)
|
||||
router(for: .home).navigate(to: .home(route))
|
||||
UIKitAppNavigation.pushHomeRoute(route, animated: animated)
|
||||
}
|
||||
|
||||
/// 同步路由栈并执行 UIKit push。
|
||||
func navigateProfile(_ route: ProfileRoute, animated: Bool = true) {
|
||||
select(.profile)
|
||||
router(for: .profile).navigate(to: .profile(route))
|
||||
UIKitAppNavigation.pushProfileRoute(route, animated: animated)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user