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

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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 { FormApi } from './form-api';
@@ -33,6 +33,15 @@ export type FormItemClassType =
| (Record<never, never> & string)
| WrapperClassType;
export type FormFieldOptions = Partial<
{
validateOnBlur?: boolean;
validateOnChange?: boolean;
validateOnInput?: boolean;
validateOnModelUpdate?: boolean;
} & FieldOptions
>;
export interface FormShape {
/** 默认值 */
default?: any;
@@ -139,11 +148,16 @@ export interface FormCommonConfig {
* @default false
*/
disabled?: boolean;
/**
* 是否禁用所有表单项的change事件监听
* @default false
*/
disabledOnChangeListener?: boolean;
/**
* 所有表单项的控件样式
* @default {}
*/
formFieldProps?: Partial<typeof Field>;
formFieldProps?: FormFieldOptions;
/**
* 所有表单项的栅格布局
* @default ""
@@ -317,6 +331,7 @@ export interface VbenFormAdapterOptions<
components: Partial<Record<T, Component>>;
config?: {
baseModelPropName?: string;
disabledOnChangeListener?: boolean;
modelPropNameMap?: Partial<Record<T, string>>;
};
defineRules?: {

View File

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

View File

@@ -4,10 +4,13 @@ import type { ExtendedFormApi, VbenFormProps } from './types';
import { useForwardPriorityValues } from '@vben-core/composables';
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 { provideFormProps, useFormInitial } from './use-form-context';
// 通过 extends 会导致热更新卡死,所以重复写了一遍
interface Props extends VbenFormProps {
formApi: ExtendedFormApi;
@@ -36,6 +39,7 @@ const handleUpdateCollapsed = (value: boolean) => {
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
:component-map="COMPONENT_MAP"
:form="form"
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
>
<template
v-for="slotName in delegatedSlots"