diff --git a/packages/@core/composables/src/use-layout-style.ts b/packages/@core/composables/src/use-layout-style.ts index 395e9e5a3..847178d06 100644 --- a/packages/@core/composables/src/use-layout-style.ts +++ b/packages/@core/composables/src/use-layout-style.ts @@ -2,61 +2,26 @@ import type { CSSProperties } from 'vue'; import type { VisibleDomRect } from '@vben-core/shared/utils'; -import { computed, onMounted, onUnmounted, ref } from 'vue'; +import { computed, ref } from 'vue'; import { - CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT, - CSS_VARIABLE_LAYOUT_CONTENT_WIDTH, CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT, CSS_VARIABLE_LAYOUT_HEADER_HEIGHT, } from '@vben-core/shared/constants'; -import { getElementVisibleRect } from '@vben-core/shared/utils'; -import { useCssVar, useDebounceFn } from '@vueuse/core'; +import { useCssVar } from '@vueuse/core'; /** * @zh_CN content style */ export function useLayoutContentStyle() { - let resizeObserver: null | ResizeObserver = null; const contentElement = ref(null); const visibleDomRect = ref(null); - const contentHeight = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT); - const contentWidth = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_WIDTH); - const overlayStyle = computed((): CSSProperties => { - const { height, left, top, width } = visibleDomRect.value ?? {}; - return { - height: `${height}px`, - left: `${left}px`, - position: 'fixed', - top: `${top}px`, - width: `${width}px`, - zIndex: 150, - }; - }); - - const debouncedCalcHeight = useDebounceFn( - (_entries: ResizeObserverEntry[]) => { - visibleDomRect.value = getElementVisibleRect(contentElement.value); - contentHeight.value = `${visibleDomRect.value.height}px`; - contentWidth.value = `${visibleDomRect.value.width}px`; - }, - 16, + const overlayStyle = computed( + (): CSSProperties => ({ inset: 0, position: 'absolute', zIndex: 150 }), ); - onMounted(() => { - if (contentElement.value && !resizeObserver) { - resizeObserver = new ResizeObserver(debouncedCalcHeight); - resizeObserver.observe(contentElement.value); - } - }); - - onUnmounted(() => { - resizeObserver?.disconnect(); - resizeObserver = null; - }); - return { contentElement, overlayStyle, visibleDomRect }; } diff --git a/packages/@core/ui-kit/layout-ui/src/components/layout-content.vue b/packages/@core/ui-kit/layout-ui/src/components/layout-content.vue index 26fbfd399..3a04b71a6 100644 --- a/packages/@core/ui-kit/layout-ui/src/components/layout-content.vue +++ b/packages/@core/ui-kit/layout-ui/src/components/layout-content.vue @@ -5,9 +5,6 @@ import type { ContentCompactType } from '@vben-core/typings'; import { computed } from 'vue'; -import { useLayoutContentStyle } from '@vben-core/composables'; -import { Slot } from '@vben-core/shadcn-ui'; - interface Props { /** * 内容区域定宽 @@ -26,8 +23,10 @@ interface Props { const props = withDefaults(defineProps(), {}); -// @ts-expect-error - unused -const { contentElement, overlayStyle } = useLayoutContentStyle(); +const overlayViewportStyle: CSSProperties = { + height: + 'calc(100dvh - var(--vben-header-height, 0px) - var(--vben-footer-height, 0px))', +}; const style = computed((): CSSProperties => { const { @@ -46,6 +45,8 @@ const style = computed((): CSSProperties => { return { ...compactStyle, flex: 1, + minHeight: 0, + minWidth: 0, padding: `${padding}px`, paddingBottom: `${paddingBottom}px`, paddingLeft: `${paddingLeft}px`, @@ -56,10 +57,19 @@ const style = computed((): CSSProperties => { 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 fd036c1c9..189683c09 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 @@ -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(), { 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(null); const dragBarRef = shallowRef(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" > - + + + diff --git a/packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue b/packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue index 148600e13..b50cf97e1 100644 --- a/packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue +++ b/packages/@core/ui-kit/layout-ui/src/components/layout-tabbar.vue @@ -23,7 +23,7 @@ const style = computed((): CSSProperties => { + + diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue index 2bc50353f..71e0d053d 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue @@ -65,7 +65,8 @@ function onTransitionEnd() { cn( 'flex-center bg-overlay-content absolute top-0 left-0 z-100 size-full backdrop-blur-xs transition-all duration-500', { - 'invisible opacity-0': !showSpinner, + 'invisible pointer-events-none opacity-0': !showSpinner, + 'pointer-events-auto': showSpinner, }, props.class, )