|
|
|
@ -25,6 +25,10 @@ interface Props { |
|
|
|
* @default true |
|
|
|
*/ |
|
|
|
domVisible?: boolean; |
|
|
|
/** |
|
|
|
* 标准侧栏展开宽度 |
|
|
|
*/ |
|
|
|
expandedWidth?: number; |
|
|
|
/** |
|
|
|
* 扩展区域extra-title的高度 |
|
|
|
*/ |
|
|
|
@ -42,6 +46,11 @@ interface Props { |
|
|
|
* 头部高度 |
|
|
|
*/ |
|
|
|
headerHeight: number; |
|
|
|
/** |
|
|
|
* 是否移动端抽屉模式 |
|
|
|
* @default false |
|
|
|
*/ |
|
|
|
isMobile?: boolean; |
|
|
|
/** |
|
|
|
* 是否侧边混合模式 |
|
|
|
* @default false |
|
|
|
@ -100,8 +109,10 @@ const props = withDefaults(defineProps<Props>(), { |
|
|
|
collapseHeight: 42, |
|
|
|
collapseWidth: 48, |
|
|
|
domVisible: true, |
|
|
|
expandedWidth: 180, |
|
|
|
extraTitleHeight: undefined, |
|
|
|
fixedExtra: false, |
|
|
|
isMobile: false, |
|
|
|
isSidebarMixed: false, |
|
|
|
marginTop: 0, |
|
|
|
mixedWidth: 70, |
|
|
|
@ -126,14 +137,36 @@ const slots = useSlots(); |
|
|
|
const asideRef = shallowRef<HTMLElement | null>(null); |
|
|
|
const dragBarRef = shallowRef<HTMLElement | null>(null); |
|
|
|
|
|
|
|
const hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true)); |
|
|
|
const hiddenSideStyle = computed((): CSSProperties => { |
|
|
|
const widthValue = props.show ? getMenuWidthValue(true) : '0px'; |
|
|
|
return { |
|
|
|
flexBasis: widthValue, |
|
|
|
flexGrow: 0, |
|
|
|
flexShrink: 0, |
|
|
|
overflow: 'hidden', |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
const sidebarVisualWidth = computed(() => { |
|
|
|
const currentWidth = Number.parseFloat(getMenuWidthValue(false)); |
|
|
|
return !props.isMobile && !props.isSidebarMixed |
|
|
|
? Math.max(currentWidth, props.expandedWidth) |
|
|
|
: currentWidth; |
|
|
|
}); |
|
|
|
|
|
|
|
const dragBarStyle = computed((): CSSProperties => { |
|
|
|
const currentWidth = Number.parseFloat(getMenuWidthValue(false)); |
|
|
|
return { |
|
|
|
right: `${Math.max(0, sidebarVisualWidth.value - currentWidth)}px`, |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
const style = computed((): CSSProperties => { |
|
|
|
const { isSidebarMixed, marginTop, paddingTop, zIndex } = props; |
|
|
|
|
|
|
|
return { |
|
|
|
'--scroll-shadow': 'var(--sidebar)', |
|
|
|
...calcMenuWidthStyle(false), |
|
|
|
...calcMenuWidthStyle(), |
|
|
|
height: `calc(100% - ${marginTop}px)`, |
|
|
|
marginTop: `${marginTop}px`, |
|
|
|
paddingTop: `${paddingTop}px`, |
|
|
|
@ -206,14 +239,13 @@ watchEffect(() => { |
|
|
|
extraVisible.value = props.fixedExtra ? true : extraVisible.value; |
|
|
|
}); |
|
|
|
|
|
|
|
function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties { |
|
|
|
function getMenuWidthValue(isHiddenDom: boolean) { |
|
|
|
const { |
|
|
|
collapseWidth, |
|
|
|
extraWidth, |
|
|
|
mixedWidth, |
|
|
|
fixedExtra, |
|
|
|
isSidebarMixed, |
|
|
|
show, |
|
|
|
width, |
|
|
|
} = props; |
|
|
|
|
|
|
|
@ -225,13 +257,22 @@ function calcMenuWidthStyle(isHiddenDom: boolean): CSSProperties { |
|
|
|
if (isHiddenDom && expandOnHovering.value && !expandOnHover.value) { |
|
|
|
widthValue = isSidebarMixed ? `${mixedWidth}px` : `${collapseWidth}px`; |
|
|
|
} |
|
|
|
return widthValue; |
|
|
|
} |
|
|
|
|
|
|
|
function calcMenuWidthStyle(): CSSProperties { |
|
|
|
const widthValue = getMenuWidthValue(false); |
|
|
|
const currentWidth = Number.parseFloat(widthValue); |
|
|
|
const clippedWidth = Math.max(0, sidebarVisualWidth.value - currentWidth); |
|
|
|
return { |
|
|
|
...(widthValue === '0px' ? { overflow: 'hidden' } : {}), |
|
|
|
flex: `0 0 ${widthValue}`, |
|
|
|
marginLeft: show ? 0 : `-${widthValue}`, |
|
|
|
maxWidth: widthValue, |
|
|
|
minWidth: widthValue, |
|
|
|
width: widthValue, |
|
|
|
clipPath: `inset(0 ${clippedWidth}px 0 0)`, |
|
|
|
transform: props.isMobile |
|
|
|
? undefined |
|
|
|
: props.show |
|
|
|
? 'translate3d(0, 0, 0)' |
|
|
|
: 'translate3d(-100%, 0, 0)', |
|
|
|
width: `${sidebarVisualWidth.value}px`, |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
@ -307,80 +348,113 @@ onUnmounted(() => { |
|
|
|
v-if="domVisible" |
|
|
|
:class="theme" |
|
|
|
:style="hiddenSideStyle" |
|
|
|
class="h-full transition-all duration-150" |
|
|
|
class="h-full" |
|
|
|
></div> |
|
|
|
<aside |
|
|
|
ref="asideRef" |
|
|
|
:style="style" |
|
|
|
class="fixed left-0 top-0 h-full transition-all duration-150" |
|
|
|
:class="theme" |
|
|
|
@mouseenter="handleMouseenter" |
|
|
|
@mouseleave="handleMouseleave" |
|
|
|
> |
|
|
|
<div |
|
|
|
class="h-full" |
|
|
|
:class="[ |
|
|
|
{ |
|
|
|
'bg-sidebar-deep': isSidebarMixed, |
|
|
|
'border-r border-border bg-sidebar': !isSidebarMixed, |
|
|
|
}, |
|
|
|
]" |
|
|
|
:style="{ width: `${width}px` }" |
|
|
|
> |
|
|
|
<SidebarFixedButton |
|
|
|
v-if="!collapse && !isSidebarMixed && showFixedButton" |
|
|
|
v-model:expand-on-hover="expandOnHover" |
|
|
|
/> |
|
|
|
<div v-if="slots.logo" :style="headerStyle"> |
|
|
|
<slot name="logo"></slot> |
|
|
|
</div> |
|
|
|
<VbenScrollbar :style="contentStyle" shadow shadow-border> |
|
|
|
<slot></slot> |
|
|
|
</VbenScrollbar> |
|
|
|
|
|
|
|
<div :style="collapseStyle"></div> |
|
|
|
<SidebarCollapseButton |
|
|
|
v-if="showCollapseButton && !isSidebarMixed" |
|
|
|
v-model:collapsed="collapse" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<div |
|
|
|
v-if="isSidebarMixed" |
|
|
|
<Transition name="mobile-sidebar"> |
|
|
|
<aside |
|
|
|
v-if="!isMobile || !collapse" |
|
|
|
ref="asideRef" |
|
|
|
data-layout-region="sidebar" |
|
|
|
:style="style" |
|
|
|
class="fixed left-0 top-0 h-full" |
|
|
|
:class="[ |
|
|
|
themeSub, |
|
|
|
theme, |
|
|
|
{ |
|
|
|
'border-l': extraVisible, |
|
|
|
'border-r border-border bg-sidebar transition-[clip-path,transform] duration-300 ease-out': |
|
|
|
!isMobile && !isSidebarMixed, |
|
|
|
'transition-transform duration-300 ease-out': |
|
|
|
!isMobile && isSidebarMixed, |
|
|
|
}, |
|
|
|
]" |
|
|
|
:style="extraStyle" |
|
|
|
class="fixed top-0 h-full overflow-hidden border-r border-border bg-sidebar transition-all duration-200" |
|
|
|
@mouseenter="handleMouseenter" |
|
|
|
@mouseleave="handleMouseleave" |
|
|
|
> |
|
|
|
<SidebarCollapseButton |
|
|
|
v-if="isSidebarMixed && expandOnHover" |
|
|
|
v-model:collapsed="extraCollapse" |
|
|
|
/> |
|
|
|
|
|
|
|
<SidebarFixedButton |
|
|
|
v-if="!extraCollapse" |
|
|
|
v-model:expand-on-hover="expandOnHover" |
|
|
|
/> |
|
|
|
<div v-if="!extraCollapse" :style="extraTitleStyle" class="pl-2"> |
|
|
|
<slot name="extra-title"></slot> |
|
|
|
<div |
|
|
|
class="h-full" |
|
|
|
:class="[ |
|
|
|
{ |
|
|
|
'bg-sidebar-deep': isSidebarMixed, |
|
|
|
'border-r border-border bg-sidebar': !isSidebarMixed, |
|
|
|
}, |
|
|
|
]" |
|
|
|
:style="{ width: `${width}px` }" |
|
|
|
> |
|
|
|
<SidebarFixedButton |
|
|
|
v-if="!collapse && !isSidebarMixed && showFixedButton" |
|
|
|
v-model:expand-on-hover="expandOnHover" |
|
|
|
/> |
|
|
|
<div v-if="slots.logo" :style="headerStyle"> |
|
|
|
<slot name="logo"></slot> |
|
|
|
</div> |
|
|
|
<VbenScrollbar :style="contentStyle" shadow shadow-border> |
|
|
|
<slot></slot> |
|
|
|
</VbenScrollbar> |
|
|
|
|
|
|
|
<div :style="collapseStyle"></div> |
|
|
|
<SidebarCollapseButton |
|
|
|
v-if="showCollapseButton && !isSidebarMixed" |
|
|
|
v-model:collapsed="collapse" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<VbenScrollbar |
|
|
|
:style="extraContentStyle" |
|
|
|
class="border-border py-2" |
|
|
|
shadow |
|
|
|
shadow-border |
|
|
|
<div |
|
|
|
v-if="isSidebarMixed" |
|
|
|
:class="[ |
|
|
|
themeSub, |
|
|
|
{ |
|
|
|
'border-l': extraVisible, |
|
|
|
}, |
|
|
|
]" |
|
|
|
:style="extraStyle" |
|
|
|
class="fixed top-0 h-full overflow-hidden border-r border-border bg-sidebar transition-[left,width] duration-300 ease-out" |
|
|
|
> |
|
|
|
<slot name="extra"></slot> |
|
|
|
</VbenScrollbar> |
|
|
|
</div> |
|
|
|
<div |
|
|
|
v-if="draggable" |
|
|
|
ref="dragBarRef" |
|
|
|
class="absolute inset-y-0 -right-px z-1000 w-0.5 cursor-col-resize hover:bg-primary" |
|
|
|
@mousedown="handleDragSidebar" |
|
|
|
></div> |
|
|
|
</aside> |
|
|
|
<SidebarCollapseButton |
|
|
|
v-if="isSidebarMixed && expandOnHover" |
|
|
|
v-model:collapsed="extraCollapse" |
|
|
|
/> |
|
|
|
|
|
|
|
<SidebarFixedButton |
|
|
|
v-if="!extraCollapse" |
|
|
|
v-model:expand-on-hover="expandOnHover" |
|
|
|
/> |
|
|
|
<div v-if="!extraCollapse" :style="extraTitleStyle" class="pl-2"> |
|
|
|
<slot name="extra-title"></slot> |
|
|
|
</div> |
|
|
|
<VbenScrollbar |
|
|
|
:style="extraContentStyle" |
|
|
|
class="border-border py-2" |
|
|
|
shadow |
|
|
|
shadow-border |
|
|
|
> |
|
|
|
<slot name="extra"></slot> |
|
|
|
</VbenScrollbar> |
|
|
|
</div> |
|
|
|
<div |
|
|
|
v-if="draggable" |
|
|
|
ref="dragBarRef" |
|
|
|
:style="dragBarStyle" |
|
|
|
class="absolute inset-y-0 -right-px z-1000 w-0.5 cursor-col-resize hover:bg-primary" |
|
|
|
@mousedown="handleDragSidebar" |
|
|
|
></div> |
|
|
|
</aside> |
|
|
|
</Transition> |
|
|
|
</template> |
|
|
|
|
|
|
|
<style scoped> |
|
|
|
.mobile-sidebar-enter-active, |
|
|
|
.mobile-sidebar-leave-active { |
|
|
|
transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1); |
|
|
|
will-change: transform; |
|
|
|
} |
|
|
|
|
|
|
|
.mobile-sidebar-enter-from, |
|
|
|
.mobile-sidebar-leave-to { |
|
|
|
transform: translate3d(-100%, 0, 0); |
|
|
|
} |
|
|
|
|
|
|
|
@media (prefers-reduced-motion: reduce) { |
|
|
|
.mobile-sidebar-enter-active, |
|
|
|
.mobile-sidebar-leave-active { |
|
|
|
transition-duration: 0ms; |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |
|
|
|
|