committed by
GitHub
16 changed files with 368 additions and 13 deletions
@ -0,0 +1,48 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { computed, type HTMLAttributes } from 'vue'; |
||||
|
|
||||
|
import { GripVertical } from '@vben-core/icons'; |
||||
|
import { cn } from '@vben-core/shared/utils'; |
||||
|
|
||||
|
import { |
||||
|
SplitterResizeHandle, |
||||
|
type SplitterResizeHandleEmits, |
||||
|
type SplitterResizeHandleProps, |
||||
|
useForwardPropsEmits, |
||||
|
} from 'radix-vue'; |
||||
|
|
||||
|
const props = defineProps< |
||||
|
{ |
||||
|
class?: HTMLAttributes['class']; |
||||
|
withHandle?: boolean; |
||||
|
} & SplitterResizeHandleProps |
||||
|
>(); |
||||
|
const emits = defineEmits<SplitterResizeHandleEmits>(); |
||||
|
|
||||
|
const delegatedProps = computed(() => { |
||||
|
const { class: _, ...delegated } = props; |
||||
|
return delegated; |
||||
|
}); |
||||
|
|
||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<SplitterResizeHandle |
||||
|
v-bind="forwarded" |
||||
|
:class=" |
||||
|
cn( |
||||
|
'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-1 [&[data-orientation=vertical]>div]:rotate-90 [&[data-orientation=vertical]]:h-px [&[data-orientation=vertical]]:w-full [&[data-orientation=vertical]]:after:left-0 [&[data-orientation=vertical]]:after:h-1 [&[data-orientation=vertical]]:after:w-full [&[data-orientation=vertical]]:after:-translate-y-1/2 [&[data-orientation=vertical]]:after:translate-x-0', |
||||
|
props.class, |
||||
|
) |
||||
|
" |
||||
|
> |
||||
|
<template v-if="props.withHandle"> |
||||
|
<div |
||||
|
class="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-sm border" |
||||
|
> |
||||
|
<GripVertical class="h-2.5 w-2.5" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
</SplitterResizeHandle> |
||||
|
</template> |
||||
@ -0,0 +1,38 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { computed, type HTMLAttributes } from 'vue'; |
||||
|
|
||||
|
import { cn } from '@vben-core/shared/utils'; |
||||
|
|
||||
|
import { |
||||
|
SplitterGroup, |
||||
|
type SplitterGroupEmits, |
||||
|
type SplitterGroupProps, |
||||
|
useForwardPropsEmits, |
||||
|
} from 'radix-vue'; |
||||
|
|
||||
|
const props = defineProps< |
||||
|
{ class?: HTMLAttributes['class'] } & SplitterGroupProps |
||||
|
>(); |
||||
|
const emits = defineEmits<SplitterGroupEmits>(); |
||||
|
|
||||
|
const delegatedProps = computed(() => { |
||||
|
const { class: _, ...delegated } = props; |
||||
|
return delegated; |
||||
|
}); |
||||
|
|
||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<SplitterGroup |
||||
|
v-bind="forwarded" |
||||
|
:class=" |
||||
|
cn( |
||||
|
'flex h-full w-full data-[panel-group-direction=vertical]:flex-col', |
||||
|
props.class, |
||||
|
) |
||||
|
" |
||||
|
> |
||||
|
<slot></slot> |
||||
|
</SplitterGroup> |
||||
|
</template> |
||||
@ -0,0 +1,3 @@ |
|||||
|
export { default as ResizableHandle } from './ResizableHandle.vue'; |
||||
|
export { default as ResizablePanelGroup } from './ResizablePanelGroup.vue'; |
||||
|
export { SplitterPanel as ResizablePanel } from 'radix-vue'; |
||||
@ -0,0 +1,107 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import type { ColPageProps } from './types'; |
||||
|
|
||||
|
import { computed, ref, useSlots } from 'vue'; |
||||
|
|
||||
|
import { |
||||
|
ResizableHandle, |
||||
|
ResizablePanel, |
||||
|
ResizablePanelGroup, |
||||
|
} from '@vben-core/shadcn-ui'; |
||||
|
|
||||
|
import Page from '../page/page.vue'; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'ColPage', |
||||
|
inheritAttrs: false, |
||||
|
}); |
||||
|
|
||||
|
const props = withDefaults(defineProps<ColPageProps>(), { |
||||
|
leftWidth: 30, |
||||
|
rightWidth: 70, |
||||
|
resizable: true, |
||||
|
}); |
||||
|
|
||||
|
const delegatedProps = computed(() => { |
||||
|
const { leftWidth: _, ...delegated } = props; |
||||
|
return delegated; |
||||
|
}); |
||||
|
|
||||
|
const slots = useSlots(); |
||||
|
|
||||
|
const delegatedSlots = computed(() => { |
||||
|
const resultSlots: string[] = []; |
||||
|
|
||||
|
for (const key of Object.keys(slots)) { |
||||
|
if (!['default', 'left'].includes(key)) { |
||||
|
resultSlots.push(key); |
||||
|
} |
||||
|
} |
||||
|
return resultSlots; |
||||
|
}); |
||||
|
|
||||
|
const leftPanelRef = ref<InstanceType<typeof ResizablePanel>>(); |
||||
|
|
||||
|
function expandLeft() { |
||||
|
leftPanelRef.value?.expand(); |
||||
|
} |
||||
|
|
||||
|
function collapseLeft() { |
||||
|
leftPanelRef.value?.collapse(); |
||||
|
} |
||||
|
|
||||
|
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> |
||||
|
|
||||
|
<ResizablePanelGroup class="w-full" direction="horizontal"> |
||||
|
<ResizablePanel |
||||
|
ref="leftPanelRef" |
||||
|
:collapsed-size="leftCollapsedWidth" |
||||
|
:collapsible="leftCollapsible" |
||||
|
:default-size="leftWidth" |
||||
|
:max-size="leftMaxWidth" |
||||
|
:min-size="leftMinWidth" |
||||
|
> |
||||
|
<template #default="slotProps"> |
||||
|
<slot |
||||
|
name="left" |
||||
|
v-bind="{ |
||||
|
...slotProps, |
||||
|
expand: expandLeft, |
||||
|
collapse: collapseLeft, |
||||
|
}" |
||||
|
></slot> |
||||
|
</template> |
||||
|
</ResizablePanel> |
||||
|
<ResizableHandle |
||||
|
v-if="resizable" |
||||
|
:style="{ backgroundColor: splitLine ? undefined : 'transparent' }" |
||||
|
:with-handle="splitHandle" |
||||
|
/> |
||||
|
<ResizablePanel |
||||
|
:collapsed-size="rightCollapsedWidth" |
||||
|
:collapsible="rightCollapsible" |
||||
|
:default-size="rightWidth" |
||||
|
:max-size="rightMaxWidth" |
||||
|
:min-size="rightMinWidth" |
||||
|
> |
||||
|
<template #default> |
||||
|
<slot></slot> |
||||
|
</template> |
||||
|
</ResizablePanel> |
||||
|
</ResizablePanelGroup> |
||||
|
</Page> |
||||
|
</template> |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as ColPage } from './col-page.vue'; |
||||
|
export * from './types'; |
||||
@ -0,0 +1,26 @@ |
|||||
|
import type { PageProps } from '../page/types'; |
||||
|
|
||||
|
export interface ColPageProps extends PageProps { |
||||
|
/** |
||||
|
* 左侧宽度 |
||||
|
* @default 30 |
||||
|
*/ |
||||
|
leftWidth?: number; |
||||
|
leftMinWidth?: number; |
||||
|
leftMaxWidth?: number; |
||||
|
leftCollapsedWidth?: number; |
||||
|
leftCollapsible?: boolean; |
||||
|
/** |
||||
|
* 右侧宽度 |
||||
|
* @default 70 |
||||
|
*/ |
||||
|
rightWidth?: number; |
||||
|
rightMinWidth?: number; |
||||
|
rightCollapsedWidth?: number; |
||||
|
rightMaxWidth?: number; |
||||
|
rightCollapsible?: boolean; |
||||
|
|
||||
|
resizable?: boolean; |
||||
|
splitLine?: boolean; |
||||
|
splitHandle?: boolean; |
||||
|
} |
||||
@ -1 +1,2 @@ |
|||||
export { default as Page } from './page.vue'; |
export { default as Page } from './page.vue'; |
||||
|
export * from './types'; |
||||
|
|||||
@ -0,0 +1,11 @@ |
|||||
|
export interface PageProps { |
||||
|
title?: string; |
||||
|
description?: string; |
||||
|
contentClass?: string; |
||||
|
/** |
||||
|
* 根据content可见高度自适应 |
||||
|
*/ |
||||
|
autoContentHeight?: boolean; |
||||
|
headerClass?: string; |
||||
|
footerClass?: string; |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
<script lang="ts" setup> |
||||
|
import { reactive, ref } from 'vue'; |
||||
|
|
||||
|
import { ColPage } from '@vben/common-ui'; |
||||
|
import { IconifyIcon } from '@vben/icons'; |
||||
|
|
||||
|
import { |
||||
|
Alert, |
||||
|
Button, |
||||
|
Card, |
||||
|
Checkbox, |
||||
|
Slider, |
||||
|
Tag, |
||||
|
Tooltip, |
||||
|
} from 'ant-design-vue'; |
||||
|
|
||||
|
const props = reactive({ |
||||
|
leftCollapsedWidth: 5, |
||||
|
leftCollapsible: true, |
||||
|
leftMaxWidth: 50, |
||||
|
leftMinWidth: 20, |
||||
|
leftWidth: 30, |
||||
|
resizable: true, |
||||
|
rightWidth: 70, |
||||
|
splitHandle: false, |
||||
|
splitLine: false, |
||||
|
}); |
||||
|
const leftMinWidth = ref(props.leftMinWidth || 1); |
||||
|
const leftMaxWidth = ref(props.leftMaxWidth || 100); |
||||
|
</script> |
||||
|
<template> |
||||
|
<ColPage |
||||
|
auto-content-height |
||||
|
description="ColPage 是一个双列布局组件,支持左侧折叠、拖拽调整宽度等功能。" |
||||
|
v-bind="props" |
||||
|
title="ColPage 双列布局组件" |
||||
|
> |
||||
|
<template #title> |
||||
|
<span class="mr-2 text-2xl font-bold">ColPage 双列布局组件</span> |
||||
|
<Tag color="hsl(var(--destructive))">Alpha</Tag> |
||||
|
</template> |
||||
|
<template #left="{ isCollapsed, expand }"> |
||||
|
<div v-if="isCollapsed" @click="expand"> |
||||
|
<Tooltip title="点击展开左侧"> |
||||
|
<Button shape="circle" type="primary"> |
||||
|
<template #icon> |
||||
|
<IconifyIcon class="text-2xl" icon="bi:arrow-right" /> |
||||
|
</template> |
||||
|
</Button> |
||||
|
</Tooltip> |
||||
|
</div> |
||||
|
<div |
||||
|
v-else |
||||
|
:style="{ minWidth: '200px' }" |
||||
|
class="border-border bg-card mr-2 rounded-[var(--radius)] border p-2" |
||||
|
> |
||||
|
<p>这里是左侧内容</p> |
||||
|
<p>这里是左侧内容</p> |
||||
|
<p>这里是左侧内容</p> |
||||
|
<p>这里是左侧内容</p> |
||||
|
<p>这里是左侧内容</p> |
||||
|
</div> |
||||
|
</template> |
||||
|
<Card class="ml-2" title="基本使用"> |
||||
|
<div class="flex flex-col gap-2"> |
||||
|
<div class="flex gap-2"> |
||||
|
<Checkbox v-model:checked="props.resizable">可拖动调整宽度</Checkbox> |
||||
|
<Checkbox v-model:checked="props.splitLine">显示拖动分隔线</Checkbox> |
||||
|
<Checkbox v-model:checked="props.splitHandle">显示拖动手柄</Checkbox> |
||||
|
<Checkbox v-model:checked="props.leftCollapsible"> |
||||
|
左侧可折叠 |
||||
|
</Checkbox> |
||||
|
</div> |
||||
|
<div class="flex items-center gap-2"> |
||||
|
<span>左侧最小宽度百分比:</span> |
||||
|
<Slider |
||||
|
v-model:value="leftMinWidth" |
||||
|
:max="props.leftMaxWidth - 1" |
||||
|
:min="1" |
||||
|
style="width: 100px" |
||||
|
@after-change="(value) => (props.leftMinWidth = value as number)" |
||||
|
/> |
||||
|
<span>左侧最大宽度百分比:</span> |
||||
|
<Slider |
||||
|
v-model:value="props.leftMaxWidth" |
||||
|
:max="100" |
||||
|
:min="leftMaxWidth + 1" |
||||
|
style="width: 100px" |
||||
|
@after-change="(value) => (props.leftMaxWidth = value as number)" |
||||
|
/> |
||||
|
</div> |
||||
|
<Alert message="实验性的组件" show-icon type="warning"> |
||||
|
<template #description> |
||||
|
<p> |
||||
|
双列布局组件是一个在Page组件上扩展的相对基础的布局组件,支持左侧折叠(当拖拽导致左侧宽度比最小宽度还要小时,还可以进入折叠状态)、拖拽调整宽度等功能。 |
||||
|
</p> |
||||
|
<p>以上宽度设置的数值是百分比,最小值为1,最大值为100。</p> |
||||
|
<p class="font-bold text-red-600"> |
||||
|
这是一个实验性的组件,用法可能会发生变动,也可能最终不会被采用。在其用法正式出现在文档中之前,不建议在生产环境中使用。 |
||||
|
</p> |
||||
|
</template> |
||||
|
</Alert> |
||||
|
</div> |
||||
|
</Card> |
||||
|
</ColPage> |
||||
|
</template> |
||||
Loading…
Reference in new issue