初始提交
This commit is contained in:
40
suixinkan/Features/Main/Main.md
Normal file
40
suixinkan/Features/Main/Main.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Main 模块业务逻辑
|
||||
|
||||
## 模块职责
|
||||
|
||||
Main 模块负责登录后的主界面 Tab 容器,以及当前尚未迁移页面的占位入口。
|
||||
|
||||
主界面由 `MainTabsView` 承载:
|
||||
- 使用 `TabView` 展示首页、订单、数据、我的四个 Tab。
|
||||
- 每个 Tab 内部都包裹独立的 `NavigationStack`。
|
||||
- 每个 Tab 的导航路径由 `AppRouter` 单独保存。
|
||||
|
||||
## Tab 结构
|
||||
|
||||
当前 Tab 来自 `AppTab`:
|
||||
- `home`:首页
|
||||
- `orders`:订单
|
||||
- `statistics`:数据
|
||||
- `profile`:我的
|
||||
|
||||
`HomeRootView` 已接入真实的 `HomeView`,`ProfileRootView` 已接入真实的 `ProfileView`。订单和数据 Tab 当前仍使用 `PlaceholderTabRootView`,用于保留入口和验证 Tab 内导航。
|
||||
|
||||
## 导航流程
|
||||
|
||||
1. `MainTabsView` 从 Environment 读取 `AppRouter`。
|
||||
2. `TabView` 使用 `appRouter.selectedTab` 作为选中状态。
|
||||
3. 每个 Tab 创建自己的 `NavigationStack(path:)`。
|
||||
4. 路径绑定来自 `appRouter.binding(for:)`。
|
||||
5. 占位页点击“打开详情”时,通过当前 Tab 注入的 `RouterPath` push 一个 `AppRoute.placeholder`。
|
||||
6. `navigationDestination` 根据 `AppRoute` 展示详情页。
|
||||
7. 子页面展示时读取 `AppRoute.hidesTabBarWhenPushed`,默认隐藏底部 TabBar。
|
||||
|
||||
## 后续迁移规则
|
||||
|
||||
迁移新页面时:
|
||||
- 优先替换对应 Tab 的 RootView。
|
||||
- 保持每个 Tab 自己的 `NavigationStack`。
|
||||
- 跨 Tab 跳转通过 `AppRouter.select` 切换 Tab。
|
||||
- Tab 内跳转通过当前 Tab 的 `RouterPath.navigate` 或扩展后的 `AppRoute` 处理。
|
||||
- 普通业务子页面默认隐藏 TabBar;只有确有产品需求时,才在 `AppRoute` 策略中单独放开。
|
||||
- 真实业务页面接入后,应同步补充该模块文档和单元测试。
|
||||
38
suixinkan/Features/Main/Views/MainTabsView.swift
Normal file
38
suixinkan/Features/Main/Views/MainTabsView.swift
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// MainTabsView.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/18.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// 主 Tab 容器视图,为每个 Tab 创建独立的 NavigationStack。
|
||||
struct MainTabsView: View {
|
||||
@Environment(AppRouter.self) private var appRouter
|
||||
|
||||
var body: some View {
|
||||
@Bindable var appRouter = appRouter
|
||||
|
||||
TabView(selection: $appRouter.selectedTab) {
|
||||
ForEach(AppTab.allCases) { tab in
|
||||
NavigationStack(path: appRouter.binding(for: tab)) {
|
||||
tab.rootView
|
||||
.navigationTitle(tab.title)
|
||||
.navigationDestination(for: AppRoute.self) { route in
|
||||
route.destinationView
|
||||
.toolbar(route.hidesTabBarWhenPushed ? .hidden : .visible, for: .tabBar)
|
||||
}
|
||||
}
|
||||
.environment(appRouter.router(for: tab))
|
||||
.tabItem { tab.label }
|
||||
.tag(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MainTabsView()
|
||||
.environment(AppRouter())
|
||||
}
|
||||
77
suixinkan/Features/Main/Views/PlaceholderRootViews.swift
Normal file
77
suixinkan/Features/Main/Views/PlaceholderRootViews.swift
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// PlaceholderRootViews.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/18.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// 首页根视图,占位承载后续首页迁移内容。
|
||||
struct HomeRootView: View {
|
||||
var body: some View {
|
||||
HomeView()
|
||||
}
|
||||
}
|
||||
|
||||
/// 订单根视图,占位承载后续订单模块迁移内容。
|
||||
struct OrdersRootView: View {
|
||||
var body: some View {
|
||||
PlaceholderTabRootView(title: "订单", systemImage: "doc.text")
|
||||
}
|
||||
}
|
||||
|
||||
/// 数据根视图,占位承载后续统计模块迁移内容。
|
||||
struct StatisticsRootView: View {
|
||||
var body: some View {
|
||||
PlaceholderTabRootView(title: "数据", systemImage: "chart.bar")
|
||||
}
|
||||
}
|
||||
|
||||
/// 我的根视图,当前承载个人信息页面。
|
||||
struct ProfileRootView: View {
|
||||
var body: some View {
|
||||
ProfileView()
|
||||
}
|
||||
}
|
||||
|
||||
/// 通用 Tab 占位视图,用于未迁移模块的临时入口。
|
||||
private struct PlaceholderTabRootView: View {
|
||||
@Environment(RouterPath.self) private var router
|
||||
|
||||
let title: String
|
||||
let systemImage: String
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: systemImage)
|
||||
.font(.system(size: 44, weight: .semibold))
|
||||
.foregroundStyle(.tint)
|
||||
|
||||
Text(title)
|
||||
.font(.title2.weight(.semibold))
|
||||
|
||||
Button {
|
||||
router.navigate(to: .placeholder(title: "\(title)详情"))
|
||||
} label: {
|
||||
Label("打开详情", systemImage: "chevron.right")
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color(.systemGroupedBackground))
|
||||
}
|
||||
}
|
||||
|
||||
/// 通用占位详情页,用于验证 Tab 内 NavigationStack 跳转。
|
||||
struct PlaceholderDetailView: View {
|
||||
let title: String
|
||||
|
||||
var body: some View {
|
||||
Text(title)
|
||||
.font(.title3.weight(.medium))
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.navigationTitle(title)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user