Vben
2021-02-26 fcee7d4eb71471dd40567c8d7c97302eeee80697
src/components/Form/src/BasicForm.vue
@@ -1,61 +1,79 @@
<template>
  <Form v-bind="$attrs" ref="formElRef" :model="formModel">
    <Row :class="getProps.compact ? 'compact-form-row' : ''">
      <slot name="formHeader" />
  <Form v-bind="{ ...$attrs, ...$props }" :class="getFormClass" ref="formElRef" :model="formModel">
    <Row :style="getRowWrapStyle">
      <slot name="formHeader"></slot>
      <template v-for="schema in getSchema" :key="schema.field">
        <FormItem
          :tableAction="tableAction"
          :formActionType="formActionType"
          :schema="schema"
          :formProps="getProps"
          :allDefaultValues="defaultValueRef"
          :formModel="formModel"
          :setFormModel="setFormModel"
        >
          <template #[item]="data" v-for="item in Object.keys($slots)">
            <slot :name="item" v-bind="data" />
            <slot :name="item" v-bind="data"></slot>
          </template>
        </FormItem>
      </template>
      <FormAction
        v-bind="{ ...getActionPropsRef, ...advanceState }"
        @toggle-advanced="handleToggleAdvanced"
      />
      <slot name="formFooter" />
      <FormAction v-bind="{ ...getProps, ...advanceState }" @toggle-advanced="handleToggleAdvanced">
        <template
          #[item]="data"
          v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
        >
          <slot :name="item" v-bind="data"></slot>
        </template>
      </FormAction>
      <slot name="formFooter"></slot>
    </Row>
  </Form>
</template>
<script lang="ts">
  import type { FormActionType, FormProps, FormSchema } from './types/form';
  import type { AdvanceState } from './types/hooks';
  import type { Ref } from 'vue';
  import type { ValidateFields } from 'ant-design-vue/lib/form/interface';
  import type { CSSProperties, Ref } from 'vue';
  import { defineComponent, reactive, ref, computed, unref, toRef, onMounted, watch } from 'vue';
  import {
    defineComponent,
    reactive,
    ref,
    computed,
    unref,
    onMounted,
    watch,
    toRefs,
    nextTick,
  } from 'vue';
  import { Form, Row } from 'ant-design-vue';
  import FormItem from './FormItem';
  import { basicProps } from './props';
  import FormAction from './FormAction';
  import FormItem from './components/FormItem';
  import FormAction from './components/FormAction.vue';
  import { dateItemType } from './helper';
  import moment from 'moment';
  import { cloneDeep } from 'lodash-es';
  import { dateUtil } from '/@/utils/dateUtil';
  // import { cloneDeep } from 'lodash-es';
  import { deepMerge } from '/@/utils';
  import { useFormValues } from './hooks/useFormValues';
  import useAdvanced from './hooks/useAdvanced';
  import { useFormAction } from './hooks/useFormAction';
  import { useFormEvents } from './hooks/useFormEvents';
  import { createFormContext } from './hooks/useFormContext';
  import { useAutoFocus } from './hooks/useAutoFocus';
  import { useModalContext } from '/@/components/Modal';
  import { basicProps } from './props';
  import { useDesign } from '/@/hooks/web/useDesign';
  export default defineComponent({
    name: 'BasicForm',
    components: { FormItem, Form, Row, FormAction },
    inheritAttrs: false,
    props: basicProps,
    emits: ['advanced-change', 'reset', 'submit', 'register'],
    setup(props, { emit }) {
      const formModel = reactive({});
      const actionState = reactive({
        resetAction: {},
        submitAction: {},
      });
      const formModel = reactive<Recordable>({});
      const modalFn = useModalContext();
      const advanceState = reactive<AdvanceState>({
        isAdvanced: true,
@@ -64,31 +82,35 @@
        actionSpan: 6,
      });
      const defaultValueRef = ref<any>({});
      const defaultValueRef = ref<Recordable>({});
      const isInitedDefaultRef = ref(false);
      const propsRef = ref<Partial<FormProps>>({});
      const schemaRef = ref<FormSchema[] | null>(null);
      const schemaRef = ref<Nullable<FormSchema[]>>(null);
      const formElRef = ref<Nullable<FormActionType>>(null);
      const getMergePropsRef = computed(
      const { prefixCls } = useDesign('basic-form');
      // Get the basic configuration of the form
      const getProps = computed(
        (): FormProps => {
          return deepMerge(cloneDeep(props), unref(propsRef));
          return { ...props, ...unref(propsRef) } as FormProps;
        }
      );
      // 获取表单基本配置
      const getProps = computed(
        (): FormProps => {
          return {
            ...unref(getMergePropsRef),
            resetButtonOptions: deepMerge(
              actionState.resetAction,
              unref(getMergePropsRef).resetButtonOptions || {}
            ),
            submitButtonOptions: deepMerge(
              actionState.submitAction,
              unref(getMergePropsRef).submitButtonOptions || {}
            ),
          };
      const getFormClass = computed(() => {
        return [
          prefixCls,
          {
            [`${prefixCls}--compact`]: unref(getProps).compact,
          },
        ];
      });
      // Get uniform row style
      const getRowWrapStyle = computed(
        (): CSSProperties => {
          const { baseRowStyle = {} } = unref(getProps);
          return baseRowStyle;
        }
      );
@@ -96,33 +118,50 @@
        const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
        for (const schema of schemas) {
          const { defaultValue, component } = schema;
          if (defaultValue && dateItemType.includes(component!)) {
            schema.defaultValue = moment(defaultValue);
          // handle date type
          if (defaultValue && dateItemType.includes(component)) {
            if (!Array.isArray(defaultValue)) {
              schema.defaultValue = dateUtil(defaultValue);
            } else {
              const def: moment.Moment[] = [];
              defaultValue.forEach((item) => {
                def.push(dateUtil(item));
              });
              schema.defaultValue = def;
            }
          }
        }
        return schemas as FormSchema[];
      });
      const { getActionPropsRef, handleToggleAdvanced } = useAdvanced({
      const { handleToggleAdvanced } = useAdvanced({
        advanceState,
        emit,
        getMergePropsRef,
        getProps,
        getSchema,
        formModel,
        defaultValueRef,
      });
      const { transformDateFunc, fieldMapToTime, autoFocusFirstItem } = toRefs(props);
      const { handleFormValues, initDefault } = useFormValues({
        transformDateFuncRef: toRef(props, 'transformDateFunc') as Ref<Fn<any>>,
        fieldMapToTimeRef: toRef(props, 'fieldMapToTime'),
        transformDateFuncRef: transformDateFunc,
        fieldMapToTimeRef: fieldMapToTime,
        defaultValueRef,
        getSchema,
        formModel,
      });
      useAutoFocus({
        getSchema,
        autoFocusFirstItem,
        isInitedDefault: isInitedDefaultRef,
        formElRef: formElRef as Ref<FormActionType>,
      });
      const {
        // handleSubmit,
        handleSubmit,
        setFieldsValue,
        clearValidate,
        validate,
@@ -132,38 +171,61 @@
        appendSchemaByField,
        removeSchemaByFiled,
        resetFields,
      } = useFormAction({
        scrollToField,
      } = useFormEvents({
        emit,
        getProps,
        formModel,
        getSchema,
        defaultValueRef,
        formElRef: formElRef as any,
        schemaRef: schemaRef as any,
        formElRef: formElRef as Ref<FormActionType>,
        schemaRef: schemaRef as Ref<FormSchema[]>,
        handleFormValues,
        actionState,
      });
      createFormContext({
        resetAction: resetFields,
        submitAction: handleSubmit,
      });
      watch(
        () => unref(getMergePropsRef).model,
        () => unref(getProps).model,
        () => {
          if (!unref(getMergePropsRef).model) return;
          setFieldsValue(unref(getMergePropsRef).model);
          const { model } = unref(getProps);
          if (!model) return;
          setFieldsValue(model);
        },
        {
          immediate: true,
        }
      );
      /**
       * @description:设置表单
       */
      function setProps(formProps: Partial<FormProps>): void {
        const mergeProps = deepMerge(unref(propsRef) || {}, formProps);
        propsRef.value = mergeProps;
      watch(
        () => getSchema.value,
        (schema) => {
          nextTick(() => {
            //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
            modalFn?.redoModalHeight?.();
          });
          if (unref(isInitedDefaultRef)) {
            return;
          }
          if (schema?.length) {
            initDefault();
            isInitedDefaultRef.value = true;
          }
        }
      );
      async function setProps(formProps: Partial<FormProps>): Promise<void> {
        propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
      }
      const methods: Partial<FormActionType> = {
      function setFormModel(key: string, value: any) {
        formModel[key] = value;
      }
      const formActionType: Partial<FormActionType> = {
        getFieldsValue,
        setFieldsValue,
        resetFields,
@@ -172,26 +234,72 @@
        removeSchemaByFiled,
        appendSchemaByField,
        clearValidate,
        validateFields: validateFields as ValidateFields,
        validate: validate as ValidateFields,
        validateFields,
        validate,
        submit: handleSubmit,
        scrollToField: scrollToField,
      };
      onMounted(() => {
        initDefault();
        emit('register', methods);
        emit('register', formActionType);
      });
      return {
        handleToggleAdvanced,
        formModel,
        getActionPropsRef,
        defaultValueRef,
        advanceState,
        getRowWrapStyle,
        getProps,
        formElRef,
        getSchema,
        ...methods,
        formActionType,
        setFormModel,
        prefixCls,
        getFormClass,
        ...formActionType,
      };
    },
  });
</script>
<style lang="less">
  @prefix-cls: ~'@{namespace}-basic-form';
  .@{prefix-cls} {
    .ant-form-item {
      &-label label::after {
        margin: 0 6px 0 2px;
      }
      &-with-help {
        margin-bottom: 0;
      }
      &:not(.ant-form-item-with-help) {
        margin-bottom: 20px;
      }
      &.suffix-item {
        .ant-form-item-children {
          display: flex;
        }
        .suffix {
          display: inline-block;
          padding-left: 6px;
        }
      }
    }
    .ant-form-explain {
      font-size: 14px;
    }
    &--compact {
      .ant-form-item {
        margin-bottom: 8px !important;
      }
    }
  }
</style>