mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-03-18 09:38:48 +08:00
Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
@@ -43,6 +43,10 @@ interface RouteMeta {
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| string;
|
||||
/**
|
||||
* 路由对应dom是否缓存起来
|
||||
*/
|
||||
domCached?: boolean;
|
||||
/**
|
||||
* 路由的完整路径作为key(默认true)
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
@@ -84,6 +85,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,
|
||||
|
||||
@@ -21,6 +21,7 @@ const defaultPreferences: Preferences = {
|
||||
dynamicTitle: true,
|
||||
enableCheckUpdates: true,
|
||||
enablePreferences: true,
|
||||
enableCopyPreferences: true,
|
||||
enableRefreshToken: false,
|
||||
enableStickyPreferencesNavigationBar: true,
|
||||
isMobile: false,
|
||||
@@ -85,6 +86,7 @@ const defaultPreferences: Preferences = {
|
||||
collapsedButton: true,
|
||||
collapsedShowTitle: false,
|
||||
collapseWidth: 60,
|
||||
draggable: true,
|
||||
enable: true,
|
||||
expandOnHover: true,
|
||||
extraCollapse: false,
|
||||
|
||||
@@ -55,6 +55,8 @@ interface AppPreferences {
|
||||
enableCheckUpdates: boolean;
|
||||
/** 是否显示偏好设置 */
|
||||
enablePreferences: boolean;
|
||||
/** 是否显示复制偏好设置按钮 */
|
||||
enableCopyPreferences: boolean;
|
||||
/**
|
||||
* @zh_CN 是否开启refreshToken
|
||||
*/
|
||||
@@ -170,6 +172,8 @@ interface SidebarPreferences {
|
||||
collapsedShowTitle: boolean;
|
||||
/** 侧边栏折叠宽度 */
|
||||
collapseWidth: number;
|
||||
/** 侧边栏菜单拖拽 */
|
||||
draggable: boolean;
|
||||
/** 侧边栏是否可见 */
|
||||
enable: boolean;
|
||||
/** 菜单自动展开状态 */
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 {
|
||||
@@ -107,7 +108,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
zIndex: 0,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ leave: [] }>();
|
||||
const emit = defineEmits<{ leave: []; 'update:width': [value: number] }>();
|
||||
const draggable = defineModel<boolean>('draggable');
|
||||
const collapse = defineModel<boolean>('collapse');
|
||||
const extraCollapse = defineModel<boolean>('extraCollapse');
|
||||
const expandOnHovering = defineModel<boolean>('expandOnHovering');
|
||||
@@ -117,8 +119,8 @@ const extraVisible = defineModel<boolean>('extraVisible');
|
||||
const isLocked = useScrollLock(document.body);
|
||||
const slots = useSlots();
|
||||
|
||||
// @ts-expect-error unused
|
||||
const asideRef = shallowRef<HTMLDivElement | null>();
|
||||
const asideRef = shallowRef<HTMLElement | null>(null);
|
||||
const dragBarRef = shallowRef<HTMLElement | null>(null);
|
||||
|
||||
const hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true));
|
||||
|
||||
@@ -155,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 {};
|
||||
});
|
||||
@@ -200,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}`,
|
||||
@@ -254,6 +261,38 @@ function handleMouseleave() {
|
||||
collapse.value = true;
|
||||
extraVisible.value = false;
|
||||
}
|
||||
|
||||
const { startDrag } = useSidebarDrag();
|
||||
|
||||
const handleDragSidebar = (e: MouseEvent) => {
|
||||
const { isSidebarMixed, collapseWidth, extraWidth, width } = props;
|
||||
const minLimit = isSidebarMixed ? width + collapseWidth : collapseWidth;
|
||||
const maxLimit = isSidebarMixed ? width + 320 : 320;
|
||||
const startWidth = isSidebarMixed ? width + extraWidth : width;
|
||||
|
||||
startDrag(
|
||||
e,
|
||||
{
|
||||
min: minLimit,
|
||||
max: maxLimit,
|
||||
startWidth,
|
||||
},
|
||||
{
|
||||
target: asideRef.value,
|
||||
dragBar: dragBarRef.value,
|
||||
},
|
||||
(newWidth) => {
|
||||
if (isSidebarMixed) {
|
||||
emit('update:width', newWidth - width);
|
||||
extraCollapse.value = collapse.value =
|
||||
newWidth - width <= collapseWidth;
|
||||
} else {
|
||||
emit('update:width', newWidth);
|
||||
collapse.value = extraCollapse.value = newWidth <= collapseWidth;
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -264,6 +303,7 @@ function handleMouseleave() {
|
||||
class="h-full transition-all duration-150"
|
||||
></div>
|
||||
<aside
|
||||
ref="asideRef"
|
||||
:style="style"
|
||||
class="fixed left-0 top-0 h-full transition-all duration-150"
|
||||
@mouseenter="handleMouseenter"
|
||||
@@ -299,7 +339,6 @@ function handleMouseleave() {
|
||||
</div>
|
||||
<div
|
||||
v-if="isSidebarMixed"
|
||||
ref="asideRef"
|
||||
:class="[
|
||||
themeSub,
|
||||
{
|
||||
@@ -330,5 +369,11 @@ function handleMouseleave() {
|
||||
<slot name="extra"></slot>
|
||||
</VbenScrollbar>
|
||||
</div>
|
||||
<div
|
||||
v-if="draggable"
|
||||
ref="dragBarRef"
|
||||
class="absolute inset-y-0 -right-[1px] z-1000 w-[2px] cursor-col-resize hover:bg-primary"
|
||||
@mousedown="handleDragSidebar"
|
||||
></div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
157
packages/@core/ui-kit/layout-ui/src/hooks/use-sidebar-drag.ts
Normal file
157
packages/@core/ui-kit/layout-ui/src/hooks/use-sidebar-drag.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { onUnmounted } from 'vue';
|
||||
|
||||
interface DragOptions {
|
||||
max: number;
|
||||
min: number;
|
||||
startWidth: number;
|
||||
}
|
||||
|
||||
interface DragElements {
|
||||
dragBar: HTMLElement | null;
|
||||
target: HTMLElement | null;
|
||||
}
|
||||
|
||||
type DragCallback = (newWidth: number) => void;
|
||||
|
||||
export function useSidebarDrag() {
|
||||
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,
|
||||
options: DragOptions,
|
||||
elements: DragElements,
|
||||
onDrag: DragCallback,
|
||||
) => {
|
||||
const { min, max, startWidth } = options;
|
||||
const { dragBar, target } = elements;
|
||||
|
||||
if (state.isDragging || !dragBar || !target) return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
state.isDragging = true;
|
||||
|
||||
state.startX = e.clientX;
|
||||
state.startWidth = startWidth;
|
||||
state.startLeft = dragBar.offsetLeft;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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) => {
|
||||
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;
|
||||
|
||||
dragBar.style.left = `${newLeft}px`;
|
||||
dragBar.classList.add('bg-primary');
|
||||
};
|
||||
|
||||
const onMouseUp = (upEvent: MouseEvent) => {
|
||||
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));
|
||||
|
||||
dragBar.classList.remove('bg-primary');
|
||||
|
||||
onDrag?.(newWidth);
|
||||
|
||||
endDrag();
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
|
||||
const cleanup = () => {
|
||||
if (!state.cleanup) return;
|
||||
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
if (target) {
|
||||
target.style.transition = state.originalStyles.targetTransition;
|
||||
}
|
||||
|
||||
state.isDragging = false;
|
||||
state.cleanup = null;
|
||||
};
|
||||
|
||||
state.cleanup = cleanup;
|
||||
};
|
||||
|
||||
const endDrag = () => {
|
||||
state.cleanup?.();
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
endDrag();
|
||||
});
|
||||
|
||||
return {
|
||||
startDrag,
|
||||
endDrag,
|
||||
get isDragging() {
|
||||
return state.isDragging;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -64,7 +64,14 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
zIndex: 200,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ sideMouseLeave: []; toggleSidebar: [] }>();
|
||||
const emit = defineEmits<{
|
||||
sideMouseLeave: [];
|
||||
toggleSidebar: [];
|
||||
'update:sidebar-width': [value: number];
|
||||
}>();
|
||||
const sidebarDraggable = defineModel<boolean>('sidebarDraggable', {
|
||||
default: true,
|
||||
});
|
||||
const sidebarCollapse = defineModel<boolean>('sidebarCollapse', {
|
||||
default: false,
|
||||
});
|
||||
@@ -120,13 +127,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;
|
||||
});
|
||||
|
||||
@@ -237,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;
|
||||
@@ -248,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})`;
|
||||
}
|
||||
}
|
||||
@@ -486,6 +498,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
||||
<div class="relative flex min-h-full w-full">
|
||||
<LayoutSidebar
|
||||
v-if="sidebarEnableState"
|
||||
v-model:draggable="sidebarDraggable"
|
||||
v-model:collapse="sidebarCollapse"
|
||||
v-model:expand-on-hover="sidebarExpandOnHover"
|
||||
v-model:expand-on-hovering="sidebarExpandOnHovering"
|
||||
@@ -507,6 +520,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
||||
:width="getSidebarWidth"
|
||||
:z-index="sidebarZIndex"
|
||||
@leave="() => emit('sideMouseLeave')"
|
||||
@update:width="(val) => emit('update:sidebar-width', val)"
|
||||
>
|
||||
<template v-if="isSideMode && !isMixedNav" #logo>
|
||||
<slot name="logo"></slot>
|
||||
|
||||
Reference in New Issue
Block a user