## 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` → ``。模板直接写 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 用 `` 包裹 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, } ``` 4 种呈现:`top`、`horizontal`、`sidebar`、`sidebar`+MixBar。MixBar 仅 `sidebar`(左右)布局可用,`horizontal`(上下)无 MixBar。**所有布局均有顶栏**(含折叠/面包屑/全屏/暗黑/设置/用户等工具),区别只在导航菜单位置:`top` 顶栏放全量导航、`horizontal` 顶栏放一级导航 + 侧栏放子菜单、`sidebar` 顶栏不放菜单(菜单全在侧栏)。顶栏 Logo 区与左侧菜单(或 MixBar+菜单)同宽,侧栏折叠时仅显 Logo。 ### 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`(版权「云南投云科技有限公司」)、内容区 `` - `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 缓存(后续再议)。