|
|
@@ -0,0 +1,135 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import type { LayoutCardProps } from './types';
|
|
|
+
|
|
|
+import { computed, ref, useSlots } from 'vue';
|
|
|
+
|
|
|
+import {
|
|
|
+ ResizableHandle,
|
|
|
+ ResizablePanel,
|
|
|
+ ResizablePanelGroup,
|
|
|
+ VbenScrollbar,
|
|
|
+} from '@vben-core/shadcn-ui';
|
|
|
+import { cn } from '@vben-core/shared/utils';
|
|
|
+
|
|
|
+import Page from '../page/page.vue';
|
|
|
+
|
|
|
+defineOptions({
|
|
|
+ name: 'LayoutCard',
|
|
|
+ inheritAttrs: false,
|
|
|
+});
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<LayoutCardProps>(), {
|
|
|
+ defaultCollapsed: false,
|
|
|
+ defaultLayout: () => [20, 60, 20],
|
|
|
+});
|
|
|
+
|
|
|
+const slots = useSlots();
|
|
|
+
|
|
|
+const delegatedProps = computed(() => {
|
|
|
+ const { defaultLayout: _, ...delegated } = props;
|
|
|
+ return delegated;
|
|
|
+});
|
|
|
+
|
|
|
+const delegatedSlots = computed(() => {
|
|
|
+ const resultSlots: string[] = [];
|
|
|
+
|
|
|
+ for (const key of Object.keys(slots)) {
|
|
|
+ if (!['default', 'left', 'right'].includes(key)) {
|
|
|
+ resultSlots.push(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return resultSlots;
|
|
|
+});
|
|
|
+
|
|
|
+const leftPanelRef = ref<InstanceType<typeof ResizablePanel>>();
|
|
|
+
|
|
|
+function expandLeft() {
|
|
|
+ leftPanelRef.value?.expand();
|
|
|
+}
|
|
|
+
|
|
|
+function collapseLeft() {
|
|
|
+ leftPanelRef.value?.collapse();
|
|
|
+}
|
|
|
+
|
|
|
+const isCollapsed = ref(props.defaultCollapsed);
|
|
|
+const onExpand = () => {
|
|
|
+ isCollapsed.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+const onCollapse = () => {
|
|
|
+ isCollapsed.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ expandLeft,
|
|
|
+ collapseLeft,
|
|
|
+});
|
|
|
+</script>
|
|
|
+<template>
|
|
|
+ <Page v-bind="delegatedProps">
|
|
|
+ <!-- 继承默认的slot -->
|
|
|
+ <template
|
|
|
+ v-for="slotName in delegatedSlots"
|
|
|
+ :key="slotName"
|
|
|
+ #[slotName]="slotProps"
|
|
|
+ >
|
|
|
+ <slot :name="slotName" v-bind="slotProps"></slot>
|
|
|
+ </template>
|
|
|
+ <div
|
|
|
+ class="bg-background h-full overflow-hidden rounded-[0.5rem] border shadow"
|
|
|
+ >
|
|
|
+ <ResizablePanelGroup direction="horizontal" class="h-full items-stretch">
|
|
|
+ <ResizablePanel
|
|
|
+ ref="leftPanelRef"
|
|
|
+ :default-size="defaultLayout[0]"
|
|
|
+ :collapsed-size="leftCollapsedWidth"
|
|
|
+ collapsible
|
|
|
+ :min-size="15"
|
|
|
+ :max-size="defaultLayout[0]"
|
|
|
+ :class="
|
|
|
+ cn(
|
|
|
+ isCollapsed &&
|
|
|
+ 'min-w-[50px] transition-all duration-300 ease-in-out',
|
|
|
+ )
|
|
|
+ "
|
|
|
+ @expand="onExpand"
|
|
|
+ @collapse="onCollapse"
|
|
|
+ v-if="$slots.left"
|
|
|
+ >
|
|
|
+ <VbenScrollbar class="h-full">
|
|
|
+ <template #default="slotProps">
|
|
|
+ <slot
|
|
|
+ name="left"
|
|
|
+ v-bind="{
|
|
|
+ ...slotProps,
|
|
|
+ isCollapsed,
|
|
|
+ expand: expandLeft,
|
|
|
+ collapse: collapseLeft,
|
|
|
+ }"
|
|
|
+ ></slot>
|
|
|
+ </template>
|
|
|
+ </VbenScrollbar>
|
|
|
+ </ResizablePanel>
|
|
|
+ <ResizableHandle with-handle v-if="$slots.left" />
|
|
|
+ <ResizablePanel :default-size="defaultLayout[1]" :min-size="30">
|
|
|
+ <template #default>
|
|
|
+ <slot></slot>
|
|
|
+ </template>
|
|
|
+ </ResizablePanel>
|
|
|
+ <ResizableHandle with-handle v-if="$slots.right" />
|
|
|
+ <ResizablePanel :default-size="defaultLayout[0]" v-if="$slots.right">
|
|
|
+ <VbenScrollbar class="h-full">
|
|
|
+ <template #default="slotProps">
|
|
|
+ <slot
|
|
|
+ name="right"
|
|
|
+ v-bind="{
|
|
|
+ ...slotProps,
|
|
|
+ }"
|
|
|
+ ></slot>
|
|
|
+ </template>
|
|
|
+ </VbenScrollbar>
|
|
|
+ </ResizablePanel>
|
|
|
+ </ResizablePanelGroup>
|
|
|
+ </div>
|
|
|
+ </Page>
|
|
|
+</template>
|