feat: add collapsible 组件,form表单增加单项可折叠,支持schema配置默认关闭/开启

feat: add collapsible 组件,form表单增加单项可折叠,支持schema配置默认关闭/开启
- shadcn-ui 增加 collapsible组件,collapsible-params组件
- form新增支持单项折叠
- collapsible-params组件在Form表单应用
This commit is contained in:
allen
2026-04-13 19:20:01 +08:00
parent 2a32715c99
commit 6f18718c87
18 changed files with 929 additions and 68 deletions

View File

@@ -30,10 +30,6 @@ const submitButtonOptions = computed(() => {
};
});
// const isQueryForm = computed(() => {
// return !!unref(rootProps).showCollapseButton;
// });
async function handleSubmit(e: Event) {
e?.preventDefault();
e?.stopPropagation();

View File

@@ -7,15 +7,24 @@ import type {
MaybeComponentProps,
} from '../types';
import { computed, nextTick, onUnmounted, useTemplateRef, watch } from 'vue';
import { CircleAlert } from '@vben-core/icons';
import {
computed,
nextTick,
onUnmounted,
ref,
useTemplateRef,
watch,
} from 'vue';
import { ChevronsDown, CircleAlert } from '@vben-core/icons';
import {
Button,
FormControl,
FormDescription,
FormField,
FormItem,
FormMessage,
VbenCollapsible,
VbenRenderContent,
VbenTooltip,
} from '@vben-core/shadcn-ui';
@@ -53,6 +62,8 @@ const {
renderComponentContent,
rules,
help,
collapsible,
defaultCollapsed = false,
} = defineProps<
Props & {
commonComponentProps: MaybeComponentProps;
@@ -67,6 +78,7 @@ const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');
const formApi = formRenderProps.form;
const compact = computed(() => formRenderProps.compact);
const isInValid = computed(() => errors.value?.length > 0);
const collapseOpen = ref(!defaultCollapsed);
function getFormApi(): FormActions {
if (!formApi) {
@@ -296,6 +308,15 @@ function autofocus() {
fieldComponentRef.value?.focus?.();
}
}
const shouldCollapsible = computed(() => {
return collapsible; /* && isVertical.value; */
});
function toggleCollapsed() {
collapseOpen.value = !collapseOpen.value;
}
const componentRefMap = injectComponentRefMap();
watch(fieldComponentRef, (componentRef) => {
componentRefMap?.set(fieldName, componentRef);
@@ -335,6 +356,7 @@ onUnmounted(() => {
{
'mr-2 shrink-0 justify-end': !isVertical,
'mb-1 flex-row': isVertical,
'self-start': shouldCollapsible && !isVertical,
},
labelClass,
)
@@ -348,65 +370,87 @@ onUnmounted(() => {
<template v-if="label">
<VbenRenderContent :content="label" />
</template>
<template #extra>
<Button
class="ml-0.5"
variant="icon"
size="icon"
@click.prevent="toggleCollapsed"
v-if="shouldCollapsible"
>
<ChevronsDown
:size="16"
class="transition-transform"
:class="{
'rotate-180': !collapseOpen,
}"
/>
</Button>
</template>
</FormLabel>
<div class="flex-auto overflow-hidden p-px">
<div :class="cn('relative flex w-full items-center', wrapperClass)">
<FormControl :class="cn(controlClass)">
<slot
v-bind="{
...slotProps,
...createComponentProps(slotProps),
disabled: shouldDisabled,
isInValid,
}"
>
<component
:is="FieldComponent"
ref="fieldComponentRef"
:class="{
'border-destructive hover:border-destructive/80 focus:border-destructive focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
<VbenCollapsible :show-trigger="false" v-model:open="collapseOpen">
<template #collapsibleContent>
<div :class="cn('relative flex w-full items-center', wrapperClass)">
<FormControl :class="cn(controlClass)">
<slot
v-bind="{
...slotProps,
...createComponentProps(slotProps),
disabled: shouldDisabled,
isInValid,
}"
v-bind="createComponentProps(slotProps)"
:disabled="shouldDisabled"
>
<template
v-for="name in renderContentKey"
:key="name"
#[name]="renderSlotProps"
}"
>
<VbenRenderContent
:content="customContentRender[name]"
v-bind="{ ...renderSlotProps, formContext: slotProps }"
/>
</template>
<!-- <slot></slot> -->
</component>
<VbenTooltip
v-if="compact && isInValid"
:delay-duration="300"
side="left"
>
<template #trigger>
<slot name="trigger">
<CircleAlert
:class="
cn(
'inline-flex size-5 cursor-pointer text-foreground/80 hover:text-foreground',
)
"
/>
</slot>
</template>
<FormMessage />
</VbenTooltip>
</slot>
</FormControl>
<!-- 自定义后缀 -->
<div v-if="suffix" class="ml-1">
<VbenRenderContent :content="suffix" />
</div>
</div>
<component
:is="FieldComponent"
ref="fieldComponentRef"
:class="{
'border-destructive hover:border-destructive/80 focus:border-destructive focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
isInValid,
}"
v-bind="createComponentProps(slotProps)"
:disabled="shouldDisabled"
>
<template
v-for="name in renderContentKey"
:key="name"
#[name]="renderSlotProps"
>
<VbenRenderContent
:content="customContentRender[name]"
v-bind="{ ...renderSlotProps, formContext: slotProps }"
/>
</template>
<!-- <slot></slot> -->
</component>
<VbenTooltip
v-if="compact && isInValid"
:delay-duration="300"
side="left"
>
<template #trigger>
<slot name="trigger">
<CircleAlert
:class="
cn(
'inline-flex size-5 cursor-pointer text-foreground/80 hover:text-foreground',
)
"
/>
</slot>
</template>
<FormMessage />
</VbenTooltip>
</slot>
</FormControl>
<!-- 自定义后缀 -->
<div v-if="suffix" class="ml-1">
<VbenRenderContent :content="suffix" />
</div>
</div>
</template>
</VbenCollapsible>
<FormDescription v-if="description" class="text-xs">
<VbenRenderContent :content="description" />
</FormDescription>

View File

@@ -26,6 +26,7 @@ const props = defineProps<Props>();
<VbenHelpTooltip v-if="help" trigger-class="size-3.5 ml-1">
<VbenRenderContent :content="help" />
</VbenHelpTooltip>
<slot name="extra"></slot>
<span v-if="colon && label" class="ml-0.5">:</span>
</FormLabel>
</template>

View File

@@ -145,6 +145,10 @@ type ComponentProps =
| MaybeComponentProps;
export interface FormCommonConfig {
/**
* 是否可折叠的
*/
collapsible?: boolean;
/**
* 在Label后显示一个冒号
*/
@@ -157,6 +161,11 @@ export interface FormCommonConfig {
* 所有表单项的控件样式
*/
controlClass?: string;
/**
* 默认折叠
* @default false
*/
defaultCollapsed?: boolean;
/**
* 所有表单项的禁用状态
* @default false