From 893f74dc3ef994876057c7c7a3937216271f5120 Mon Sep 17 00:00:00 2001
From: zouawen <846027729@qq.com>
Date: Mon, 9 Feb 2026 16:31:29 +0800
Subject: [PATCH 01/13] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=A8=AA?=
=?UTF-8?q?=E5=90=91=E5=B8=83=E5=B1=80=E6=97=B6=E8=8F=9C=E5=8D=95=E6=BF=80?=
=?UTF-8?q?=E6=B4=BB=E6=88=96=E8=81=9A=E7=84=A6=E6=97=B6=E8=83=8C=E6=99=AF?=
=?UTF-8?q?=E8=89=B2=EF=BC=8C=E6=A0=87=E7=AD=BE=E5=B7=A5=E5=85=B7=E6=A0=8F?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=88=B7=E6=96=B0=E6=8C=89=E9=92=AE=EF=BC=8C?=
=?UTF-8?q?=E5=85=B6=E4=BB=96=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/@core/base/icons/src/lucide.ts | 2 +
packages/@core/preferences/src/config.ts | 1 +
packages/@core/preferences/src/constants.ts | 3 --
packages/@core/preferences/src/types.ts | 2 +
.../ui-kit/menu-ui/src/components/menu.vue | 53 +++++++++++--------
.../menu-ui/src/components/sub-menu.vue | 1 +
.../src/components/tabs-chrome/tabs.vue | 2 +-
.../tabs-ui/src/components/tabs/tabs.vue | 2 +-
.../tabs-ui/src/components/widgets/index.ts | 1 +
.../src/components/widgets/tool-more.vue | 4 +-
.../src/components/widgets/tool-refresh.vue | 18 +++++++
.../@core/ui-kit/tabs-ui/src/tabs-view.vue | 6 +--
packages/effects/layouts/src/basic/layout.vue | 2 +-
.../layouts/src/basic/tabbar/tabbar.vue | 13 ++++-
.../preferences/blocks/theme/builtin.vue | 1 -
.../plugins/src/vxe-table/use-vxe-grid.vue | 6 ++-
16 files changed, 79 insertions(+), 38 deletions(-)
create mode 100644 packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-refresh.vue
diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts
index a167aea0..97325131 100644
--- a/packages/@core/base/icons/src/lucide.ts
+++ b/packages/@core/base/icons/src/lucide.ts
@@ -29,6 +29,7 @@ export {
FoldHorizontal,
Fullscreen,
Github,
+ Grid,
Grip,
GripVertical,
Menu as IconDefault,
@@ -36,6 +37,7 @@ export {
Info,
InspectionPanel,
Languages,
+ LayoutGrid,
LoaderCircle,
LockKeyhole,
LogOut,
diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts
index f788e895..43cc73fa 100644
--- a/packages/@core/preferences/src/config.ts
+++ b/packages/@core/preferences/src/config.ts
@@ -105,6 +105,7 @@ const defaultPreferences: Preferences = {
showIcon: true,
showMaximize: true,
showMore: true,
+ showRefresh: true,
styleType: 'chrome',
visitHistory: true,
wheelable: true,
diff --git a/packages/@core/preferences/src/constants.ts b/packages/@core/preferences/src/constants.ts
index 7ec2007d..562a7afa 100644
--- a/packages/@core/preferences/src/constants.ts
+++ b/packages/@core/preferences/src/constants.ts
@@ -38,12 +38,10 @@ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
primaryColor: 'hsl(240 5.9% 10%)',
type: 'zinc',
},
-
{
color: 'hsl(181 84% 32%)',
type: 'deep-green',
},
-
{
color: 'hsl(211 91% 39%)',
type: 'deep-blue',
@@ -56,7 +54,6 @@ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
color: 'hsl(0 75% 42%)',
type: 'rose',
},
-
{
color: 'hsl(0 0% 25%)',
darkPrimaryColor: 'hsl(0 0% 98%)',
diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts
index e1ef0a38..6db64af2 100644
--- a/packages/@core/preferences/src/types.ts
+++ b/packages/@core/preferences/src/types.ts
@@ -222,6 +222,8 @@ interface TabbarPreferences {
showMaximize: boolean;
/** 显示更多按钮 */
showMore: boolean;
+ /** 显示刷新按钮 */
+ showRefresh: boolean;
/** 标签页风格 */
styleType: TabsStyleType;
/** 是否开启访问历史记录 */
diff --git a/packages/@core/ui-kit/menu-ui/src/components/menu.vue b/packages/@core/ui-kit/menu-ui/src/components/menu.vue
index 644ff59e..4d3ed617 100644
--- a/packages/@core/ui-kit/menu-ui/src/components/menu.vue
+++ b/packages/@core/ui-kit/menu-ui/src/components/menu.vue
@@ -463,33 +463,33 @@ $namespace: vben;
&.is-dark {
--menu-background-color: hsl(var(--menu));
// --menu-submenu-opened-background-color: hsl(var(--menu-opened-dark));
- --menu-item-background-color: var(--menu-background-color);
--menu-item-color: hsl(var(--foreground) / 80%);
+ --menu-item-background-color: var(--menu-background-color);
--menu-item-hover-color: hsl(var(--accent-foreground));
--menu-item-hover-background-color: hsl(var(--accent));
--menu-item-active-color: hsl(var(--accent-foreground));
--menu-item-active-background-color: hsl(var(--accent));
- --menu-submenu-hover-color: hsl(var(--foreground));
- --menu-submenu-hover-background-color: hsl(var(--accent));
- --menu-submenu-active-color: hsl(var(--foreground));
- --menu-submenu-active-background-color: transparent;
--menu-submenu-background-color: var(--menu-background-color);
+ --menu-submenu-hover-color: hsl(var(--accent-foreground));
+ --menu-submenu-hover-background-color: hsl(var(--accent));
+ --menu-submenu-active-color: hsl(var(--accent-foreground));
+ --menu-submenu-active-background-color: transparent;
}
&.is-light {
--menu-background-color: hsl(var(--menu));
// --menu-submenu-opened-background-color: hsl(var(--menu-opened));
+ --menu-item-color: hsl(var(--accent-foreground));
--menu-item-background-color: var(--menu-background-color);
- --menu-item-color: hsl(var(--foreground));
--menu-item-hover-color: var(--menu-item-color);
--menu-item-hover-background-color: hsl(var(--accent));
--menu-item-active-color: hsl(var(--primary));
--menu-item-active-background-color: hsl(var(--primary) / 15%);
+ --menu-submenu-background-color: var(--menu-background-color);
--menu-submenu-hover-color: hsl(var(--primary));
--menu-submenu-hover-background-color: hsl(var(--accent));
--menu-submenu-active-color: hsl(var(--primary));
--menu-submenu-active-background-color: transparent;
- --menu-submenu-background-color: var(--menu-background-color);
}
&.is-rounded {
@@ -518,25 +518,33 @@ $namespace: vben;
--menu-background-color: transparent;
&.is-dark {
+ --menu-background-color: hsl(var(--menu));
+ --menu-item-color: hsl(var(--foreground) / 80%);
+ --menu-item-background-color: var(--menu-background-color);
--menu-item-hover-color: hsl(var(--accent-foreground));
--menu-item-hover-background-color: hsl(var(--accent));
--menu-item-active-color: hsl(var(--accent-foreground));
--menu-item-active-background-color: hsl(var(--accent));
- --menu-submenu-active-color: hsl(var(--foreground));
- --menu-submenu-active-background-color: hsl(var(--accent));
+ --menu-submenu-background-color: var(--menu-background-color);
--menu-submenu-hover-color: hsl(var(--accent-foreground));
--menu-submenu-hover-background-color: hsl(var(--accent));
+ --menu-submenu-active-color: hsl(var(--accent-foreground));
+ --menu-submenu-active-background-color: hsl(var(--accent));
}
&.is-light {
+ --menu-background-color: hsl(var(--menu));
+ --menu-item-color: hsl(var(--accent-foreground));
+ --menu-item-background-color: var(--menu-background-color);
+ --menu-item-hover-color: hsl(var(--menu-item-color));
+ --menu-item-hover-background-color: hsl(var(--accent));
--menu-item-active-color: hsl(var(--primary));
--menu-item-active-background-color: hsl(var(--primary) / 15%);
- --menu-item-hover-background-color: hsl(var(--accent));
- --menu-item-hover-color: hsl(var(--primary));
- --menu-submenu-active-color: hsl(var(--primary));
- --menu-submenu-active-background-color: hsl(var(--primary) / 15%);
+ --menu-submenu-background-color: var(--menu-background-color);
--menu-submenu-hover-color: hsl(var(--primary));
--menu-submenu-hover-background-color: hsl(var(--accent));
+ --menu-submenu-active-color: hsl(var(--primary));
+ --menu-submenu-active-background-color: hsl(var(--primary) / 15%);
}
}
}
@@ -862,16 +870,17 @@ $namespace: vben;
padding-right: 12px !important;
}
- // &:not(.is-active):hover {
- &:hover {
- color: var(--menu-submenu-hover-color);
- text-decoration: none;
- cursor: pointer;
- background: var(--menu-submenu-hover-background-color) !important;
+ &:not(.is-active):hover {
+ &:hover {
+ //color: var(--menu-submenu-hover-color);
+ text-decoration: none;
+ cursor: pointer;
+ background: var(--menu-submenu-hover-background-color) !important;
- // svg {
- // fill: var(--menu-submenu-hover-color);
- // }
+ // svg {
+ // fill: var(--menu-submenu-hover-color);
+ // }
+ }
}
}
diff --git a/packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue b/packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue
index 6dfeb77f..5c8c8a86 100644
--- a/packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue
+++ b/packages/@core/ui-kit/menu-ui/src/components/sub-menu.vue
@@ -210,6 +210,7 @@ onBeforeUnmount(() => {
opened ? '' : 'hidden',
'overflow-auto',
'max-h-[calc(var(--reka-hover-card-content-available-height)-20px)]',
+ mode === 'horizontal' ? 'is-horizontal' : '',
]"
:content-props="contentProps"
:open="true"
diff --git a/packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue b/packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue
index 941ecfe2..dfb4a5e1 100644
--- a/packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue
+++ b/packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue
@@ -158,7 +158,7 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
diff --git a/packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue b/packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue
index effa93cf..a573f449 100644
--- a/packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue
+++ b/packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue
@@ -132,7 +132,7 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
diff --git a/packages/@core/ui-kit/tabs-ui/src/components/widgets/index.ts b/packages/@core/ui-kit/tabs-ui/src/components/widgets/index.ts
index a26899ef..f7634ce8 100644
--- a/packages/@core/ui-kit/tabs-ui/src/components/widgets/index.ts
+++ b/packages/@core/ui-kit/tabs-ui/src/components/widgets/index.ts
@@ -1,2 +1,3 @@
export { default as TabsToolMore } from './tool-more.vue';
+export { default as TabsToolRefresh } from './tool-refresh.vue';
export { default as TabsToolScreen } from './tool-screen.vue';
diff --git a/packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-more.vue b/packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-more.vue
index 24f43a9e..045a670b 100644
--- a/packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-more.vue
+++ b/packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-more.vue
@@ -1,7 +1,7 @@
+
+
+
+
+
+
diff --git a/packages/@core/ui-kit/tabs-ui/src/tabs-view.vue b/packages/@core/ui-kit/tabs-ui/src/tabs-view.vue
index fbc6b0d9..b5464f00 100644
--- a/packages/@core/ui-kit/tabs-ui/src/tabs-view.vue
+++ b/packages/@core/ui-kit/tabs-ui/src/tabs-view.vue
@@ -2,7 +2,7 @@
import type { TabsEmits, TabsProps } from './types';
import { useForwardPropsEmits } from '@vben-core/composables';
-import { ChevronLeft, ChevronRight } from '@vben-core/icons';
+import { ChevronsLeft, ChevronsRight } from '@vben-core/icons';
import { VbenScrollbar } from '@vben-core/shadcn-ui';
import { Tabs, TabsChrome } from './components';
@@ -60,7 +60,7 @@ useTabsDrag(props, emit);
class="border-r px-2"
@click="scrollDirection('left')"
>
-
+
-
+
diff --git a/packages/effects/layouts/src/basic/layout.vue b/packages/effects/layouts/src/basic/layout.vue
index 45b97833..b8912c09 100644
--- a/packages/effects/layouts/src/basic/layout.vue
+++ b/packages/effects/layouts/src/basic/layout.vue
@@ -411,7 +411,7 @@ const headerSlots = computed(() => {
diff --git a/packages/effects/layouts/src/basic/tabbar/tabbar.vue b/packages/effects/layouts/src/basic/tabbar/tabbar.vue
index 8e4f33ba..367604ea 100644
--- a/packages/effects/layouts/src/basic/tabbar/tabbar.vue
+++ b/packages/effects/layouts/src/basic/tabbar/tabbar.vue
@@ -6,7 +6,12 @@ import { useContentMaximize, useTabs } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import { useTabbarStore } from '@vben/stores';
-import { TabsToolMore, TabsToolScreen, TabsView } from '@vben-core/tabs-ui';
+import {
+ TabsToolMore,
+ TabsToolRefresh,
+ TabsToolScreen,
+ TabsView,
+} from '@vben-core/tabs-ui';
import { useTabbar } from './use-tabbar';
@@ -19,7 +24,7 @@ defineProps<{ showIcon?: boolean; theme?: string }>();
const route = useRoute();
const tabbarStore = useTabbarStore();
const { contentIsMaximize, toggleMaximize } = useContentMaximize();
-const { unpinTab } = useTabs();
+const { refreshTab, unpinTab } = useTabs();
const {
createContextMenus,
@@ -65,6 +70,10 @@ if (!preferences.tabbar.persist) {
/>
+
{
-
+
{{ tableTitle }}
-
+
{{ tableTitleHelp }}
From 463bfde2acb3bce3b5cad46469942c9e3d4ff326 Mon Sep 17 00:00:00 2001
From: zouawen <846027729@qq.com>
Date: Tue, 10 Feb 2026 08:50:02 +0800
Subject: [PATCH 02/13] =?UTF-8?q?fix:=20config.test.ts.snap=E6=96=B0?=
=?UTF-8?q?=E5=A2=9EshowRefresh=E5=8F=82=E6=95=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../preferences/__tests__/__snapshots__/config.test.ts.snap | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap
index 86eff1b9..bccc7e92 100644
--- a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap
+++ b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap
@@ -104,6 +104,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"showIcon": true,
"showMaximize": true,
"showMore": true,
+ "showRefresh": true,
"styleType": "chrome",
"visitHistory": true,
"wheelable": true,
From 7d04b600fbb0d03b12101e4a83e3ef9c0b9d539d Mon Sep 17 00:00:00 2001
From: Aliner <108054754+abcd0f@users.noreply.github.com>
Date: Tue, 10 Feb 2026 11:19:45 +0800
Subject: [PATCH 03/13] fix: correct updateDate to updateData in the echarts
hook (#7538)
* fix(@vben/plugins): Fixed the misspelling of the data update method name in the echarts hook
Correct updateDate to updateData, ensuring that the API method name is correct and consistent
* Revert "fix(@vben/plugins): Fixed the misspelling of the data update method name in the echarts hook"
This reverts commit 86d679cf25631bd1abd56d4f971e6db3a9b9d6d5.
* fix(@vben/plugins): fixed the misspelling of the data update method name in the echarts hook
Correct updateDate to updateData, ensuring that the API method name is correct and consistent
---
packages/effects/plugins/src/echarts/use-echarts.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/effects/plugins/src/echarts/use-echarts.ts b/packages/effects/plugins/src/echarts/use-echarts.ts
index 92aea5f3..dc74fd46 100644
--- a/packages/effects/plugins/src/echarts/use-echarts.ts
+++ b/packages/effects/plugins/src/echarts/use-echarts.ts
@@ -105,7 +105,7 @@ function useEcharts(chartRef: Ref
) {
});
};
- const updateDate = (
+ const updateData = (
option: EChartsOption,
notMerge = false, // false = 合并(保留动画),true = 完全替换
lazyUpdate = false, // true 时不立即重绘,适合短时间内多次调用
@@ -170,7 +170,7 @@ function useEcharts(chartRef: Ref) {
return {
renderEcharts,
resize,
- updateDate,
+ updateData,
getChartInstance: () => chartInstance,
};
}
From aace726a91918a5d3c5bb8e62b9e70a50dd8443f Mon Sep 17 00:00:00 2001
From: Bin <106022674+ffgenius@users.noreply.github.com>
Date: Tue, 10 Feb 2026 13:09:34 +0800
Subject: [PATCH 04/13] feat(playground): add antdv-next router link (#7532)
Co-authored-by: fuwb
---
playground/src/router/routes/modules/vben.ts | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/playground/src/router/routes/modules/vben.ts b/playground/src/router/routes/modules/vben.ts
index 6b621e9e..3a3c4254 100644
--- a/playground/src/router/routes/modules/vben.ts
+++ b/playground/src/router/routes/modules/vben.ts
@@ -2,6 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
import {
VBEN_ANT_PREVIEW_URL,
+ VBEN_ANTDV_NEXT_PREVIEW_URL,
VBEN_DOC_URL,
VBEN_ELE_PREVIEW_URL,
VBEN_GITHUB_URL,
@@ -9,7 +10,11 @@ import {
VBEN_NAIVE_PREVIEW_URL,
VBEN_TD_PREVIEW_URL,
} from '@vben/constants';
-import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons';
+import {
+ SvgAntdvLogoIcon,
+ SvgAntdvNextLogoIcon,
+ SvgTDesignIcon,
+} from '@vben/icons';
import { IFrameView } from '#/layouts';
import { $t } from '#/locales';
@@ -56,6 +61,18 @@ const routes: RouteRecordRaw[] = [
title: $t('demos.vben.antdv'),
},
},
+ {
+ name: 'VbenAntdVNext',
+ path: '/vben-admin/antdv-next',
+ component: IFrameView,
+ meta: {
+ badgeType: 'dot',
+ icon: SvgAntdvNextLogoIcon,
+ link: VBEN_ANTDV_NEXT_PREVIEW_URL,
+ title: $t('demos.vben.antdv-next'),
+ },
+ },
+
{
name: 'VbenNaive',
path: '/vben-admin/naive',
From 7fe8d7b4be8916ce54830ed2ed179a96efc3ec0e Mon Sep 17 00:00:00 2001
From: moil-xm <64048303+moil-xm@users.noreply.github.com>
Date: Tue, 10 Feb 2026 16:13:36 +0800
Subject: [PATCH 05/13] =?UTF-8?q?fix:=20ts=20=E9=94=99=E8=AF=AF:=20?=
=?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=AE=9E=E4=BE=8B=E5=8C=96=E8=BF=87=E6=B7=B1?=
=?UTF-8?q?=EF=BC=8C=E4=B8=94=E5=8F=AF=E8=83=BD=E6=97=A0=E9=99=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
packages/@core/base/typings/src/helper.d.ts | 34 ++++++++++++++++-----
1 file changed, 26 insertions(+), 8 deletions(-)
diff --git a/packages/@core/base/typings/src/helper.d.ts b/packages/@core/base/typings/src/helper.d.ts
index 96d4f37b..737b6157 100644
--- a/packages/@core/base/typings/src/helper.d.ts
+++ b/packages/@core/base/typings/src/helper.d.ts
@@ -1,20 +1,38 @@
import type { ComputedRef, MaybeRef } from 'vue';
+/**
+ * 类型级递归中增加深度计数
+ */
+type Increment = [...A, unknown];
/**
* 深层递归所有属性为可选
*/
-type DeepPartial = T extends object
- ? {
- [P in keyof T]?: DeepPartial;
- }
- : T;
+type DeepPartial<
+ T,
+ D extends number = 10,
+ C extends unknown[] = [],
+> = C['length'] extends D
+ ? T
+ : T extends object
+ ? {
+ [P in keyof T]?: DeepPartial>;
+ }
+ : T;
/**
* 深层递归所有属性为只读
*/
-type DeepReadonly = {
- readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P];
-};
+type DeepReadonly<
+ T,
+ D extends number = 10,
+ C extends unknown[] = [],
+> = C['length'] extends D
+ ? T
+ : T extends object
+ ? {
+ readonly [P in keyof T]: DeepReadonly>;
+ }
+ : T;
/**
* 任意类型的异步函数
From 32379ba4b753654f1303a14452a17620887756c4 Mon Sep 17 00:00:00 2001
From: zouawen <846027729@qq.com>
Date: Wed, 11 Feb 2026 16:08:32 +0800
Subject: [PATCH 06/13] =?UTF-8?q?fix:=20=E5=8F=8C=E5=88=97=E8=8F=9C?=
=?UTF-8?q?=E5=8D=95=E6=A8=A1=E5=BC=8F=E4=B8=8B=E6=96=B0=E5=A2=9E=E6=B7=B1?=
=?UTF-8?q?=E8=89=B2=E4=BE=A7=E8=BE=B9=E6=A0=8F=E5=92=8C=E6=B7=B1=E8=89=B2?=
=?UTF-8?q?=E4=BE=A7=E8=BE=B9=E6=A0=8F=E5=AD=90=E6=A0=8F=20(#7542)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix: 双列菜单模式下新增深色侧边栏和深色侧边栏子栏
* fix: 修复报错 config.test.ts.snap
* fix: 修复lint报错
* fix: 修复侧边栏菜单文本内容溢出问题
* fix: 修复lint报错
---
.../__snapshots__/config.test.ts.snap | 1 +
packages/@core/preferences/src/config.ts | 1 +
packages/@core/preferences/src/types.ts | 2 +
.../src/components/layout-sidebar.vue | 68 ++++++++++---------
.../@core/ui-kit/layout-ui/src/vben-layout.ts | 5 ++
.../ui-kit/layout-ui/src/vben-layout.vue | 2 +
.../components/normal-menu/normal-menu.vue | 2 +
packages/effects/layouts/src/basic/layout.vue | 8 ++-
.../preferences/blocks/theme/theme.vue | 33 ++++++++-
.../preferences/preferences-drawer.vue | 2 +
.../locales/src/langs/en-US/preferences.json | 3 +
.../locales/src/langs/zh-CN/preferences.json | 3 +
12 files changed, 97 insertions(+), 33 deletions(-)
diff --git a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap
index bccc7e92..d66f1065 100644
--- a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap
+++ b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap
@@ -120,6 +120,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
"radius": "0.5",
"semiDarkHeader": false,
"semiDarkSidebar": false,
+ "semiDarkSidebarSub": false,
},
"transition": {
"enable": true,
diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts
index 43cc73fa..6bba8099 100644
--- a/packages/@core/preferences/src/config.ts
+++ b/packages/@core/preferences/src/config.ts
@@ -121,6 +121,7 @@ const defaultPreferences: Preferences = {
fontSize: 16,
semiDarkHeader: false,
semiDarkSidebar: false,
+ semiDarkSidebarSub: false,
},
transition: {
enable: true,
diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts
index 6db64af2..24c41c85 100644
--- a/packages/@core/preferences/src/types.ts
+++ b/packages/@core/preferences/src/types.ts
@@ -253,6 +253,8 @@ interface ThemePreferences {
semiDarkHeader: boolean;
/** 是否开启半深色菜单(只在theme='light'时生效) */
semiDarkSidebar: boolean;
+ /** 是否开启半深色子菜单(只在theme='light'时生效) */
+ semiDarkSidebarSub: boolean;
}
interface TransitionPreferences {
diff --git a/packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue b/packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue
index 7c0e9c10..6193517a 100644
--- a/packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue
+++ b/packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue
@@ -77,7 +77,10 @@ interface Props {
* 主题
*/
theme: string;
-
+ /**
+ * 子主题
+ */
+ themeSub: string;
/**
* 宽度
*/
@@ -289,35 +292,38 @@ function handleMouseleave() {
v-if="showCollapseButton && !isSidebarMixed"
v-model:collapsed="collapse"
/>
-
+
diff --git a/packages/@core/ui-kit/layout-ui/src/vben-layout.ts b/packages/@core/ui-kit/layout-ui/src/vben-layout.ts
index 9b77ba96..b6b41375 100644
--- a/packages/@core/ui-kit/layout-ui/src/vben-layout.ts
+++ b/packages/@core/ui-kit/layout-ui/src/vben-layout.ts
@@ -146,6 +146,11 @@ interface VbenLayoutProps {
* @default dark
*/
sidebarTheme?: ThemeModeType;
+ /**
+ * 侧边栏子栏
+ * @default dark
+ */
+ sidebarThemeSub?: ThemeModeType;
/**
* 侧边栏宽度
* @default 210
diff --git a/packages/@core/ui-kit/layout-ui/src/vben-layout.vue b/packages/@core/ui-kit/layout-ui/src/vben-layout.vue
index a35ed349..b51be1e6 100644
--- a/packages/@core/ui-kit/layout-ui/src/vben-layout.vue
+++ b/packages/@core/ui-kit/layout-ui/src/vben-layout.vue
@@ -56,6 +56,7 @@ const props = withDefaults(defineProps(), {
sidebarHidden: false,
sidebarMixedWidth: 80,
sidebarTheme: 'dark',
+ sidebarThemeSub: 'dark',
sidebarWidth: 180,
sideCollapseWidth: 60,
tabbarEnable: true,
@@ -502,6 +503,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
:mixed-width="sidebarMixedWidth"
:show="showSidebar"
:theme="sidebarTheme"
+ :theme-sub="sidebarThemeSub"
:width="getSidebarWidth"
:z-index="sidebarZIndex"
@leave="() => emit('sideMouseLeave')"
diff --git a/packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue b/packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue
index 86e86c91..11661386 100644
--- a/packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue
+++ b/packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue
@@ -151,10 +151,12 @@ $namespace: vben;
}
&__name {
+ width: 100%;
margin-top: 8px;
margin-bottom: 0;
font-size: calc(var(--font-size-base, 16px) * 0.75);
font-weight: 400;
+ text-align: center;
transition: all 0.25s ease;
}
}
diff --git a/packages/effects/layouts/src/basic/layout.vue b/packages/effects/layouts/src/basic/layout.vue
index b8912c09..687fd647 100644
--- a/packages/effects/layouts/src/basic/layout.vue
+++ b/packages/effects/layouts/src/basic/layout.vue
@@ -60,6 +60,11 @@ const sidebarTheme = computed(() => {
return dark ? 'dark' : 'light';
});
+const sidebarThemeSub = computed(() => {
+ const dark = isDark.value || preferences.theme.semiDarkSidebarSub;
+ return dark ? 'dark' : 'light';
+});
+
const headerTheme = computed(() => {
const dark = isDark.value || preferences.theme.semiDarkHeader;
return dark ? 'dark' : 'light';
@@ -240,6 +245,7 @@ const headerSlots = computed(() => {
:sidebar-hidden="preferences.sidebar.hidden"
:sidebar-mixed-width="preferences.sidebar.mixedWidth"
:sidebar-theme="sidebarTheme"
+ :sidebar-theme-sub="sidebarThemeSub"
:sidebar-width="preferences.sidebar.width"
:side-collapse-width="preferences.sidebar.collapseWidth"
:tabbar-enable="preferences.tabbar.enable"
@@ -355,7 +361,7 @@ const headerSlots = computed(() => {
:collapse="preferences.sidebar.extraCollapse"
:menus="wrapperMenus(extraMenus)"
:rounded="isMenuRounded"
- :theme="sidebarTheme"
+ :theme="sidebarThemeSub"
/>
diff --git a/packages/effects/layouts/src/widgets/preferences/blocks/theme/theme.vue b/packages/effects/layouts/src/widgets/preferences/blocks/theme/theme.vue
index 1a5ca4bf..9d8ff62d 100644
--- a/packages/effects/layouts/src/widgets/preferences/blocks/theme/theme.vue
+++ b/packages/effects/layouts/src/widgets/preferences/blocks/theme/theme.vue
@@ -3,8 +3,11 @@ import type { Component } from 'vue';
import type { ThemeModeType } from '@vben/types';
+import { watch } from 'vue';
+
import { MoonStar, Sun, SunMoon } from '@vben/icons';
import { $t } from '@vben/locales';
+import { usePreferences } from '@vben/preferences';
import SwitchItem from '../switch-item.vue';
@@ -14,8 +17,20 @@ defineOptions({
const modelValue = defineModel({ default: 'auto' });
const themeSemiDarkSidebar = defineModel('themeSemiDarkSidebar');
+const themeSemiDarkSidebarSub = defineModel('themeSemiDarkSidebarSub');
const themeSemiDarkHeader = defineModel('themeSemiDarkHeader');
+const { layout } = usePreferences();
+
+watch(
+ () => themeSemiDarkSidebar.value,
+ () => {
+ if (!themeSemiDarkSidebar.value) {
+ themeSemiDarkSidebarSub.value = themeSemiDarkSidebar.value;
+ }
+ },
+);
+
const THEME_PRESET: Array<{ icon: Component; name: ThemeModeType }> = [
{
icon: Sun,
@@ -71,11 +86,27 @@ function nameView(name: string) {
{{ $t('preferences.theme.darkSidebar') }}
+
+ {{ $t('preferences.theme.darkSidebarSub') }}
+
{{ $t('preferences.theme.darkHeader') }}
diff --git a/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue b/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
index aaf7125d..ca68d355 100644
--- a/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
+++ b/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
@@ -88,6 +88,7 @@ const themeMode = defineModel('themeMode');
const themeRadius = defineModel('themeRadius');
const themeFontSize = defineModel('themeFontSize');
const themeSemiDarkSidebar = defineModel('themeSemiDarkSidebar');
+const themeSemiDarkSidebarSub = defineModel('themeSemiDarkSidebarSub');
const themeSemiDarkHeader = defineModel('themeSemiDarkHeader');
const sidebarEnable = defineModel('sidebarEnable');
@@ -319,6 +320,7 @@ async function handleReset() {
v-model="themeMode"
v-model:theme-semi-dark-header="themeSemiDarkHeader"
v-model:theme-semi-dark-sidebar="themeSemiDarkSidebar"
+ v-model:theme-semi-dark-sidebar-sub="themeSemiDarkSidebarSub"
/>
diff --git a/packages/locales/src/langs/en-US/preferences.json b/packages/locales/src/langs/en-US/preferences.json
index f32d0b6a..85db4f12 100644
--- a/packages/locales/src/langs/en-US/preferences.json
+++ b/packages/locales/src/langs/en-US/preferences.json
@@ -127,6 +127,9 @@
"light": "Light",
"dark": "Dark",
"darkSidebar": "Semi Dark Sidebar",
+ "darkSidebarTip": "It can be enabled when the theme is light, and the layout is neither 'Horizontal' nor 'Full Content'.",
+ "darkSidebarSub": "Semi Dark Sidebar Sub",
+ "darkSidebarSubTip": "It can be enabled when the theme is light, the semi dark sidebar is enabled, and the layout uses 'Two-Column' menu mode.",
"darkHeader": "Semi Dark Header",
"weakMode": "Weak Mode",
"grayMode": "Gray Mode",
diff --git a/packages/locales/src/langs/zh-CN/preferences.json b/packages/locales/src/langs/zh-CN/preferences.json
index a36a9dbb..d17a802d 100644
--- a/packages/locales/src/langs/zh-CN/preferences.json
+++ b/packages/locales/src/langs/zh-CN/preferences.json
@@ -127,6 +127,9 @@
"light": "浅色",
"dark": "深色",
"darkSidebar": "深色侧边栏",
+ "darkSidebarTip": "当主题为浅色,布局不为水平菜单或不为内容全屏时可开启",
+ "darkSidebarSub": "深色侧边栏子栏",
+ "darkSidebarSubTip": "当主题为浅色,开启深色侧边栏且布局使用双列菜单模式时可开启",
"darkHeader": "深色顶栏",
"weakMode": "色弱模式",
"grayMode": "灰色模式",
From aa74a2535ba3d69b9a71b44df6b623c781e9772d Mon Sep 17 00:00:00 2001
From: AxiosLeo <13862149+AxiosLeo@users.noreply.github.com>
Date: Wed, 11 Feb 2026 16:09:37 +0800
Subject: [PATCH 07/13] fix(tabbar): visitHistory field (#7543)
High Severity
The visitHistory field is a Stack class instance persisted to sessionStorage via pinia-plugin-persistedstate. There's no custom serializer or hydration hook. When the page reloads, JSON.parse(JSON.stringify(stack)) produces a plain object {dedup, items, maxSize} that lacks all Stack methods (push, pop, remove, retain, etc.) and the size getter. Pinia's $patch replaces the Stack instance with this plain object, so subsequent calls like this.visitHistory.push(...) will throw a TypeError.
---
packages/stores/src/modules/tabbar.ts | 75 ++++++++++++---------------
1 file changed, 32 insertions(+), 43 deletions(-)
diff --git a/packages/stores/src/modules/tabbar.ts b/packages/stores/src/modules/tabbar.ts
index 6b18c26e..8d18d442 100644
--- a/packages/stores/src/modules/tabbar.ts
+++ b/packages/stores/src/modules/tabbar.ts
@@ -1,9 +1,5 @@
import type { ComputedRef } from 'vue';
-import type {
- RouteLocationNormalized,
- Router,
- RouteRecordNormalized,
-} from 'vue-router';
+import type { RouteLocationNormalized, Router, RouteRecordNormalized } from 'vue-router';
import type { TabDefinition } from '@vben-core/typings';
@@ -70,9 +66,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
*/
async _bulkCloseByKeys(keys: string[]) {
const keySet = new Set(keys);
- this.tabs = this.tabs.filter(
- (item) => !keySet.has(getTabKeyFromTab(item)),
- );
+ this.tabs = this.tabs.filter((item) => !keySet.has(getTabKeyFromTab(item)));
if (isVisitHistory()) {
this.visitHistory.remove(...keys);
}
@@ -136,25 +130,20 @@ export const useTabbarStore = defineStore('core-tabbar', {
if (tabIndex === -1) {
const maxCount = preferences.tabbar.maxCount;
// 获取动态路由打开数,超过 0 即代表需要控制打开数
- const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ??
- -1) as number;
+ const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ?? -1) as number;
// 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了
// 获取到已经打开的动态路由数, 判断是否大于某一个值
if (
maxNumOfOpenTab > 0 &&
- this.tabs.filter((tab) => tab.name === routeTab.name).length >=
- maxNumOfOpenTab
+ this.tabs.filter((tab) => tab.name === routeTab.name).length >= maxNumOfOpenTab
) {
// 关闭第一个
- const index = this.tabs.findIndex(
- (item) => item.name === routeTab.name,
- );
+ const index = this.tabs.findIndex((item) => item.name === routeTab.name);
index !== -1 && this.tabs.splice(index, 1);
} else if (maxCount > 0 && this.tabs.length >= maxCount) {
// 关闭第一个
const index = this.tabs.findIndex(
- (item) =>
- !Reflect.has(item.meta, 'affixTab') || !item.meta.affixTab,
+ (item) => !Reflect.has(item.meta, 'affixTab') || !item.meta.affixTab
);
index !== -1 && this.tabs.splice(index, 1);
}
@@ -194,9 +183,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
this.tabs = newTabs.length > 0 ? newTabs : [...this.tabs].splice(0, 1);
// 设置访问历史记录
if (isVisitHistory()) {
- this.visitHistory.retain(
- this.tabs.map((item) => getTabKeyFromTab(item)),
- );
+ this.visitHistory.retain(this.tabs.map((item) => getTabKeyFromTab(item)));
}
await this._goToDefaultTab(router);
this.updateCacheTabs();
@@ -233,9 +220,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
for (const key of closeKeys) {
if (key !== getTabKeyFromTab(tab)) {
- const closeTab = this.tabs.find(
- (item) => getTabKeyFromTab(item) === key,
- );
+ const closeTab = this.tabs.find((item) => getTabKeyFromTab(item) === key);
if (!closeTab) {
continue;
}
@@ -305,14 +290,12 @@ export const useTabbarStore = defineStore('core-tabbar', {
break;
}
}
- await (previousTab
- ? this._goToTab(previousTab, router)
- : this._goToDefaultTab(router));
+ await (previousTab ? this._goToTab(previousTab, router) : this._goToDefaultTab(router));
return;
}
// 未开启访问历史记录,直接跳转下一个或上一个tab
const index = this.getTabs.findIndex(
- (item) => getTabKeyFromTab(item) === getTabKey(currentRoute.value),
+ (item) => getTabKeyFromTab(item) === getTabKey(currentRoute.value)
);
const before = this.getTabs[index - 1];
@@ -336,9 +319,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
*/
async closeTabByKey(key: string, router: Router) {
const originKey = decodeURIComponent(key);
- const index = this.tabs.findIndex(
- (item) => getTabKeyFromTab(item) === originKey,
- );
+ const index = this.tabs.findIndex((item) => getTabKeyFromTab(item) === originKey);
if (index === -1) {
return;
}
@@ -354,9 +335,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
* @param key
*/
getTabByKey(key: string) {
- return this.getTabs.find(
- (item) => getTabKeyFromTab(item) === key,
- ) as TabDefinition;
+ return this.getTabs.find((item) => getTabKeyFromTab(item) === key) as TabDefinition;
},
/**
* @zh_CN 新窗口打开标签页
@@ -583,6 +562,23 @@ export const useTabbarStore = defineStore('core-tabbar', {
{
pick: ['tabs', 'visitHistory'],
storage: sessionStorage,
+ serializer: {
+ serialize: JSON.stringify,
+ deserialize(value: string) {
+ const parsed = JSON.parse(value);
+ // Stack 类实例经 JSON 序列化后会变成普通对象 {dedup, items, maxSize},
+ // 丢失所有方法和 getter,需要重新构建 Stack 实例
+ if (parsed.visitHistory && !(parsed.visitHistory instanceof Stack)) {
+ const raw = parsed.visitHistory;
+ const stack = createStack(true, MAX_VISIT_HISTORY);
+ if (Array.isArray(raw.items)) {
+ stack.push(...raw.items);
+ }
+ parsed.visitHistory = stack;
+ }
+ return parsed;
+ },
+ },
},
],
state: (): TabbarState => ({
@@ -660,21 +656,14 @@ function isTabShown(tab: TabDefinition) {
* @param tab
*/
function getTabKey(tab: RouteLocationNormalized | RouteRecordNormalized) {
- const {
- fullPath,
- path,
- meta: { fullPathKey } = {},
- query = {},
- } = tab as RouteLocationNormalized;
+ const { fullPath, path, meta: { fullPathKey } = {}, query = {} } = tab as RouteLocationNormalized;
// pageKey可能是数组(查询参数重复时可能出现)
- const pageKey = Array.isArray(query.pageKey)
- ? query.pageKey[0]
- : query.pageKey;
+ const pageKey = Array.isArray(query.pageKey) ? query.pageKey[0] : query.pageKey;
let rawKey;
if (pageKey) {
rawKey = pageKey;
} else {
- rawKey = fullPathKey === false ? path : (fullPath ?? path);
+ rawKey = fullPathKey === false ? path : fullPath ?? path;
}
try {
return decodeURIComponent(rawKey);
From 8e7a5d1ec3a1168503e02304f9d56acc7472dc8d Mon Sep 17 00:00:00 2001
From: zouawen <846027729@qq.com>
Date: Thu, 12 Feb 2026 22:22:34 +0800
Subject: [PATCH 08/13] fix: Fix layout change, ensure div[ref="asideRef"] is
contained within
-
+
+
+
+
+
+
+
+
+
+
+
+
+
From 03ebbea46aed722f6999c7cda6834452b9edfe88 Mon Sep 17 00:00:00 2001
From: AxiosLeo <13862149+AxiosLeo@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:22:53 +0800
Subject: [PATCH 09/13] fix(menu): update hover color variable to use the
correct reference (#7544)
* fix(menu): update hover color variable to use the correct reference
Medium Severity
In the horizontal .is-light menu section, --menu-item-hover-color is set to hsl(var(--menu-item-color)), but --menu-item-color is already defined as hsl(var(--accent-foreground)). This results in hsl(hsl(...)) at computed-value time, which is invalid CSS. The non-horizontal .is-light block correctly uses var(--menu-item-color) without the extra hsl() wrapper.
* fix(menu): simplify hover styles by removing redundant nested hover rules
Low Severity
The SCSS &:not(.is-active):hover { &:hover { ... } } compiles to a :hover:hover pseudo-class chain, which is functionally identical to a single :hover. The inner &:hover nesting is redundant and adds unnecessary complexity compared to placing styles directly inside the &:not(.is-active):hover block.
---
.../ui-kit/menu-ui/src/components/menu.vue | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/packages/@core/ui-kit/menu-ui/src/components/menu.vue b/packages/@core/ui-kit/menu-ui/src/components/menu.vue
index 4d3ed617..d7894a17 100644
--- a/packages/@core/ui-kit/menu-ui/src/components/menu.vue
+++ b/packages/@core/ui-kit/menu-ui/src/components/menu.vue
@@ -536,7 +536,7 @@ $namespace: vben;
--menu-background-color: hsl(var(--menu));
--menu-item-color: hsl(var(--accent-foreground));
--menu-item-background-color: var(--menu-background-color);
- --menu-item-hover-color: hsl(var(--menu-item-color));
+ --menu-item-hover-color: var(--menu-item-color);
--menu-item-hover-background-color: hsl(var(--accent));
--menu-item-active-color: hsl(var(--primary));
--menu-item-active-background-color: hsl(var(--primary) / 15%);
@@ -871,16 +871,14 @@ $namespace: vben;
}
&:not(.is-active):hover {
- &:hover {
- //color: var(--menu-submenu-hover-color);
- text-decoration: none;
- cursor: pointer;
- background: var(--menu-submenu-hover-background-color) !important;
+ //color: var(--menu-submenu-hover-color);
+ text-decoration: none;
+ cursor: pointer;
+ background: var(--menu-submenu-hover-background-color) !important;
- // svg {
- // fill: var(--menu-submenu-hover-color);
- // }
- }
+ // svg {
+ // fill: var(--menu-submenu-hover-color);
+ // }
}
}
From 01508d5e42f929e18ed4cd697bafc2c0283a305c Mon Sep 17 00:00:00 2001
From: Jin Mao
Date: Thu, 26 Feb 2026 05:45:36 +0800
Subject: [PATCH 10/13] fix: fix lint
---
packages/stores/src/modules/tabbar.ts | 58 ++++++++++++++++++++-------
1 file changed, 43 insertions(+), 15 deletions(-)
diff --git a/packages/stores/src/modules/tabbar.ts b/packages/stores/src/modules/tabbar.ts
index 8d18d442..66a3758b 100644
--- a/packages/stores/src/modules/tabbar.ts
+++ b/packages/stores/src/modules/tabbar.ts
@@ -1,5 +1,9 @@
import type { ComputedRef } from 'vue';
-import type { RouteLocationNormalized, Router, RouteRecordNormalized } from 'vue-router';
+import type {
+ RouteLocationNormalized,
+ Router,
+ RouteRecordNormalized,
+} from 'vue-router';
import type { TabDefinition } from '@vben-core/typings';
@@ -66,7 +70,9 @@ export const useTabbarStore = defineStore('core-tabbar', {
*/
async _bulkCloseByKeys(keys: string[]) {
const keySet = new Set(keys);
- this.tabs = this.tabs.filter((item) => !keySet.has(getTabKeyFromTab(item)));
+ this.tabs = this.tabs.filter(
+ (item) => !keySet.has(getTabKeyFromTab(item)),
+ );
if (isVisitHistory()) {
this.visitHistory.remove(...keys);
}
@@ -130,20 +136,25 @@ export const useTabbarStore = defineStore('core-tabbar', {
if (tabIndex === -1) {
const maxCount = preferences.tabbar.maxCount;
// 获取动态路由打开数,超过 0 即代表需要控制打开数
- const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ?? -1) as number;
+ const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ??
+ -1) as number;
// 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了
// 获取到已经打开的动态路由数, 判断是否大于某一个值
if (
maxNumOfOpenTab > 0 &&
- this.tabs.filter((tab) => tab.name === routeTab.name).length >= maxNumOfOpenTab
+ this.tabs.filter((tab) => tab.name === routeTab.name).length >=
+ maxNumOfOpenTab
) {
// 关闭第一个
- const index = this.tabs.findIndex((item) => item.name === routeTab.name);
+ const index = this.tabs.findIndex(
+ (item) => item.name === routeTab.name,
+ );
index !== -1 && this.tabs.splice(index, 1);
} else if (maxCount > 0 && this.tabs.length >= maxCount) {
// 关闭第一个
const index = this.tabs.findIndex(
- (item) => !Reflect.has(item.meta, 'affixTab') || !item.meta.affixTab
+ (item) =>
+ !Reflect.has(item.meta, 'affixTab') || !item.meta.affixTab,
);
index !== -1 && this.tabs.splice(index, 1);
}
@@ -183,7 +194,9 @@ export const useTabbarStore = defineStore('core-tabbar', {
this.tabs = newTabs.length > 0 ? newTabs : [...this.tabs].splice(0, 1);
// 设置访问历史记录
if (isVisitHistory()) {
- this.visitHistory.retain(this.tabs.map((item) => getTabKeyFromTab(item)));
+ this.visitHistory.retain(
+ this.tabs.map((item) => getTabKeyFromTab(item)),
+ );
}
await this._goToDefaultTab(router);
this.updateCacheTabs();
@@ -220,7 +233,9 @@ export const useTabbarStore = defineStore('core-tabbar', {
for (const key of closeKeys) {
if (key !== getTabKeyFromTab(tab)) {
- const closeTab = this.tabs.find((item) => getTabKeyFromTab(item) === key);
+ const closeTab = this.tabs.find(
+ (item) => getTabKeyFromTab(item) === key,
+ );
if (!closeTab) {
continue;
}
@@ -290,12 +305,14 @@ export const useTabbarStore = defineStore('core-tabbar', {
break;
}
}
- await (previousTab ? this._goToTab(previousTab, router) : this._goToDefaultTab(router));
+ await (previousTab
+ ? this._goToTab(previousTab, router)
+ : this._goToDefaultTab(router));
return;
}
// 未开启访问历史记录,直接跳转下一个或上一个tab
const index = this.getTabs.findIndex(
- (item) => getTabKeyFromTab(item) === getTabKey(currentRoute.value)
+ (item) => getTabKeyFromTab(item) === getTabKey(currentRoute.value),
);
const before = this.getTabs[index - 1];
@@ -319,7 +336,9 @@ export const useTabbarStore = defineStore('core-tabbar', {
*/
async closeTabByKey(key: string, router: Router) {
const originKey = decodeURIComponent(key);
- const index = this.tabs.findIndex((item) => getTabKeyFromTab(item) === originKey);
+ const index = this.tabs.findIndex(
+ (item) => getTabKeyFromTab(item) === originKey,
+ );
if (index === -1) {
return;
}
@@ -335,7 +354,9 @@ export const useTabbarStore = defineStore('core-tabbar', {
* @param key
*/
getTabByKey(key: string) {
- return this.getTabs.find((item) => getTabKeyFromTab(item) === key) as TabDefinition;
+ return this.getTabs.find(
+ (item) => getTabKeyFromTab(item) === key,
+ ) as TabDefinition;
},
/**
* @zh_CN 新窗口打开标签页
@@ -656,14 +677,21 @@ function isTabShown(tab: TabDefinition) {
* @param tab
*/
function getTabKey(tab: RouteLocationNormalized | RouteRecordNormalized) {
- const { fullPath, path, meta: { fullPathKey } = {}, query = {} } = tab as RouteLocationNormalized;
+ const {
+ fullPath,
+ path,
+ meta: { fullPathKey } = {},
+ query = {},
+ } = tab as RouteLocationNormalized;
// pageKey可能是数组(查询参数重复时可能出现)
- const pageKey = Array.isArray(query.pageKey) ? query.pageKey[0] : query.pageKey;
+ const pageKey = Array.isArray(query.pageKey)
+ ? query.pageKey[0]
+ : query.pageKey;
let rawKey;
if (pageKey) {
rawKey = pageKey;
} else {
- rawKey = fullPathKey === false ? path : fullPath ?? path;
+ rawKey = fullPathKey === false ? path : (fullPath ?? path);
}
try {
return decodeURIComponent(rawKey);
From 05920cd66d447198680590f8e6dc143d6f941d83 Mon Sep 17 00:00:00 2001
From: moil-xm <64048303+moil-xm@users.noreply.github.com>
Date: Thu, 26 Feb 2026 06:14:12 +0800
Subject: [PATCH 11/13] feat(vite-config): vite export typing (#7569)
* feat(vite-config): vite export typing
* feat(vite-config): add type
---------
Co-authored-by: Jin Mao <50581550+jinmao88@users.noreply.github.com>
---
internal/vite-config/src/index.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/internal/vite-config/src/index.ts b/internal/vite-config/src/index.ts
index 352a3235..c9c2b20d 100644
--- a/internal/vite-config/src/index.ts
+++ b/internal/vite-config/src/index.ts
@@ -1,4 +1,5 @@
export * from './config';
export * from './options';
export * from './plugins';
+export type * from './typing';
export { loadAndConvertEnv } from './utils/env';
From 191fd90f0695c4227291398c3511da96315dec00 Mon Sep 17 00:00:00 2001
From: Wu Clan
Date: Thu, 26 Feb 2026 06:17:04 +0800
Subject: [PATCH 12/13] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E8=A1=A8?=
=?UTF-8?q?=E5=8D=95=E6=8F=8F=E8=BF=B0=E6=98=BE=E7=A4=BA=E6=A0=B7=E5=BC=8F?=
=?UTF-8?q?=20(#6938)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../@core/ui-kit/form-ui/src/form-render/form-field.vue | 6 +++---
playground/src/views/examples/form/basic.vue | 7 +++++++
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue b/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue
index 53c731e7..cc610d59 100644
--- a/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue
+++ b/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue
@@ -381,10 +381,10 @@ onUnmounted(() => {
-
-
-
+
+
+
diff --git a/playground/src/views/examples/form/basic.vue b/playground/src/views/examples/form/basic.vue
index 52d04319..05be1f3a 100644
--- a/playground/src/views/examples/form/basic.vue
+++ b/playground/src/views/examples/form/basic.vue
@@ -67,6 +67,13 @@ const [BaseForm, baseFormApi] = useVbenForm({
label: '字符串',
rules: 'required',
},
+ {
+ component: 'Input',
+ fieldName: 'desc',
+ // 界面显示的description
+ description: '这是表单描述',
+ label: '字符串(带描述)',
+ },
{
// 组件需要在 #/adapter.ts内注册,并加上类型
component: 'ApiSelect',
From 45b843f3441b3b45cae6192981f5422ff1310831 Mon Sep 17 00:00:00 2001
From: ming4762
Date: Thu, 26 Feb 2026 06:21:08 +0800
Subject: [PATCH 13/13] fix: fix bug where `renderEcharts` gets stuck in a dead
loop (#7561)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 触发条件:echart所在页面开启keepalive 在其他页面切换颜色模式
---
.../plugins/src/echarts/use-echarts.ts | 27 ++++++++++++++++---
.../src/router/routes/modules/dashboard.ts | 1 +
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/packages/effects/plugins/src/echarts/use-echarts.ts b/packages/effects/plugins/src/echarts/use-echarts.ts
index dc74fd46..4f1989ce 100644
--- a/packages/effects/plugins/src/echarts/use-echarts.ts
+++ b/packages/effects/plugins/src/echarts/use-echarts.ts
@@ -6,7 +6,17 @@ import type { Nullable } from '@vben/types';
import type EchartsUI from './echarts-ui.vue';
-import { computed, nextTick, watch } from 'vue';
+import {
+ computed,
+ nextTick,
+ onActivated,
+ onBeforeUnmount,
+ onDeactivated,
+ onMounted,
+ ref,
+ unref,
+ watch,
+} from 'vue';
import { usePreferences } from '@vben/preferences';
@@ -27,6 +37,8 @@ type EchartsThemeType = 'dark' | 'light' | null;
function useEcharts(chartRef: Ref) {
let chartInstance: echarts.ECharts | null = null;
let cacheOptions: EChartsOption = {};
+ // echart是否处于激活状态
+ const isActiveRef = ref(false);
const { isDark } = usePreferences();
const { height, width } = useWindowSize();
@@ -42,6 +54,11 @@ function useEcharts(chartRef: Ref) {
return maybeComponent.$el ?? null;
};
+ onMounted(() => (isActiveRef.value = true));
+ onActivated(() => (isActiveRef.value = true));
+ onDeactivated(() => (isActiveRef.value = false));
+ onBeforeUnmount(() => (isActiveRef.value = false));
+
const isElHidden = (el: HTMLElement | null): boolean => {
if (!el) return true;
return el.offsetHeight === 0 || el.offsetWidth === 0;
@@ -71,6 +88,9 @@ function useEcharts(chartRef: Ref) {
options: EChartsOption,
clear = true,
): Promise> => {
+ if (!unref(isActiveRef)) {
+ return Promise.resolve(null);
+ }
cacheOptions = options;
const currentOptions = {
...options,
@@ -154,8 +174,8 @@ function useEcharts(chartRef: Ref) {
useResizeObserver(chartRef as never, resizeHandler);
- watch(isDark, () => {
- if (chartInstance) {
+ watch([isDark, isActiveRef], () => {
+ if (chartInstance && unref(isActiveRef)) {
chartInstance.dispose();
initCharts();
renderEcharts(cacheOptions);
@@ -168,6 +188,7 @@ function useEcharts(chartRef: Ref) {
chartInstance?.dispose();
});
return {
+ isActive: isActiveRef,
renderEcharts,
resize,
updateData,
diff --git a/playground/src/router/routes/modules/dashboard.ts b/playground/src/router/routes/modules/dashboard.ts
index 5254dc65..7ef3cfb6 100644
--- a/playground/src/router/routes/modules/dashboard.ts
+++ b/playground/src/router/routes/modules/dashboard.ts
@@ -20,6 +20,7 @@ const routes: RouteRecordRaw[] = [
affixTab: true,
icon: 'lucide:area-chart',
title: $t('page.dashboard.analytics'),
+ keepAlive: true,
},
},
{