Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin
This commit is contained in:
@@ -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]);
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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?: {
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -64,6 +64,10 @@
|
||||
"required": "Please enter {0}",
|
||||
"selectRequired": "Please select {0}"
|
||||
},
|
||||
"placeholder": {
|
||||
"input": "Please enter",
|
||||
"select": "Please select"
|
||||
},
|
||||
"widgets": {
|
||||
"document": "Document",
|
||||
"profile": "Profile",
|
||||
|
@@ -64,6 +64,10 @@
|
||||
"required": "请输入{0}",
|
||||
"selectRequired": "请选择{0}"
|
||||
},
|
||||
"placeholder": {
|
||||
"input": "请输入",
|
||||
"select": "请选择"
|
||||
},
|
||||
"widgets": {
|
||||
"document": "文档",
|
||||
"profile": "个人中心",
|
||||
|
Reference in New Issue
Block a user