feat: add collapsible 组件,form表单增加单项可折叠,支持schema配置默认关闭/开启
feat: add collapsible 组件,form表单增加单项可折叠,支持schema配置默认关闭/开启 - shadcn-ui 增加 collapsible组件,collapsible-params组件 - form新增支持单项折叠 - collapsible-params组件在Form表单应用pull/348/MERGE
parent
2a32715c99
commit
6f18718c87
|
|
@ -26,6 +26,7 @@
|
||||||
"#/*": "./src/*"
|
"#/*": "./src/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vben-core/shadcn-ui": "workspace:*",
|
||||||
"@vben/access": "workspace:*",
|
"@vben/access": "workspace:*",
|
||||||
"@vben/common-ui": "workspace:*",
|
"@vben/common-ui": "workspace:*",
|
||||||
"@vben/constants": "workspace:*",
|
"@vben/constants": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,19 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import type { CollapsibleParamSchema } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
import { NButton, NCard, useMessage } from 'naive-ui';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal, z } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { VbenCollapsibleParams } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NRadioButton,
|
||||||
|
NRadioGroup,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { getAllMenusApi } from '#/api';
|
import { getAllMenusApi } from '#/api';
|
||||||
|
|
@ -9,6 +21,111 @@ import { getAllMenusApi } from '#/api';
|
||||||
import modalDemo from './modal.vue';
|
import modalDemo from './modal.vue';
|
||||||
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
|
||||||
|
const layouts = [
|
||||||
|
{ label: 'Vertical', value: 'vertical' },
|
||||||
|
{ label: 'Horizontal', value: 'horizontal' },
|
||||||
|
{ label: 'Inline', value: 'inline' },
|
||||||
|
];
|
||||||
|
const layout = ref(layouts[0].value);
|
||||||
|
|
||||||
|
function getNumberValidator(key: string, limit?: [number, number]) {
|
||||||
|
const validator = z.number({
|
||||||
|
required_error: `${key} 值不能为空`,
|
||||||
|
invalid_type_error: `${key} 值只能为数字`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (limit) {
|
||||||
|
validator.min(limit[0], { message: `${key} 值不在区间范围内` });
|
||||||
|
validator.max(limit[1], { message: `${key} 值不在区间范围内` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsSchema = [
|
||||||
|
{
|
||||||
|
key: 'micro_batch_size',
|
||||||
|
description: `批次大小,代表模型训练过程中,模型更新模型参数的数据步长,可理解为模型每看多少数据即更新一次模型参数,
|
||||||
|
一般建议的批次大小为16/32,表示模型每看16或32条数据即更新一次参数`,
|
||||||
|
// defaultValue: 8,
|
||||||
|
option: {
|
||||||
|
min: 8,
|
||||||
|
max: 1024,
|
||||||
|
step: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'learning_rate',
|
||||||
|
description:
|
||||||
|
'学习率,代表每次更新数据的增量参数权重,学习率数值越大参数变化越大,对模型影响越大',
|
||||||
|
// defaultValue: 1e-5,
|
||||||
|
option: {
|
||||||
|
step: 1e-4,
|
||||||
|
type: 'exponential',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'eval_steps',
|
||||||
|
description:
|
||||||
|
'验证步数,训练阶段针模型的验证间隔步长,用于阶段性评估模型训练准确率、训练损失',
|
||||||
|
// defaultValue: 50,
|
||||||
|
option: {
|
||||||
|
min: 1,
|
||||||
|
max: 2_147_483_647,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'num_train_epochs',
|
||||||
|
description:
|
||||||
|
'循环次数,代表模型训练过程中模型学习数据集的次数,可理解为看几遍数据,一般建议的范围是1-3遍即可,可依据需求进行调整',
|
||||||
|
// defaultValue: 3,
|
||||||
|
option: {
|
||||||
|
min: 1,
|
||||||
|
max: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'max_length',
|
||||||
|
description: `序列长度,单个训练数据样本的最大长度,超出配置长度将丢弃`,
|
||||||
|
// defaultValue: 32_768,
|
||||||
|
option: {
|
||||||
|
min: 500,
|
||||||
|
max: 131_072,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'warmup_ratio',
|
||||||
|
description: '学习率预热比例,学习率预热阶段占总训练步数的比例',
|
||||||
|
// defaultValue: 0.05,
|
||||||
|
option: {
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
precision: 2,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'save_steps',
|
||||||
|
description: 'Checkpoint保存间隔',
|
||||||
|
// defaultValue: 50,
|
||||||
|
option: {
|
||||||
|
min: 1,
|
||||||
|
max: 2_147_483_647,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as CollapsibleParamSchema[];
|
||||||
|
|
||||||
|
const paramsValidator = z.object({
|
||||||
|
micro_batch_size: getNumberValidator('micro_batch_size', [8, 1024]),
|
||||||
|
learning_rate: getNumberValidator('learning_rate'),
|
||||||
|
eval_steps: getNumberValidator('eval_steps', [1, 2_147_483_647]),
|
||||||
|
num_train_epochs: getNumberValidator('num_train_epochs', [1, 200]),
|
||||||
|
max_length: getNumberValidator('max_length', [1, 131_072]),
|
||||||
|
warmup_ratio: getNumberValidator('warmup_ratio', [0, 1]),
|
||||||
|
save_steps: getNumberValidator('save_steps', [1, 2_147_483_647]),
|
||||||
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
// 所有表单项
|
// 所有表单项
|
||||||
|
|
@ -16,7 +133,7 @@ const [Form, formApi] = useVbenForm({
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
layout: 'horizontal',
|
layout: 'vertical',
|
||||||
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
handleSubmit: (values) => {
|
handleSubmit: (values) => {
|
||||||
|
|
@ -133,8 +250,29 @@ const [Form, formApi] = useVbenForm({
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'collapsibleTextArea',
|
||||||
|
label: 'vertical时可折叠',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
collapsible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: VbenCollapsibleParams,
|
||||||
|
componentProps: {
|
||||||
|
params: paramsSchema,
|
||||||
|
},
|
||||||
|
modelPropName: 'value',
|
||||||
|
fieldName: 'params',
|
||||||
|
label: '参数配置',
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
rules: paramsValidator,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
function setFormValues() {
|
function setFormValues() {
|
||||||
formApi.setValues({
|
formApi.setValues({
|
||||||
string: 'string',
|
string: 'string',
|
||||||
|
|
@ -143,20 +281,43 @@ function setFormValues() {
|
||||||
radioButton: 'C',
|
radioButton: 'C',
|
||||||
checkbox: ['A', 'C'],
|
checkbox: ['A', 'C'],
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
|
params: {
|
||||||
|
micro_batch_size: 8,
|
||||||
|
learning_rate: 1e-5,
|
||||||
|
eval_steps: 50,
|
||||||
|
num_train_epochs: 3,
|
||||||
|
max_length: 32_768,
|
||||||
|
warmup_ratio: 0.05,
|
||||||
|
save_steps: 50,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
connectedComponent: modalDemo,
|
connectedComponent: modalDemo,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function onLayoutChange(layout: string) {
|
||||||
|
formApi.setState({
|
||||||
|
layout,
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Page
|
<Page
|
||||||
description="表单适配器重新包装了CheckboxGroup和RadioGroup,可以通过options属性传递选项数据(选项数据将作为子组件的属性)"
|
description="表单适配器重新包装了CheckboxGroup和RadioGroup,可以通过options属性传递选项数据(选项数据将作为子组件的属性)"
|
||||||
title="表单演示"
|
title="表单演示"
|
||||||
>
|
>
|
||||||
<NCard title="基础表单">
|
<NCard title="基础表单" header-extra-class="gap-4">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
|
<NRadioGroup v-model:value="layout" @update:value="onLayoutChange">
|
||||||
|
<NRadioButton
|
||||||
|
v-for="layoutItem in layouts"
|
||||||
|
:key="layoutItem.value"
|
||||||
|
:value="layoutItem.value"
|
||||||
|
:label="layoutItem.label"
|
||||||
|
/>
|
||||||
|
</NRadioGroup>
|
||||||
<NButton type="primary" @click="setFormValues">设置表单值</NButton>
|
<NButton type="primary" @click="setFormValues">设置表单值</NButton>
|
||||||
<NButton type="primary" @click="modalApi.open()" class="ml-2">
|
<NButton type="primary" @click="modalApi.open()" class="ml-2">
|
||||||
打开弹窗
|
打开弹窗
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
ChevronsDown,
|
||||||
ChevronsLeft,
|
ChevronsLeft,
|
||||||
ChevronsRight,
|
ChevronsRight,
|
||||||
Circle,
|
Circle,
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,6 @@ const submitButtonOptions = computed(() => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// const isQueryForm = computed(() => {
|
|
||||||
// return !!unref(rootProps).showCollapseButton;
|
|
||||||
// });
|
|
||||||
|
|
||||||
async function handleSubmit(e: Event) {
|
async function handleSubmit(e: Event) {
|
||||||
e?.preventDefault();
|
e?.preventDefault();
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,24 @@ import type {
|
||||||
MaybeComponentProps,
|
MaybeComponentProps,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
import { computed, nextTick, onUnmounted, useTemplateRef, watch } from 'vue';
|
|
||||||
|
|
||||||
import { CircleAlert } from '@vben-core/icons';
|
|
||||||
import {
|
import {
|
||||||
|
computed,
|
||||||
|
nextTick,
|
||||||
|
onUnmounted,
|
||||||
|
ref,
|
||||||
|
useTemplateRef,
|
||||||
|
watch,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
|
import { ChevronsDown, CircleAlert } from '@vben-core/icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
|
VbenCollapsible,
|
||||||
VbenRenderContent,
|
VbenRenderContent,
|
||||||
VbenTooltip,
|
VbenTooltip,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
|
@ -53,6 +62,8 @@ const {
|
||||||
renderComponentContent,
|
renderComponentContent,
|
||||||
rules,
|
rules,
|
||||||
help,
|
help,
|
||||||
|
collapsible,
|
||||||
|
defaultCollapsed = false,
|
||||||
} = defineProps<
|
} = defineProps<
|
||||||
Props & {
|
Props & {
|
||||||
commonComponentProps: MaybeComponentProps;
|
commonComponentProps: MaybeComponentProps;
|
||||||
|
|
@ -67,6 +78,7 @@ const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');
|
||||||
const formApi = formRenderProps.form;
|
const formApi = formRenderProps.form;
|
||||||
const compact = computed(() => formRenderProps.compact);
|
const compact = computed(() => formRenderProps.compact);
|
||||||
const isInValid = computed(() => errors.value?.length > 0);
|
const isInValid = computed(() => errors.value?.length > 0);
|
||||||
|
const collapseOpen = ref(!defaultCollapsed);
|
||||||
|
|
||||||
function getFormApi(): FormActions {
|
function getFormApi(): FormActions {
|
||||||
if (!formApi) {
|
if (!formApi) {
|
||||||
|
|
@ -296,6 +308,15 @@ function autofocus() {
|
||||||
fieldComponentRef.value?.focus?.();
|
fieldComponentRef.value?.focus?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldCollapsible = computed(() => {
|
||||||
|
return collapsible; /* && isVertical.value; */
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleCollapsed() {
|
||||||
|
collapseOpen.value = !collapseOpen.value;
|
||||||
|
}
|
||||||
|
|
||||||
const componentRefMap = injectComponentRefMap();
|
const componentRefMap = injectComponentRefMap();
|
||||||
watch(fieldComponentRef, (componentRef) => {
|
watch(fieldComponentRef, (componentRef) => {
|
||||||
componentRefMap?.set(fieldName, componentRef);
|
componentRefMap?.set(fieldName, componentRef);
|
||||||
|
|
@ -335,6 +356,7 @@ onUnmounted(() => {
|
||||||
{
|
{
|
||||||
'mr-2 shrink-0 justify-end': !isVertical,
|
'mr-2 shrink-0 justify-end': !isVertical,
|
||||||
'mb-1 flex-row': isVertical,
|
'mb-1 flex-row': isVertical,
|
||||||
|
'self-start': shouldCollapsible && !isVertical,
|
||||||
},
|
},
|
||||||
labelClass,
|
labelClass,
|
||||||
)
|
)
|
||||||
|
|
@ -348,65 +370,87 @@ onUnmounted(() => {
|
||||||
<template v-if="label">
|
<template v-if="label">
|
||||||
<VbenRenderContent :content="label" />
|
<VbenRenderContent :content="label" />
|
||||||
</template>
|
</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>
|
</FormLabel>
|
||||||
<div class="flex-auto overflow-hidden p-px">
|
<div class="flex-auto overflow-hidden p-px">
|
||||||
<div :class="cn('relative flex w-full items-center', wrapperClass)">
|
<VbenCollapsible :show-trigger="false" v-model:open="collapseOpen">
|
||||||
<FormControl :class="cn(controlClass)">
|
<template #collapsibleContent>
|
||||||
<slot
|
<div :class="cn('relative flex w-full items-center', wrapperClass)">
|
||||||
v-bind="{
|
<FormControl :class="cn(controlClass)">
|
||||||
...slotProps,
|
<slot
|
||||||
...createComponentProps(slotProps),
|
v-bind="{
|
||||||
disabled: shouldDisabled,
|
...slotProps,
|
||||||
isInValid,
|
...createComponentProps(slotProps),
|
||||||
}"
|
disabled: shouldDisabled,
|
||||||
>
|
|
||||||
<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,
|
isInValid,
|
||||||
}"
|
}"
|
||||||
v-bind="createComponentProps(slotProps)"
|
|
||||||
:disabled="shouldDisabled"
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
v-for="name in renderContentKey"
|
|
||||||
:key="name"
|
|
||||||
#[name]="renderSlotProps"
|
|
||||||
>
|
>
|
||||||
<VbenRenderContent
|
<component
|
||||||
:content="customContentRender[name]"
|
:is="FieldComponent"
|
||||||
v-bind="{ ...renderSlotProps, formContext: slotProps }"
|
ref="fieldComponentRef"
|
||||||
/>
|
:class="{
|
||||||
</template>
|
'border-destructive hover:border-destructive/80 focus:border-destructive focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
|
||||||
<!-- <slot></slot> -->
|
isInValid,
|
||||||
</component>
|
}"
|
||||||
<VbenTooltip
|
v-bind="createComponentProps(slotProps)"
|
||||||
v-if="compact && isInValid"
|
:disabled="shouldDisabled"
|
||||||
:delay-duration="300"
|
>
|
||||||
side="left"
|
<template
|
||||||
>
|
v-for="name in renderContentKey"
|
||||||
<template #trigger>
|
:key="name"
|
||||||
<slot name="trigger">
|
#[name]="renderSlotProps"
|
||||||
<CircleAlert
|
>
|
||||||
:class="
|
<VbenRenderContent
|
||||||
cn(
|
:content="customContentRender[name]"
|
||||||
'inline-flex size-5 cursor-pointer text-foreground/80 hover:text-foreground',
|
v-bind="{ ...renderSlotProps, formContext: slotProps }"
|
||||||
)
|
/>
|
||||||
"
|
</template>
|
||||||
/>
|
<!-- <slot></slot> -->
|
||||||
</slot>
|
</component>
|
||||||
</template>
|
<VbenTooltip
|
||||||
<FormMessage />
|
v-if="compact && isInValid"
|
||||||
</VbenTooltip>
|
:delay-duration="300"
|
||||||
</slot>
|
side="left"
|
||||||
</FormControl>
|
>
|
||||||
<!-- 自定义后缀 -->
|
<template #trigger>
|
||||||
<div v-if="suffix" class="ml-1">
|
<slot name="trigger">
|
||||||
<VbenRenderContent :content="suffix" />
|
<CircleAlert
|
||||||
</div>
|
:class="
|
||||||
</div>
|
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">
|
<FormDescription v-if="description" class="text-xs">
|
||||||
<VbenRenderContent :content="description" />
|
<VbenRenderContent :content="description" />
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ const props = defineProps<Props>();
|
||||||
<VbenHelpTooltip v-if="help" trigger-class="size-3.5 ml-1">
|
<VbenHelpTooltip v-if="help" trigger-class="size-3.5 ml-1">
|
||||||
<VbenRenderContent :content="help" />
|
<VbenRenderContent :content="help" />
|
||||||
</VbenHelpTooltip>
|
</VbenHelpTooltip>
|
||||||
|
<slot name="extra"></slot>
|
||||||
<span v-if="colon && label" class="ml-0.5">:</span>
|
<span v-if="colon && label" class="ml-0.5">:</span>
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,10 @@ type ComponentProps =
|
||||||
| MaybeComponentProps;
|
| MaybeComponentProps;
|
||||||
|
|
||||||
export interface FormCommonConfig {
|
export interface FormCommonConfig {
|
||||||
|
/**
|
||||||
|
* 是否可折叠的
|
||||||
|
*/
|
||||||
|
collapsible?: boolean;
|
||||||
/**
|
/**
|
||||||
* 在Label后显示一个冒号
|
* 在Label后显示一个冒号
|
||||||
*/
|
*/
|
||||||
|
|
@ -157,6 +161,11 @@ export interface FormCommonConfig {
|
||||||
* 所有表单项的控件样式
|
* 所有表单项的控件样式
|
||||||
*/
|
*/
|
||||||
controlClass?: string;
|
controlClass?: string;
|
||||||
|
/**
|
||||||
|
* 默认折叠
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
defaultCollapsed?: boolean;
|
||||||
/**
|
/**
|
||||||
* 所有表单项的禁用状态
|
* 所有表单项的禁用状态
|
||||||
* @default false
|
* @default false
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CollapsibleParamSchema } from './type';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: CollapsibleParamSchema;
|
||||||
|
}
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const modelValue = defineModel('value');
|
||||||
|
|
||||||
|
const finalOption = computed(() => {
|
||||||
|
const { type, ...otherOption } = props.data.option;
|
||||||
|
|
||||||
|
if (type === 'number') {
|
||||||
|
return {
|
||||||
|
step: props.data.option.step ?? 1,
|
||||||
|
min: props.data.option.min,
|
||||||
|
max: props.data.option.max,
|
||||||
|
precision: props.data.option.precision ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return otherOption;
|
||||||
|
});
|
||||||
|
|
||||||
|
const components = globalShareState.getComponents();
|
||||||
|
|
||||||
|
const FieldComponent = computed(() => {
|
||||||
|
switch (props.data.option.type) {
|
||||||
|
case 'exponential':
|
||||||
|
case 'number': {
|
||||||
|
return components.InputNumber;
|
||||||
|
}
|
||||||
|
case 'select': {
|
||||||
|
return components.Select;
|
||||||
|
}
|
||||||
|
case 'string': {
|
||||||
|
return components.Input;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return components.InputNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
modelValue.value = props.data.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
reset,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="body-row">
|
||||||
|
<div class="body-cell">{{ data.key }}</div>
|
||||||
|
<div class="body-cell">
|
||||||
|
<div class="flex-auto w-full">
|
||||||
|
<component
|
||||||
|
:is="FieldComponent"
|
||||||
|
v-bind="finalOption"
|
||||||
|
v-model:value="modelValue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center flex-none text-muted-foreground pl-2 gap-2">
|
||||||
|
<span v-if="data.option.min && data.option.max">
|
||||||
|
[{{ data.option.min }},{{ data.option.max }}]
|
||||||
|
</span>
|
||||||
|
<span v-if="data.option.step && data.option.step !== 1">
|
||||||
|
step:{{ data.option.step }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body-cell w-full">
|
||||||
|
<p
|
||||||
|
class="line-clamp-2"
|
||||||
|
v-tippy="{
|
||||||
|
content: data.description,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ data.description }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.body-row {
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
@apply border-b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CollapsibleParamSchema } from './type';
|
||||||
|
|
||||||
|
import { computed, nextTick, ref, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
|
import { useNamespace } from '@vben-core/composables';
|
||||||
|
|
||||||
|
import { ChevronsDown } from 'lucide-vue-next';
|
||||||
|
import {
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleRoot,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from 'reka-ui';
|
||||||
|
|
||||||
|
import CollapsibleParamsItem from './collapsible-params-item.vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
defaultOpen?: boolean;
|
||||||
|
maxHeight?: number | string;
|
||||||
|
params: CollapsibleParamSchema[];
|
||||||
|
visibleCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
visibleCount: 3,
|
||||||
|
defaultOpen: false,
|
||||||
|
maxHeight: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits<{ 'update:value': [any, string] }>();
|
||||||
|
|
||||||
|
const modelValue = defineModel('value');
|
||||||
|
const visibleRefs = useTemplateRef('visibleRefs');
|
||||||
|
const collapsibleRefs = useTemplateRef('collapsibleRefs');
|
||||||
|
|
||||||
|
const { b } = useNamespace('collapsible-params');
|
||||||
|
|
||||||
|
const open = ref(props.defaultOpen);
|
||||||
|
|
||||||
|
const visibleRows = computed(() => {
|
||||||
|
return props.params.slice(0, props.visibleCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
const collapsibleRows = computed(() => {
|
||||||
|
return props.params.slice(props.visibleCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
const bodyStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
maxHeight:
|
||||||
|
typeof props.maxHeight === 'number'
|
||||||
|
? `${props.maxHeight}px`
|
||||||
|
: props.maxHeight,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
for (const param of props.params) {
|
||||||
|
modelValue.value[param.key] = param.defaultValue ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCollapsed() {
|
||||||
|
open.value = !open.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onParamValueChange(value: any, key: string) {
|
||||||
|
await nextTick();
|
||||||
|
emits('update:value', modelValue.value, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetValue() {
|
||||||
|
if (visibleRefs.value)
|
||||||
|
for (const rowRef of visibleRefs.value) {
|
||||||
|
rowRef.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collapsibleRefs.value)
|
||||||
|
for (const rowRef of collapsibleRefs.value) {
|
||||||
|
rowRef.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
toggleCollapsed,
|
||||||
|
resetValue,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CollapsibleRoot v-model:open="open" :class="b()" :unmount-on-hide="false">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="w-full min-w-fit">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-cell">参数名称</div>
|
||||||
|
<div class="header-cell">配置</div>
|
||||||
|
<div class="header-cell">说明</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="body"
|
||||||
|
:class="[
|
||||||
|
open && !!props.maxHeight ? 'overflow-y-auto' : 'overflow-y-hidden',
|
||||||
|
]"
|
||||||
|
:style="bodyStyle"
|
||||||
|
>
|
||||||
|
<CollapsibleParamsItem
|
||||||
|
:data="row"
|
||||||
|
v-for="row in visibleRows"
|
||||||
|
:key="row.key"
|
||||||
|
ref="visibleRefs"
|
||||||
|
v-model:value="modelValue[row.key]"
|
||||||
|
@update:value="(v) => onParamValueChange(v, row.key)"
|
||||||
|
/>
|
||||||
|
<CollapsibleContent
|
||||||
|
class="data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up"
|
||||||
|
>
|
||||||
|
<CollapsibleParamsItem
|
||||||
|
:data="row"
|
||||||
|
v-for="row in collapsibleRows"
|
||||||
|
:key="row.key"
|
||||||
|
ref="collapsibleRefs"
|
||||||
|
v-model:value="modelValue[row.key]"
|
||||||
|
@update:value="(v) => onParamValueChange(v, row.key)"
|
||||||
|
/>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gutter" v-if="!open && collapsibleRows.length > 0"></div>
|
||||||
|
<div
|
||||||
|
class="trigger-bar"
|
||||||
|
:class="{
|
||||||
|
collapsed: !open,
|
||||||
|
}"
|
||||||
|
v-if="collapsibleRows.length > 0"
|
||||||
|
>
|
||||||
|
<CollapsibleTrigger
|
||||||
|
class="cursor-pointer h-[2rem] flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<ChevronsDown
|
||||||
|
class="transition-transform"
|
||||||
|
:size="16"
|
||||||
|
:class="{
|
||||||
|
'rotate-180': open,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
{{ open ? '收起' : '展开' }}
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
</div>
|
||||||
|
</CollapsibleRoot>
|
||||||
|
</template>
|
||||||
|
<style lang="css">
|
||||||
|
.vben-collapsible-params {
|
||||||
|
@apply border rounded-[0.5rem] flex flex-col w-full overflow-hidden;
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
--column1: 11.25rem;
|
||||||
|
--column2: 18.25rem;
|
||||||
|
--column3: 27.5rem;
|
||||||
|
|
||||||
|
@apply w-full relative flex flex-col overflow-x-auto;
|
||||||
|
|
||||||
|
/* min-width: calc(var(--column1) + var(--column2) + var(--column3)); */
|
||||||
|
|
||||||
|
.header,
|
||||||
|
.body {
|
||||||
|
@apply w-full flex-none flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
@apply bg-accent items-center rounded-t-[0.5rem] border-b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
@apply flex-col overflow-x-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-row {
|
||||||
|
@apply flex items-center w-full flex-nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-cell,
|
||||||
|
.body-cell {
|
||||||
|
@apply py-2 px-5 leading-[1.5rem] flex items-center flex-nowrap;
|
||||||
|
|
||||||
|
&:nth-of-type(1) {
|
||||||
|
flex: 0 0 var(--column1);
|
||||||
|
|
||||||
|
/* min-width: var(--column1); */
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-of-type(2) {
|
||||||
|
flex: 0 0 var(--column2);
|
||||||
|
|
||||||
|
/* min-width: var(--column2); */
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-of-type(3) {
|
||||||
|
flex: 1 1 var(--column3);
|
||||||
|
min-width: var(--column3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gutter {
|
||||||
|
@apply h-[1.5rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-bar {
|
||||||
|
@apply flex min-h-[2rem] border-t px-5 py-1 rounded-b-[0.5rem] z-1;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
@apply absolute bottom-[1px] left-[1px] right-[1px] border-t-0 pt-6;
|
||||||
|
|
||||||
|
background-image: linear-gradient(
|
||||||
|
hsl(var(--foreground) / 0%) 0%,
|
||||||
|
hsl(var(--foreground) / 12%) 31.76%,
|
||||||
|
var(--color-border) 31.76%,
|
||||||
|
var(--color-border) 33.43%,
|
||||||
|
var(--color-background) 31.76%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CollapsibleRootEmits, CollapsibleRootProps } from 'reka-ui';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ChevronsDown } from 'lucide-vue-next';
|
||||||
|
import {
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleRoot,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from 'reka-ui';
|
||||||
|
|
||||||
|
const props = defineProps<
|
||||||
|
CollapsibleRootProps & {
|
||||||
|
class?: ClassType;
|
||||||
|
showTrigger?: boolean;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
const emits = defineEmits<CollapsibleRootEmits>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _cls, ...delegated } = props;
|
||||||
|
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
|
const open = defineModel('open', { default: true });
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
open.value = !open.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
toggle,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CollapsibleRoot
|
||||||
|
v-bind="forwarded"
|
||||||
|
v-model:open="open"
|
||||||
|
class="flex flex-col"
|
||||||
|
:unmount-on-hide="false"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between"
|
||||||
|
v-if="$slots.label || showTrigger"
|
||||||
|
>
|
||||||
|
<slot name="label" v-if="$slots.label"> </slot>
|
||||||
|
<CollapsibleTrigger
|
||||||
|
v-if="showTrigger"
|
||||||
|
class="cursor-pointer rounded-full h-[25px] w-[25px] inline-flex items-center justify-center outline-none data-[state=closed]:bg-white data-[state=open]:bg-primary/20 hover:bg-primary/20 text-primary"
|
||||||
|
>
|
||||||
|
<slot name="trigger" :open>
|
||||||
|
<ChevronsDown
|
||||||
|
class="h-3.5 w-3.5 transition-transform"
|
||||||
|
:class="{
|
||||||
|
'rotate-180': open,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot name="visibleContent" :open></slot>
|
||||||
|
|
||||||
|
<CollapsibleContent
|
||||||
|
class="data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up overflow-hidden justify-start"
|
||||||
|
>
|
||||||
|
<slot name="collapsibleContent" :open></slot>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</CollapsibleRoot>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export { default as VbenCollapsibleParams } from './collapsible-params.vue';
|
||||||
|
export { default as VbenCollapsible } from './collapsible.vue';
|
||||||
|
|
||||||
|
export * from './type';
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
export interface CollapsibleParamOption {
|
||||||
|
[key: string]: any;
|
||||||
|
max?: number;
|
||||||
|
min?: number;
|
||||||
|
precision?: number;
|
||||||
|
step?: number;
|
||||||
|
type: 'exponential' | 'number' | 'select' | 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollapsibleParamSchema {
|
||||||
|
defaultValue: number | number[] | string | string[];
|
||||||
|
description: string;
|
||||||
|
key: string;
|
||||||
|
option: CollapsibleParamOption;
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ export * from './back-top';
|
||||||
export * from './breadcrumb';
|
export * from './breadcrumb';
|
||||||
export * from './button';
|
export * from './button';
|
||||||
export * from './checkbox';
|
export * from './checkbox';
|
||||||
|
export * from './collapsible';
|
||||||
export * from './context-menu';
|
export * from './context-menu';
|
||||||
export * from './count-to-animator';
|
export * from './count-to-animator';
|
||||||
export * from './dropdown-menu';
|
export * from './dropdown-menu';
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@
|
||||||
"upload-urls": "Urls after file upload",
|
"upload-urls": "Urls after file upload",
|
||||||
"file": "file",
|
"file": "file",
|
||||||
"crop-image": "Crop image",
|
"crop-image": "Crop image",
|
||||||
"upload-image": "Click to upload image"
|
"upload-image": "Click to upload image",
|
||||||
|
"collapsible": "Collapsible FormItem Content"
|
||||||
},
|
},
|
||||||
"vxeTable": {
|
"vxeTable": {
|
||||||
"title": "Vxe Table",
|
"title": "Vxe Table",
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@
|
||||||
"upload-urls": "文件上传后的网址",
|
"upload-urls": "文件上传后的网址",
|
||||||
"file": "文件",
|
"file": "文件",
|
||||||
"crop-image": "裁剪图片",
|
"crop-image": "裁剪图片",
|
||||||
"upload-image": "点击上传图片"
|
"upload-image": "点击上传图片",
|
||||||
|
"collapsible": "单项表单折叠"
|
||||||
},
|
},
|
||||||
"vxeTable": {
|
"vxeTable": {
|
||||||
"title": "Vxe 表格",
|
"title": "Vxe 表格",
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,14 @@ const routes: RouteRecordRaw[] = [
|
||||||
title: $t('examples.form.scrollToError'),
|
title: $t('examples.form.scrollToError'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'FormCollapsibleExample',
|
||||||
|
path: '/examples/form/collapsible-test',
|
||||||
|
component: () => import('#/views/examples/form/collapsible.vue'),
|
||||||
|
meta: {
|
||||||
|
title: $t('examples.form.collapsible'),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { VbenCollapsibleParams } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
import { Button, Card, message, RadioGroup } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useVbenForm, z } from '#/adapter/form';
|
||||||
|
|
||||||
|
import DocButton from '../doc-button.vue';
|
||||||
|
|
||||||
|
const layouts = [
|
||||||
|
{ label: 'Vertical', value: 'vertical' },
|
||||||
|
{ label: 'Horizontal', value: 'horizontal' },
|
||||||
|
];
|
||||||
|
const layout = ref(layouts[0].value);
|
||||||
|
|
||||||
|
function getNumberValidator(key: string, limit?: [number, number]) {
|
||||||
|
const validator = z.number({
|
||||||
|
required_error: `${key} 值不能为空`,
|
||||||
|
invalid_type_error: `${key} 值只能为数字`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (limit) {
|
||||||
|
validator.min(limit[0], { message: `${key} 值不在区间范围内` });
|
||||||
|
validator.max(limit[1], { message: `${key} 值不在区间范围内` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return validator.default(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsSchema = [
|
||||||
|
{
|
||||||
|
key: 'micro_batch_size',
|
||||||
|
description: `批次大小,代表模型训练过程中,模型更新模型参数的数据步长,可理解为模型每看多少数据即更新一次模型参数,
|
||||||
|
一般建议的批次大小为16/32,表示模型每看16或32条数据即更新一次参数`,
|
||||||
|
option: {
|
||||||
|
min: 8,
|
||||||
|
max: 1024,
|
||||||
|
step: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'learning_rate',
|
||||||
|
description:
|
||||||
|
'学习率,代表每次更新数据的增量参数权重,学习率数值越大参数变化越大,对模型影响越大',
|
||||||
|
option: {
|
||||||
|
step: 1e-4,
|
||||||
|
type: 'exponential',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'eval_steps',
|
||||||
|
description:
|
||||||
|
'验证步数,训练阶段针模型的验证间隔步长,用于阶段性评估模型训练准确率、训练损失',
|
||||||
|
option: {
|
||||||
|
min: 1,
|
||||||
|
max: 2_147_483_647,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'num_train_epochs',
|
||||||
|
description:
|
||||||
|
'循环次数,代表模型训练过程中模型学习数据集的次数,可理解为看几遍数据,一般建议的范围是1-3遍即可,可依据需求进行调整',
|
||||||
|
option: {
|
||||||
|
min: 1,
|
||||||
|
max: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'max_length',
|
||||||
|
description: `序列长度,单个训练数据样本的最大长度,超出配置长度将丢弃`,
|
||||||
|
option: {
|
||||||
|
min: 500,
|
||||||
|
max: 131_072,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'warmup_ratio',
|
||||||
|
description: '学习率预热比例,学习率预热阶段占总训练步数的比例',
|
||||||
|
option: {
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
precision: 2,
|
||||||
|
step: 0.01,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'save_steps',
|
||||||
|
description: 'Checkpoint保存间隔',
|
||||||
|
option: {
|
||||||
|
min: 1,
|
||||||
|
max: 2_147_483_647,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as CollapsibleParamSchema[];
|
||||||
|
|
||||||
|
const paramsValidator = z.object({
|
||||||
|
micro_batch_size: getNumberValidator('micro_batch_size', [8, 1024]),
|
||||||
|
learning_rate: getNumberValidator('learning_rate'),
|
||||||
|
eval_steps: getNumberValidator('eval_steps', [1, 2_147_483_647]),
|
||||||
|
num_train_epochs: getNumberValidator('num_train_epochs', [1, 200]),
|
||||||
|
max_length: getNumberValidator('max_length', [1, 131_072]),
|
||||||
|
warmup_ratio: getNumberValidator('warmup_ratio', [0, 1]),
|
||||||
|
save_steps: getNumberValidator('save_steps', [1, 2_147_483_647]),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [BaseForm, baseFormApi] = useVbenForm({
|
||||||
|
// 所有表单项共用,可单独在表单内覆盖
|
||||||
|
commonConfig: {
|
||||||
|
// 在label后显示一个冒号
|
||||||
|
colon: true,
|
||||||
|
// 所有表单项
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fieldMappingTime: [['rangePicker', ['startTime', 'endTime'], 'YYYY-MM-DD']],
|
||||||
|
// 提交函数
|
||||||
|
handleSubmit: onSubmit,
|
||||||
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
|
// 水平布局,label和input在同一行
|
||||||
|
layout: 'vertical',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
component: VbenCollapsibleParams,
|
||||||
|
componentProps: {
|
||||||
|
params: paramsSchema,
|
||||||
|
// maxHeight: 200, //限制最大高度,展开后可滚动
|
||||||
|
},
|
||||||
|
modelPropName: 'value',
|
||||||
|
fieldName: 'params',
|
||||||
|
label: '参数配置',
|
||||||
|
formItemClass: 'col-span-2 items-baseline',
|
||||||
|
rules: paramsValidator,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RichEditor',
|
||||||
|
fieldName: 'richEditor',
|
||||||
|
label: '富文本',
|
||||||
|
formItemClass: 'col-span-3 items-baseline',
|
||||||
|
collapsible: true,
|
||||||
|
defaultCollapsed: false, // 默认false
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSubmit(values: Record<string, any>) {
|
||||||
|
message.info({
|
||||||
|
content: `form values: ${JSON.stringify(values)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSetFormValue() {
|
||||||
|
baseFormApi.setFieldValue('params', {
|
||||||
|
micro_batch_size: 8,
|
||||||
|
learning_rate: 1e-5,
|
||||||
|
eval_steps: 50,
|
||||||
|
num_train_epochs: 3,
|
||||||
|
max_length: 32_768,
|
||||||
|
warmup_ratio: 0.05,
|
||||||
|
save_steps: 50,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLayoutChange(layout: string) {
|
||||||
|
baseFormApi.setState({
|
||||||
|
layout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
auto-content-height
|
||||||
|
content-class="flex flex-col gap-4"
|
||||||
|
title="可折叠表单项"
|
||||||
|
>
|
||||||
|
<template #description>
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
<p>可折叠表单项、以及可折叠参数配置组件示例</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<DocButton class="mb-2" path="/components/common-ui/vben-form" />
|
||||||
|
</template>
|
||||||
|
<Card title="基础示例">
|
||||||
|
<template #extra>
|
||||||
|
<div class="inline-flex items-center gap-4!">
|
||||||
|
<RadioGroup
|
||||||
|
:options="layouts"
|
||||||
|
option-type="button"
|
||||||
|
v-model:value="layout"
|
||||||
|
@update:value="onLayoutChange"
|
||||||
|
>
|
||||||
|
设置表单值
|
||||||
|
</RadioGroup>
|
||||||
|
<Button type="primary" @click="handleSetFormValue">设置表单值</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="w-full overflow-hidden">
|
||||||
|
<BaseForm />
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -848,6 +848,9 @@ importers:
|
||||||
|
|
||||||
apps/web-naive:
|
apps/web-naive:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@vben-core/shadcn-ui':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/@core/ui-kit/shadcn-ui
|
||||||
'@vben/access':
|
'@vben/access':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/effects/access
|
version: link:../../packages/effects/access
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue