# 实施计划 — 登录 IP 门禁 + SPA 无感切换

**设计**: [`2026-06-08-login-gate-seamless-nav-design.md`](../specs/2026-06-08-login-gate-seamless-nav-design.md)

## 阶段划分

| 阶段 | 范围 | 估时 | 可独立部署 |
|------|------|------|------------|
| **P0** | `GET /api/auth/login-access` + LoginPage 门禁 UI | 0.5～1d | 是 |
| **P1** | `router-view` key、登录 replace + chunk 预载 | 0.5d | 是 |
| **P2** | keep-alive + 各页 `onActivated` 静默刷新 | 2～3d | 是（逐页） |
| **P3** | `useCachedQuery` + 统计/分类共享 store | 3～5d | 否（需全链路） |
| **P4** | 侧栏预取 + 骨架屏 | 1～2d | 是 |

## P0 实施步骤

1. 新增 `server/utils/login_allowlist.py`：`is_login_allowed(client_ip) -> tuple[bool, str|None]`
2. `auth_service.login` 改为调用上述函数
3. `GET /api/auth/login-access` + `PUBLIC_EXACT_PATHS`
4. `LoginPage`：checking / blocked / form 三态
5. `tests/test_login_access.py`
6. changelog + audit + 部署

## P1 实施步骤

1. `App.vue`：`route.fullPath` → `route.name`（或移除 key）
2. 回归 `ScriptRunsPage`：`watch(route.params.id)`
3. `LoginPage`：`router.replace` + 动态 import 预载 Dashboard
4. 浏览器：登录 → 仪表盘无闪屏验证

## P2 实施步骤

1. `App.vue`：`keep-alive` + `include` 列表
2. 各页 `defineOptions({ name: '...' })`
3. 将 `onMounted(load)` 拆为 `onMounted` + `onActivated(() => load({ silent: true }))`
4. **排除** `TerminalPage`
5. E2E：连续切换 10 次菜单，Network 观察无重复全量请求风暴

## P3/P4（后续迭代）

见设计文档 §4.2；单独开 changelog 与验收报告。

## 测试要点

- P0：`pytest tests/test_login_access.py`；非白名单 curl 预检与 POST login 结论一致
- P1～P2：Playwright 或手动 — 侧栏往返服务器↔脚本，无整页白屏
- 安全：登出后 keep-alive 内无上一用户数据残留

## 回滚

各阶段可 git revert 独立 commit；P0 回滚不影响已登录会话。
