Browse Source

fix: fixed an error in the form onChange within Naive (#4558)

* fix: fixed an error in the form onChange within Naive

* chore: update
Vben 5 months ago
parent
commit
0cd865e211

+ 3 - 2
apps/web-antd/src/adapter/form.ts

@@ -4,7 +4,8 @@ import type {
   VbenFormProps,
 } from '@vben/common-ui';
 
-import { type Component, h, type SetupContext } from 'vue';
+import type { Component, SetupContext } from 'vue';
+import { h } from 'vue';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
@@ -63,7 +64,7 @@ const withDefaultPlaceholder = <T extends Component>(
 ) => {
   return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
     const placeholder = props?.placeholder || $t(`placeholder.${type}`);
-    return h(component, { ...props, attrs, placeholder }, slots);
+    return h(component, { ...props, ...attrs, placeholder }, slots);
   };
 };
 

+ 15 - 4
apps/web-ele/src/adapter/form.ts

@@ -4,6 +4,7 @@ import type {
   VbenFormProps,
 } from '@vben/common-ui';
 
+import type { Component, SetupContext } from 'vue';
 import { h } from 'vue';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@@ -42,6 +43,16 @@ export type FormComponentType =
   | 'Upload'
   | 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组件内部
 setupVbenForm<FormComponentType>({
   components: {
@@ -56,14 +67,14 @@ setupVbenForm<FormComponentType>({
       return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
     },
     Divider: ElDivider,
-    Input: ElInput,
-    InputNumber: ElInputNumber,
+    Input: withDefaultPlaceholder(ElInput, 'input'),
+    InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
     RadioGroup: ElRadioGroup,
-    Select: ElSelect,
+    Select: withDefaultPlaceholder(ElSelect, 'select'),
     Space: ElSpace,
     Switch: ElSwitch,
     TimePicker: ElTimePicker,
-    TreeSelect: ElTreeSelect,
+    TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
     Upload: ElUpload,
   },
   config: {

+ 16 - 4
apps/web-naive/src/adapter/form.ts

@@ -4,6 +4,7 @@ import type {
   VbenFormProps,
 } from '@vben/common-ui';
 
+import type { Component, SetupContext } from 'vue';
 import { h } from 'vue';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@@ -43,6 +44,16 @@ export type FormComponentType =
   | 'Upload'
   | 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组件内部
 setupVbenForm<FormComponentType>({
   components: {
@@ -62,17 +73,18 @@ setupVbenForm<FormComponentType>({
       );
     },
     Divider: NDivider,
-    Input: NInput,
-    InputNumber: NInputNumber,
+    Input: withDefaultPlaceholder(NInput, 'input'),
+    InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
     RadioGroup: NRadioGroup,
-    Select: NSelect,
+    Select: withDefaultPlaceholder(NSelect, 'select'),
     Space: NSpace,
     Switch: NSwitch,
     TimePicker: NTimePicker,
-    TreeSelect: NTreeSelect,
+    TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
     Upload: NUpload,
   },
   config: {
+    disabledOnChangeListener: true,
     baseModelPropName: 'value',
     modelPropNameMap: {
       Checkbox: 'checked',

+ 0 - 1
apps/web-naive/src/router/routes/modules/demos.ts

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

+ 10 - 1
packages/@core/ui-kit/form-ui/src/config.ts

@@ -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';
@@ -16,6 +20,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' }),
@@ -37,6 +43,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]);

+ 25 - 21
packages/@core/ui-kit/form-ui/src/form-render/form-field.vue

@@ -3,7 +3,7 @@ import type { ZodType } from 'zod';
 
 import type { FormSchema, MaybeComponentProps } from '../types';
 
-import { computed, nextTick, ref, watch } from 'vue';
+import { computed, nextTick, useTemplateRef, watch } from 'vue';
 
 import {
   FormControl,
@@ -32,6 +32,7 @@ const {
   dependencies,
   description,
   disabled,
+  disabledOnChangeListener,
   fieldName,
   formFieldProps,
   label,
@@ -49,19 +50,10 @@ 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);
-const fieldComponentRef = ref<HTMLInputElement | null>(null);
-const focus = () => {
-  if (
-    fieldComponentRef.value &&
-    typeof fieldComponentRef.value.focus === 'function' &&
-    document.activeElement !== fieldComponentRef.value // 检查当前是否有元素被聚焦
-  ) {
-    fieldComponentRef.value.focus();
-  }
-};
 
 const fieldComponent = computed(() => {
   const finalComponent = isString(component)
@@ -171,7 +163,7 @@ watch(
   (value) => {
     if (value === true) {
       nextTick(() => {
-        focus();
+        autofocus();
       });
     }
   },
@@ -222,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: () => {},
     };
   }
@@ -248,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>

+ 20 - 9
packages/@core/ui-kit/form-ui/src/form-render/form.vue

@@ -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,

+ 6 - 0
packages/@core/ui-kit/form-ui/src/types.ts

@@ -139,6 +139,11 @@ export interface FormCommonConfig {
    * @default false
    */
   disabled?: boolean;
+  /**
+   * 是否禁用所有表单项的change事件监听
+   * @default false
+   */
+  disabledOnChangeListener?: boolean;
   /**
    * 所有表单项的控件样式
    * @default {}
@@ -317,6 +322,7 @@ export interface VbenFormAdapterOptions<
   components: Partial<Record<T, Component>>;
   config?: {
     baseModelPropName?: string;
+    disabledOnChangeListener?: boolean;
     modelPropNameMap?: Partial<Record<T, string>>;
   };
   defineRules?: {

+ 6 - 1
packages/@core/ui-kit/form-ui/src/vben-form.vue

@@ -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"

+ 6 - 2
packages/@core/ui-kit/form-ui/src/vben-use-form.vue

@@ -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"

+ 3 - 2
playground/src/adapter/form.ts

@@ -4,7 +4,8 @@ import type {
   VbenFormProps,
 } from '@vben/common-ui';
 
-import { type Component, h, type SetupContext } from 'vue';
+import type { Component, SetupContext } from 'vue';
+import { h } from 'vue';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
@@ -63,7 +64,7 @@ const withDefaultPlaceholder = <T extends Component>(
 ) => {
   return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
     const placeholder = props?.placeholder || $t(`placeholder.${type}`);
-    return h(component, { ...props, attrs, placeholder }, slots);
+    return h(component, { ...props, ...attrs, placeholder }, slots);
   };
 };
 

+ 18 - 18
pnpm-lock.yaml

@@ -10910,7 +10910,7 @@ snapshots:
 
   '@babel/generator@7.25.6':
     dependencies:
-      '@babel/types': 7.25.6
+      '@babel/types': 7.25.7
       '@jridgewell/gen-mapping': 0.3.5
       '@jridgewell/trace-mapping': 0.3.25
       jsesc: 2.5.2
@@ -10924,7 +10924,7 @@ snapshots:
 
   '@babel/helper-annotate-as-pure@7.24.7':
     dependencies:
-      '@babel/types': 7.25.6
+      '@babel/types': 7.25.7
 
   '@babel/helper-annotate-as-pure@7.25.7':
     dependencies:
@@ -11000,7 +11000,7 @@ snapshots:
   '@babel/helper-member-expression-to-functions@7.24.8':
     dependencies:
       '@babel/traverse': 7.25.6
-      '@babel/types': 7.25.6
+      '@babel/types': 7.25.7
     transitivePeerDependencies:
       - supports-color
 
@@ -11014,7 +11014,7 @@ snapshots:
   '@babel/helper-module-imports@7.24.7':
     dependencies:
       '@babel/traverse': 7.25.6
-      '@babel/types': 7.25.6
+      '@babel/types': 7.25.7
     transitivePeerDependencies:
       - supports-color
 
@@ -11047,7 +11047,7 @@ snapshots:
 
   '@babel/helper-optimise-call-expression@7.24.7':
     dependencies:
-      '@babel/types': 7.25.6
+      '@babel/types': 7.25.7
 
   '@babel/helper-optimise-call-expression@7.25.7':
     dependencies:
@@ -11087,7 +11087,7 @@ snapshots:
   '@babel/helper-simple-access@7.24.7':
     dependencies:
       '@babel/traverse': 7.25.6
-      '@babel/types': 7.25.6
+      '@babel/types': 7.25.7
     transitivePeerDependencies:
       - supports-color
 
@@ -11101,7 +11101,7 @@ snapshots:
   '@babel/helper-skip-transparent-expression-wrappers@7.24.7':
     dependencies:
       '@babel/traverse': 7.25.6
-      '@babel/types': 7.25.6
+      '@babel/types': 7.25.7
     transitivePeerDependencies:
       - supports-color
 
@@ -11135,11 +11135,11 @@ snapshots:
   '@babel/helpers@7.25.6':
     dependencies:
       '@babel/template': 7.25.0
-      '@babel/types': 7.25.6
+      '@babel/types': 7.25.7
 
   '@babel/highlight@7.24.7':
     dependencies:
-      '@babel/helper-validator-identifier': 7.24.7
+      '@babel/helper-validator-identifier': 7.25.7
       chalk: 2.4.2
       js-tokens: 4.0.0
       picocolors: 1.1.0
@@ -11787,8 +11787,8 @@ snapshots:
   '@babel/template@7.25.0':
     dependencies:
       '@babel/code-frame': 7.24.7
-      '@babel/parser': 7.25.6
-      '@babel/types': 7.25.6
+      '@babel/parser': 7.25.7
+      '@babel/types': 7.25.7
 
   '@babel/template@7.25.7':
     dependencies:
@@ -11800,7 +11800,7 @@ snapshots:
     dependencies:
       '@babel/code-frame': 7.24.7
       '@babel/generator': 7.25.6
-      '@babel/parser': 7.25.6
+      '@babel/parser': 7.25.7
       '@babel/template': 7.25.0
       '@babel/types': 7.25.6
       debug: 4.3.7
@@ -13053,7 +13053,7 @@ snapshots:
 
   '@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@10.0.0)(@vue/compiler-dom@3.5.10)(vue-i18n@10.0.3(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2))':
     dependencies:
-      '@babel/parser': 7.25.6
+      '@babel/parser': 7.25.7
     optionalDependencies:
       '@intlify/shared': 10.0.0
       '@vue/compiler-dom': 3.5.10
@@ -14244,14 +14244,14 @@ snapshots:
       '@babel/core': 7.25.2
       '@babel/helper-module-imports': 7.24.7
       '@babel/helper-plugin-utils': 7.24.8
-      '@babel/parser': 7.25.6
+      '@babel/parser': 7.25.7
       '@vue/compiler-sfc': 3.5.10
     transitivePeerDependencies:
       - supports-color
 
   '@vue/compiler-core@3.5.10':
     dependencies:
-      '@babel/parser': 7.25.6
+      '@babel/parser': 7.25.7
       '@vue/shared': 3.5.10
       entities: 4.5.0
       estree-walker: 2.0.2
@@ -14259,7 +14259,7 @@ snapshots:
 
   '@vue/compiler-core@3.5.8':
     dependencies:
-      '@babel/parser': 7.25.6
+      '@babel/parser': 7.25.7
       '@vue/shared': 3.5.8
       entities: 4.5.0
       estree-walker: 2.0.2
@@ -14277,7 +14277,7 @@ snapshots:
 
   '@vue/compiler-sfc@3.5.10':
     dependencies:
-      '@babel/parser': 7.25.6
+      '@babel/parser': 7.25.7
       '@vue/compiler-core': 3.5.10
       '@vue/compiler-dom': 3.5.10
       '@vue/compiler-ssr': 3.5.10
@@ -14289,7 +14289,7 @@ snapshots:
 
   '@vue/compiler-sfc@3.5.8':
     dependencies:
-      '@babel/parser': 7.25.6
+      '@babel/parser': 7.25.7
       '@vue/compiler-core': 3.5.8
       '@vue/compiler-dom': 3.5.8
       '@vue/compiler-ssr': 3.5.8