浏览代码

fix: fix a series of known problems,fixed #54

vince 8 月之前
父节点
当前提交
276ef2ebc3
共有 36 个文件被更改,包括 314 次插入293 次删除
  1. 1 1
      apps/web-antd/package.json
  2. 6 0
      apps/web-antd/src/locales/langs/en-US.json
  3. 6 0
      apps/web-antd/src/locales/langs/zh-CN.json
  4. 42 0
      apps/web-antd/src/router/routes/modules/demos.ts
  5. 9 0
      apps/web-antd/src/views/demos/badge/index.vue
  6. 1 1
      internal/node-utils/package.json
  7. 1 1
      internal/tailwind-config/package.json
  8. 1 1
      internal/tailwind-config/src/index.ts
  9. 1 1
      internal/vite-config/package.json
  10. 1 1
      package.json
  11. 1 1
      packages/@core/forward/preferences/src/config.ts
  12. 5 0
      packages/@core/forward/preferences/src/use-preferences.ts
  13. 8 7
      packages/@core/shared/design/src/css/global.css
  14. 14 14
      packages/@core/shared/design/src/design-tokens/dark/index.css
  15. 2 2
      packages/@core/shared/design/src/design-tokens/default/index.css
  16. 9 39
      packages/@core/ui-kit/layout-ui/src/components/layout-content.vue
  17. 5 22
      packages/@core/ui-kit/layout-ui/src/components/layout-footer.vue
  18. 11 32
      packages/@core/ui-kit/layout-ui/src/components/layout-header.vue
  19. 21 8
      packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue
  20. 2 6
      packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue
  21. 5 0
      packages/@core/ui-kit/layout-ui/src/vben-layout.ts
  22. 40 22
      packages/@core/ui-kit/layout-ui/src/vben-layout.vue
  23. 7 8
      packages/@core/ui-kit/menu-ui/src/components/menu.vue
  24. 6 20
      packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue
  25. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue
  26. 10 21
      packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue
  27. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/menu-badge/menu-badge.vue
  28. 6 2
      packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue
  29. 2 13
      packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue
  30. 2 10
      packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue
  31. 1 1
      packages/effects/layouts/src/authentication/form.vue
  32. 37 23
      packages/effects/layouts/src/basic/layout.vue
  33. 1 5
      packages/effects/layouts/src/basic/menu/mixed-menu.vue
  34. 13 1
      packages/effects/layouts/src/basic/menu/use-extra-menu.ts
  35. 15 8
      packages/effects/layouts/src/basic/menu/use-mixed-menu.ts
  36. 20 20
      pnpm-lock.yaml

+ 1 - 1
apps/web-antd/package.json

@@ -43,7 +43,7 @@
     "@vben/utils": "workspace:*",
     "@vueuse/core": "^10.11.0",
     "ant-design-vue": "^4.2.3",
-    "dayjs": "^1.11.11",
+    "dayjs": "^1.11.12",
     "pinia": "2.1.7",
     "vue": "^3.4.32",
     "vue-router": "^4.4.0"

+ 6 - 0
apps/web-antd/src/locales/langs/en-US.json

@@ -27,6 +27,12 @@
         "embedded": "Embedded",
         "externalLink": "External Link"
       },
+      "badge": {
+        "title": "Menu Badge",
+        "dot": "Dot Badge",
+        "text": "Text Badge",
+        "color": "Badge Color"
+      },
       "fallback": { "title": "Fallback Page" },
       "features": {
         "title": "Features",

+ 6 - 0
apps/web-antd/src/locales/langs/zh-CN.json

@@ -27,6 +27,12 @@
         "embedded": "内嵌",
         "externalLink": "外链"
       },
+      "badge": {
+        "title": "菜单徽标",
+        "dot": "点徽标",
+        "text": "文本徽标",
+        "color": "徽标颜色"
+      },
       "fallback": {
         "title": "缺省页"
       },

+ 42 - 0
apps/web-antd/src/router/routes/modules/demos.ts

@@ -177,6 +177,48 @@ const routes: RouteRecordRaw[] = [
           },
         ],
       },
+      {
+        meta: {
+          icon: 'lucide:circle-dot',
+          title: $t('page.demos.badge.title'),
+        },
+        name: 'BadgeDemo',
+        path: 'badge',
+        redirect: '/demos/badge/dot',
+        children: [
+          {
+            name: 'BadgeDotDemo',
+            component: () => import('#/views/demos/badge/index.vue'),
+            path: 'dot',
+            meta: {
+              badgeType: 'dot',
+              icon: 'lucide:square-dot',
+              title: $t('page.demos.badge.dot'),
+            },
+          },
+          {
+            name: 'BadgeTextDemo',
+            component: () => import('#/views/demos/badge/index.vue'),
+            path: 'text',
+            meta: {
+              badge: 'New',
+              icon: 'lucide:square-dot',
+              title: $t('page.demos.badge.text'),
+            },
+          },
+          {
+            name: 'BadgeColorDemo',
+            component: () => import('#/views/demos/badge/index.vue'),
+            path: 'color',
+            meta: {
+              badge: 'Hot',
+              badgeVariants: 'destructive',
+              icon: 'lucide:square-dot',
+              title: $t('page.demos.badge.color'),
+            },
+          },
+        ],
+      },
       {
         meta: {
           icon: 'ic:round-settings-input-composite',

+ 9 - 0
apps/web-antd/src/views/demos/badge/index.vue

@@ -0,0 +1,9 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/common-ui';
+
+defineOptions({ name: 'Menu321' });
+</script>
+
+<template>
+  <Fallback description="用于徽标示例" status="coming-soon" title="徽标示例" />
+</template>

+ 1 - 1
internal/node-utils/package.json

@@ -31,7 +31,7 @@
     "@changesets/git": "^3.0.0",
     "@manypkg/get-packages": "^2.2.2",
     "consola": "^3.2.3",
-    "dayjs": "^1.11.11",
+    "dayjs": "^1.11.12",
     "find-up": "^7.0.0",
     "nanoid": "^5.0.7",
     "pkg-types": "^1.1.3",

+ 1 - 1
internal/tailwind-config/package.json

@@ -47,7 +47,7 @@
     "tailwindcss": "^3.4.3"
   },
   "dependencies": {
-    "@iconify/json": "^2.2.228",
+    "@iconify/json": "^2.2.229",
     "@iconify/tailwind": "^1.1.1",
     "@tailwindcss/forms": "^0.5.7",
     "@tailwindcss/nesting": "0.0.0-insiders.565cd3e",

+ 1 - 1
internal/tailwind-config/src/index.ts

@@ -32,7 +32,7 @@ const shadcnUiColors = {
   },
   background: {
     DEFAULT: 'hsl(var(--background))',
-    content: 'hsl(var(--background-content))',
+    deep: 'hsl(var(--background-deep))',
   },
   border: {
     DEFAULT: 'hsl(var(--border))',

+ 1 - 1
internal/vite-config/package.json

@@ -41,7 +41,7 @@
     "@vben/node-utils": "workspace:*",
     "@vitejs/plugin-vue": "^5.0.5",
     "@vitejs/plugin-vue-jsx": "^4.0.0",
-    "dayjs": "^1.11.11",
+    "dayjs": "^1.11.12",
     "dotenv": "^16.4.5",
     "rollup": "^4.18.1",
     "rollup-plugin-visualizer": "^5.12.0",

+ 1 - 1
package.json

@@ -66,7 +66,7 @@
     "@vue/test-utils": "^2.4.6",
     "cross-env": "^7.0.3",
     "cspell": "^8.11.0",
-    "husky": "^9.1.0",
+    "husky": "^9.1.1",
     "is-ci": "^3.0.1",
     "jsdom": "^24.1.0",
     "rimraf": "^6.0.1",

+ 1 - 1
packages/@core/forward/preferences/src/config.ts

@@ -61,7 +61,7 @@ const defaultPreferences: Preferences = {
   },
   sidebar: {
     collapsed: false,
-    collapsedShowTitle: true,
+    collapsedShowTitle: false,
     enable: true,
     expandOnHover: true,
     extraCollapse: true,

+ 5 - 0
packages/@core/forward/preferences/src/use-preferences.ts

@@ -84,6 +84,10 @@ function usePreferences() {
     return isMixedNav.value || isSideMixedNav.value || isSideNav.value;
   });
 
+  const sidebarCollapsed = computed(() => {
+    return preferences.sidebar.collapsed;
+  });
+
   /**
    * @zh_CN 是否开启keep-alive
    * 在tabs可见以及开启keep-alive的情况下才开启
@@ -172,6 +176,7 @@ function usePreferences() {
     isSideNav,
     keepAlive,
     layout,
+    sidebarCollapsed,
     theme,
   };
 }

+ 8 - 7
packages/@core/shared/design/src/css/global.css

@@ -23,14 +23,14 @@
     scroll-behavior: smooth;
     text-rendering: optimizelegibility;
     -webkit-tap-highlight-color: transparent;
-  }
 
-  html.invert-mode {
-    @apply invert;
-  }
+    &.invert-mode {
+      @apply invert;
+    }
 
-  html.grayscale-mode {
-    @apply grayscale;
+    &.grayscale-mode {
+      @apply grayscale;
+    }
   }
 
   #app,
@@ -41,7 +41,8 @@
 
   body {
     min-height: 100vh;
-    overflow: overlay;
+
+    /* overflow: overlay; */
     -webkit-font-smoothing: antialiased;
   }
 

+ 14 - 14
packages/@core/shared/design/src/design-tokens/dark/index.css

@@ -5,7 +5,7 @@
   --background: 222.34deg 10.43% 12.27%;
 
   /* 主体区域背景色 */
-  --background-content: 220deg 13.06% 9%;
+  --background-deep: 220deg 13.06% 9%;
   --foreground: 220 13% 91%;
 
   /* Background color for <Card /> */
@@ -95,7 +95,7 @@
 .dark[data-theme='violet'],
 [data-theme='violet'] .dark {
   --background: 224 71.4% 4.1%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 210 20% 98%;
   --card: 224 71.4% 4.1%;
   --card-foreground: 210 20% 98%;
@@ -120,7 +120,7 @@
 .dark[data-theme='pink'],
 [data-theme='pink'] .dark {
   --background: 20 14.3% 4.1%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 0 0% 95%;
   --card: 24 9.8% 10%;
   --card-foreground: 0 0% 95%;
@@ -145,7 +145,7 @@
 .dark[data-theme='rose'],
 [data-theme='rose'] .dark {
   --background: 0 0% 3.9%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 0 0% 98%;
   --card: 0 0% 3.9%;
   --card-foreground: 0 0% 98%;
@@ -170,7 +170,7 @@
 .dark[data-theme='sky-blue'],
 [data-theme='sky-blue'] .dark {
   --background: 222.2 84% 4.9%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 210 40% 98%;
   --card: 222.2 84% 4.9%;
   --card-foreground: 210 40% 98%;
@@ -195,7 +195,7 @@
 .dark[data-theme='deep-blue'],
 [data-theme='deep-blue'] .dark {
   --background: 222.2 84% 4.9%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 210 40% 98%;
   --card: 222.2 84% 4.9%;
   --card-foreground: 210 40% 98%;
@@ -220,7 +220,7 @@
 .dark[data-theme='green'],
 [data-theme='green'] .dark {
   --background: 20 14.3% 4.1%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 0 0% 95%;
   --card: 24 9.8% 10%;
   --card-foreground: 0 0% 95%;
@@ -245,7 +245,7 @@
 .dark[data-theme='deep-green'],
 [data-theme='deep-green'] .dark {
   --background: 20 14.3% 4.1%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 0 0% 95%;
   --card: 24 9.8% 10%;
   --card-foreground: 0 0% 95%;
@@ -270,7 +270,7 @@
 .dark[data-theme='orange'],
 [data-theme='orange'] .dark {
   --background: 20 14.3% 4.1%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 60 9.1% 97.8%;
   --card: 20 14.3% 4.1%;
   --card-foreground: 60 9.1% 97.8%;
@@ -295,7 +295,7 @@
 .dark[data-theme='yellow'],
 [data-theme='yellow'] .dark {
   --background: 20 14.3% 4.1%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 60 9.1% 97.8%;
   --card: 20 14.3% 4.1%;
   --card-foreground: 60 9.1% 97.8%;
@@ -320,7 +320,7 @@
 .dark[data-theme='zinc'],
 [data-theme='zinc'] .dark {
   --background: 240 10% 3.9%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 0 0% 98%;
   --card: 240 10% 3.9%;
   --card-foreground: 0 0% 98%;
@@ -345,7 +345,7 @@
 .dark[data-theme='neutral'],
 [data-theme='neutral'] .dark {
   --background: 0 0% 3.9%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 0 0% 98%;
   --card: 0 0% 3.9%;
   --card-foreground: 0 0% 98%;
@@ -370,7 +370,7 @@
 .dark[data-theme='slate'],
 [data-theme='slate'] .dark {
   --background: 222.2 84% 4.9%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 210 40% 98%;
   --card: 222.2 84% 4.9%;
   --card-foreground: 210 40% 98%;
@@ -395,7 +395,7 @@
 .dark[data-theme='gray'],
 [data-theme='gray'] .dark {
   --background: 224 71.4% 4.1%;
-  --background-content: var(--background);
+  --background-deep: var(--background);
   --foreground: 210 20% 98%;
   --card: 224 71.4% 4.1%;
   --card-foreground: 210 20% 98%;

+ 2 - 2
packages/@core/shared/design/src/design-tokens/default/index.css

@@ -8,7 +8,7 @@
   --background: 0 0 100%;
 
   /* 主体区域背景色 */
-  --background-content: 210 11.11% 96.47%;
+  --background-deep: 210 11.11% 96.47%;
   --foreground: 210 6% 21%;
 
   /* Background color for <Card /> */
@@ -74,7 +74,7 @@
   /* ============= custom ============= */
 
   /* 遮罩颜色 */
-  --overlay: 0deg 0% 0% / 40%;
+  --overlay: 0deg 0% 0% / 30%;
 
   /* 基本文字大小 */
   --font-size-base: 16px;

+ 9 - 39
packages/@core/ui-kit/layout-ui/src/components/layout-content.vue

@@ -9,50 +9,20 @@ import { useContentHeightListener } from '@vben-core/hooks';
 interface Props {
   /**
    * 内容区域定宽
-   * @default 'wide'
    */
-  contentCompact?: ContentCompactType;
+  contentCompact: ContentCompactType;
   /**
    * 定宽布局宽度
-   * @default 1200
    */
-  contentCompactWidth?: number;
-  /**
-   * padding
-   * @default 16
-   */
-  padding?: number;
-  /**
-   * paddingBottom
-   * @default 16
-   */
-  paddingBottom?: number;
-  /**
-   * paddingLeft
-   * @default 16
-   */
-  paddingLeft?: number;
-  /**
-   * paddingRight
-   * @default 16
-   */
-  paddingRight?: number;
-  /**
-   * paddingTop
-   * @default 16
-   */
-  paddingTop?: number;
+  contentCompactWidth: number;
+  padding: number;
+  paddingBottom: number;
+  paddingLeft: number;
+  paddingRight: number;
+  paddingTop: number;
 }
 
-const props = withDefaults(defineProps<Props>(), {
-  contentCompact: 'wide',
-  contentCompactWidth: 1200,
-  padding: 16,
-  paddingBottom: 16,
-  paddingLeft: 16,
-  paddingRight: 16,
-  paddingTop: 16,
-});
+const props = withDefaults(defineProps<Props>(), {});
 
 const { contentElement } = useContentHeightListener();
 
@@ -83,7 +53,7 @@ const style = computed((): CSSProperties => {
 </script>
 
 <template>
-  <main ref="contentElement" :style="style" class="bg-background-content">
+  <main ref="contentElement" :style="style" class="bg-background-deep">
     <slot></slot>
   </main>
 </template>

+ 5 - 22
packages/@core/ui-kit/layout-ui/src/components/layout-footer.vue

@@ -4,38 +4,21 @@ import { computed } from 'vue';
 
 interface Props {
   /**
-   * 是否固定在顶部
-   * @default true
+   * 是否固定在底部
    */
   fixed?: boolean;
-  /**
-   * 高度
-   * @default 32
-   */
-  height?: number;
+  height: number;
   /**
    * 是否显示
    * @default true
    */
   show?: boolean;
-  /**
-   * 高度
-   * @default 100%
-   */
-  width?: string;
-  /**
-   * zIndex
-   * @default 0
-   */
-  zIndex?: number;
+  width: string;
+  zIndex: number;
 }
 
 const props = withDefaults(defineProps<Props>(), {
-  fixed: true,
-  height: 32,
   show: true,
-  width: '100%',
-  zIndex: 0,
 });
 
 const style = computed((): CSSProperties => {
@@ -53,7 +36,7 @@ const style = computed((): CSSProperties => {
 <template>
   <footer
     :style="style"
-    class="bg-background-content bottom-0 w-full transition-all duration-200"
+    class="bg-background-deep bottom-0 w-full transition-all duration-200"
   >
     <slot></slot>
   </footer>

+ 11 - 32
packages/@core/ui-kit/layout-ui/src/components/layout-header.vue

@@ -8,61 +8,45 @@ import { VbenIconButton } from '@vben-core/shadcn-ui';
 interface Props {
   /**
    * 横屏
-   * @default false
    */
-  fullWidth?: boolean;
+  fullWidth: boolean;
   /**
    * 高度
-   * @default 60
    */
-  height?: number;
+  height: number;
   /**
    * 是否混合导航
    * @default false
    */
-  isMixedNav?: boolean;
+  isMixedNav: boolean;
   /**
    * 是否移动端
-   * @default false
    */
-  isMobile?: boolean;
+  isMobile: boolean;
   /**
    * 是否显示
-   * @default true
    */
-  show?: boolean;
+  show: boolean;
   /**
    * 是否显示关闭菜单按钮
-   * @default true
    */
-  showToggleBtn?: boolean;
+  showToggleBtn: boolean;
 
   /**
    * 侧边菜单宽度
-   * @default 0
    */
-  sidebarWidth?: number;
+  sidebarWidth: number;
   /**
    * 宽度
-   * @default 100%
    */
-  width?: string;
+  width: string;
   /**
    * zIndex
-   * @default 0
    */
-  zIndex?: number;
+  zIndex: number;
 }
 
-const props = withDefaults(defineProps<Props>(), {
-  height: 60,
-  isMixedNav: false,
-  show: true,
-  showToggleBtn: false,
-  sidebarWidth: 0,
-  width: '100%',
-  zIndex: 0,
-});
+const props = withDefaults(defineProps<Props>(), {});
 
 const emit = defineEmits<{ openMenu: []; toggleSidebar: [] }>();
 
@@ -73,7 +57,6 @@ const style = computed((): CSSProperties => {
   const right = !show || !fullWidth ? undefined : 0;
 
   return {
-    // ...(props.isMixedNav ? { left: 0, position: `fixed` } : {}),
     height: `${height}px`,
     marginTop: show ? 0 : `-${height}px`,
     right,
@@ -87,11 +70,7 @@ const logoStyle = computed((): CSSProperties => {
 });
 
 function handleToggleMenu() {
-  if (props.isMobile) {
-    emit('openMenu');
-  } else {
-    emit('toggleSidebar');
-  }
+  props.isMobile ? emit('openMenu') : emit('toggleSidebar');
 }
 </script>
 

+ 21 - 8
packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue

@@ -9,7 +9,7 @@ import { SidebarCollapseButton, SidebarFixedButton } from './widgets';
 interface Props {
   /**
    * 折叠区域高度
-   * @default 32
+   * @default 42
    */
   collapseHeight?: number;
   /**
@@ -41,6 +41,11 @@ interface Props {
    * @default false
    */
   isSidebarMixed?: boolean;
+  /**
+   * 顶部margin
+   * @default 60
+   */
+  marginTop?: number;
   /**
    * 混合菜单宽度
    * @default 80
@@ -85,8 +90,9 @@ const props = withDefaults(defineProps<Props>(), {
   extraWidth: 180,
   fixedExtra: false,
   isSidebarMixed: false,
-  mixedWidth: 80,
-  paddingTop: 60,
+  marginTop: 0,
+  mixedWidth: 70,
+  paddingTop: 0,
   show: true,
   showCollapseButton: true,
   theme: 'dark',
@@ -108,11 +114,13 @@ const asideRef = shallowRef<HTMLDivElement | null>();
 const hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true));
 
 const style = computed((): CSSProperties => {
-  const { isSidebarMixed, paddingTop, zIndex } = props;
+  const { isSidebarMixed, marginTop, paddingTop, zIndex } = props;
 
   return {
     '--scroll-shadow': 'var(--sidebar)',
     ...calcMenuWidthStyle(false),
+    height: `calc(100% - ${marginTop}px)`,
+    marginTop: `${marginTop}px`,
     paddingTop: `${paddingTop}px`,
     zIndex,
     ...(isSidebarMixed && extraVisible.value ? { transition: 'none' } : {}),
@@ -222,6 +230,7 @@ function handleMouseleave() {
   if (expandOnHover.value) {
     return;
   }
+
   expandOnHovering.value = false;
   collapse.value = true;
   extraVisible.value = false;
@@ -233,7 +242,7 @@ function handleMouseleave() {
     v-if="domVisible"
     :class="theme"
     :style="hiddenSideStyle"
-    class="h-full transition-all duration-200"
+    class="h-full transition-all duration-150"
   ></div>
   <aside
     :class="[
@@ -244,7 +253,7 @@ function handleMouseleave() {
       },
     ]"
     :style="style"
-    class="border-border fixed left-0 top-0 h-full border-r transition-all duration-200"
+    class="border-border fixed left-0 top-0 h-full border-r transition-all duration-150"
     @mouseenter="handleMouseenter"
     @mouseleave="handleMouseleave"
   >
@@ -255,7 +264,7 @@ function handleMouseleave() {
     <div v-if="slots.logo" :style="headerStyle">
       <slot name="logo"></slot>
     </div>
-    <VbenScrollbar :style="contentStyle" shadow>
+    <VbenScrollbar :style="contentStyle" shadow shadow-border>
       <slot></slot>
     </VbenScrollbar>
 
@@ -267,8 +276,11 @@ function handleMouseleave() {
     <div
       v-if="isSidebarMixed"
       ref="asideRef"
+      :class="{
+        'border-r': extraVisible,
+      }"
       :style="extraStyle"
-      class="border-border bg-sidebar fixed top-0 h-full overflow-hidden border-x transition-all duration-200"
+      class="border-border bg-sidebar fixed top-0 h-full overflow-hidden transition-all duration-200"
     >
       <SidebarCollapseButton
         v-if="isSidebarMixed && expandOnHover"
@@ -286,6 +298,7 @@ function handleMouseleave() {
         :style="extraContentStyle"
         class="border-border border-t py-2"
         shadow
+        shadow-border
       >
         <slot name="extra"></slot>
       </VbenScrollbar>

+ 2 - 6
packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue

@@ -5,15 +5,11 @@ import { computed } from 'vue';
 interface Props {
   /**
    * 高度
-   * @default 30
    */
-  height?: number;
+  height: number;
 }
 
-const props = withDefaults(defineProps<Props>(), {
-  fixed: true,
-  height: 38,
-});
+const props = withDefaults(defineProps<Props>(), {});
 
 const style = computed((): CSSProperties => {
   const { height } = props;

+ 5 - 0
packages/@core/ui-kit/layout-ui/src/vben-layout.ts

@@ -123,6 +123,11 @@ interface VbenLayoutProps {
    * @default true
    */
   sidebarEnable?: boolean;
+  /**
+   * 侧边菜单折叠额外宽度
+   * @default 48
+   */
+  sidebarExtraCollapsedWidth?: number;
   /**
    * 侧边栏是否隐藏
    * @default false

+ 40 - 22
packages/@core/ui-kit/layout-ui/src/vben-layout.vue

@@ -21,6 +21,7 @@ defineOptions({
 
 const props = withDefaults(defineProps<Props>(), {
   contentCompact: 'wide',
+  contentCompactWidth: 1200,
   contentPadding: 0,
   contentPaddingBottom: 0,
   contentPaddingLeft: 0,
@@ -39,6 +40,7 @@ const props = withDefaults(defineProps<Props>(), {
   layout: 'sidebar-nav',
   sideCollapseWidth: 60,
   sidebarCollapseShowTitle: false,
+  sidebarExtraCollapsedWidth: 60,
   sidebarHidden: false,
   sidebarMixedWidth: 80,
   sidebarSemiDark: true,
@@ -62,16 +64,16 @@ const {
   isScrolling,
   y: scrollY,
 } = useScroll(document);
+
 const { y: mouseY } = useMouse({ type: 'client' });
 
 // side是否处于hover状态展开菜单中
 const sidebarExpandOnHovering = ref(false);
-// const sideHidden = ref(false);
 const headerIsHidden = ref(false);
 
-const realLayout = computed(() => {
-  return props.isMobile ? 'sidebar-nav' : props.layout;
-});
+const realLayout = computed(() =>
+  props.isMobile ? 'sidebar-nav' : props.layout,
+);
 
 /**
  * 是否全屏显示content,不需要侧边、底部、顶部、tab区域
@@ -98,7 +100,7 @@ const isMixedNav = computed(() => realLayout.value === 'mixed-nav');
 /**
  * 顶栏是否自动隐藏
  */
-const isHeaderAuto = computed(() => props.headerMode === 'auto');
+const isHeaderAutoMode = computed(() => props.headerMode === 'auto');
 
 /**
  * header区域高度
@@ -146,7 +148,7 @@ const sidebarEnableState = computed(() => {
 /**
  * 侧边区域离顶部高度
  */
-const sidePaddingTop = computed(() => {
+const sidebarMarginTop = computed(() => {
   const { isMobile } = props;
   return isMixedNav.value && !isMobile ? getHeaderHeight.value : 0;
 });
@@ -182,10 +184,10 @@ const getSidebarWidth = computed(() => {
 /**
  * 获取扩展区域宽度
  */
-const getExtraWidth = computed(() => {
-  const { sidebarWidth } = props;
+const sidebarExtraWidth = computed(() => {
+  const { sidebarExtraCollapsedWidth, sidebarWidth } = props;
 
-  return sidebarExtraCollapse.value ? getSideCollapseWidth.value : sidebarWidth;
+  return sidebarExtraCollapse.value ? sidebarExtraCollapsedWidth : sidebarWidth;
 });
 
 /**
@@ -269,19 +271,29 @@ const mainStyle = computed(() => {
   };
 });
 
+// 计算 tabbar 的样式
 const tabbarStyle = computed((): CSSProperties => {
   let width = '';
   let marginLeft = 0;
 
+  // 如果不是混合导航,tabbar 的宽度为 100%
   if (!isMixedNav.value) {
     width = '100%';
   } else if (sidebarEnable.value) {
+    // 鼠标在侧边栏上时,且侧边栏展开时的宽度
+    const onHoveringWidth = sidebarExpandOnHover.value
+      ? props.sidebarWidth
+      : getSideCollapseWidth.value;
+
+    // 设置 marginLeft,根据侧边栏是否折叠来决定
     marginLeft = sidebarCollapse.value
       ? getSideCollapseWidth.value
-      : props.sidebarWidth;
+      : onHoveringWidth;
 
-    width = `calc(100% - ${getSidebarWidth.value}px)`;
+    // 设置 tabbar 的宽度,计算方式为 100% 减去侧边栏的宽度
+    width = `calc(100% - ${sidebarCollapse.value ? getSidebarWidth.value : onHoveringWidth}px)`;
   } else {
+    // 默认情况下,tabbar 的宽度为 100%
     width = '100%';
   }
 
@@ -300,7 +312,7 @@ const contentStyle = computed((): CSSProperties => {
       fixed &&
       !fullContent.value &&
       !headerIsHidden.value &&
-      (!isHeaderAuto.value || scrollY.value < headerWrapperHeight.value)
+      (!isHeaderAutoMode.value || scrollY.value < headerWrapperHeight.value)
         ? `${headerWrapperHeight.value}px`
         : 0,
     paddingBottom: `${footerEnable && footerFixed ? footerHeight : 0}px`,
@@ -333,7 +345,12 @@ const headerWrapperStyle = computed((): CSSProperties => {
  */
 const sidebarZIndex = computed(() => {
   const { isMobile, zIndex } = props;
-  const offset = isMobile || isSideMode.value ? 1 : -1;
+  let offset = isMobile || isSideMode.value ? 1 : -1;
+
+  if (isMixedNav.value) {
+    offset += 1;
+  }
+
   return zIndex + offset;
 });
 
@@ -366,7 +383,12 @@ const showHeaderLogo = computed(() => {
 watch(
   () => props.isMobile,
   (val) => {
-    sidebarCollapse.value = val;
+    if (val) {
+      sidebarCollapse.value = true;
+    }
+  },
+  {
+    immediate: true,
   },
 );
 
@@ -379,7 +401,7 @@ watch(
   watch(
     [() => props.headerMode, () => mouseY.value],
     () => {
-      if (!isHeaderAuto.value || isMixedNav.value || fullContent.value) {
+      if (!isHeaderAutoMode.value || isMixedNav.value || fullContent.value) {
         return;
       }
       headerIsHidden.value = true;
@@ -434,10 +456,6 @@ function handleClickMask() {
   sidebarCollapse.value = true;
 }
 
-function handleToggleSidebar() {
-  emit('toggleSidebar');
-}
-
 function handleOpenMenu() {
   sidebarCollapse.value = false;
 }
@@ -456,12 +474,12 @@ function handleOpenMenu() {
       v-model:extra-visible="sidebarExtraVisible"
       :collapse-width="getSideCollapseWidth"
       :dom-visible="!isMobile"
-      :extra-width="getExtraWidth"
+      :extra-width="sidebarExtraWidth"
       :fixed-extra="sidebarExpandOnHover"
       :header-height="isMixedNav ? 0 : getHeaderHeight"
       :is-sidebar-mixed="isSidebarMixedNav"
+      :margin-top="sidebarMarginTop"
       :mixed-width="sidebarMixedWidth"
-      :padding-top="sidePaddingTop"
       :show="showSidebar"
       :theme="sidebarFace.theme"
       :width="getSidebarWidth"
@@ -506,7 +524,7 @@ function handleOpenMenu() {
           :width="mainStyle.width"
           :z-index="headerZIndex"
           @open-menu="handleOpenMenu"
-          @toggle-sidebar="handleToggleSidebar"
+          @toggle-sidebar="() => emit('toggleSidebar')"
         >
           <template v-if="showHeaderLogo" #logo>
             <slot name="logo"></slot>

+ 7 - 8
packages/@core/ui-kit/menu-ui/src/components/menu.vue

@@ -397,7 +397,7 @@ $namespace: vben;
 
   &:hover {
     .#{$namespace}-menu__icon {
-      transform: scale(1.3);
+      transform: scale(1.2);
     }
   }
 
@@ -422,7 +422,7 @@ $namespace: vben;
 .#{$namespace}-menu__popup-container,
 .#{$namespace}-menu {
   --menu-title-width: 140px;
-  --menu-item-icon-width: 20px;
+  --menu-item-icon-size: 16px;
   --menu-item-height: 38px;
   --menu-item-padding-y: 22px;
   --menu-item-padding-x: 12px;
@@ -430,7 +430,7 @@ $namespace: vben;
   --menu-item-popup-padding-x: 12px;
   --menu-item-margin-y: 3px;
   --menu-item-margin-x: 0px;
-  --menu-item-collapse-padding-y: 25px;
+  --menu-item-collapse-padding-y: 23.5px;
   --menu-item-collapse-padding-x: 0px;
   --menu-item-collapse-margin-y: 4px;
   --menu-item-collapse-margin-x: 0px;
@@ -701,10 +701,9 @@ $namespace: vben;
 
   &__icon {
     flex-shrink: 0;
-    // width: var(--menu-item-icon-width);
-    max-height: var(--menu-item-icon-width);
-    margin-right: 12px;
-    font-size: 20px;
+    width: var(--menu-item-icon-size);
+    height: var(--menu-item-icon-size);
+    margin-right: 8px;
     text-align: center;
     vertical-align: middle;
   }
@@ -810,7 +809,7 @@ $namespace: vben;
     width: inherit;
     margin-top: -8px;
     margin-right: 0;
-    font-size: 16px;
+    // font-size: 16px;
     font-weight: normal;
     opacity: 1;
     transition: transform 0.25s ease;

+ 6 - 20
packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue

@@ -25,14 +25,6 @@ const emit = defineEmits<{
 }>();
 
 const { b, e, is } = useNamespace('normal-menu');
-
-function handleClick(menu: MenuRecordRaw) {
-  emit('select', menu);
-}
-
-function handleMouseenter(menu: MenuRecordRaw) {
-  emit('enter', menu);
-}
 </script>
 
 <template>
@@ -49,8 +41,8 @@ function handleMouseenter(menu: MenuRecordRaw) {
     <template v-for="menu in menus" :key="menu.path">
       <li
         :class="[e('item'), is('active', activePath === menu.path)]"
-        @click="handleClick(menu)"
-        @mouseenter="handleMouseenter(menu)"
+        @click="() => emit('select', menu)"
+        @mouseenter="() => emit('enter', menu)"
       >
         <VbenIcon :class="e('icon')" :icon="menu.icon" fallback />
         <span :class="e('name')" class="truncate"> {{ menu.name }}</span>
@@ -64,10 +56,9 @@ $namespace: vben;
 .#{$namespace}-normal-menu {
   --menu-item-margin-y: 4px;
   --menu-item-margin-x: 0px;
-  --menu-item-padding-y: 8px;
+  --menu-item-padding-y: 9px;
   --menu-item-padding-x: 0px;
   --menu-item-radius: 0px;
-  --menu-dark-background: 0deg 0% 100% / 10%;
 
   height: calc(100% - 4px);
 
@@ -82,12 +73,9 @@ $namespace: vben;
 
       &:not(.is-active):hover {
         color: hsl(var(--primary-foreground));
-        background-color: hsl(var(--menu-dark-background));
       }
 
       &.is-active {
-        background-color: hsl(var(--menu-dark-background));
-
         .#{$namespace}-normal-menu__name,
         .#{$namespace}-normal-menu__icon {
           color: hsl(var(--primary-foreground));
@@ -129,7 +117,7 @@ $namespace: vben;
       border-color 0.15s ease;
 
     &.is-active {
-      @apply text-primary bg-primary/20;
+      @apply text-primary bg-primary/15 dark:bg-accent;
 
       .#{$namespace}-normal-menu__name,
       .#{$namespace}-normal-menu__icon {
@@ -138,14 +126,12 @@ $namespace: vben;
     }
 
     &:not(.is-active):hover {
-      @apply text-foreground;
-
-      background-color: hsl(var(--menu-dark-background));
+      @apply dark:bg-accent text-primary bg-heavy dark:text-foreground;
     }
 
     &:hover {
       .#{$namespace}-normal-menu__icon {
-        transform: scale(1.3);
+        transform: scale(1.2);
       }
     }
   }

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue

@@ -35,7 +35,7 @@ function handleClick(path?: string) {
               <VbenIcon
                 v-if="item.icon && showIcon"
                 :icon="item.icon"
-                class="mr-1 size-5 flex-shrink-0"
+                class="mr-1 size-4 flex-shrink-0"
               />
               <span
                 :class="{

+ 10 - 21
packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue

@@ -1,15 +1,9 @@
 <script setup lang="ts">
-import { computed } from 'vue';
-
 interface Props {
-  /**
-   * Logo 图标 alt
-   */
-  alt?: string;
   /**
    * 是否收起文本
    */
-  collapse?: boolean;
+  collapsed?: boolean;
   /**
    * Logo 跳转地址
    */
@@ -25,7 +19,7 @@ interface Props {
   /**
    * Logo 文本
    */
-  text?: string;
+  text: string;
   /**
    * Logo 主题
    */
@@ -33,39 +27,34 @@ interface Props {
 }
 
 defineOptions({
-  name: 'Logo',
+  name: 'VbenLogo',
 });
 
-const props = withDefaults(defineProps<Props>(), {
-  alt: 'Vben',
-  collapse: false,
+withDefaults(defineProps<Props>(), {
+  collapsed: false,
   href: 'javascript:void 0',
-  logoSize: 36,
+  logoSize: 32,
   src: '',
-  text: '',
   theme: 'light',
 });
-const logoClass = computed(() => {
-  return [props.theme, props.collapse ? 'collapsed' : ''];
-});
 </script>
 
 <template>
-  <div :class="logoClass" class="flex h-full items-center text-lg">
+  <div :class="theme" class="flex h-full items-center text-lg">
     <a
       :class="$attrs.class"
       :href="href"
-      class="flex h-full items-center gap-2 overflow-hidden px-3 font-semibold leading-normal transition-all duration-500"
+      class="flex h-full items-center gap-2 overflow-hidden px-3 text-lg font-semibold leading-normal transition-all duration-500"
     >
       <img
         v-if="src"
-        :alt="alt"
+        :alt="text"
         :src="src"
         :width="logoSize"
         class="relative rounded-none bg-transparent"
       />
       <span
-        v-if="!collapse"
+        v-if="!collapsed"
         class="text-primary dark:text-foreground truncate text-nowrap"
       >
         {{ text }}

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/menu-badge/menu-badge.vue

@@ -49,7 +49,7 @@ const badgeStyle = computed(() => {
       v-else
       :class="badgeClass"
       :style="badgeStyle"
-      class="rounded-md px-1.5 py-0.5 text-xs"
+      class="text-primary-foreground rounded-xl px-1.5 py-0.5 text-xs"
     >
       {{ badge }}
     </div>

+ 6 - 2
packages/@core/ui-kit/shadcn-ui/src/components/scrollbar/scrollbar.vue

@@ -12,12 +12,14 @@ interface Props {
   class?: HTMLAttributes['class'];
   horizontal?: boolean;
   shadow?: boolean;
+  shadowBorder?: boolean;
 }
 
 const props = withDefaults(defineProps<Props>(), {
   class: '',
   horizontal: false,
   shadow: false,
+  shadowBorder: false,
 });
 
 const isAtTop = ref(true);
@@ -42,7 +44,8 @@ function handleScroll(event: Event) {
     <div
       v-if="shadow"
       :class="{
-        'border-border border-t opacity-100': !isAtTop,
+        'opacity-100': !isAtTop,
+        'border-border border-t': shadowBorder && !isAtTop,
       }"
       class="scrollbar-top-shadow pointer-events-none absolute top-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
     ></div>
@@ -50,7 +53,8 @@ function handleScroll(event: Event) {
     <div
       v-if="shadow"
       :class="{
-        'border-border border-b opacity-100': !isAtTop && !isAtBottom,
+        'opacity-100': !isAtTop && !isAtBottom,
+        'border-border border-t': shadowBorder && !isAtTop && !isAtBottom,
       }"
       class="scrollbar-bottom-shadow pointer-events-none absolute bottom-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
     ></div>

+ 2 - 13
packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue

@@ -58,14 +58,6 @@ watch(active, () => {
   scrollIntoView();
 });
 
-function handleClose(key: string) {
-  emit('close', key);
-}
-
-function handleUnpinTab(tab: TabConfig) {
-  emit('unpin', tab);
-}
-
 function scrollIntoView() {
   setTimeout(() => {
     const element = document.querySelector(`.tabs-chrome__item.is-active`);
@@ -142,21 +134,18 @@ function scrollIntoView() {
                 <div
                   class="tabs-chrome__extra absolute right-[calc(var(--gap)*1.5)] top-1/2 z-[3] size-4 translate-y-[-50%]"
                 >
-                  <!-- <div
-                class="tabs-chrome__extra absolute right-[calc(var(--gap)*2)] top-1/2 z-[3] size-4 translate-y-[-50%] opacity-0 transition-opacity group-hover:opacity-100"
-              > -->
                   <!-- close-icon -->
                   <X
                     v-show="
                       !tab.affixTab && tabsView.length > 1 && tab.closable
                     "
                     class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3 cursor-pointer rounded-full transition-all"
-                    @click.stop="handleClose(tab.key)"
+                    @click.stop="() => emit('close', tab.key)"
                   />
                   <MdiPin
                     v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
                     class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
-                    @click.stop="handleUnpinTab(tab)"
+                    @click.stop="() => emit('unpin', tab)"
                   />
                 </div>
 

+ 2 - 10
packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue

@@ -59,14 +59,6 @@ watch(active, () => {
   scrollIntoView();
 });
 
-function handleClose(key: string) {
-  emit('close', key);
-}
-
-function handleUnpinTab(tab: TabConfig) {
-  emit('unpin', tab);
-}
-
 function scrollIntoView() {
   setTimeout(() => {
     const element = document.querySelector(`.tabs-chrome__item.is-active`);
@@ -120,12 +112,12 @@ function scrollIntoView() {
                       !tab.affixTab && tabsView.length > 1 && tab.closable
                     "
                     class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all"
-                    @click.stop="handleClose(tab.key)"
+                    @click.stop="() => emit('close', tab.key)"
                   />
                   <MdiPin
                     v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
                     class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
-                    @click.stop="handleUnpinTab(tab)"
+                    @click.stop="() => emit('unpin', tab)"
                   />
                 </div>
 

+ 1 - 1
packages/effects/layouts/src/authentication/form.vue

@@ -11,7 +11,7 @@ defineOptions({
 
 <template>
   <div
-    class="flex-col-center bg-background-content relative px-6 py-10 lg:flex-initial lg:px-8"
+    class="flex-col-center bg-background-deep relative px-6 py-10 lg:flex-initial lg:px-8"
   >
     <!-- Toolbar Slot -->
     <slot name="toolbar">

+ 37 - 23
packages/effects/layouts/src/basic/layout.vue

@@ -30,8 +30,15 @@ defineOptions({ name: 'BasicLayout' });
 
 const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
 
-const { isDark, isHeaderNav, isMixedNav, isMobile, isSideMixedNav, layout } =
-  usePreferences();
+const {
+  isDark,
+  isHeaderNav,
+  isMixedNav,
+  isMobile,
+  isSideMixedNav,
+  layout,
+  sidebarCollapsed,
+} = usePreferences();
 
 const headerMenuTheme = computed(() => {
   return isDark.value ? 'dark' : 'light';
@@ -43,38 +50,45 @@ const theme = computed(() => {
 });
 
 const logoClass = computed(() => {
-  let cls = '';
-  const { collapsed, collapsedShowTitle } = preferences.sidebar;
-  if (collapsedShowTitle && collapsed && !isMixedNav.value) {
-    cls += ' mx-auto';
+  const { collapsedShowTitle } = preferences.sidebar;
+  const classes: string[] = [];
+
+  if (collapsedShowTitle && sidebarCollapsed.value && !isMixedNav.value) {
+    classes.push('mx-auto');
   }
+
   if (isSideMixedNav.value) {
-    cls += ' flex-center';
+    classes.push('flex-center');
   }
-  return cls;
+
+  return classes.join(' ');
 });
 
 const isMenuRounded = computed(() => {
   return preferences.navigation.styleType === 'rounded';
 });
 
-const logoCollapse = computed(() => {
-  if (isHeaderNav.value || isMixedNav.value) {
+const logoCollapsed = computed(() => {
+  const shouldCollapse = isHeaderNav.value || isMixedNav.value;
+
+  if (shouldCollapse) {
     return false;
   }
 
-  const { collapsed } = preferences.sidebar;
+  const shouldExpandOnMobile = !sidebarCollapsed.value && isMobile.value;
 
-  if (!collapsed && isMobile) {
+  if (shouldExpandOnMobile) {
     return false;
   }
-  return collapsed || isSideMixedNav.value;
+
+  return sidebarCollapsed.value || isSideMixedNav.value;
 });
 
 const showHeaderNav = computed(() => {
   return isHeaderNav.value || isMixedNav.value;
 });
 
+// 侧边多列菜单
 const {
   extraActiveMenu,
   extraMenus,
@@ -89,9 +103,9 @@ const {
   handleMenuSelect,
   headerActive,
   headerMenus,
-  sideActive,
-  sideMenus,
-  sideVisible,
+  sidebarActive,
+  sidebarMenus,
+  sidebarVisible,
 } = useMixedMenu();
 
 function wrapperMenus(menus: MenuRecordRaw[]) {
@@ -127,7 +141,7 @@ function clearPreferencesAndLogout() {
     :layout="layout"
     :sidebar-collapse="preferences.sidebar.collapsed"
     :sidebar-collapse-show-title="preferences.sidebar.collapsedShowTitle"
-    :sidebar-enable="sideVisible"
+    :sidebar-enable="sidebarVisible"
     :sidebar-expand-on-hover="preferences.sidebar.expandOnHover"
     :sidebar-extra-collapse="preferences.sidebar.extraCollapse"
     :sidebar-hidden="preferences.sidebar.hidden"
@@ -168,9 +182,9 @@ function clearPreferencesAndLogout() {
     <!-- logo -->
     <template #logo>
       <VbenLogo
-        :alt="preferences.app.name"
+        v-if="preferences.logo.enable"
         :class="logoClass"
-        :collapse="logoCollapse"
+        :collapsed="logoCollapsed"
         :src="preferences.logo.source"
         :text="preferences.app.name"
         :theme="showHeaderNav ? headerMenuTheme : theme"
@@ -215,8 +229,8 @@ function clearPreferencesAndLogout() {
         :accordion="preferences.navigation.accordion"
         :collapse="preferences.sidebar.collapsed"
         :collapse-show-title="preferences.sidebar.collapsedShowTitle"
-        :default-active="sideActive"
-        :menus="wrapperMenus(sideMenus)"
+        :default-active="sidebarActive"
+        :menus="wrapperMenus(sidebarMenus)"
         :rounded="isMenuRounded"
         :theme="theme"
         mode="vertical"
@@ -224,9 +238,9 @@ function clearPreferencesAndLogout() {
       />
     </template>
     <template #mixed-menu>
+      <!-- :collapse="!preferences.sidebar.collapsedShowTitle" -->
       <LayoutMixedMenu
         :active-path="extraActiveMenu"
-        :collapse="!preferences.sidebar.collapsedShowTitle"
         :menus="wrapperMenus(headerMenus)"
         :rounded="isMenuRounded"
         :theme="theme"
@@ -248,7 +262,6 @@ function clearPreferencesAndLogout() {
     <template #side-extra-title>
       <VbenLogo
         v-if="preferences.logo.enable"
-        :alt="preferences.app.name"
         :text="preferences.app.name"
         :theme="theme"
       />
@@ -266,6 +279,7 @@ function clearPreferencesAndLogout() {
     <template #content>
       <LayoutContent />
     </template>
+
     <!-- 页脚 -->
     <template v-if="preferences.footer.enable" #footer>
       <LayoutFooter>

+ 1 - 5
packages/effects/layouts/src/basic/menu/mixed-menu.vue

@@ -20,10 +20,6 @@ const emit = defineEmits<{
 
 const route = useRoute();
 
-function handleSelect(menu: MenuRecordRaw) {
-  emit('select', menu);
-}
-
 onBeforeMount(() => {
   const menu = findMenuByPath(props.menus || [], route.path);
   if (menu) {
@@ -43,6 +39,6 @@ onBeforeMount(() => {
     :rounded="rounded"
     :theme="theme"
     @enter="(menu) => emit('enter', menu)"
-    @select="handleSelect"
+    @select="(menu) => emit('select', menu)"
   />
 </template>

+ 13 - 1
packages/effects/layouts/src/basic/menu/use-extra-menu.ts

@@ -1,6 +1,6 @@
 import type { MenuRecordRaw } from '@vben-core/typings';
 
-import { computed, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { findRootMenuByPath } from '@vben-core/helpers';
@@ -78,6 +78,18 @@ function useExtraMenu() {
     }
   };
 
+  watch(
+    () => route.path,
+    () => {
+      const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(
+        menus.value,
+        route.path,
+      );
+      extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? '';
+      extraMenus.value = rootMenu?.children ?? [];
+    },
+  );
+
   return {
     extraActiveMenu,
     extraMenus,

+ 15 - 8
packages/effects/layouts/src/basic/menu/use-mixed-menu.ts

@@ -1,6 +1,6 @@
 import type { MenuRecordRaw } from '@vben-core/typings';
 
-import { computed, onBeforeMount, ref } from 'vue';
+import { computed, onBeforeMount, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { findRootMenuByPath } from '@vben-core/helpers';
@@ -10,8 +10,8 @@ import { useCoreAccessStore } from '@vben-core/stores';
 import { useNavigation } from './use-navigation';
 
 function useMixedMenu() {
-  const accessStore = useCoreAccessStore();
   const { navigation } = useNavigation();
+  const accessStore = useCoreAccessStore();
   const route = useRoute();
   const splitSideMenus = ref<MenuRecordRaw[]>([]);
   const rootMenuPath = ref<string>('');
@@ -22,7 +22,7 @@ function useMixedMenu() {
     () => preferences.navigation.split && isMixedNav.value,
   );
 
-  const sideVisible = computed(() => {
+  const sidebarVisible = computed(() => {
     const enableSidebar = preferences.sidebar.enable;
     if (needSplit.value) {
       return enableSidebar && splitSideMenus.value.length > 0;
@@ -49,14 +49,14 @@ function useMixedMenu() {
   /**
    * 侧边菜单
    */
-  const sideMenus = computed(() => {
+  const sidebarMenus = computed(() => {
     return needSplit.value ? splitSideMenus.value : menus.value;
   });
 
   /**
    * 侧边菜单激活路径
    */
-  const sideActive = computed(() => {
+  const sidebarActive = computed(() => {
     return route.path;
   });
 
@@ -102,6 +102,13 @@ function useMixedMenu() {
     splitSideMenus.value = rootMenu?.children ?? [];
   }
 
+  watch(
+    () => route.path,
+    (path: string) => {
+      calcSideMenus(path);
+    },
+  );
+
   // 初始化计算侧边菜单
   onBeforeMount(() => {
     calcSideMenus();
@@ -111,9 +118,9 @@ function useMixedMenu() {
     handleMenuSelect,
     headerActive,
     headerMenus,
-    sideActive,
-    sideMenus,
-    sideVisible,
+    sidebarActive,
+    sidebarMenus,
+    sidebarVisible,
   };
 }
 

+ 20 - 20
pnpm-lock.yaml

@@ -67,8 +67,8 @@ importers:
         specifier: ^8.11.0
         version: 8.11.0
       husky:
-        specifier: ^9.1.0
-        version: 9.1.0
+        specifier: ^9.1.1
+        version: 9.1.1
       is-ci:
         specifier: ^3.0.1
         version: 3.0.1
@@ -236,8 +236,8 @@ importers:
         specifier: ^4.2.3
         version: 4.2.3(vue@3.4.32(typescript@5.5.3))
       dayjs:
-        specifier: ^1.11.11
-        version: 1.11.11
+        specifier: ^1.11.12
+        version: 1.11.12
       pinia:
         specifier: 2.1.7
         version: 2.1.7(typescript@5.5.3)(vue@3.4.32(typescript@5.5.3))
@@ -418,8 +418,8 @@ importers:
         specifier: ^3.2.3
         version: 3.2.3
       dayjs:
-        specifier: ^1.11.11
-        version: 1.11.11
+        specifier: ^1.11.12
+        version: 1.11.12
       find-up:
         specifier: ^7.0.0
         version: 7.0.0
@@ -442,8 +442,8 @@ importers:
   internal/tailwind-config:
     dependencies:
       '@iconify/json':
-        specifier: ^2.2.228
-        version: 2.2.228
+        specifier: ^2.2.229
+        version: 2.2.229
       '@iconify/tailwind':
         specifier: ^1.1.1
         version: 1.1.1
@@ -537,8 +537,8 @@ importers:
         specifier: ^4.0.0
         version: 4.0.0(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3))(vue@3.4.32(typescript@5.5.3))
       dayjs:
-        specifier: ^1.11.11
-        version: 1.11.11
+        specifier: ^1.11.12
+        version: 1.11.12
       dotenv:
         specifier: ^16.4.5
         version: 16.4.5
@@ -2944,8 +2944,8 @@ packages:
     resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
     deprecated: Use @eslint/object-schema instead
 
-  '@iconify/json@2.2.228':
-    resolution: {integrity: sha512-Xd1CgQ1bCFLrp4t+J2TU+AXM+kVHAFLfhK9FcZD54aMPlzENdQMJ5JhfFzCgnsLBAIW1PbSY2Edbm4tt2Tw9Lg==}
+  '@iconify/json@2.2.229':
+    resolution: {integrity: sha512-DD1k97sjm87n+C15Ey1dVX1cBKCawfms6N0d+1vvAon5P3yurpPEO9OyU88f53+9Chpo+CuIp3+TihvsghlfQQ==}
 
   '@iconify/tailwind@1.1.1':
     resolution: {integrity: sha512-4mmA//qjZigv7D4KlqcVSYTqfRIJzyts2/lSCAJfCL0rVMIE76+ifJnaE5jxCo1+nYGBF8FsFo0qFOs+sX4EnA==}
@@ -4794,8 +4794,8 @@ packages:
   dataloader@1.4.0:
     resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
 
-  dayjs@1.11.11:
-    resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==}
+  dayjs@1.11.12:
+    resolution: {integrity: sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==}
 
   de-indent@1.0.2:
     resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@@ -5847,8 +5847,8 @@ packages:
   humanize-ms@1.2.1:
     resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
 
-  husky@9.1.0:
-    resolution: {integrity: sha512-8XCjbomYTGdNF2h50dio3T3zghmZ9f/ZNzr99YwSkvDdhEjJGs5qzy8tbFx+SG8yCx2wn9nMVfZxVrr/yT8gNQ==}
+  husky@9.1.1:
+    resolution: {integrity: sha512-fCqlqLXcBnXa/TJXmT93/A36tJsjdJkibQ1MuIiFyCCYUlpYpIaj2mv1w+3KR6Rzu1IC3slFTje5f6DUp2A2rg==}
     engines: {node: '>=18'}
     hasBin: true
 
@@ -11402,7 +11402,7 @@ snapshots:
 
   '@humanwhocodes/object-schema@2.0.3': {}
 
-  '@iconify/json@2.2.228':
+  '@iconify/json@2.2.229':
     dependencies:
       '@iconify/types': 2.0.0
       pathe: 1.1.2
@@ -12772,7 +12772,7 @@ snapshots:
       array-tree-filter: 2.1.0
       async-validator: 4.2.5
       csstype: 3.1.3
-      dayjs: 1.11.11
+      dayjs: 1.11.12
       dom-align: 1.12.4
       dom-scroll-into-view: 2.0.1
       lodash: 4.17.21
@@ -13652,7 +13652,7 @@ snapshots:
 
   dataloader@1.4.0: {}
 
-  dayjs@1.11.11: {}
+  dayjs@1.11.12: {}
 
   de-indent@1.0.2: {}
 
@@ -14948,7 +14948,7 @@ snapshots:
     dependencies:
       ms: 2.1.3
 
-  husky@9.1.0: {}
+  husky@9.1.1: {}
 
   iconv-lite@0.4.24:
     dependencies: