chore(openspec): track fe-admin-layout implementation

sysmgr-admin 布局壳:Element Plus + 可配置布局/主题系统(5 种模式 + 主题色 + 暗黑 + 折叠/宽度 + 固定顶栏/底栏 + 面包屑/多页签/全屏/手风琴)。任务全部完成,待归档。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
liuyun 2026-10-11 00:13:10 +08:00
parent 1790fb30a8
commit 6bb227d635
6 changed files with 309 additions and 1 deletions

View File

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

View File

@ -0,0 +1,109 @@
## Context
sysmgr-admin 基于 agarwood 模板,已具备:Vue 3 + Vite 8 + TypeScript、Pinia(`pinia-plugin-persistedstate`)、vue-router 5(`unplugin-vue-router` 自动路由 + `vite-plugin-vue-layouts`)、UnoCSS、`unplugin-auto-import` + `unplugin-vue-components`(`deep: true, directoryAsNamespace: false`)、@iconify/vue、vue-i18n、@vueuse(`useDark`)。现有 `src/layouts/default.vue` 是占位 header + router-view;登录页 `layout:false`。全局设计令牌在 `src/assets/scss/settings/_color.scss`(`--wm-color-*`,深色用 `[data-theme="dark"]`)。
动机见 proposal.md。
## Goals / Non-Goals
**Goals:**
- Element Plus 按需接入,中文 locale。
- 可配置布局壳(7 种模式)+ 主题色 + 暗黑 + 折叠 + 宽度 + 固定顶栏/底栏 + 面包屑/多页签/全屏/手风琴。
- 布局/主题偏好 localStorage 持久化;默认值可在代码中集中配置。
- 组件库友好:组件命名/放置/定义规范化,便于将来抽取独立组件库。
**Non-Goals:**
- 不接后端菜单数据(本任务用占位菜单,但字段结构对齐后端)。
- 不做水印、色弱模式、路由过渡动画(后续再议)。
- 不改后端。
## Decisions
### D1. 组件命名与放置(用户约束)
公共/布局/全局组件统一放 `src/components/`(自动注册目录),目录与文件名均中划线 `xx-xxx.vue`,组件名 `ty-` 前缀(投云科技),如 `ty-header.vue` → `<ty-header>`。模板直接写 kebab 标签,不手动 import。
- 组件清单:`ty-header` / `ty-mixbar` / `ty-sidebar` / `ty-tabs` / `ty-breadcrumb` / `ty-footer` / `ty-setting`(齿轮触发 + drawer 外壳)/ `ty-setting-panel`(面板)。
- 依据:`Components({ deep: true, directoryAsNamespace: false })` 按文件名注册,`ty-` 前缀避免与 Element Plus(`el-`)及业务组件撞名。
### D2. 组件库友好定义(用户约束:将来抽独立组件库)
每个组件显式 `defineProps` 类型化 + `defineEmits` + 需要时 `defineSlots`/`defineExpose`;组件 **props 驱动、与 Pinia store 解耦**——store 只在 `src/layouts/default.vue` 壳里读,通过 props 传给组件、组件通过 emits 回传交互。组件不直接 import store,便于整体抽出。
### D3. Element Plus 接入:on-demand resolver
`unplugin-vue-components` 加 `ElementPlusResolver({ importStyle: 'sass' })`;`unplugin-auto-import` 加 `ElementPlusResolver`(自动引入 `ElMessage` 等)。中文 locale 用 `<el-config-provider :locale="zhCn">` 包裹 App。
- 备选:全量引入。放弃:脚手架体积敏感,模板已有 on-demand 基础设施。
### D4. 布局状态模型 + 默认值集中配置
状态存 Pinia `app` store(persist)。**默认值不散落在 store 里,而集中在一个配置模块** `src/config/layout.ts` 导出 `defaultLayout`,store 用它作初始值。开发者改这一个文件即可统一调整默认值,用户在页面上的修改覆盖默认值并持久化到 localStorage。
`defaultLayout`(初始值):
```ts
{
layout: 'horizontal', // horizontal(上下) | top(纯上下) | sidebar(左右)
mixbar: false,
themeColor: '#2E7FA3',
isDark: false,
headerFixed: true,
sidebarWidth: 210, // 200–340
footerFixed: false,
sidebarCollapsed: false,
showBreadcrumb: true,
showTabs: true,
showFullscreen: true,
menuAccordion: false,
}
```
5 种呈现由 `layout × mixbar` 派生:`top`、`horizontal`(±mixbar)、`sidebar`(±mixbar);`top` 布局忽略 mixbar。顶栏存在性:`horizontal`/`top` 有顶栏,`sidebar`(左右)无顶栏(与用户确认,vertical 与 left 一致、均无顶栏)。
### D5. 主题色运行时覆盖
由所选主色用 JS 计算派生色阶(light-3/5/7/8/9、dark-2),写入 `:root` 的 `--el-color-primary` 及派生变量;默认 `#2E7FA3` 预置。色值计算用 `colord` 或自写 mix(混白/黑)。
- 不用编译期 SCSS `$colors` 覆盖——无法运行时换色。
### D6. 暗黑模式与现有令牌对齐
暗黑开关同时:① `html` 加/去 `dark` class(Element Plus 深色 css-vars);② `html` 设 `data-theme="dark"/"light"`(现有 `--wm-color-*` 深色令牌)。统一由 `applyTheme()` 维护,避免两套状态漂移。
### D7. 菜单数据结构对齐后端
占位菜单**完全按后端 `sys_resource` / `Resource` 模型的字段**定义,不自造字段。类型 `src/types/resource.ts`:
```ts
export type ResourceType = 'menu' | 'button' | 'api'
export interface ResourceNode {
id: string
code: string
name: string
parentCode: string | null
type: ResourceType
path: string | null
component: string | null
icon: string | null
sort: number
children?: ResourceNode[] // 前端由 parentCode 组装,仅用于渲染
}
```
占位数据内置在 `src/config/menu.ts`(树已组装好 `children`),字段名与后端一致(`code/name/parentCode/type/path/component/icon/sort`),下个任务直接换成「当前用户菜单树」接口返回值。
- 菜单 `icon` 字段存 **iconify 图标名**(如 `mdi:home`),渲染时复用已有 `src/components/icon/icon.vue`(`type="iconify"`),不自建图标体系。
### D8. 布局壳结构(store 只在壳层)
`src/layouts/default.vue` 读 `app` store,按 `layout`/`mixbar` 条件渲染并接线:
- `ty-header`(一级导航或工具栏 + 折叠 + 面包屑 + 全屏 + 设置入口 + 用户下拉),props 接收模式/折叠/菜单/面包屑等
- `ty-mixbar`(一级图标)、`ty-sidebar`(`el-menu`:collapse + width + accordion)
- `ty-tabs`(多页签)、`ty-footer`(版权「云南投云科技有限公司」)、内容区 `<router-view>`
- `ty-setting` / `ty-setting-panel`(设置抽屉与面板)
布局切换用 Vue 条件渲染 + flex/grid,不引第三方布局库。
## Risks / Trade-offs
- [主题色派生色阶与 Element Plus 视觉一致性] → 用标准 mix 公式生成 light-3/5/7/8/9、dark-2。
- [props 驱动 vs 开发便利] → 组件更通用但壳层接线略多;为将来抽组件库值得。
- [暗黑两套令牌漂移] → 集中在 `applyTheme()` 维护 `dark` class 与 `data-theme`。
- [多页签 keep-alive 复杂度] → 本任务先做展示/切换/关闭,缓存后续再上。
## Migration Plan
- 纯前端新增,无迁移。
- 部署:合并 → `pnpm install`(装 element-plus)→ `pnpm dev:dev` 或构建。
- 回滚:`git revert`。
## Open Questions
- 占位菜单图标资源(@iconify vs 本地 svg)后续接后端时一并定。
- 多页签是否 keep-alive 缓存(后续再议)。

View File

@ -0,0 +1,37 @@
## Why
Phase 0 前端只有登录页与路由守卫,登录后没有真正的后台布局壳。Phase 1 的管理后台需要一套可配置的布局框架——它是所有后台页面的容器,支持多种布局/导航模式、主题色、暗黑模式等。先搭好这个地基,后续用户/角色/资源等页面才有地方挂。
## What Changes
- **集成 Element Plus**:走 on-demand(`unplugin-vue-components` 的 `ElementPlusResolver` + 组件样式按需),配置中文 locale。
- **布局壳**:替换 `src/layouts/default.vue`;布局/全局组件统一放 `src/components/`(自动注册目录),命名 `ty-` 前缀 + 中划线(`ty-header.vue` → `<ty-header>`),充分复用 auto-import。
- **组件库友好**:组件 props/emits 显式类型化、props 驱动、与 Pinia store 解耦,为将来抽取独立组件库打基础。
- **布局与导航模式**:支持 3 种布局(上下/纯上下/左右)× MixBar 开关,共 5 种呈现;MixBar 固定最左;「左右」无顶栏;左侧菜单支持 collapse/expand。
- **主题色**:颜色选择器自定义,默认 `#2E7FA3`,运行时覆盖 Element Plus 主色 CSS 变量。
- **暗黑模式**:明亮/暗黑切换。
- **顶栏固定**:可固定/不固定;纯左右布局(无顶栏)时该选项禁用。
- **侧边菜单宽度**:200–340 可调。
- **底部版权**:显示「云南投云科技有限公司」,可固定。
- **补充设置**:面包屑、多页签(tabs)、顶栏全屏、菜单手风琴。
- **持久化与默认值**:布局/主题偏好存 localStorage(Pinia `persistedstate`);默认值集中在代码配置模块(`src/config/layout.ts`),改一处统一生效。
- **导航菜单数据**:本任务用占位菜单,但字段结构严格对齐后端 `resource` 实体(`id/code/name/parentCode/type/path/component/icon/sort`),下个任务直接换接口数据。
## Capabilities
### New Capabilities
- `frontend/AD/layout`: 管理后台布局壳与主题系统(布局/导航模式、主题色、暗黑、折叠、宽度、固定顶栏/底栏、面包屑/多页签/全屏/手风琴、持久化)
### Modified Capabilities
(无)
## Impact
- 前端 `sources/sysmgr-admin`:
- 新增依赖 `element-plus`(+ `@element-plus/icons-vue` 如需)
- `vite.config.ts`:`Components`/`AutoImport` 加 `ElementPlusResolver`
- 新增 `src/components/ty-*.vue` 组件(ty-header/ty-sidebar/ty-mixbar/ty-tabs/ty-breadcrumb/ty-footer/ty-setting/ty-setting-panel)
- 新增 `src/stores/modules/app.ts`(布局/主题偏好,persist)+ `src/config/layout.ts`(默认值)+ `src/types/resource.ts`(菜单类型)
- 替换 `src/layouts/default.vue` 为布局壳
- 主题色/暗黑与现有 `--wm-color-*` 令牌对齐
- 后端无变更;本任务菜单为占位数据(字段对齐后端 resource)。

View File

@ -0,0 +1,115 @@
## Purpose
定义管理后台布局壳与主题系统的行为:可配置的布局/导航模式、主题色、暗黑模式、侧边栏折叠与宽度、固定顶栏/底栏,以及面包屑/多页签/全屏/手风琴等配套能力。
## ADDED Requirements
### Requirement: 布局与导航模式
系统 SHALL 支持 3 种布局(上下 / 纯上下 / 左右)与 MixBar 开关的组合,共 5 种呈现;MixBar 始终固定在布局最左侧;「左右」布局不渲染顶栏。
#### Scenario: 切换布局即时生效
- **WHEN** 用户在布局设置中切换布局或开启/关闭 MixBar
- **THEN** 主框架立即按新布局重新排布顶栏、侧边栏与内容区
#### Scenario: 左右无顶栏
- **WHEN** 布局为「左右」
- **THEN** 主框架只渲染左侧菜单与内容区,不渲染顶栏
#### Scenario: 上下有顶栏
- **WHEN** 布局为「上下」或「纯上下」
- **THEN** 主框架渲染顶栏,其中「上下」顶部放一级导航、左侧放子菜单,「纯上下」仅顶部导航
### Requirement: 侧边菜单折叠
左侧导航菜单 SHALL 支持 collapse(折叠为图标)与 expand(展开显示文字+图标)。
#### Scenario: 折叠与展开
- **WHEN** 用户点击折叠按钮
- **THEN** 侧边栏在折叠(仅图标)与展开(文字+图标)之间切换
### Requirement: 主题色
系统 SHALL 支持通过颜色选择器自定义主题色,默认 `#2E7FA3`;选择后运行时更新 Element Plus 主色及其派生色阶。
#### Scenario: 选择主题色
- **WHEN** 用户在设置面板用颜色选择器选取新主题色
- **THEN** 全站 Element Plus 主色(按钮/链接/高亮等)即时变为所选颜色,刷新后仍保留
### Requirement: 暗黑模式
系统 SHALL 支持明亮/暗黑模式切换;暗黑模式下切换 Element Plus 深色主题与全局深色令牌。
#### Scenario: 切换暗黑模式
- **WHEN** 用户切换暗黑模式开关
- **THEN** 全站即时切换为深色配色,刷新后仍保留
### Requirement: 顶栏固定
顶栏 SHALL 支持固定(内容滚动时顶栏常驻顶部)与不固定;「左右」布局(无顶栏)时该选项禁用。
#### Scenario: 顶栏固定开关
- **WHEN** 用户切换「固定顶栏」
- **THEN** 顶栏在固定/随内容滚动之间切换
#### Scenario: 左右布局禁用
- **WHEN** 布局为「左右」
- **THEN** 「固定顶栏」选项置灰不可选
### Requirement: 侧边菜单宽度
左侧菜单宽度 SHALL 可调,范围 200–340(单位 px)。
#### Scenario: 调整宽度
- **WHEN** 用户拖动或输入侧边栏宽度
- **THEN** 宽度在 200–340 之间变化并即时生效,超界被限制在边界值
### Requirement: 底部版权固定
系统 SHALL 在底部显示版权信息「云南投云科技有限公司」,并支持固定/不固定。
#### Scenario: 底部版权
- **WHEN** 用户开启「固定底部」
- **THEN** 版权条常驻视口底部;关闭时随内容滚动
### Requirement: 布局设置持久化
所有布局与主题设置 SHALL 持久化到 localStorage,刷新页面后保持。
#### Scenario: 刷新保留
- **WHEN** 用户调整布局/主题/宽度等后刷新页面
- **THEN** 所有设置恢复为用户上次的配置
### Requirement: 面包屑
系统 SHALL 在内容区顶部显示面包屑,随当前路由与菜单层级展示当前位置。
#### Scenario: 面包屑导航
- **WHEN** 用户进入某个菜单页面
- **THEN** 面包屑显示当前菜单的层级路径
### Requirement: 多页签
系统 SHALL 支持多页签(tabs)视图:打开的页面保留为页签,可切换与关闭。
#### Scenario: 页签切换与关闭
- **WHEN** 用户打开多个菜单页面
- **THEN** 各页面以页签形式保留,可切换、可关闭
### Requirement: 顶栏全屏
系统 SHALL 在顶栏提供全屏切换按钮,进入/退出浏览器全屏。
#### Scenario: 全屏切换
- **WHEN** 用户点击全屏按钮
- **THEN** 页面进入浏览器全屏,再次点击退出
### Requirement: 菜单手风琴
侧边菜单 SHALL 支持手风琴模式:同一级只展开一个子菜单。
#### Scenario: 手风琴展开
- **WHEN** 开启手风琴且用户展开某个子菜单
- **THEN** 同级的其他已展开子菜单自动收起
### Requirement: 默认值集中配置
布局与主题的默认值 SHALL 在代码中集中配置(单一配置模块),开发者改一处即可统一调整;用户在页面上的修改覆盖默认值并持久化。
#### Scenario: 改默认值生效
- **WHEN** 开发者修改配置模块中的默认主题色或默认布局
- **THEN** 未手动改过的用户首次进入时按新默认值渲染;已手动改过的用户仍按自己的持久化设置
### Requirement: 菜单数据结构对齐后端
占位菜单数据 SHALL 采用后端资源实体(`resource`)的字段结构(`id/code/name/parentCode/type/path/component/icon/sort`),不自造字段名。
#### Scenario: 字段对齐
- **WHEN** 占位菜单被替换为后端「当前用户菜单树」接口数据
- **THEN** 前端无需改动数据结构即可直接渲染

View File

@ -0,0 +1,45 @@
## 1. Element Plus 集成
- [x] 1.1 安装 element-plus(及 @element-plus/icons-vue)
- [x] 1.2 vite.config.ts:`Components`/`AutoImport` 增加 `ElementPlusResolver`(on-demand + sass 样式)
- [x] 1.3 App.vue 用 `<el-config-provider :locale="zhCn">` 包裹,配中文 locale
- [x] 1.4 冒烟:页面上 el-button 能渲染、ElMessage 可调用
## 2. 类型与默认值配置
- [x] 2.1 `src/types/resource.ts`:`ResourceType` 与 `ResourceNode` 类型,字段严格对齐后端 resource(id/code/name/parentCode/type/path/component/icon/sort + children)
- [x] 2.2 `src/config/layout.ts`:`defaultLayout` 集中配置默认值(themeColor `#2E7FA3`、sidebarWidth 210 等)
- [x] 2.3 `src/config/menu.ts`:占位菜单树(字段对齐后端,已组装 children)
## 3. 布局/主题状态(app store)
- [x] 3.1 `src/stores/modules/app.ts`:以 `defaultLayout` 作初始值,开启 persist;含 layout/mixbar/themeColor/isDark/headerFixed/sidebarWidth/footerFixed/sidebarCollapsed/showBreadcrumb/showTabs/showFullscreen/menuAccordion
## 4. 主题系统
- [x] 4.1 主题色工具:由主色计算 light-3/5/7/8/9、dark-2,写入 `:root` 的 `--el-color-primary*`
- [x] 4.2 `applyTheme()`:统一切换 `html.dark` class 与 `data-theme`(对齐现有 `--wm-color-*` 深色令牌)
- [x] 4.3 监听 store 的 themeColor/isDark 变化自动应用
## 5. 布局壳组件(src/components/ty-*.vue,kebab 命名 + 自动导入,props/emits 显式类型化)
- [x] 5.1 `ty-header`:顶栏(一级导航或工具栏 + 折叠 + 全屏 + 设置入口 + 用户下拉),响应 headerFixed;props 接收菜单/模式/折叠等,emits 折叠/打开设置
- [x] 5.2 `ty-mixbar`:固定最左一级图标栏
- [x] 5.3 `ty-sidebar`:左侧菜单(el-menu),支持 collapse/expand、width、accordion
- [x] 5.4 `ty-tabs`:多页签(展示/切换/关闭)
- [x] 5.5 `ty-breadcrumb`:面包屑
- [x] 5.6 `ty-footer`:底部版权「云南投云科技有限公司」,响应 footerFixed
- [x] 5.7 替换 `layouts/default.vue` 为布局壳:读 store,按 layout/mixbar 条件渲染并 props/emits 接线(组件不直接依赖 store)
## 6. 设置组件与面板
- [x] 6.1 `ty-setting`:设置组件(齿轮触发 + el-drawer 外壳)
- [x] 6.2 `ty-setting-panel`:设置面板——布局模式、MixBar、主题色选择器(默认 #2E7FA3)、暗黑、顶栏固定(纯左右禁用)、侧边宽度 slider(200–340)、底部固定、面包屑/多页签/全屏/手风琴开关
## 7. 验证
- [x] 7.1 `pnpm dev` 启动,登录后进入布局壳
- [x] 7.2 切换 5 种模式即时生效;「左右」无顶栏且「固定顶栏」禁用
- [x] 7.3 主题色、暗黑、侧边宽度、折叠、手风琴、多页签、全屏、面包屑、底部版权均生效
- [x] 7.4 刷新后设置保留(localStorage);改 `defaultLayout` 后未改过的用户按新默认值渲染
- [x] 7.5 `pnpm type-check` 与 `pnpm build:prod` 通过

@ -1 +1 @@
Subproject commit 7738abbbfd3ddad6401a53a4cdaab643b1cc7908
Subproject commit 6ef0b1121c86c01fa75563dbb79c6568a3cf96a4