From 57cf6cbc9e9fd8a6e3ce3a786de532bb8943940b Mon Sep 17 00:00:00 2001 From: zouawen <846027729@qq.com> Date: Wed, 25 Feb 2026 17:50:19 +0800 Subject: [PATCH 01/13] =?UTF-8?q?feature:=20=E7=AE=80=E6=98=93=E7=89=88?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E5=AE=BD=E5=BA=A6=E6=8B=96=E6=8B=BD=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout-sidebar.vue | 36 +++++++++++++++++-- .../ui-kit/layout-ui/src/vben-layout.vue | 11 +++++- packages/effects/layouts/src/basic/layout.vue | 9 +++++ 3 files changed, 53 insertions(+), 3 deletions(-) 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 76ba74d1..dddb282b 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 @@ -1,7 +1,7 @@ @@ -266,6 +292,7 @@ function handleMouseleave() { diff --git a/packages/effects/layouts/src/basic/layout.vue b/packages/effects/layouts/src/basic/layout.vue index cedd0cd6..874951f9 100644 --- a/packages/effects/layouts/src/basic/layout.vue +++ b/packages/effects/layouts/src/basic/layout.vue @@ -211,14 +211,6 @@ const slots: SetupContext['slots'] = useSlots(); const headerSlots = computed(() => { return Object.keys(slots).filter((key) => key.startsWith('header-')); }); - -function handleUpdateSidebarWidth(newWidth: number) { - updatePreferences({ - sidebar: { - width: newWidth, - }, - }); -} @@ -275,7 +267,9 @@ function handleUpdateSidebarWidth(newWidth: number) { (value: boolean) => updatePreferences({ sidebar: { extraCollapse: value } }) " - @update:sidebar-width="handleUpdateSidebarWidth" + @update:sidebar-width=" + (value: number) => updatePreferences({ sidebar: { width: value } }) + " > From afffc4b3f0e05f6ed8443c5d2da3f6d21c6456d8 Mon Sep 17 00:00:00 2001 From: zouawen <846027729@qq.com> Date: Fri, 27 Feb 2026 10:53:50 +0800 Subject: [PATCH 04/13] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E4=BE=A7?= =?UTF-8?q?=E8=BE=B9=E6=A0=8F=E6=8B=96=E6=8B=BD=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B1=95=E5=BC=80=E5=92=8C=E6=8A=98=E5=8F=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@core/composables/src/index.ts | 1 - .../src/components/layout-sidebar.vue | 31 +++++++++++++------ .../layout-ui/src/hooks/use-sidebar-drag.ts} | 19 +++++------- .../ui-kit/layout-ui/src/vben-layout.vue | 9 ++++-- 4 files changed, 35 insertions(+), 25 deletions(-) rename packages/@core/{composables/src/use-resizable.ts => ui-kit/layout-ui/src/hooks/use-sidebar-drag.ts} (89%) diff --git a/packages/@core/composables/src/index.ts b/packages/@core/composables/src/index.ts index 2f6826f2..2dbd4b80 100644 --- a/packages/@core/composables/src/index.ts +++ b/packages/@core/composables/src/index.ts @@ -2,7 +2,6 @@ export * from './use-is-mobile'; export * from './use-layout-style'; export * from './use-namespace'; export * from './use-priority-value'; -export * from './use-resizable'; export * from './use-scroll-lock'; export * from './use-simple-locale'; export * from './use-sortable'; 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 2f56bdc6..feca2045 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 @@ -3,11 +3,11 @@ import type { CSSProperties } from 'vue'; import { computed, shallowRef, useSlots, watchEffect } from 'vue'; -import { useResizable } from '@vben-core/composables'; import { VbenScrollbar } from '@vben-core/shadcn-ui'; import { useScrollLock } from '@vueuse/core'; +import { useSidebarDrag } from '../hooks/use-sidebar-drag'; import { SidebarCollapseButton, SidebarFixedButton } from './widgets'; interface Props { @@ -256,18 +256,29 @@ function handleMouseleave() { extraVisible.value = false; } -const { startDrag } = useResizable({ - min: 160, - max: 320, - onChange: (newWidth) => { - emit('update:width', newWidth); - }, -}); +const { startDrag } = useSidebarDrag(); const handleDragSidebar = (e: MouseEvent) => { - const { isSidebarMixed, extraWidth, width } = props; + const { isSidebarMixed, collapseWidth, extraWidth, width } = props; + const minLimit = isSidebarMixed ? width + collapseWidth : collapseWidth; + const maxLimit = isSidebarMixed ? width + 320 : 320; const currentWidth = isSidebarMixed ? extraWidth : width; - startDrag(e, currentWidth, asideRef.value, dragBarRef.value); + startDrag( + e, + minLimit, + maxLimit, + currentWidth, + asideRef.value, + dragBarRef.value, + (newWidth) => { + emit('update:width', newWidth); + if (isSidebarMixed) { + extraCollapse.value = newWidth <= collapseWidth; + } else { + collapse.value = newWidth <= collapseWidth; + } + }, + ); }; diff --git a/packages/@core/composables/src/use-resizable.ts b/packages/@core/ui-kit/layout-ui/src/hooks/use-sidebar-drag.ts similarity index 89% rename from packages/@core/composables/src/use-resizable.ts rename to packages/@core/ui-kit/layout-ui/src/hooks/use-sidebar-drag.ts index 9939819d..cccddea4 100644 --- a/packages/@core/composables/src/use-resizable.ts +++ b/packages/@core/ui-kit/layout-ui/src/hooks/use-sidebar-drag.ts @@ -1,14 +1,6 @@ import { onUnmounted } from 'vue'; -interface ResizableOptions { - max?: number; - min?: number; - onChange?: (newWidth: number) => void; -} - -export function useResizable(options: ResizableOptions = {}) { - const { min = 0, max = 999, onChange } = options; - +export function useSidebarDrag() { let startX = 0; let startWidth = 0; let targetTransition = ''; @@ -22,9 +14,12 @@ export function useResizable(options: ResizableOptions = {}) { const startDrag = ( e: MouseEvent, + min: number, + max: number, currentWidth: number, targetElement: HTMLElement | null, dragBarElement: HTMLElement | null, + onDrag: (newWidth: number) => void, ) => { cleanup?.(); @@ -57,7 +52,9 @@ export function useResizable(options: ResizableOptions = {}) { const onMouseMove = (moveEvent: MouseEvent) => { const deltaX = moveEvent.clientX - startX; - const newLeft = dragBarOffsetLeft + deltaX; + let newLeft = dragBarOffsetLeft + deltaX; + if (newLeft < min) newLeft = min; + if (newLeft > max) newLeft = max; dragBarElement.style.left = `${newLeft}px`; }; @@ -71,7 +68,7 @@ export function useResizable(options: ResizableOptions = {}) { dragBarElement.style.right = dragBarRight; } - onChange?.(newWidth); + onDrag?.(newWidth); cleanup?.(); }; 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 92363e61..a7b9ab58 100644 --- a/packages/@core/ui-kit/layout-ui/src/vben-layout.vue +++ b/packages/@core/ui-kit/layout-ui/src/vben-layout.vue @@ -124,13 +124,16 @@ const headerWrapperHeight = computed(() => { }); const getSideCollapseWidth = computed(() => { - const { sidebarCollapseShowTitle, sidebarMixedWidth, sideCollapseWidth } = - props; + const { + sidebarCollapseShowTitle, + sidebarExtraCollapsedWidth, + sideCollapseWidth, + } = props; return sidebarCollapseShowTitle || isSidebarMixedNav.value || isHeaderMixedNav.value - ? sidebarMixedWidth + ? sidebarExtraCollapsedWidth : sideCollapseWidth; }); From cc808cb8c5210dcb1bd6b4e0739ca79db9c475de Mon Sep 17 00:00:00 2001 From: zouawen <846027729@qq.com> Date: Fri, 27 Feb 2026 11:06:57 +0800 Subject: [PATCH 05/13] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=9C=80?= =?UTF-8?q?=E5=B0=8F=E5=80=BC=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@core/ui-kit/layout-ui/src/components/layout-sidebar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 feca2045..06917acc 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 @@ -260,7 +260,7 @@ const { startDrag } = useSidebarDrag(); const handleDragSidebar = (e: MouseEvent) => { const { isSidebarMixed, collapseWidth, extraWidth, width } = props; - const minLimit = isSidebarMixed ? width + collapseWidth : collapseWidth; + const minLimit = collapseWidth; const maxLimit = isSidebarMixed ? width + 320 : 320; const currentWidth = isSidebarMixed ? extraWidth : width; startDrag( From b2013436c5ec0baffa7dbb7ab4490562ffd90186 Mon Sep 17 00:00:00 2001 From: zouawen <846027729@qq.com> Date: Fri, 27 Feb 2026 11:12:50 +0800 Subject: [PATCH 06/13] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E5=80=BC=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@core/ui-kit/layout-ui/src/components/layout-sidebar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 06917acc..74f12311 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 @@ -261,7 +261,7 @@ const { startDrag } = useSidebarDrag(); const handleDragSidebar = (e: MouseEvent) => { const { isSidebarMixed, collapseWidth, extraWidth, width } = props; const minLimit = collapseWidth; - const maxLimit = isSidebarMixed ? width + 320 : 320; + const maxLimit = 320; const currentWidth = isSidebarMixed ? extraWidth : width; startDrag( e, From bd22793ceb1f185cc2952151cd04005b5f94b146 Mon Sep 17 00:00:00 2001 From: Jin Mao Date: Fri, 27 Feb 2026 16:04:01 +0800 Subject: [PATCH 07/13] feat: add the attribute routeCached to route to control cache the DOM corresponding to the route --- docs/src/guide/essentials/route.md | 7 ++ .../@core/base/typings/src/vue-router.d.ts | 4 + .../layouts/src/basic/content/content.vue | 102 ++++-------------- packages/effects/layouts/src/hooks/index.ts | 98 +++++++++++++++++ .../effects/layouts/src/route-cached/index.ts | 2 + .../src/route-cached/route-cached-page.vue | 36 +++++++ .../src/route-cached/route-cached-view.vue | 98 +++++++++++++++++ packages/stores/src/modules/tabbar.ts | 36 ++++++- playground/src/router/routes/modules/demos.ts | 1 + 9 files changed, 301 insertions(+), 83 deletions(-) create mode 100644 packages/effects/layouts/src/hooks/index.ts create mode 100644 packages/effects/layouts/src/route-cached/index.ts create mode 100644 packages/effects/layouts/src/route-cached/route-cached-page.vue create mode 100644 packages/effects/layouts/src/route-cached/route-cached-view.vue diff --git a/docs/src/guide/essentials/route.md b/docs/src/guide/essentials/route.md index 985c48a9..8383e762 100644 --- a/docs/src/guide/essentials/route.md +++ b/docs/src/guide/essentials/route.md @@ -599,6 +599,13 @@ _注意:_ 排序仅针对一级菜单有效,二级菜单的排序需要在对 用于配置当前路由不使用基础布局,仅在顶级时生效。默认情况下,所有的路由都会被包裹在基础布局中(包含顶部以及侧边等导航部件),如果你的页面不需要这些部件,可以设置 `noBasicLayout` 为 `true`。 +### domCached + +- 类型:`boolean` +- 默认值:`false` + +用于配置当前路由是否要将route对应dom元素缓存起来。对于一些复杂页面切换tab浏览器回流/重绘会导致卡顿, `domCached` 设为 `true`可解决该问题,但是也有代价:1、内存占用升高 2、vue的部分生命周期不会触发 + ## 路由刷新 路由刷新方式如下: diff --git a/packages/@core/base/typings/src/vue-router.d.ts b/packages/@core/base/typings/src/vue-router.d.ts index 1577520e..d7a80fe4 100644 --- a/packages/@core/base/typings/src/vue-router.d.ts +++ b/packages/@core/base/typings/src/vue-router.d.ts @@ -43,6 +43,10 @@ interface RouteMeta { | 'success' | 'warning' | string; + /** + * 路由对应dom是否缓存起来 + */ + domCached?: boolean; /** * 路由的完整路径作为key(默认true) */ diff --git a/packages/effects/layouts/src/basic/content/content.vue b/packages/effects/layouts/src/basic/content/content.vue index 8b8a6391..2d6bb916 100644 --- a/packages/effects/layouts/src/basic/content/content.vue +++ b/packages/effects/layouts/src/basic/content/content.vue @@ -1,17 +1,15 @@ + + @@ -132,14 +72,14 @@ function transformComponent( > diff --git a/packages/effects/layouts/src/hooks/index.ts b/packages/effects/layouts/src/hooks/index.ts new file mode 100644 index 00000000..5f825ac9 --- /dev/null +++ b/packages/effects/layouts/src/hooks/index.ts @@ -0,0 +1,98 @@ +import type { VNode } from 'vue'; +import type { + RouteLocationNormalizedLoaded, + RouteLocationNormalizedLoadedGeneric, +} from 'vue-router'; + +import { computed } from 'vue'; + +import { preferences, usePreferences } from '@vben/preferences'; + +/** + * 转换组件,自动添加 name + * @param component + * @param route + */ +export function transformComponent( + component: VNode, + route: RouteLocationNormalizedLoadedGeneric, +) { + // 组件视图未找到,如果有设置后备视图,则返回后备视图,如果没有,则抛出错误 + if (!component) { + console.error( + 'Component view not found,please check the route configuration', + ); + return undefined; + } + + const routeName = route.name as string; + // 如果组件没有 name,则直接返回 + if (!routeName) { + return component; + } + const componentName = (component?.type as any)?.name; + + // 已经设置过 name,则直接返回 + if (componentName) { + return component; + } + + // componentName 与 routeName 一致,则直接返回 + if (componentName === routeName) { + return component; + } + + // 设置 name + component.type ||= {}; + (component.type as any).name = routeName; + + return component; +} + +/** + * Layout相关hook + */ +export function useLayoutHook() { + const { keepAlive } = usePreferences(); + /** + * 是否使用动画 + */ + const getEnabledTransition = computed(() => { + const { transition } = preferences; + const transitionName = transition.name; + return transitionName && transition.enable; + }); + + /** + * 获取路由过渡动画 + * @param _route + */ + function getTransitionName(_route: RouteLocationNormalizedLoaded) { + // 如果偏好设置未设置,则不使用动画 + const { tabbar, transition } = preferences; + const transitionName = transition.name; + if (!transitionName || !transition.enable) { + return; + } + + // 标签页未启用或者未开启缓存,则使用全局配置动画 + if (!tabbar.enable || !keepAlive) { + return transitionName; + } + + // 如果页面已经加载过,则不使用动画 + // if (route.meta.loaded) { + // return; + // } + // 已经打开且已经加载过的页面不使用动画 + // const inTabs = getCachedTabs.value.includes(route.name as string); + + // return inTabs && route.meta.loaded ? undefined : transitionName; + return transitionName; + } + + return { + getEnabledTransition, + getTransitionName, + }; +} diff --git a/packages/effects/layouts/src/route-cached/index.ts b/packages/effects/layouts/src/route-cached/index.ts new file mode 100644 index 00000000..daae4a07 --- /dev/null +++ b/packages/effects/layouts/src/route-cached/index.ts @@ -0,0 +1,2 @@ +export { default as RouteCachedPage } from './route-cached-page.vue'; +export { default as RouteCachedView } from './route-cached-view.vue'; diff --git a/packages/effects/layouts/src/route-cached/route-cached-page.vue b/packages/effects/layouts/src/route-cached/route-cached-page.vue new file mode 100644 index 00000000..63708ce8 --- /dev/null +++ b/packages/effects/layouts/src/route-cached/route-cached-page.vue @@ -0,0 +1,36 @@ + + diff --git a/packages/effects/layouts/src/route-cached/route-cached-view.vue b/packages/effects/layouts/src/route-cached/route-cached-view.vue new file mode 100644 index 00000000..96616aa1 --- /dev/null +++ b/packages/effects/layouts/src/route-cached/route-cached-view.vue @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + diff --git a/packages/stores/src/modules/tabbar.ts b/packages/stores/src/modules/tabbar.ts index 66a3758b..14af7be9 100644 --- a/packages/stores/src/modules/tabbar.ts +++ b/packages/stores/src/modules/tabbar.ts @@ -1,13 +1,15 @@ -import type { ComputedRef } from 'vue'; +import type { ComputedRef, VNode } from 'vue'; import type { RouteLocationNormalized, + RouteLocationNormalizedLoaded, + RouteLocationNormalizedLoadedGeneric, Router, RouteRecordNormalized, } from 'vue-router'; import type { TabDefinition } from '@vben-core/typings'; -import { toRaw } from 'vue'; +import { markRaw, toRaw } from 'vue'; import { preferences } from '@vben-core/preferences'; import { @@ -20,7 +22,14 @@ import { import { acceptHMRUpdate, defineStore } from 'pinia'; +interface RouteCached { + component: VNode; + key: string; + route: RouteLocationNormalizedLoadedGeneric; +} + interface TabbarState { + cachedRoutes: Map; /** * @zh_CN 当前打开的标签页列表缓存 */ @@ -553,6 +562,25 @@ export const useTabbarStore = defineStore('core-tabbar', { } this.cachedTabs = cacheMap; }, + /** + * 添加缓存的route + * @param component + * @param route + */ + addCachedRoute(component: VNode, route: RouteLocationNormalizedLoaded) { + const key = getTabKey(route); + if (this.cachedRoutes.has(key)) { + return; + } + this.cachedRoutes.set(key, { + key, + component: markRaw(component), + route: markRaw(route), + }); + }, + removeCachedRoute(key: string) { + this.cachedRoutes.delete(key); + }, }, getters: { affixTabs(): TabDefinition[] { @@ -577,6 +605,9 @@ export const useTabbarStore = defineStore('core-tabbar', { const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab)); return [...this.affixTabs, ...normalTabs].filter(Boolean); }, + getCachedRoutes(): Map { + return this.cachedRoutes; + }, }, persist: [ // tabs不需要保存在localStorage @@ -604,6 +635,7 @@ export const useTabbarStore = defineStore('core-tabbar', { ], state: (): TabbarState => ({ visitHistory: createStack(true, MAX_VISIT_HISTORY), + cachedRoutes: new Map(), cachedTabs: new Set(), dragEndIndex: 0, excludeCachedTabs: new Set(), diff --git a/playground/src/router/routes/modules/demos.ts b/playground/src/router/routes/modules/demos.ts index b8d27ef5..8cb9f511 100644 --- a/playground/src/router/routes/modules/demos.ts +++ b/playground/src/router/routes/modules/demos.ts @@ -30,6 +30,7 @@ const routes: RouteRecordRaw[] = [ meta: { icon: 'mdi:page-previous-outline', title: $t('demos.access.pageAccess'), + domCached: true, }, }, { From 49e45eab54abf64eec54f63f853f2ba547fc417e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:06:18 +0800 Subject: [PATCH 08/13] chore(deps): bump vue-router from 4.6.4 to 5.0.3 (#7583) Bumps [vue-router](https://github.com/vuejs/router) from 4.6.4 to 5.0.3. - [Release notes](https://github.com/vuejs/router/releases) - [Commits](https://github.com/vuejs/router/compare/v4.6.4...v5.0.3) --- updated-dependencies: - dependency-name: vue-router dependency-version: 5.0.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 143 ++++++++++++++++++++++++++++++++++++-------- pnpm-workspace.yaml | 2 +- 2 files changed, 118 insertions(+), 27 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4647dec..9ada9b8e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -496,8 +496,8 @@ catalogs: specifier: ^3.0.4 version: 3.0.4 vue-router: - specifier: ^4.6.4 - version: 4.6.4 + specifier: ^5.0.3 + version: 5.0.3 vue-tippy: specifier: ^6.7.1 version: 6.7.1 @@ -713,7 +713,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) apps/web-antdv-next: dependencies: @@ -776,7 +776,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) apps/web-ele: dependencies: @@ -839,7 +839,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) devDependencies: unplugin-element-plus: specifier: 'catalog:' @@ -903,7 +903,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) apps/web-tdesign: dependencies: @@ -969,7 +969,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) docs: dependencies: @@ -1024,7 +1024,7 @@ importers: dependencies: '@commitlint/cli': specifier: 'catalog:' - version: 19.8.1(@types/node@24.10.12)(typescript@5.9.3) + version: 19.8.1(@types/node@25.2.2)(typescript@5.9.3) '@commitlint/config-conventional': specifier: 'catalog:' version: 19.8.1 @@ -1418,7 +1418,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) packages/@core/composables: dependencies: @@ -1698,7 +1698,7 @@ importers: version: 3.0.4(vue@3.5.27(typescript@5.9.3)) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) vue-tippy: specifier: 'catalog:' version: 6.7.1(vue@3.5.27(typescript@5.9.3)) @@ -1732,7 +1732,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) watermark-js-plus: specifier: 'catalog:' version: 1.6.3 @@ -1795,7 +1795,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) packages/effects/plugins: dependencies: @@ -1922,7 +1922,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) packages/styles: dependencies: @@ -1940,7 +1940,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) packages/utils: dependencies: @@ -1952,7 +1952,7 @@ importers: version: link:../@core/base/typings vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) playground: dependencies: @@ -2027,7 +2027,7 @@ importers: version: 3.5.27(typescript@5.9.3) vue-router: specifier: 'catalog:' - version: 4.6.4(vue@3.5.27(typescript@5.9.3)) + version: 5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)) devDependencies: '@types/json-bigint': specifier: 'catalog:' @@ -5188,6 +5188,15 @@ packages: '@volar/typescript@2.4.28': resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + '@vue-macros/common@3.1.2': + resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==} + engines: {node: '>=20.19.0'} + peerDependencies: + vue: ^3.5.27 + peerDependenciesMeta: + vue: + optional: true + '@vue/babel-helper-vue-transform-on@1.5.0': resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} @@ -5241,6 +5250,9 @@ packages: '@vue/devtools-api@7.7.9': resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + '@vue/devtools-api@8.0.6': + resolution: {integrity: sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA==} + '@vue/devtools-core@8.0.6': resolution: {integrity: sha512-fN7iVtpSQQdtMORWwVZ1JiIAKriinhD+lCHqPw9Rr252ae2TczILEmW0zcAZifPW8HfYcbFkn+h7Wv6kQQCayw==} peerDependencies: @@ -5604,6 +5616,14 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-kit@2.2.0: + resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} + engines: {node: '>=20.19.0'} + + ast-walker-scope@0.8.3: + resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==} + engines: {node: '>=20.19.0'} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -8188,6 +8208,10 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true + magic-string-ast@1.0.3: + resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==} + engines: {node: '>=20.19.0'} + magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} @@ -10535,6 +10559,10 @@ packages: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} + unplugin@3.0.0: + resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} + engines: {node: ^20.19.0 || >=22.12.0} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -10885,10 +10913,20 @@ packages: peerDependencies: vue: ^3.5.27 - vue-router@4.6.4: - resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + vue-router@5.0.3: + resolution: {integrity: sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==} peerDependencies: + '@pinia/colada': '>=0.21.2' + '@vue/compiler-sfc': ^3.5.17 + pinia: ^3.0.4 vue: ^3.5.27 + peerDependenciesMeta: + '@pinia/colada': + optional: true + '@vue/compiler-sfc': + optional: true + pinia: + optional: true vue-tippy@6.7.1: resolution: {integrity: sha512-gdHbBV5/Vc8gH87hQHLA7TN1K4BlLco3MAPrTb70ZYGXxx+55rAU4a4mt0fIoP+gB3etu1khUZ6c29Br1n0CiA==} @@ -12290,11 +12328,11 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} - '@commitlint/cli@19.8.1(@types/node@24.10.12)(typescript@5.9.3)': + '@commitlint/cli@19.8.1(@types/node@25.2.2)(typescript@5.9.3)': dependencies: '@commitlint/format': 19.8.1 '@commitlint/lint': 19.8.1 - '@commitlint/load': 19.8.1(@types/node@24.10.12)(typescript@5.9.3) + '@commitlint/load': 19.8.1(@types/node@25.2.2)(typescript@5.9.3) '@commitlint/read': 19.8.1 '@commitlint/types': 19.8.1 tinyexec: 1.0.2 @@ -12341,7 +12379,7 @@ snapshots: '@commitlint/rules': 19.8.1 '@commitlint/types': 19.8.1 - '@commitlint/load@19.8.1(@types/node@24.10.12)(typescript@5.9.3)': + '@commitlint/load@19.8.1(@types/node@25.2.2)(typescript@5.9.3)': dependencies: '@commitlint/config-validator': 19.8.1 '@commitlint/execute-rule': 19.8.1 @@ -12349,7 +12387,7 @@ snapshots: '@commitlint/types': 19.8.1 chalk: 5.6.2 cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.2.0(@types/node@24.10.12)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@25.2.2)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -14719,6 +14757,16 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 + '@vue-macros/common@3.1.2(vue@3.5.27(typescript@5.9.3))': + dependencies: + '@vue/compiler-sfc': 3.5.27 + ast-kit: 2.2.0 + local-pkg: 1.1.2 + magic-string-ast: 1.0.3 + unplugin-utils: 0.3.1 + optionalDependencies: + vue: 3.5.27(typescript@5.9.3) + '@vue/babel-helper-vue-transform-on@1.5.0': {} '@vue/babel-helper-vue-transform-on@2.0.1': {} @@ -14818,6 +14866,10 @@ snapshots: dependencies: '@vue/devtools-kit': 7.7.9 + '@vue/devtools-api@8.0.6': + dependencies: + '@vue/devtools-kit': 8.0.6 + '@vue/devtools-core@8.0.6(vite@7.3.1(@types/node@25.2.2)(jiti@2.6.1)(less@4.5.1)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.2))(vue@3.5.27(typescript@5.9.3))': dependencies: '@vue/devtools-kit': 8.0.6 @@ -15281,6 +15333,16 @@ snapshots: assertion-error@2.0.1: {} + ast-kit@2.2.0: + dependencies: + '@babel/parser': 7.29.0 + pathe: 2.0.3 + + ast-walker-scope@0.8.3: + dependencies: + '@babel/parser': 7.29.0 + ast-kit: 2.2.0 + astral-regex@2.0.0: {} async-function@1.0.0: {} @@ -15808,9 +15870,9 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@6.2.0(@types/node@24.10.12)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@6.2.0(@types/node@25.2.2)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: - '@types/node': 24.10.12 + '@types/node': 25.2.2 cosmiconfig: 9.0.0(typescript@5.9.3) jiti: 2.6.1 typescript: 5.9.3 @@ -18062,6 +18124,10 @@ snapshots: lz-string@1.5.0: {} + magic-string-ast@1.0.3: + dependencies: + magic-string: 0.30.21 + magic-string@0.25.9: dependencies: sourcemap-codec: 1.4.8 @@ -20618,6 +20684,12 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 + unplugin@3.0.0: + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 @@ -21128,10 +21200,29 @@ snapshots: clipboard: 2.0.11 vue: 3.5.27(typescript@5.9.3) - vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)): + vue-router@5.0.3(@vue/compiler-sfc@3.5.27)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)): dependencies: - '@vue/devtools-api': 6.6.4 + '@babel/generator': 7.29.1 + '@vue-macros/common': 3.1.2(vue@3.5.27(typescript@5.9.3)) + '@vue/devtools-api': 8.0.6 + ast-walker-scope: 0.8.3 + chokidar: 5.0.0 + json5: 2.2.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.0 + muggle-string: 0.4.1 + pathe: 2.0.3 + picomatch: 4.0.3 + scule: 1.3.0 + tinyglobby: 0.2.15 + unplugin: 3.0.0 + unplugin-utils: 0.3.1 vue: 3.5.27(typescript@5.9.3) + yaml: 2.8.2 + optionalDependencies: + '@vue/compiler-sfc': 3.5.27 + pinia: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)) vue-tippy@6.7.1(vue@3.5.27(typescript@5.9.3)): dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index be87ca2d..e3140134 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -192,7 +192,7 @@ catalog: vue-eslint-parser: ^10.2.0 vue-i18n: ^11.2.8 vue-json-viewer: ^3.0.4 - vue-router: ^4.6.4 + vue-router: ^5.0.3 vue-tippy: ^6.7.1 vue-tsc: ^3.2.4 vxe-pc-ui: ^4.12.16 From 8e71261d49a7b953c38a4dca16a5784441c5d539 Mon Sep 17 00:00:00 2001 From: zouawen <846027729@qq.com> Date: Sat, 28 Feb 2026 11:19:01 +0800 Subject: [PATCH 09/13] =?UTF-8?q?fix:=20=E4=BE=A7=E8=BE=B9=E6=A0=8F?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=8B=96=E6=8B=BD=E5=8A=9F=E8=83=BD=E5=9C=A8?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=86=85=E5=A2=9E=E5=8A=A0=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@core/preferences/src/config.ts | 1 + packages/@core/preferences/src/types.ts | 2 + .../src/components/layout-sidebar.vue | 19 +- .../layout-ui/src/hooks/use-sidebar-drag.ts | 169 ++++++++++++------ .../ui-kit/layout-ui/src/vben-layout.vue | 4 + packages/effects/layouts/src/basic/layout.vue | 1 + .../preferences/blocks/layout/sidebar.vue | 4 + .../preferences/preferences-drawer.vue | 2 + .../locales/src/langs/en-US/preferences.json | 1 + .../locales/src/langs/zh-CN/preferences.json | 1 + 10 files changed, 139 insertions(+), 65 deletions(-) diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts index 6bba8099..d2caf154 100644 --- a/packages/@core/preferences/src/config.ts +++ b/packages/@core/preferences/src/config.ts @@ -85,6 +85,7 @@ const defaultPreferences: Preferences = { collapsedButton: true, collapsedShowTitle: false, collapseWidth: 60, + draggable: true, enable: true, expandOnHover: true, extraCollapse: false, diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts index 24c41c85..7348e42a 100644 --- a/packages/@core/preferences/src/types.ts +++ b/packages/@core/preferences/src/types.ts @@ -170,6 +170,8 @@ interface SidebarPreferences { collapsedShowTitle: boolean; /** 侧边栏折叠宽度 */ collapseWidth: number; + /** 侧边栏菜单拖拽 */ + draggable: boolean; /** 侧边栏是否可见 */ enable: boolean; /** 菜单自动展开状态 */ 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 74f12311..dfeca386 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 @@ -109,6 +109,7 @@ const props = withDefaults(defineProps(), { }); const emit = defineEmits<{ leave: []; 'update:width': [value: number] }>(); +const draggable = defineModel('draggable'); const collapse = defineModel('collapse'); const extraCollapse = defineModel('extraCollapse'); const expandOnHovering = defineModel('expandOnHovering'); @@ -262,14 +263,19 @@ const handleDragSidebar = (e: MouseEvent) => { const { isSidebarMixed, collapseWidth, extraWidth, width } = props; const minLimit = collapseWidth; const maxLimit = 320; - const currentWidth = isSidebarMixed ? extraWidth : width; + const startWidth = isSidebarMixed ? extraWidth : width; + startDrag( e, - minLimit, - maxLimit, - currentWidth, - asideRef.value, - dragBarRef.value, + { + min: minLimit, + max: maxLimit, + startWidth, + }, + { + target: asideRef.value, + dragBar: dragBarRef.value, + }, (newWidth) => { emit('update:width', newWidth); if (isSidebarMixed) { @@ -357,6 +363,7 @@ const handleDragSidebar = (e: MouseEvent) => { void; + export function useSidebarDrag() { - let startX = 0; - let startWidth = 0; - let targetTransition = ''; - let dragBarTransition = ''; - let dragBarOffsetLeft = 0; - let dragBarLeft = ''; - let dragBarRight = ''; - let userSelect = ''; - let cursor = ''; - let cleanup: (() => void) | null = null; + const state: { + cleanup: (() => void) | null; + isDragging: boolean; + originalStyles: { + bodyCursor: string; + bodyUserSelect: string; + dragBarLeft: string; + dragBarRight: string; + dragBarTransition: string; + targetTransition: string; + }; + startLeft: number; + startWidth: number; + startX: number; + } = { + cleanup: null, + isDragging: false, + startLeft: 0, + startWidth: 0, + startX: 0, + originalStyles: { + bodyCursor: '', + bodyUserSelect: '', + dragBarLeft: '', + dragBarRight: '', + dragBarTransition: '', + targetTransition: '', + }, + }; const startDrag = ( e: MouseEvent, - min: number, - max: number, - currentWidth: number, - targetElement: HTMLElement | null, - dragBarElement: HTMLElement | null, - onDrag: (newWidth: number) => void, + options: DragOptions, + elements: DragElements, + onDrag: DragCallback, ) => { - cleanup?.(); + const { min, max, startWidth } = options; + const { dragBar, target } = elements; + + if (state.isDragging || !dragBar || !target) return; e.preventDefault(); e.stopPropagation(); - if (!dragBarElement || !targetElement) return; + state.isDragging = true; - startX = e.clientX; - startWidth = currentWidth; + state.startX = e.clientX; + state.startWidth = startWidth; + state.startLeft = dragBar.offsetLeft; - targetTransition = targetElement.style.transition; - dragBarTransition = dragBarElement.style.transition; + state.originalStyles = { + bodyCursor: document.body.style.cursor, + bodyUserSelect: document.body.style.userSelect, + dragBarLeft: dragBar.style.left, + dragBarRight: dragBar.style.right, + dragBarTransition: dragBar.style.transition, + targetTransition: target.style.transition, + }; - dragBarOffsetLeft = dragBarElement.offsetLeft; - dragBarLeft = dragBarElement.style.left; - dragBarRight = dragBarElement.style.right; - - userSelect = document.body.style.userSelect; - cursor = document.body.style.cursor; - - targetElement.style.transition = 'none'; - dragBarElement.style.transition = 'none'; - - dragBarElement.style.left = `${dragBarOffsetLeft}px`; - dragBarElement.style.right = 'auto'; - - document.body.style.userSelect = 'none'; document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + + dragBar.style.left = `${state.startLeft}px`; + dragBar.style.right = 'auto'; + dragBar.style.transition = 'none'; + target.style.transition = 'none'; const onMouseMove = (moveEvent: MouseEvent) => { - const deltaX = moveEvent.clientX - startX; - let newLeft = dragBarOffsetLeft + deltaX; + if (!state.isDragging || !dragBar) return; + + const deltaX = moveEvent.clientX - state.startX; + let newLeft = state.startLeft + deltaX; + if (newLeft < min) newLeft = min; if (newLeft > max) newLeft = max; - dragBarElement.style.left = `${newLeft}px`; + + dragBar.style.left = `${newLeft}px`; + dragBar.classList.add('bg-primary'); }; const onMouseUp = (upEvent: MouseEvent) => { - const deltaX = upEvent.clientX - startX; - let newWidth = startWidth + deltaX; + if (!state.isDragging || !dragBar || !target) return; + + const deltaX = upEvent.clientX - state.startX; + let newWidth = state.startWidth + deltaX; + newWidth = Math.min(max, Math.max(min, newWidth)); - if (dragBarElement) { - dragBarElement.style.left = dragBarLeft; - dragBarElement.style.right = dragBarRight; - } + dragBar.classList.remove('bg-primary'); onDrag?.(newWidth); - cleanup?.(); + endDrag(); }; - document.addEventListener('mousemove', onMouseMove, { passive: true }); + document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); - cleanup = () => { + const cleanup = () => { + if (!state.cleanup) return; + document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); - if (targetElement) { - targetElement.style.transition = targetTransition; - } - if (dragBarElement) { - dragBarElement.style.transition = dragBarTransition; - dragBarElement.style.left = dragBarLeft; - dragBarElement.style.right = dragBarRight; + document.body.style.cursor = state.originalStyles.bodyCursor; + document.body.style.userSelect = state.originalStyles.bodyUserSelect; + + if (dragBar) { + dragBar.style.left = state.originalStyles.dragBarLeft; + dragBar.style.right = state.originalStyles.dragBarRight; + dragBar.style.transition = state.originalStyles.dragBarTransition; + dragBar.classList.remove('bg-primary'); } - document.body.style.userSelect = userSelect; - document.body.style.cursor = cursor; + if (target) { + target.style.transition = state.originalStyles.targetTransition; + } - cleanup = null; + state.isDragging = false; + state.cleanup = null; }; + + state.cleanup = cleanup; + }; + + const endDrag = () => { + state.cleanup?.(); }; onUnmounted(() => { - cleanup?.(); + endDrag(); }); return { startDrag, + endDrag, + get isDragging() { + return state.isDragging; + }, }; } 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 a7b9ab58..2ab4aaa2 100644 --- a/packages/@core/ui-kit/layout-ui/src/vben-layout.vue +++ b/packages/@core/ui-kit/layout-ui/src/vben-layout.vue @@ -69,6 +69,9 @@ const emit = defineEmits<{ toggleSidebar: []; 'update:sidebar-width': [value: number]; }>(); +const sidebarDraggable = defineModel('sidebarDraggable', { + default: true, +}); const sidebarCollapse = defineModel('sidebarCollapse', { default: false, }); @@ -493,6 +496,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT; { :header-visible="preferences.header.enable" :is-mobile="preferences.app.isMobile" :layout="layout" + :sidebar-draggable="preferences.sidebar.draggable" :sidebar-collapse="preferences.sidebar.collapsed" :sidebar-collapse-show-title="preferences.sidebar.collapsedShowTitle" :sidebar-enable="sidebarVisible" diff --git a/packages/effects/layouts/src/widgets/preferences/blocks/layout/sidebar.vue b/packages/effects/layouts/src/widgets/preferences/blocks/layout/sidebar.vue index e8d84afa..913775a9 100644 --- a/packages/effects/layouts/src/widgets/preferences/blocks/layout/sidebar.vue +++ b/packages/effects/layouts/src/widgets/preferences/blocks/layout/sidebar.vue @@ -19,6 +19,7 @@ const sidebarCollapsedShowTitle = defineModel( const sidebarAutoActivateChild = defineModel( 'sidebarAutoActivateChild', ); +const sidebarDraggable = defineModel('sidebarDraggable'); const sidebarCollapsed = defineModel('sidebarCollapsed'); const sidebarExpandOnHover = defineModel('sidebarExpandOnHover'); @@ -48,6 +49,9 @@ const handleCheckboxChange = () => { {{ $t('preferences.sidebar.visible') }} + + {{ $t('preferences.sidebar.draggable') }} + {{ $t('preferences.sidebar.collapsed') }} diff --git a/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue b/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue index ca68d355..1abb3342 100644 --- a/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue +++ b/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue @@ -93,6 +93,7 @@ const themeSemiDarkHeader = defineModel('themeSemiDarkHeader'); const sidebarEnable = defineModel('sidebarEnable'); const sidebarWidth = defineModel('sidebarWidth'); +const sidebarDraggable = defineModel('sidebarDraggable'); const sidebarCollapsed = defineModel('sidebarCollapsed'); const sidebarCollapsedShowTitle = defineModel( 'sidebarCollapsedShowTitle', @@ -354,6 +355,7 @@ async function handleReset() { Date: Sat, 28 Feb 2026 11:25:07 +0800 Subject: [PATCH 10/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dlint=E6=8A=A5?= =?UTF-8?q?=E9=94=99?= 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 d66f1065..d1f7122f 100644 --- a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap @@ -84,6 +84,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "collapsed": false, "collapsedButton": true, "collapsedShowTitle": false, + "draggable": true, "enable": true, "expandOnHover": true, "extraCollapse": false, From a46ed55a86c5b164a53e331847e7078df990e62e Mon Sep 17 00:00:00 2001 From: Bryan Qiu Date: Mon, 2 Mar 2026 04:22:21 +0800 Subject: [PATCH 11/13] fix: bump version to 5.6.0 (#7592) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cf616d16..b0e988c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vben-admin-monorepo", - "version": "5.5.9", + "version": "5.6.0", "private": true, "keywords": [ "monorepo", From b8a0199cde1d1f12b2b896358d14fe45b6344394 Mon Sep 17 00:00:00 2001 From: han <83765419+han1548772930@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:30:38 +0800 Subject: [PATCH 12/13] feat(preferences): add toggle for copy preferences button (#7594) Co-authored-by: hl --- docs/src/en/guide/essentials/settings.md | 3 +++ docs/src/guide/essentials/settings.md | 3 +++ .../preferences/__tests__/__snapshots__/config.test.ts.snap | 1 + packages/@core/preferences/src/config.ts | 1 + packages/@core/preferences/src/types.ts | 2 ++ .../src/widgets/preferences/blocks/general/general.vue | 4 ++++ .../layouts/src/widgets/preferences/preferences-drawer.vue | 3 +++ packages/locales/src/langs/en-US/preferences.json | 1 + packages/locales/src/langs/zh-CN/preferences.json | 1 + 9 files changed, 19 insertions(+) diff --git a/docs/src/en/guide/essentials/settings.md b/docs/src/en/guide/essentials/settings.md index ba3f860d..59bb3900 100644 --- a/docs/src/en/guide/essentials/settings.md +++ b/docs/src/en/guide/essentials/settings.md @@ -219,6 +219,7 @@ const defaultPreferences: Preferences = { dynamicTitle: true, enableCheckUpdates: true, enablePreferences: true, + enableCopyPreferences: true, enableRefreshToken: false, isMobile: false, layout: 'sidebar-nav', @@ -376,6 +377,8 @@ interface AppPreferences { enableCheckUpdates: boolean; /** Whether to display preferences */ enablePreferences: boolean; + /** Whether to display copy preferences button */ + enableCopyPreferences: boolean; /** * @zh_CN Whether to enable refreshToken */ diff --git a/docs/src/guide/essentials/settings.md b/docs/src/guide/essentials/settings.md index f2290492..9637114f 100644 --- a/docs/src/guide/essentials/settings.md +++ b/docs/src/guide/essentials/settings.md @@ -218,6 +218,7 @@ const defaultPreferences: Preferences = { dynamicTitle: true, enableCheckUpdates: true, enablePreferences: true, + enableCopyPreferences: true, enableRefreshToken: false, isMobile: false, layout: 'sidebar-nav', @@ -375,6 +376,8 @@ interface AppPreferences { enableCheckUpdates: boolean; /** 是否显示偏好设置 */ enablePreferences: boolean; + /** 是否显示复制偏好设置按钮 */ + enableCopyPreferences: boolean; /** * @zh_CN 是否开启refreshToken */ diff --git a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap index d1f7122f..9064fef4 100644 --- a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap @@ -21,6 +21,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "dynamicTitle": true, "enableCheckUpdates": true, "enablePreferences": true, + "enableCopyPreferences": true, "enableRefreshToken": false, "enableStickyPreferencesNavigationBar": true, "isMobile": false, diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts index d2caf154..95624f42 100644 --- a/packages/@core/preferences/src/config.ts +++ b/packages/@core/preferences/src/config.ts @@ -21,6 +21,7 @@ const defaultPreferences: Preferences = { dynamicTitle: true, enableCheckUpdates: true, enablePreferences: true, + enableCopyPreferences: true, enableRefreshToken: false, enableStickyPreferencesNavigationBar: true, isMobile: false, diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts index 7348e42a..44323d34 100644 --- a/packages/@core/preferences/src/types.ts +++ b/packages/@core/preferences/src/types.ts @@ -55,6 +55,8 @@ interface AppPreferences { enableCheckUpdates: boolean; /** 是否显示偏好设置 */ enablePreferences: boolean; + /** 是否显示复制偏好设置按钮 */ + enableCopyPreferences: boolean; /** * @zh_CN 是否开启refreshToken */ diff --git a/packages/effects/layouts/src/widgets/preferences/blocks/general/general.vue b/packages/effects/layouts/src/widgets/preferences/blocks/general/general.vue index c69cd3aa..9c95511f 100644 --- a/packages/effects/layouts/src/widgets/preferences/blocks/general/general.vue +++ b/packages/effects/layouts/src/widgets/preferences/blocks/general/general.vue @@ -15,6 +15,7 @@ const appDynamicTitle = defineModel('appDynamicTitle'); const appWatermark = defineModel('appWatermark'); const appWatermarkContent = defineModel('appWatermarkContent'); const appEnableCheckUpdates = defineModel('appEnableCheckUpdates'); +const appEnableCopyPreferences = defineModel('appEnableCopyPreferences'); @@ -44,4 +45,7 @@ const appEnableCheckUpdates = defineModel('appEnableCheckUpdates'); {{ $t('preferences.checkUpdates') }} + + {{ $t('preferences.enableCopyPreferences') }} + diff --git a/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue b/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue index 1abb3342..5c23c446 100644 --- a/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue +++ b/packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue @@ -70,6 +70,7 @@ const appContentCompact = defineModel('appContentCompact'); const appWatermark = defineModel('appWatermark'); const appWatermarkContent = defineModel('appWatermarkContent'); const appEnableCheckUpdates = defineModel('appEnableCheckUpdates'); +const appEnableCopyPreferences = defineModel('appEnableCopyPreferences'); const appEnableStickyPreferencesNavigationBar = defineModel( 'appEnableStickyPreferencesNavigationBar', ); @@ -300,6 +301,7 @@ async function handleReset() { Date: Mon, 2 Mar 2026 15:31:29 +0800 Subject: [PATCH 13/13] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B7=B7?= =?UTF-8?q?=E5=90=88=E5=8F=8C=E5=88=97=E5=B8=83=E5=B1=80=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E6=8B=96=E6=8B=BD=E7=BA=BF=E6=9D=A1=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E6=98=BE=E7=A4=BAbug=EF=BC=8C=E5=90=8C=E6=AD=A5=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=99=AE=E9=80=9A=E5=B8=83=E5=B1=80=E5=92=8C=E6=B7=B7?= =?UTF-8?q?=E5=90=88=E5=8F=8C=E5=88=97=E5=B8=83=E5=B1=80=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E6=97=B6width=E8=AE=A1=E7=AE=97=E5=AF=BC=E8=87=B4=E4=BE=A7?= =?UTF-8?q?=E8=BE=B9=E6=A0=8F=E5=AE=BD=E5=BA=A6=E6=98=BE=E7=A4=BA=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E9=97=AE=E9=A2=98=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=99=AE?= =?UTF-8?q?=E9=80=9A=E5=B8=83=E5=B1=80=E5=92=8C=E6=B7=B7=E5=90=88=E5=8F=8C?= =?UTF-8?q?=E5=88=97=E5=B8=83=E5=B1=80=E4=BE=A7=E8=BE=B9=E6=A0=8F=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E6=8A=98=E5=8F=A0=E7=8A=B6=E6=80=81=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=20(#7596)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout-sidebar.vue | 33 +++++++++++-------- .../ui-kit/layout-ui/src/vben-layout.vue | 16 +++++---- 2 files changed, 29 insertions(+), 20 deletions(-) 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 dfeca386..e717c905 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 @@ -157,9 +157,9 @@ const extraTitleStyle = computed((): CSSProperties => { }); const contentWidthStyle = computed((): CSSProperties => { - const { collapseWidth, fixedExtra, isSidebarMixed, mixedWidth } = props; + const { fixedExtra, isSidebarMixed, mixedWidth } = props; if (isSidebarMixed && fixedExtra) { - return { width: `${collapse.value ? collapseWidth : mixedWidth}px` }; + return { width: `${mixedWidth}px` }; } return {}; }); @@ -202,19 +202,24 @@ watchEffect(() => { }); function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties { - const { extraWidth, fixedExtra, isSidebarMixed, show, width } = props; + const { + collapseWidth, + extraWidth, + mixedWidth, + fixedExtra, + isSidebarMixed, + show, + width, + } = props; let widthValue = width === 0 ? '0px' : `${width + (isSidebarMixed && fixedExtra && extraVisible.value ? extraWidth : 0)}px`; - const { collapseWidth } = props; - if (isHiddenDom && expandOnHovering.value && !expandOnHover.value) { - widthValue = `${collapseWidth}px`; + widthValue = isSidebarMixed ? `${mixedWidth}px` : `${collapseWidth}px`; } - return { ...(widthValue === '0px' ? { overflow: 'hidden' } : {}), flex: `0 0 ${widthValue}`, @@ -261,9 +266,9 @@ const { startDrag } = useSidebarDrag(); const handleDragSidebar = (e: MouseEvent) => { const { isSidebarMixed, collapseWidth, extraWidth, width } = props; - const minLimit = collapseWidth; - const maxLimit = 320; - const startWidth = isSidebarMixed ? extraWidth : width; + const minLimit = isSidebarMixed ? width + collapseWidth : collapseWidth; + const maxLimit = isSidebarMixed ? width + 320 : 320; + const startWidth = isSidebarMixed ? width + extraWidth : width; startDrag( e, @@ -277,11 +282,13 @@ const handleDragSidebar = (e: MouseEvent) => { dragBar: dragBarRef.value, }, (newWidth) => { - emit('update:width', newWidth); if (isSidebarMixed) { - extraCollapse.value = newWidth <= collapseWidth; + emit('update:width', newWidth - width); + extraCollapse.value = collapse.value = + newWidth - width <= collapseWidth; } else { - collapse.value = newWidth <= collapseWidth; + emit('update:width', newWidth); + collapse.value = extraCollapse.value = newWidth <= collapseWidth; } }, ); 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 2ab4aaa2..7e444068 100644 --- a/packages/@core/ui-kit/layout-ui/src/vben-layout.vue +++ b/packages/@core/ui-kit/layout-ui/src/vben-layout.vue @@ -247,9 +247,7 @@ const mainStyle = computed(() => { sidebarExtraVisible.value; if (isSideNavEffective) { - const sideCollapseWidth = sidebarCollapse.value - ? getSideCollapseWidth.value - : props.sidebarMixedWidth; + const sideCollapseWidth = props.sidebarMixedWidth; const sideWidth = sidebarExtraCollapse.value ? props.sidebarExtraCollapsedWidth : props.sidebarWidth; @@ -258,10 +256,14 @@ const mainStyle = computed(() => { sidebarAndExtraWidth = `${sideCollapseWidth + sideWidth}px`; width = `calc(100% - ${sidebarAndExtraWidth})`; } else { - sidebarAndExtraWidth = - sidebarExpandOnHovering.value && !sidebarExpandOnHover.value - ? `${getSideCollapseWidth.value}px` - : `${getSidebarWidth.value}px`; + let sidebarWidth = getSidebarWidth.value; + if (sidebarExpandOnHovering.value && !sidebarExpandOnHover.value) { + sidebarWidth = + isSidebarMixedNav.value || isHeaderMixedNav.value + ? props.sidebarMixedWidth + : getSideCollapseWidth.value; + } + sidebarAndExtraWidth = `${sidebarWidth}px`; width = `calc(100% - ${sidebarAndExtraWidth})`; } }