sysmgr-admin 布局壳:Element Plus + 可配置布局/主题系统(5 种模式 + 主题色 + 暗黑 + 折叠/宽度 + 固定顶栏/底栏 + 面包屑/多页签/全屏/手风琴)。任务全部完成,待归档。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.5 KiB
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(初始值):
{
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:
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()维护darkclass 与data-theme。 - [多页签 keep-alive 复杂度] → 本任务先做展示/切换/关闭,缓存后续再上。
Migration Plan
- 纯前端新增,无迁移。
- 部署:合并 →
pnpm install(装 element-plus)→pnpm dev:dev或构建。 - 回滚:
git revert。
Open Questions
- 占位菜单图标资源(@iconify vs 本地 svg)后续接后端时一并定。
- 多页签是否 keep-alive 缓存(后续再议)。