# App 模块业务逻辑 ## 模块职责 App 模块负责应用入口、根视图切换、全局状态注入、主导航状态、登录态恢复和全局 Toast。 该模块不直接处理具体页面业务,主要承担 App 级基础设施编排: - 根据登录状态展示登录页、恢复态或主 Tab。 - 创建并注入全局状态和共享服务。 - 管理每个 Tab 独立的 `NavigationStack` 路径。 - 协调登录成功、退出登录、冷启动恢复和本地缓存同步。 ## 核心对象 - `suixinkanApp`:SwiftUI 应用入口,挂载 `RootView`。 - `RootView`:创建 `AppSession`、`AccountContext`、`PermissionContext`、`ScenicSpotContext`、`AppRouter`、`ToastCenter`、`APIClient` 和业务 API,并注入 SwiftUI Environment。 - `AppSession`:保存认证阶段和正式 token,只负责登录态,不承载业务资料。 - `AccountContext`:保存当前账号资料、景区作用域和门店作用域。 - `PermissionContext`:保存角色权限、当前角色和扁平化权限 URI。 - `ScenicSpotContext`:保存当前景区下的景点/打卡点列表和加载状态。 - `AppRouter`:保存当前 Tab 和每个 Tab 自己的导航路径。 - `ToastCenter`:管理当前全局 Toast 文案和自动隐藏任务。 - `AuthSessionCoordinator`:统一处理登录完成、退出登录、偏好读取和账号快照刷新。 - `SessionBootstrapper`:冷启动时读取本地 token 和账号快照,并向服务端校验登录态。 - `AccountContextLoader`:统一同步用户资料、角色权限、景区和门店。 ## 启动流程 1. `suixinkanApp` 创建 `RootView`。 2. `RootView` 初始化共享依赖,并把它们注入 Environment。 3. `APIClient` 绑定 `AppSession.token` 作为默认 token provider。 4. `SessionBootstrapper.restore` 尝试从 Keychain 读取正式 token。 5. 无 token 时保持 `loggedOut`,展示 `LoginView`。 6. 有 token 时进入 `restoring`,先恢复本地账号快照。 7. `AccountContextLoader` 并行请求用户资料和角色权限,再补全景区与门店。 8. 校验成功后进入 `loggedIn`,展示 `MainTabsView`。 9. `ScenicSpotContext` 按当前景区懒加载景点/打卡点。 10. 明确 token 失效时清空 token 和账号快照,回到登录页。 11. 普通网络失败时保留本地登录态,使用账号快照进入主界面。 ## UI Test 启动约定 - `AppUITestLaunchState` 在收到 `-suixinkan-ui-tests` 时跳过推送注册和排队 WebSocket,避免系统弹窗干扰自动化。 - `-suixinkan-ui-tests-reset-state` 用于冷启动清理 Keychain 与 UserDefaults。 - `-suixinkan-ui-tests-open-menu <菜单标题>` 登录后直达首页调试目录中的目标页。 - `-suixinkan-ui-tests-open-profile <路由名>` 登录后直达个人中心二级页(如 `settings`、`realNameAuth`)。 - `AppUITestRouteDriver` 仅在 DEBUG 构建下解析上述直达参数,供 XCUITest 逐页验证。 - 详细运行方式见 `suixinkanUITests/README.md`。 ## 登录和退出 登录完成由 `AuthSessionCoordinator.completeLogin` 统一处理: - 正式 token 写入 `SessionTokenStore`。 - 上次手机号和协议状态写入 `AppPreferencesStore`。 - 账号资料、景区列表、门店列表写入 `AccountContext`。 - 角色权限和当前角色写入 `PermissionContext`。 - 非敏感账号快照写入 `AccountSnapshotStore`。 - `AppSession` 切换为 `loggedIn`。 退出登录由 `AuthSessionCoordinator.logout` 统一处理: - 清空 Keychain token。 - 清空账号快照。 - 重置账号上下文、权限上下文、景点上下文、导航路径和 Toast。 - `AppSession` 切换为 `loggedOut`。 - 保留上次手机号、协议状态等非敏感偏好。 ## Toast 全局 Toast 由 `RootView` 挂载在页面最上层,业务页面只调用 `toastCenter.show(...)` 发出提示命令。 Toast 展示为顶部全宽横幅,背景使用不透明主色并延伸到屏幕顶部、左边和右边;文案居中展示,不提供关闭按钮,默认 2.2 秒后自动消失。连续展示新 Toast 时会覆盖旧文案并重新计时,旧的自动隐藏任务不会影响新的 Toast。 ## 导航规则 主界面使用 `TabView`,每个 Tab 内部由单独的 `NavigationStack` 包裹。`AppRouter` 为每个 `AppTab` 持有独立 `RouterPath`,切换 Tab 不会丢失该 Tab 的内部导航路径。 Tab 根页面显示底部 TabBar。通过 `AppRoute` push 到子页面时,`MainTabsView` 会根据 `AppRoute.hidesTabBarWhenPushed` 统一隐藏 TabBar,避免每个业务页面重复处理。 当前路由枚举 `AppRoute` 仍以占位详情页为主,后续新增真实页面时应优先扩展 `AppRoute`,再由对应 Tab 的 `NavigationStack` 处理跳转。