无木
2022-10-29 58b30aae9a1d749fdbeaca4f1310059a7cc96db2
提交 | 用户 | age
2f6253 1 <template>
9b2d41 2   <Form
4f20d4 3     v-bind="getBindValue"
9b2d41 4     :class="getFormClass"
N 5     ref="formElRef"
6     :model="formModel"
7     @keypress.enter="handleEnterPress"
8   >
4f20d4 9     <Row v-bind="getRow">
9edc28 10       <slot name="formHeader"></slot>
2f6253 11       <template v-for="schema in getSchema" :key="schema.field">
12         <FormItem
58b30a 13           :isAdvanced="fieldsIsAdvancedMap[schema.field]"
5832ee 14           :tableAction="tableAction"
1d4561 15           :formActionType="formActionType"
2f6253 16           :schema="schema"
17           :formProps="getProps"
1c075a 18           :allDefaultValues="defaultValueRef"
2f6253 19           :formModel="formModel"
4ff1c4 20           :setFormModel="setFormModel"
2f6253 21         >
faf3f4 22           <template #[item]="data" v-for="item in Object.keys($slots)">
b1f317 23             <slot :name="item" v-bind="data || {}"></slot>
2f6253 24           </template>
25         </FormItem>
26       </template>
498278 27
e15b4f 28       <FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
de5bf7 29         <template
V 30           #[item]="data"
31           v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
32         >
47a448 33           <slot :name="item" v-bind="data || {}"></slot>
de5bf7 34         </template>
V 35       </FormAction>
9edc28 36       <slot name="formFooter"></slot>
2f6253 37     </Row>
38   </Form>
39 </template>
40 <script lang="ts">
41   import type { FormActionType, FormProps, FormSchema } from './types/form';
84c9d7 42   import type { AdvanceState } from './types/hooks';
4f20d4 43   import type { Ref } from 'vue';
2f6253 44
3ff70b 45   import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
2f6253 46   import { Form, Row } from 'ant-design-vue';
1418dc 47   import FormItem from './components/FormItem.vue';
4ff1c4 48   import FormAction from './components/FormAction.vue';
2f6253 49
50   import { dateItemType } from './helper';
3509eb 51   import { dateUtil } from '/@/utils/dateUtil';
V 52
a305e5 53   // import { cloneDeep } from 'lodash-es';
84b830 54   import { deepMerge } from '/@/utils';
V 55
2f6253 56   import { useFormValues } from './hooks/useFormValues';
84c9d7 57   import useAdvanced from './hooks/useAdvanced';
4ff1c4 58   import { useFormEvents } from './hooks/useFormEvents';
V 59   import { createFormContext } from './hooks/useFormContext';
ac1a36 60   import { useAutoFocus } from './hooks/useAutoFocus';
2882d6 61   import { useModalContext } from '/@/components/Modal';
f96453 62   import { useDebounceFn } from '@vueuse/core';
4ff1c4 63
V 64   import { basicProps } from './props';
ac1a36 65   import { useDesign } from '/@/hooks/web/useDesign';
d21578 66   import { cloneDeep } from 'lodash-es';
785732 67
2f6253 68   export default defineComponent({
69     name: 'BasicForm',
70     components: { FormItem, Form, Row, FormAction },
71     props: basicProps,
c0441c 72     emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
4f20d4 73     setup(props, { emit, attrs }) {
4ff1c4 74       const formModel = reactive<Recordable>({});
2882d6 75       const modalFn = useModalContext();
84c9d7 76
V 77       const advanceState = reactive<AdvanceState>({
2f6253 78         isAdvanced: true,
79         hideAdvanceBtn: false,
80         isLoad: false,
81         actionSpan: 6,
82       });
84c9d7 83
4ff1c4 84       const defaultValueRef = ref<Recordable>({});
46e087 85       const isInitedDefaultRef = ref(false);
2f6253 86       const propsRef = ref<Partial<FormProps>>({});
0b6110 87       const schemaRef = ref<Nullable<FormSchema[]>>(null);
84b830 88       const formElRef = ref<Nullable<FormActionType>>(null);
b9d3d6 89
ac1a36 90       const { prefixCls } = useDesign('basic-form');
V 91
4ff1c4 92       // Get the basic configuration of the form
9b2d41 93       const getProps = computed((): FormProps => {
N 94         return { ...props, ...unref(propsRef) } as FormProps;
95       });
ac1a36 96
V 97       const getFormClass = computed(() => {
98         return [
99           prefixCls,
100           {
101             [`${prefixCls}--compact`]: unref(getProps).compact,
102           },
103         ];
104       });
bb1b26 105
785732 106       // Get uniform row style and Row configuration for the entire form
e15b4f 107       const getRow = computed((): Recordable => {
785732 108         const { baseRowStyle = {}, rowProps } = unref(getProps);
L 109         return {
110           style: baseRowStyle,
111           ...rowProps,
112         };
9b2d41 113       });
4f20d4 114
V 115       const getBindValue = computed(
56a966 116         () => ({ ...attrs, ...props, ...unref(getProps) } as Recordable),
4f20d4 117       );
2f6253 118
119       const getSchema = computed((): FormSchema[] => {
120         const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
121         for (const schema of schemas) {
bc4997 122           const { defaultValue, component, isHandleDateDefaultValue = true } = schema;
498278 123           // handle date type
bc4997 124           if (isHandleDateDefaultValue && defaultValue && dateItemType.includes(component)) {
0b6110 125             if (!Array.isArray(defaultValue)) {
3509eb 126               schema.defaultValue = dateUtil(defaultValue);
0b6110 127             } else {
3fcfac 128               const def: any[] = [];
0b6110 129               defaultValue.forEach((item) => {
3509eb 130                 def.push(dateUtil(item));
0b6110 131               });
V 132               schema.defaultValue = def;
133             }
2f6253 134           }
135         }
47a448 136         if (unref(getProps).showAdvancedButton) {
d21578 137           return cloneDeep(
A 138             schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
139           );
47a448 140         } else {
d21578 141           return cloneDeep(schemas as FormSchema[]);
47a448 142         }
2f6253 143       });
144
58b30a 145       const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
84c9d7 146         advanceState,
V 147         emit,
148         getProps,
149         getSchema,
150         formModel,
151         defaultValueRef,
2f6253 152       });
4ff1c4 153
84c9d7 154       const { handleFormValues, initDefault } = useFormValues({
3ff70b 155         getProps,
84c9d7 156         defaultValueRef,
V 157         getSchema,
158         formModel,
ac1a36 159       });
V 160
161       useAutoFocus({
162         getSchema,
3ff70b 163         getProps,
ac1a36 164         isInitedDefault: isInitedDefaultRef,
V 165         formElRef: formElRef as Ref<FormActionType>,
84c9d7 166       });
1c075a 167
84c9d7 168       const {
4ff1c4 169         handleSubmit,
84c9d7 170         setFieldsValue,
V 171         clearValidate,
172         validate,
173         validateFields,
174         getFieldsValue,
175         updateSchema,
c639e4 176         resetSchema,
84c9d7 177         appendSchemaByField,
768fad 178         removeSchemaByField,
84c9d7 179         resetFields,
4ff1c4 180         scrollToField,
V 181       } = useFormEvents({
84c9d7 182         emit,
V 183         getProps,
184         formModel,
185         getSchema,
186         defaultValueRef,
0b6110 187         formElRef: formElRef as Ref<FormActionType>,
V 188         schemaRef: schemaRef as Ref<FormSchema[]>,
84c9d7 189         handleFormValues,
4ff1c4 190       });
V 191
192       createFormContext({
193         resetAction: resetFields,
194         submitAction: handleSubmit,
84c9d7 195       });
1c075a 196
1db72c 197       watch(
4ff1c4 198         () => unref(getProps).model,
1db72c 199         () => {
4ff1c4 200           const { model } = unref(getProps);
V 201           if (!model) return;
202           setFieldsValue(model);
1db72c 203         },
V 204         {
205           immediate: true,
56a966 206         },
1db72c 207       );
1c075a 208
2882d6 209       watch(
808328 210         () => unref(getProps).schemas,
211         (schemas) => {
212           resetSchema(schemas ?? []);
56a966 213         },
808328 214       );
215
216       watch(
0b6110 217         () => getSchema.value,
46e087 218         (schema) => {
2882d6 219           nextTick(() => {
V 220             //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
221             modalFn?.redoModalHeight?.();
222           });
46e087 223           if (unref(isInitedDefaultRef)) {
2882d6 224             return;
46e087 225           }
4ff1c4 226           if (schema?.length) {
46e087 227             initDefault();
V 228             isInitedDefaultRef.value = true;
229           }
56a966 230         },
0b6110 231       );
V 232
f96453 233       watch(
1 234         () => formModel,
235         useDebounceFn(() => {
236           unref(getProps).submitOnChange && handleSubmit();
237         }, 300),
238         { deep: true },
239       );
240
4ff1c4 241       async function setProps(formProps: Partial<FormProps>): Promise<void> {
V 242         propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
243       }
244
245       function setFormModel(key: string, value: any) {
246         formModel[key] = value;
f84401 247         const { validateTrigger } = unref(getBindValue);
248         if (!validateTrigger || validateTrigger === 'change') {
571f28 249           validateFields([key]).catch((_) => {});
f84401 250         }
c0441c 251         emit('field-value-change', key, value);
2f6253 252       }
253
9b2d41 254       function handleEnterPress(e: KeyboardEvent) {
N 255         const { autoSubmitOnEnter } = unref(getProps);
256         if (!autoSubmitOnEnter) return;
257         if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
258           const target: HTMLElement = e.target as HTMLElement;
259           if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
260             handleSubmit();
261           }
262         }
263       }
264
1d4561 265       const formActionType: Partial<FormActionType> = {
2f6253 266         getFieldsValue,
267         setFieldsValue,
268         resetFields,
269         updateSchema,
c639e4 270         resetSchema,
2f6253 271         setProps,
768fad 272         removeSchemaByField,
2f6253 273         appendSchemaByField,
274         clearValidate,
4ff1c4 275         validateFields,
V 276         validate,
277         submit: handleSubmit,
278         scrollToField: scrollToField,
2f6253 279       };
1c075a 280
2f6253 281       onMounted(() => {
1c075a 282         initDefault();
1d4561 283         emit('register', formActionType);
2f6253 284       });
1c075a 285
2f6253 286       return {
4f20d4 287         getBindValue,
2f6253 288         handleToggleAdvanced,
9b2d41 289         handleEnterPress,
2f6253 290         formModel,
1c075a 291         defaultValueRef,
2f6253 292         advanceState,
785732 293         getRow,
2f6253 294         getProps,
295         formElRef,
296         getSchema,
e15b4f 297         formActionType: formActionType as any,
4ff1c4 298         setFormModel,
ac1a36 299         getFormClass,
e15b4f 300         getFormActionBindProps: computed(
56a966 301           (): Recordable => ({ ...getProps.value, ...advanceState }),
e15b4f 302         ),
58b30a 303         fieldsIsAdvancedMap,
1d4561 304         ...formActionType,
2f6253 305       };
306     },
307   });
308 </script>
ac1a36 309 <style lang="less">
V 310   @prefix-cls: ~'@{namespace}-basic-form';
311
312   .@{prefix-cls} {
313     .ant-form-item {
314       &-label label::after {
315         margin: 0 6px 0 2px;
316       }
317
318       &-with-help {
319         margin-bottom: 0;
320       }
321
322       &:not(.ant-form-item-with-help) {
323         margin-bottom: 20px;
324       }
325
326       &.suffix-item {
327         .ant-form-item-children {
328           display: flex;
329         }
330
67a7a7 331         .ant-form-item-control {
V 332           margin-top: 4px;
333         }
334
ac1a36 335         .suffix {
67a7a7 336           display: inline-flex;
ac1a36 337           padding-left: 6px;
67a7a7 338           margin-top: 1px;
V 339           line-height: 1;
340           align-items: center;
ac1a36 341         }
V 342       }
343     }
344
345     .ant-form-explain {
346       font-size: 14px;
347     }
348
349     &--compact {
350       .ant-form-item {
08df19 351         margin-bottom: 8px !important;
ac1a36 352       }
V 353     }
354   }
355 </style>