feat(layout): 多页签 keep-alive(子模块 + openspec change)
Some checks are pending
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
liuyun 2026-10-11 22:13:25 +08:00
parent 950e13a4a8
commit da6edcfcfd
6 changed files with 108 additions and 1 deletions

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-11

View File

@ -0,0 +1,50 @@
## Context
多页签由 `stores/modules/tabs.ts` 按 `path` 追踪(持久化),`layouts/default.vue` 的 `activePath` watch 调 `addTab`,`<router-view />` 直接渲染。菜单页面均为 `index.vue`(`<script setup>` 无显式 `name`),故 keep-alive 无法按组件名区分。动机见 proposal.md - Why。
## Goals / Non-Goals
**Goals:**
- 布局内菜单页面切换页签保留状态,关闭页签销毁状态。
- keep-alive 按页面可配置:默认开启,页面可经路由 meta 关闭。
**Non-Goals:**
- 不缓存空白布局页(login/register/forgot-password/force-change-password/403,均不在本布局)。
## Decisions
### D1 — 缓存标识 = 组件 name(路径末段 kebab)
给 11 个菜单页面补 `defineOptions({ name })`,name 取路径末段 kebab-case(`/`→`dashboard`、`/system/user`→`user`、`/monitor/login-log`→`login-log`…),天然唯一无冲突。
- **备选**:用 SFC 文件名派生(全是 `index`)——冲突,否决。
### D2 — 默认缓存,路由 meta 可关闭(用户已拍板 opt-out)
页面默认缓存;在 `<route>` 里声明 `meta.keepAlive: false` 即关闭缓存。`TabItem` 新增 `keepAlive` 字段(`addTab` 时取 `route.meta.keepAlive !== false`)。
### D3 — 布局 keep-alive + include 由「打开且未关闭缓存」的页签推导
`layouts/default.vue` 将 `<router-view />` 改为:
```vue
<router-view v-slot="{ Component, route }">
<keep-alive :include="cachedNames">
<component :is="Component" :key="route.path" />
</keep-alive>
</router-view>
```
`cachedNames = tabsStore.tabs.filter(t => t.keepAlive).map(t => pathToName(t.path))`,`pathToName` 从 tabs store 导出。
### D4 — 关闭页签 = 移出 include = 销毁
关闭页签即从 `tabs` 移除,`cachedNames` 不再含该 name,keep-alive 自动销毁缓存实例;重新打开时重建(重建后重新加载数据)。
## Risks / Trade-offs
- **[页面 name 与 pathToName 不一致 → 缓存静默失效]** → 11 个 name 与 pathToName 采用同一约定,并在 type-check 外靠冒烟验证。
- **[缓存页面数据陈旧]** → 既有 useRequest 已在进入页面时按需拉取;重建/重新打开时重新请求,不影响正确性。
## Migration Plan
无数据迁移;纯前端,回滚即撤销。
## Open Questions
无。

View File

@ -0,0 +1,19 @@
## Why
多页签已存在,但切换页签时组件会被重建,滚动位置、搜索筛选、表单输入等状态全部丢失,后台高频「列表↔详情/表单」切换体验差。需要为布局内菜单页面启用 keep-alive,切换页签保留状态、关闭页签销毁状态。
## What Changes
- 布局内菜单页面启用 keep-alive:切换页签保留组件状态,关闭页签销毁;默认缓存,页面可通过路由 meta `keepAlive: false` 关闭缓存。
- 为 11 个菜单页面补充组件 `name`(keep-alive 缓存标识),布局用 `<keep-alive :include>` 按「打开且未关闭缓存」的页签 name 集合缓存。
## Capabilities
### Modified Capabilities
- `frontend/AD/layout`: 「多页签」需求新增 keep-alive 状态保留/销毁行为。
## Impact
- **前端**:`layouts/default.vue`(router-view 包 keep-alive)、`stores/modules/tabs.ts`(path→name 辅助 + keepAlive 字段)、11 个菜单页面加 `defineOptions({ name })`(可声明 `meta.keepAlive: false` 关闭缓存)。
- 无后端/数据变更。

View File

@ -0,0 +1,20 @@
## MODIFIED Requirements
### Requirement: 多页签
系统 SHALL 支持多页签(tabs)视图:打开的页面保留为页签,可切换与关闭;切换页签时保留各页面的组件状态(keep-alive),关闭页签时销毁其状态。页面 SHALL 默认缓存,可通过路由 meta `keepAlive: false` 关闭缓存。
#### Scenario: 页签切换与关闭
- **WHEN** 用户打开多个菜单页面
- **THEN** 各页面以页签形式保留,可切换、可关闭
#### Scenario: 切换页签保留状态
- **WHEN** 用户在多个页签间切换
- **THEN** 各页签页面的组件状态(滚动/筛选/表单)被保留,不因切换而重建
#### Scenario: 关闭页签销毁状态
- **WHEN** 用户关闭某页签
- **THEN** 该页面的组件状态被销毁,再次打开时重建
#### Scenario: 页面关闭缓存
- **WHEN** 某页面声明路由 meta `keepAlive: false`
- **THEN** 该页面切换页签时不保留状态,每次进入重新构建

View File

@ -0,0 +1,16 @@
## 1. 页面 name
- [x] 1.1 给 11 个菜单页面补 `defineOptions({ name })`(`/`→dashboard、`/system/{user,role,resource,org,tenant,dict,config}`→末段、`/monitor/{audit,login-log,session}`→末段)
## 2. tabs store 辅助
- [x] 2.1 `stores/modules/tabs.ts`:`TabItem` 加 `keepAlive` 字段;导出 `pathToName(path)`(`/`→dashboard,其余取路径末段 kebab)
## 3. 布局 keep-alive
- [x] 3.1 `layouts/default.vue` 用 `<keep-alive :include="cachedNames">` 包裹 router-view;`cachedNames` = 打开且 keepAlive 的页签 name 集合;`addTab` 传 `route.meta.keepAlive !== false`;`:key="route.path"`
## 4. 验证
- [x] 4.1 前端 type-check 通过
- [x] 4.2 冒烟:默认页签切换保留状态;声明 `meta.keepAlive:false` 的页面切换重建

@ -1 +1 @@
Subproject commit 5de298a9569027f4b40766814733bea209ccada4
Subproject commit ff0e1dcd27295a9cd2eb99a828c9f367aee583e9