This commit is contained in:
dap 2024-10-03 15:20:39 +08:00
commit 445489bc38
19 changed files with 1577 additions and 738 deletions

View File

@ -4,6 +4,7 @@ import type {
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue'; import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -61,6 +62,16 @@ export type FormComponentType =
| 'Upload' | 'Upload'
| BaseFormComponentType; | BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部 // 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({ setupVbenForm<FormComponentType>({
components: { components: {
@ -77,21 +88,21 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots); return h(Button, { ...props, attrs, type: 'primary' }, slots);
}, },
Divider, Divider,
Input, Input: withDefaultPlaceholder(Input, 'input'),
InputNumber, InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword, InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions, Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio, Radio,
RadioGroup, RadioGroup,
RangePicker, RangePicker,
Rate, Rate,
RichTextarea, RichTextarea,
Select, Select: withDefaultPlaceholder(Select, 'select'),
Space, Space,
Switch, Switch,
Textarea, Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker, TimePicker,
TreeSelect, TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload, Upload,
}, },
config: { config: {

View File

@ -4,6 +4,7 @@ import type {
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue'; import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -42,6 +43,16 @@ export type FormComponentType =
| 'Upload' | 'Upload'
| BaseFormComponentType; | BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部 // 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({ setupVbenForm<FormComponentType>({
components: { components: {
@ -56,14 +67,14 @@ setupVbenForm<FormComponentType>({
return h(ElButton, { ...props, attrs, type: 'primary' }, slots); return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
}, },
Divider: ElDivider, Divider: ElDivider,
Input: ElInput, Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: ElInputNumber, InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup, RadioGroup: ElRadioGroup,
Select: ElSelect, Select: withDefaultPlaceholder(ElSelect, 'select'),
Space: ElSpace, Space: ElSpace,
Switch: ElSwitch, Switch: ElSwitch,
TimePicker: ElTimePicker, TimePicker: ElTimePicker,
TreeSelect: ElTreeSelect, TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload, Upload: ElUpload,
}, },
config: { config: {

View File

@ -4,6 +4,7 @@ import type {
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue'; import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -43,6 +44,16 @@ export type FormComponentType =
| 'Upload' | 'Upload'
| BaseFormComponentType; | BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部 // 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({ setupVbenForm<FormComponentType>({
components: { components: {
@ -62,17 +73,18 @@ setupVbenForm<FormComponentType>({
); );
}, },
Divider: NDivider, Divider: NDivider,
Input: NInput, Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: NInputNumber, InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup, RadioGroup: NRadioGroup,
Select: NSelect, Select: withDefaultPlaceholder(NSelect, 'select'),
Space: NSpace, Space: NSpace,
Switch: NSwitch, Switch: NSwitch,
TimePicker: NTimePicker, TimePicker: NTimePicker,
TreeSelect: NTreeSelect, TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
Upload: NUpload, Upload: NUpload,
}, },
config: { config: {
disabledOnChangeListener: true,
baseModelPropName: 'value', baseModelPropName: 'value',
modelPropNameMap: { modelPropNameMap: {
Checkbox: 'checked', Checkbox: 'checked',

View File

@ -25,7 +25,6 @@ const routes: RouteRecordRaw[] = [
}, },
{ {
meta: { meta: {
icon: 'mdi:shield-key-outline',
title: $t('page.demos.table'), title: $t('page.demos.table'),
}, },
name: 'Table', name: 'Table',

View File

@ -31,6 +31,7 @@ import type {
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue'; import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -84,6 +85,16 @@ export type FormComponentType =
| 'Upload' | 'Upload'
| BaseFormComponentType; | BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部 // 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({ setupVbenForm<FormComponentType>({
components: { components: {
@ -100,26 +111,27 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots); return h(Button, { ...props, attrs, type: 'primary' }, slots);
}, },
Divider, Divider,
Input, Input: withDefaultPlaceholder(Input, 'input'),
InputNumber, InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword, InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions, Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio, Radio,
RadioGroup, RadioGroup,
RangePicker, RangePicker,
Rate, Rate,
Select, Select: withDefaultPlaceholder(Select, 'select'),
Space, Space,
Switch, Switch,
Textarea, Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker, TimePicker,
TreeSelect, TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload, Upload,
}, },
config: { config: {
// 是否禁用onChange事件监听naive ui组件库默认不需要监听onChange事件否则会在控制台报错
disabledOnChangeListener: true,
// ant design vue组件库默认都是 v-model:value // ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value', baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList // 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: { modelPropNameMap: {
Checkbox: 'checked', Checkbox: 'checked',

View File

@ -99,7 +99,7 @@
"node": ">=20.10.0", "node": ">=20.10.0",
"pnpm": ">=9.5.0" "pnpm": ">=9.5.0"
}, },
"packageManager": "pnpm@9.11.0", "packageManager": "pnpm@9.12.0",
"pnpm": { "pnpm": {
"peerDependencyRules": { "peerDependencyRules": {
"allowedVersions": { "allowedVersions": {

View File

@ -1,4 +1,8 @@
import type { BaseFormComponentType, VbenFormAdapterOptions } from './types'; import type {
BaseFormComponentType,
FormCommonConfig,
VbenFormAdapterOptions,
} from './types';
import type { Component } from 'vue'; import type { Component } from 'vue';
import { h } from 'vue'; import { h } from 'vue';
@ -17,6 +21,8 @@ import { defineRule } from 'vee-validate';
const DEFAULT_MODEL_PROP_NAME = 'modelValue'; const DEFAULT_MODEL_PROP_NAME = 'modelValue';
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = { export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }), DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }), DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
@ -39,6 +45,9 @@ export function setupVbenForm<
>(options: VbenFormAdapterOptions<T>) { >(options: VbenFormAdapterOptions<T>) {
const { components, config, defineRules } = options; const { components, config, defineRules } = options;
DEFAULT_FORM_COMMON_CONFIG.disabledOnChangeListener =
config?.disabledOnChangeListener ?? false;
if (defineRules) { if (defineRules) {
for (const key of Object.keys(defineRules)) { for (const key of Object.keys(defineRules)) {
defineRule(key, defineRules[key as never]); defineRule(key, defineRules[key as never]);

View File

@ -3,7 +3,7 @@ import type { ZodType } from 'zod';
import type { FormSchema, MaybeComponentProps } from '../types'; import type { FormSchema, MaybeComponentProps } from '../types';
import { computed } from 'vue'; import { computed, nextTick, useTemplateRef, watch } from 'vue';
import { import {
FormControl, FormControl,
@ -32,6 +32,7 @@ const {
dependencies, dependencies,
description, description,
disabled, disabled,
disabledOnChangeListener,
fieldName, fieldName,
formFieldProps, formFieldProps,
label, label,
@ -49,6 +50,7 @@ const { componentBindEventMap, componentMap, isVertical } = useFormContext();
const formRenderProps = injectRenderFormProps(); const formRenderProps = injectRenderFormProps();
const values = useFormValues(); const values = useFormValues();
const errors = useFieldError(fieldName); const errors = useFieldError(fieldName);
const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');
const formApi = formRenderProps.form; const formApi = formRenderProps.form;
const isInValid = computed(() => errors.value?.length > 0); const isInValid = computed(() => errors.value?.length > 0);
@ -156,6 +158,18 @@ const computedProps = computed(() => {
}; };
}); });
watch(
() => computedProps.value?.autofocus,
(value) => {
if (value === true) {
nextTick(() => {
autofocus();
});
}
},
{ immediate: true },
);
const shouldDisabled = computed(() => { const shouldDisabled = computed(() => {
return isDisabled.value || disabled || computedProps.value?.disabled; return isDisabled.value || disabled || computedProps.value?.disabled;
}); });
@ -177,7 +191,7 @@ const fieldProps = computed(() => {
keepValue: true, keepValue: true,
label, label,
...(rules ? { rules } : {}), ...(rules ? { rules } : {}),
...formFieldProps, ...(formFieldProps as Record<string, any>),
}; };
}); });
@ -200,13 +214,14 @@ function fieldBindEvent(slotProps: Record<string, any>) {
return { return {
[`onUpdate:${bindEventField}`]: handler, [`onUpdate:${bindEventField}`]: handler,
[bindEventField]: value, [bindEventField]: value,
onChange: (e: Record<string, any>) => { onChange: disabledOnChangeListener
? undefined
: (e: Record<string, any>) => {
const shouldUnwrap = isEventObjectLike(e); const shouldUnwrap = isEventObjectLike(e);
const onChange = slotProps?.componentField?.onChange; const onChange = slotProps?.componentField?.onChange;
if (!shouldUnwrap) { if (!shouldUnwrap) {
return onChange?.(e); return onChange?.(e);
} }
return onChange?.(e?.target?.[bindEventField] ?? e); return onChange?.(e?.target?.[bindEventField] ?? e);
}, },
onInput: () => {}, onInput: () => {},
@ -226,6 +241,17 @@ function createComponentProps(slotProps: Record<string, any>) {
return binds; return binds;
} }
function autofocus() {
if (
fieldComponentRef.value &&
isFunction(fieldComponentRef.value.focus) &&
//
document.activeElement !== fieldComponentRef.value
) {
fieldComponentRef.value?.focus?.();
}
}
</script> </script>
<template> <template>
@ -275,6 +301,7 @@ function createComponentProps(slotProps: Record<string, any>) {
> >
<component <component
:is="fieldComponent" :is="fieldComponent"
ref="fieldComponentRef"
:class="{ :class="{
'border-destructive focus:border-destructive hover:border-destructive/80 focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]': 'border-destructive focus:border-destructive hover:border-destructive/80 focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
isInValid, isInValid,

View File

@ -1,12 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ZodTypeAny } from 'zod'; import type { ZodTypeAny } from 'zod';
import type { FormRenderProps, FormSchema, FormShape } from '../types'; import type {
FormCommonConfig,
FormRenderProps,
FormSchema,
FormShape,
} from '../types';
import { computed } from 'vue'; import { computed } from 'vue';
import { Form } from '@vben-core/shadcn-ui'; import { Form } from '@vben-core/shadcn-ui';
import { cn, isString } from '@vben-core/shared/utils'; import { cn, isString, mergeWithArrayOverride } from '@vben-core/shared/utils';
import { type GenericObject } from 'vee-validate'; import { type GenericObject } from 'vee-validate';
@ -17,12 +22,16 @@ import { getBaseRules, getDefaultValueInZodStack } from './helper';
interface Props extends FormRenderProps {} interface Props extends FormRenderProps {}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(
defineProps<{ globalCommonConfig?: FormCommonConfig } & Props>(),
{
collapsedRows: 1, collapsedRows: 1,
commonConfig: () => ({}), commonConfig: () => ({}),
globalCommonConfig: () => ({}),
showCollapseButton: false, showCollapseButton: false,
wrapperClass: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3', wrapperClass: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
}); },
);
const emits = defineEmits<{ const emits = defineEmits<{
submit: [event: any]; submit: [event: any];
@ -77,6 +86,7 @@ const computedSchema = computed(
componentProps = {}, componentProps = {},
controlClass = '', controlClass = '',
disabled, disabled,
disabledOnChangeListener = false,
formFieldProps = {}, formFieldProps = {},
formItemClass = '', formItemClass = '',
hideLabel = false, hideLabel = false,
@ -84,7 +94,7 @@ const computedSchema = computed(
labelClass = '', labelClass = '',
labelWidth = 100, labelWidth = 100,
wrapperClass = '', wrapperClass = '',
} = props.commonConfig; } = mergeWithArrayOverride(props.commonConfig, props.globalCommonConfig);
return (props.schema || []).map((schema, index) => { return (props.schema || []).map((schema, index) => {
const keepIndex = keepFormItemIndex.value; const keepIndex = keepFormItemIndex.value;
@ -96,6 +106,7 @@ const computedSchema = computed(
return { return {
disabled, disabled,
disabledOnChangeListener,
hideLabel, hideLabel,
hideRequiredMark, hideRequiredMark,
labelWidth, labelWidth,

View File

@ -1,5 +1,5 @@
import type { VbenButtonProps } from '@vben-core/shadcn-ui'; import type { VbenButtonProps } from '@vben-core/shadcn-ui';
import type { Field, FormContext, GenericObject } from 'vee-validate'; import type { FieldOptions, FormContext, GenericObject } from 'vee-validate';
import type { ZodTypeAny } from 'zod'; import type { ZodTypeAny } from 'zod';
import type { FormApi } from './form-api'; import type { FormApi } from './form-api';
@ -33,6 +33,15 @@ export type FormItemClassType =
| (Record<never, never> & string) | (Record<never, never> & string)
| WrapperClassType; | WrapperClassType;
export type FormFieldOptions = Partial<
{
validateOnBlur?: boolean;
validateOnChange?: boolean;
validateOnInput?: boolean;
validateOnModelUpdate?: boolean;
} & FieldOptions
>;
export interface FormShape { export interface FormShape {
/** 默认值 */ /** 默认值 */
default?: any; default?: any;
@ -139,11 +148,16 @@ export interface FormCommonConfig {
* @default false * @default false
*/ */
disabled?: boolean; disabled?: boolean;
/**
* change事件监听
* @default false
*/
disabledOnChangeListener?: boolean;
/** /**
* *
* @default {} * @default {}
*/ */
formFieldProps?: Partial<typeof Field>; formFieldProps?: FormFieldOptions;
/** /**
* *
* @default "" * @default ""
@ -317,6 +331,7 @@ export interface VbenFormAdapterOptions<
components: Partial<Record<T, Component>>; components: Partial<Record<T, Component>>;
config?: { config?: {
baseModelPropName?: string; baseModelPropName?: string;
disabledOnChangeListener?: boolean;
modelPropNameMap?: Partial<Record<T, string>>; modelPropNameMap?: Partial<Record<T, string>>;
}; };
defineRules?: { defineRules?: {

View File

@ -6,7 +6,11 @@ import { ref, watchEffect } from 'vue';
import { useForwardPropsEmits } from '@vben-core/composables'; import { useForwardPropsEmits } from '@vben-core/composables';
import FormActions from './components/form-actions.vue'; import FormActions from './components/form-actions.vue';
import { COMPONENT_BIND_EVENT_MAP, COMPONENT_MAP } from './config'; import {
COMPONENT_BIND_EVENT_MAP,
COMPONENT_MAP,
DEFAULT_FORM_COMMON_CONFIG,
} from './config';
import { Form } from './form-render'; import { Form } from './form-render';
import { provideFormProps, useFormInitial } from './use-form-context'; import { provideFormProps, useFormInitial } from './use-form-context';
@ -51,6 +55,7 @@ watchEffect(() => {
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP" :component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
:component-map="COMPONENT_MAP" :component-map="COMPONENT_MAP"
:form="form" :form="form"
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
> >
<template <template
v-for="slotName in delegatedSlots" v-for="slotName in delegatedSlots"

View File

@ -4,10 +4,13 @@ import type { ExtendedFormApi, VbenFormProps } from './types';
import { useForwardPriorityValues } from '@vben-core/composables'; import { useForwardPriorityValues } from '@vben-core/composables';
import FormActions from './components/form-actions.vue'; import FormActions from './components/form-actions.vue';
import { COMPONENT_BIND_EVENT_MAP, COMPONENT_MAP } from './config'; import {
COMPONENT_BIND_EVENT_MAP,
COMPONENT_MAP,
DEFAULT_FORM_COMMON_CONFIG,
} from './config';
import { Form } from './form-render'; import { Form } from './form-render';
import { provideFormProps, useFormInitial } from './use-form-context'; import { provideFormProps, useFormInitial } from './use-form-context';
// extends // extends
interface Props extends VbenFormProps { interface Props extends VbenFormProps {
formApi: ExtendedFormApi; formApi: ExtendedFormApi;
@ -36,6 +39,7 @@ const handleUpdateCollapsed = (value: boolean) => {
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP" :component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
:component-map="COMPONENT_MAP" :component-map="COMPONENT_MAP"
:form="form" :form="form"
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
> >
<template <template
v-for="slotName in delegatedSlots" v-for="slotName in delegatedSlots"

View File

@ -64,6 +64,10 @@
"required": "Please enter {0}", "required": "Please enter {0}",
"selectRequired": "Please select {0}" "selectRequired": "Please select {0}"
}, },
"placeholder": {
"input": "Please enter",
"select": "Please select"
},
"widgets": { "widgets": {
"document": "Document", "document": "Document",
"profile": "Profile", "profile": "Profile",

View File

@ -64,6 +64,10 @@
"required": "请输入{0}", "required": "请输入{0}",
"selectRequired": "请选择{0}" "selectRequired": "请选择{0}"
}, },
"placeholder": {
"input": "请输入",
"select": "请选择"
},
"widgets": { "widgets": {
"document": "文档", "document": "文档",
"profile": "个人中心", "profile": "个人中心",

View File

@ -4,6 +4,7 @@ import type {
VbenFormProps, VbenFormProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue'; import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -57,6 +58,16 @@ export type FormComponentType =
| 'Upload' | 'Upload'
| BaseFormComponentType; | BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部 // 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({ setupVbenForm<FormComponentType>({
components: { components: {
@ -73,20 +84,20 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots); return h(Button, { ...props, attrs, type: 'primary' }, slots);
}, },
Divider, Divider,
Input, Input: withDefaultPlaceholder(Input, 'input'),
InputNumber, InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword, InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions, Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio, Radio,
RadioGroup, RadioGroup,
RangePicker, RangePicker,
Rate, Rate,
Select, Select: withDefaultPlaceholder(Select, 'select'),
Space, Space,
Switch, Switch,
Textarea, Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker, TimePicker,
TreeSelect, TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload, Upload,
}, },
config: { config: {

View File

@ -223,6 +223,75 @@ const [BaseForm, baseFormApi] = useVbenForm({
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
}); });
const [CustomLayoutForm] = useVbenForm({
//
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
layout: 'horizontal',
schema: [
{
component: 'Select',
fieldName: 'field1',
label: '字符串',
},
{
component: 'TreeSelect',
fieldName: 'field2',
label: '字符串',
},
{
component: 'Mentions',
fieldName: 'field3',
label: '字符串',
},
{
component: 'Input',
fieldName: 'field4',
label: '字符串',
},
{
component: 'InputNumber',
fieldName: 'field5',
//
formItemClass: 'col-start-3',
label: '前面空了一列',
},
{
component: 'Textarea',
fieldName: 'field6',
// 线
formItemClass: 'col-span-3 items-baseline',
label: '占满三列',
},
{
component: 'Input',
fieldName: 'field7',
// 2
formItemClass: 'col-span-2 col-start-2',
label: '占满2列',
},
{
component: 'Input',
fieldName: 'field8',
//
formItemClass: 'col-start-2',
label: '左右留空',
},
{
component: 'InputPassword',
fieldName: 'field9',
formItemClass: 'col-start-1',
label: '字符串',
},
],
//
wrapperClass: 'grid-cols-3',
});
function onSubmit(values: Record<string, any>) { function onSubmit(values: Record<string, any>) {
message.success({ message.success({
content: `form values: ${JSON.stringify(values)}`, content: `form values: ${JSON.stringify(values)}`,
@ -256,6 +325,7 @@ function handleSetFormValue() {
<template> <template>
<Page <Page
content-class="flex flex-col gap-4"
description="表单组件基础示例,请注意,该页面用到的参数代码会添加一些简单注释,方便理解,请仔细查看。" description="表单组件基础示例,请注意,该页面用到的参数代码会添加一些简单注释,方便理解,请仔细查看。"
title="表单组件" title="表单组件"
> >
@ -265,5 +335,8 @@ function handleSetFormValue() {
</template> </template>
<BaseForm /> <BaseForm />
</Card> </Card>
<Card title="使用tailwind自定义布局">
<CustomLayoutForm />
</Card>
</Page> </Page>
</template> </template>

View File

@ -175,6 +175,47 @@ const [Form, formApi] = useVbenForm({
label: '密码', label: '密码',
rules: 'required', rules: 'required',
}, },
{
component: 'Input',
componentProps: {
placeholder: '请输入',
},
fieldName: 'input-blur',
formFieldProps: {
validateOnChange: false,
validateOnModelUpdate: false,
},
help: 'blur时才会触发校验',
label: 'blur触发',
rules: 'required',
},
{
component: 'Input',
componentProps: {
placeholder: '请输入',
},
fieldName: 'input-async',
label: '异步校验',
rules: z
.string()
.min(3, '用户名至少需要3个字符')
.refine(
async (username) => {
//
const checkUsernameExists = async (
username: string,
): Promise<boolean> => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return username === 'existingUser';
};
const exists = await checkUsernameExists(username);
return !exists;
},
{
message: '用户名已存在',
},
),
},
], ],
// 321 // 321
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ catalog:
'@commitlint/config-conventional': ^19.5.0 '@commitlint/config-conventional': ^19.5.0
'@ctrl/tinycolor': ^4.1.0 '@ctrl/tinycolor': ^4.1.0
'@eslint/js': ^9.11.1 '@eslint/js': ^9.11.1
'@iconify/json': ^2.2.254 '@iconify/json': ^2.2.255
'@iconify/tailwind': ^1.1.3 '@iconify/tailwind': ^1.1.3
'@iconify/vue': ^4.1.2 '@iconify/vue': ^4.1.2
'@intlify/core-base': ^10.0.3 '@intlify/core-base': ^10.0.3
@ -33,7 +33,7 @@ catalog:
'@stylistic/stylelint-plugin': ^3.1.0 '@stylistic/stylelint-plugin': ^3.1.0
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e '@tailwindcss/nesting': 0.0.0-insiders.565cd3e
'@tailwindcss/typography': ^0.5.15 '@tailwindcss/typography': ^0.5.15
'@tanstack/vue-query': ^5.56.2 '@tanstack/vue-query': ^5.59.0
'@tanstack/vue-store': ^0.5.5 '@tanstack/vue-store': ^0.5.5
'@types/archiver': ^6.0.2 '@types/archiver': ^6.0.2
'@types/chalk': ^2.2.0 '@types/chalk': ^2.2.0
@ -46,8 +46,8 @@ catalog:
'@types/postcss-import': ^14.0.3 '@types/postcss-import': ^14.0.3
'@types/qrcode': ^1.5.5 '@types/qrcode': ^1.5.5
'@types/sortablejs': ^1.15.8 '@types/sortablejs': ^1.15.8
'@typescript-eslint/eslint-plugin': ^8.7.0 '@typescript-eslint/eslint-plugin': ^8.8.0
'@typescript-eslint/parser': ^8.7.0 '@typescript-eslint/parser': ^8.8.0
'@vee-validate/zod': ^4.13.2 '@vee-validate/zod': ^4.13.2
'@vite-pwa/vitepress': ^0.5.3 '@vite-pwa/vitepress': ^0.5.3
'@vitejs/plugin-vue': ^5.1.4 '@vitejs/plugin-vue': ^5.1.4
@ -82,15 +82,15 @@ catalog:
echarts: ^5.5.1 echarts: ^5.5.1
element-plus: ^2.8.4 element-plus: ^2.8.4
eslint: ^9.11.1 eslint: ^9.11.1
eslint-config-turbo: ^2.1.2 eslint-config-turbo: ^2.1.3
eslint-plugin-command: ^0.2.6 eslint-plugin-command: ^0.2.6
eslint-plugin-eslint-comments: ^3.2.0 eslint-plugin-eslint-comments: ^3.2.0
eslint-plugin-import-x: ^4.3.0 eslint-plugin-import-x: ^4.3.1
eslint-plugin-jsdoc: ^50.3.0 eslint-plugin-jsdoc: ^50.3.1
eslint-plugin-jsonc: ^2.16.0 eslint-plugin-jsonc: ^2.16.0
eslint-plugin-n: ^17.10.3 eslint-plugin-n: ^17.10.3
eslint-plugin-no-only-tests: ^3.3.0 eslint-plugin-no-only-tests: ^3.3.0
eslint-plugin-perfectionist: ^3.7.0 eslint-plugin-perfectionist: ^3.8.0
eslint-plugin-prettier: ^5.2.1 eslint-plugin-prettier: ^5.2.1
eslint-plugin-regexp: ^2.6.0 eslint-plugin-regexp: ^2.6.0
eslint-plugin-unicorn: ^55.0.0 eslint-plugin-unicorn: ^55.0.0
@ -100,7 +100,7 @@ catalog:
execa: ^9.4.0 execa: ^9.4.0
find-up: ^7.0.0 find-up: ^7.0.0
get-port: ^7.1.0 get-port: ^7.1.0
globals: ^15.9.0 globals: ^15.10.0
h3: ^1.12.0 h3: ^1.12.0
happy-dom: ^15.7.4 happy-dom: ^15.7.4
html-minifier-terser: ^7.2.0 html-minifier-terser: ^7.2.0
@ -110,7 +110,7 @@ catalog:
jsonwebtoken: ^9.0.2 jsonwebtoken: ^9.0.2
lint-staged: ^15.2.10 lint-staged: ^15.2.10
lodash.clonedeep: ^4.5.0 lodash.clonedeep: ^4.5.0
lucide-vue-next: ^0.446.0 lucide-vue-next: ^0.447.0
medium-zoom: ^1.1.0 medium-zoom: ^1.1.0
naive-ui: ^2.40.1 naive-ui: ^2.40.1
nanoid: ^5.0.7 nanoid: ^5.0.7
@ -134,7 +134,7 @@ catalog:
radix-vue: ^1.9.6 radix-vue: ^1.9.6
resolve.exports: ^2.0.2 resolve.exports: ^2.0.2
rimraf: ^6.0.1 rimraf: ^6.0.1
rollup: ^4.22.5 rollup: ^4.24.0
rollup-plugin-visualizer: ^5.12.0 rollup-plugin-visualizer: ^5.12.0
sass: ^1.79.4 sass: ^1.79.4
sortablejs: ^1.15.3 sortablejs: ^1.15.3
@ -151,7 +151,7 @@ catalog:
tailwindcss: ^3.4.13 tailwindcss: ^3.4.13
tailwindcss-animate: ^1.0.7 tailwindcss-animate: ^1.0.7
theme-colors: ^0.1.0 theme-colors: ^0.1.0
turbo: ^2.1.2 turbo: ^2.1.3
typescript: ^5.6.2 typescript: ^5.6.2
unbuild: ^2.0.0 unbuild: ^2.0.0
unplugin-element-plus: ^0.8.0 unplugin-element-plus: ^0.8.0
@ -165,7 +165,7 @@ catalog:
vite-plugin-vue-devtools: ^7.4.6 vite-plugin-vue-devtools: ^7.4.6
vitepress: ^1.3.4 vitepress: ^1.3.4
vitepress-plugin-group-icons: ^1.2.4 vitepress-plugin-group-icons: ^1.2.4
vitest: ^2.1.1 vitest: ^2.1.2
vue: ^3.5.10 vue: ^3.5.10
vue-eslint-parser: ^9.4.3 vue-eslint-parser: ^9.4.3
vue-i18n: ^10.0.3 vue-i18n: ^10.0.3