Files
workspace/code/fms/.codebuddy/plans/migrate-default-layout_5046b992.md
T
2026-08-16 22:01:32 +08:00

11 KiB
Raw Blame History

name, overview, todos
name overview todos
migrate-default-layout 将 fms-vue-old2 的 DefaultLayout 布局体系(AppSidebar/AppTopbar/AppTabs/NavMain/NavMenuItem)整体迁移到 fms-vue,tailwind 改为 scoped scss + --fms-* token,不引入新依赖,动态菜单/页签/主题/权限逻辑与 old2 保持一致。
id content status
migrate-utils-stores 迁移 utils/tree.js、utils/lucideIcons.js 与 stores/permissions.js,扩展 stores/app.js(页签/折叠/语言/刷新状态) completed
id content status dependencies
layout-shell 创建 DefaultLayout.vue 与 scss 布局骨架:拖拽调宽、keep-alive、页面过渡,手写事件替代 @vueuse/core completed
migrate-utils-stores
id content status dependencies
sidebar-menu 自研 NavMain/NavMenuItem 递归菜单与 AppSidebar(团队切换 + 动态权限菜单),参考 [skill:ui-ux-pro-max] 视觉规则 completed
layout-shell
id content status dependencies
topbar-tabs 实现 AppTopbar(面包屑/主题/语言/用户下拉,复用 ThemeToggle/ColorThemePicker/Dropdown/Message)与 AppTabs 页签栏 completed
layout-shell
id content status dependencies
router-dashboard 改造路由挂 DefaultLayout 承载子路由并加权限守卫,调整 dashboard 为布局内内容页 completed
sidebar-menu
topbar-tabs

产品概述

将 fms-vue-old2 的整个布局体系(DefaultLayout 及其 menu/topbar/tabs/content/navmain)迁移到 fms-vue 业务项目,样式从 tailwindcss 改写为自研 scss(基于 --fms-* token),不需要响应式。菜单组件自研并放置于 src/layouts/components(不进 components/ui)。同步迁移权限 store、树/图标工具,扩展 app store 以支持页签、路由刷新、侧栏折叠、语言状态,路由改造为布局壳承载子路由。

核心功能

  • 布局壳 DefaultLayout:左侧可拖拽调宽侧边栏(180-400px)+ 右侧内容区(topbar + 页签栏 + router-view),页面过渡动画 + keep-alive 缓存
  • AppSidebar:顶部团队切换 + 动态菜单(基于权限模块数据构建树,仅保留有权限的菜单项),可折叠为图标栏
  • AppTopbar:侧栏折叠按钮、面包屑(路由 meta.title/section)、语言切换、主题色板与深浅色切换(复用 ThemeToggle/ColorThemePicker)、通知、用户下拉(头像/退出登录,退出用 Message 提示)
  • AppTabs:多页签栏(跟随访问路由自动添加、可关闭、溢出滚动、双击刷新),dashboard 固定不可关
  • 权限体系:permissions store(loadDataApi 拉取模块/用户权限数据,g3soft 提权,canAccess/canPower),路由守卫在 moduleId 存在时做权限拦截
  • 路由改造:/login 独立;/ 父路由挂 DefaultLayout(redirect /dashboard),dashboard 为子路由;/demo 保持独立不进布局;catchAll 不变

边界

  • 不引入任何新 npm 依赖:@vueuse/core(useEventListener/usePreferredReducedMotion/useResizeObserver)全部手写;vue-sonner toast 用自研 Message 替代;shadcn sidebar/dropdown-menu/avatar/breadcrumb/separator/collapsible 一律不迁移
  • 无响应式:移除所有断点类,布局固定桌面形态
  • 主题沿用 fms-vue 现有体系(--fms-primary + .dark),不迁移 old2 的 oklch scheme 面板,色板复用 ColorThemePicker

技术栈

  • Vue 3.5 + Vite 8(现有),纯 JavaScript,自研 scss 样式(sass 已安装)
  • 现有依赖复用:@lucide/vue(图标)、pinia + pinia-plugin-persistedstate、vue-router、nprogress、自研组件库(button/dropdown/message/popover/tooltip/spin)
  • 不新增依赖;@vueuse/core / vue-sonner / shadcn-vue 相关代码以手写或现有组件替代

实现方案

分层架构与 old2 一致:layouts(布局壳)→ stores(app/permissions/auth)→ services(http/api)→ utils(tree/lucideIcons)。

关键决策:

  1. 组件替换映射(tailwind/shadcn → 自研):
  • SidebarProvider/SidebarInset/SidebarTrigger → 自研 flex scss 布局 + 折叠状态存 app store(sidebarCollapsed),侧栏宽 240px(展开)/ 64px(折叠)
  • DropdownMenu(topbar 用户菜单/色板、sidebar 团队切换)→ fms-vue Dropdown(items 数组或 #menu 插槽),icon/shortcut/divider 均已支持
  • Avatar → 自研圆形首字母头像(CSS)
  • Breadcrumb/Separator → 自研面包屑(flex + span)与 CSS 分隔线
  • toast → Message.info/success/error(已有命令式 API)
  • Collapsible(菜单展开)→ NavMenuItem 自研递归组件 + 手写展开状态
  1. 手写 composables(替代 @vueuse/core):onMounted/onBeforeUnmount 注册移除 window 事件(mousemove/mouseup 拖拽调宽)、matchMedia('(prefers-reduced-motion: reduce)')、ResizeObserver(页签溢出检测);组件卸载时全部清理,避免内存泄漏
  2. 菜单自研:NavMain 渲染 NavMenuItem 递归(depth 递归):有 children 的目录项 → 可展开/收起(默认展开含激活子项的父级,hasActiveChild 判断);叶子项 → router-link + isActive 高亮(route.path === url 或 startsWith url + '/');折叠态下目录项点击展开、叶子仅图标 + tooltip 悬浮提示
  3. app store 扩展(保持现有 options API 兼容):新增 sidebarCollapsed、visitedTabs(初始含 dashboard)、routeRefreshKeys + addVisitedTab/removeVisitedTab/getRouteRefreshKey/refreshRoute/toggleSidebar;现有 darkMode/primaryColor/initThemeSync 不变(ThemeToggle/ColorThemePicker 依赖它们)
  4. 权限 store 迁移:JS 版逻辑与 old2 完全一致(load 去重加载、g3soft 提权、resolvePageId/canAccess/canPower、watch auth 变化 clear),依赖已有 loadDataApi
  5. 路由改造:/ 父路由 component: DefaultLayout, redirect: '/dashboard', meta: { requiresAuth: true },子路由 /dashboard(title 工作台,id 固定);beforeEach 保留登录守卫 + 新增 moduleId 权限校验(await permissionStore.load() 后 canAccess 失败跳 /dashboard);/demo 独立路由不进布局
  6. dashboard 页面:移除原独立全屏占位样式,改为 layout 内容区内的简单内容页(标题 + 建设中提示,删除退出登录按钮——退出已由 topbar 负责)
  7. 性能与可靠性:keep-alive max 8 + routeInstanceKey(fullPath:refreshKey)保持;拖拽调宽用 rAF 节流避免频繁重排;页签滚动按钮按需渲染(scrollWidth > clientWidth 判定);所有手写监听在 onBeforeUnmount 清理

实施要点

  • DefaultLayout 的页面过渡 CSS(.page-enter-active 等)写在非 scoped style,并包裹 @media (prefers-reduced-motion: reduce) 禁用
  • 拖拽 rail 交互:mousedown 记录 startX/startWidth,mousemove 计算 180-400px 夹逼,直接写 inline width 到侧栏根元素;mouseup 移除监听
  • AppTabs 的 wheel 横向滚动需 e.preventDefault()(非 passive 监听),overflow 检测用 ResizeObserver + scroll 事件
  • topbar 语言切换仅切换 store.locale 状态(项目暂无 i18n 文案,按钮保留占位),通知按钮保留占位
  • 面包屑从 route.meta.title/section 派生;dashboard 无 section 时显示单级"工作台"
  • 用户下拉:Dropdown #menu 插槽自定义内容(用户名/orgId/退出登录),退出调 authStore.clearSession + Message.info + router.replace('/login')
  • 删除项:old2 的 oklch scheme 面板、shadcn sidebar 相关选择器(data-slot、group-data 等)不迁移;@vueuse/core/vue-sonner import 全部移除
  • 不主动执行构建/测试;交付后 lint 检查 + 说明手动验证路径(pnpm dev → 登录 → dashboard 布局/页签/菜单/退出)

架构与数据流

flowchart LR
    A[访问 /] --> B{router.beforeEach}
    B -->|未登录| C[/login/]
    B -->|已登录| D[DefaultLayout]
    D --> E[AppSidebar]
    D --> F[AppTopbar]
    D --> G[AppTabs]
    D --> H[router-view + keep-alive]
    E --> I[permissions store]
    I --> J[loadDataApi 拉取模块]
    E --> K[NavMain/NavMenuItem 递归菜单]
    F --> L[退出登录 clearSession]
    G --> M[app store visitedTabs]
    H --> N[子路由 dashboard 等]

目录结构(变更清单)

fms-vue/
├── src/
│   ├── layouts/
│   │   ├── DefaultLayout.vue       # [NEW] 布局壳:侧栏 + 内容区 + keep-alive/过渡 + 拖拽调宽
│   │   ├── index.scss              # [NEW] 布局壳 scss(非 scoped 全局布局样式 + 页面过渡)
│   │   └── components/
│   │       ├── AppSidebar.vue      # [NEW] 团队切换 + 动态菜单容器(权限数据构建树)
│   │       ├── AppTopbar.vue       # [NEW] 折叠/面包屑/语言/主题/通知/用户下拉
│   │       ├── AppTabs.vue         # [NEW] 页签栏(添加/关闭/滚动/双击刷新)
│   │       ├── NavMain.vue         # [NEW] 菜单列表容器
│   │       ├── NavMenuItem.vue     # [NEW] 递归菜单项(展开/折叠/高亮/图标)
│   │       └── index.scss          # [NEW] 侧栏/菜单/topbar/页签 scss(--fms-* token)
│   ├── stores/
│   │   ├── app.js                  # [MODIFY] 新增 sidebarCollapsed/visitedTabs/routeRefreshKeys/locale + actions
│   │   ├── permissions.js          # [NEW] 权限 store(load/canAccess/canPower/resolvePageId/clear)
│   │   └── auth.js                 # 不变
│   ├── utils/
│   │   ├── tree.js                 # [NEW] buildTree(b_id/b_parent_id)/sortTree(b_xh,b_id)
│   │   └── lucideIcons.js          # [NEW] getIcon(name) 按名取 @lucide/vue 图标
│   ├── router/
│   │   └── index.js                # [MODIFY] / 挂 DefaultLayout + dashboard 子路由 + 权限守卫
│   ├── views/
│   │   ├── dashboard/index.vue     # [MODIFY] 改为布局内内容页(去掉独立全屏壳/退出按钮)
│   │   ├── login/index.vue         # 不变
│   │   └── demo/index.vue          # 不变
│   ├── App.vue                     # 不变(router-view)
│   └── main.js                     # 不变(initThemeSync 保持)
└── package.json                    # 不变(不新增依赖)

关键约定

  • 所有组件 props/事件命名沿用自研组件库现有 API(如 Button type/block/loading、Dropdown items/#menu(close))
  • 页面过渡与全局布局样式放 DefaultLayout 非 scoped style;组件内布局细节用 scoped scss
  • 不使用 TS 类型标注;复杂接口用 JSDoc 注释说明(如 NavItem:{ title, url, icon?, items?, moduleId? })

Agent Extensions

Skill

  • ui-ux-pro-max
  • 用途:查询企业后台管理布局(侧边栏导航/顶栏/页签)的视觉与交互规则,指导自研菜单与 topbar 的 scss 样式细节(hover/active 态、间距、层级),确保迁移后视觉符合现代企业 SaaS 标准
  • 预期结果:自研 NavMain/NavMenuItem/AppTopbar/AppTabs 的 scss 达到与 old2 视觉质量相当的水平(若脚本无法执行则读取其 references 规则文档落地)