bowen
2023-10-10 30b3ee5c89c31cb5794faab40e800c36507d258a
提交 | 用户 | 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';
30b3ee 46   import { Form, Row, type FormProps as AntFormProps } 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 }) {
ba2415 74       const formModel = reactive({});
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
ba2415 84       const defaultValueRef = ref({});
46e087 85       const isInitedDefaultRef = ref(false);
2f6253 86       const propsRef = ref<Partial<FormProps>>({});
ba2415 87       const schemaRef = ref<FormSchema[] | null>(null);
V 88       const formElRef = ref<FormActionType | null>(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 => {
ba2415 94         return { ...props, ...unref(propsRef) };
9b2d41 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
ba2415 107       const getRow = computed(() => {
785732 108         const { baseRowStyle = {}, rowProps } = unref(getProps);
L 109         return {
110           style: baseRowStyle,
111           ...rowProps,
112         };
9b2d41 113       });
4f20d4 114
30b3ee 115       const getBindValue = computed(
B 116         () => ({ ...attrs, ...props, ...unref(getProps) }) as AntFormProps,
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) {
828933 122           const {
B 123             defaultValue,
124             component,
125             componentProps,
126             isHandleDateDefaultValue = true,
127           } = schema;
498278 128           // handle date type
bc4997 129           if (isHandleDateDefaultValue && defaultValue && dateItemType.includes(component)) {
828933 130             const valueFormat = componentProps ? componentProps['valueFormat'] : null;
0b6110 131             if (!Array.isArray(defaultValue)) {
828933 132               schema.defaultValue = valueFormat
B 133                 ? dateUtil(defaultValue).format(valueFormat)
134                 : dateUtil(defaultValue);
0b6110 135             } else {
3fcfac 136               const def: any[] = [];
0b6110 137               defaultValue.forEach((item) => {
cb64e5 138                 def.push(valueFormat ? dateUtil(item).format(valueFormat) : dateUtil(item));
0b6110 139               });
V 140               schema.defaultValue = def;
141             }
2f6253 142           }
143         }
47a448 144         if (unref(getProps).showAdvancedButton) {
d21578 145           return cloneDeep(
A 146             schemas.filter((schema) => schema.component !== 'Divider') as FormSchema[],
147           );
47a448 148         } else {
d21578 149           return cloneDeep(schemas as FormSchema[]);
47a448 150         }
2f6253 151       });
152
58b30a 153       const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
84c9d7 154         advanceState,
V 155         emit,
156         getProps,
157         getSchema,
158         formModel,
159         defaultValueRef,
2f6253 160       });
4ff1c4 161
84c9d7 162       const { handleFormValues, initDefault } = useFormValues({
3ff70b 163         getProps,
84c9d7 164         defaultValueRef,
V 165         getSchema,
166         formModel,
ac1a36 167       });
V 168
169       useAutoFocus({
170         getSchema,
3ff70b 171         getProps,
ac1a36 172         isInitedDefault: isInitedDefaultRef,
V 173         formElRef: formElRef as Ref<FormActionType>,
84c9d7 174       });
1c075a 175
84c9d7 176       const {
4ff1c4 177         handleSubmit,
84c9d7 178         setFieldsValue,
V 179         clearValidate,
180         validate,
181         validateFields,
182         getFieldsValue,
183         updateSchema,
c639e4 184         resetSchema,
84c9d7 185         appendSchemaByField,
768fad 186         removeSchemaByField,
84c9d7 187         resetFields,
4ff1c4 188         scrollToField,
V 189       } = useFormEvents({
84c9d7 190         emit,
V 191         getProps,
192         formModel,
193         getSchema,
194         defaultValueRef,
0b6110 195         formElRef: formElRef as Ref<FormActionType>,
V 196         schemaRef: schemaRef as Ref<FormSchema[]>,
84c9d7 197         handleFormValues,
4ff1c4 198       });
V 199
200       createFormContext({
201         resetAction: resetFields,
202         submitAction: handleSubmit,
84c9d7 203       });
1c075a 204
1db72c 205       watch(
4ff1c4 206         () => unref(getProps).model,
1db72c 207         () => {
4ff1c4 208           const { model } = unref(getProps);
V 209           if (!model) return;
210           setFieldsValue(model);
1db72c 211         },
V 212         {
213           immediate: true,
56a966 214         },
1db72c 215       );
1c075a 216
2882d6 217       watch(
808328 218         () => unref(getProps).schemas,
219         (schemas) => {
220           resetSchema(schemas ?? []);
56a966 221         },
808328 222       );
223
224       watch(
0b6110 225         () => getSchema.value,
46e087 226         (schema) => {
2882d6 227           nextTick(() => {
V 228             //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
229             modalFn?.redoModalHeight?.();
230           });
46e087 231           if (unref(isInitedDefaultRef)) {
2882d6 232             return;
46e087 233           }
4ff1c4 234           if (schema?.length) {
46e087 235             initDefault();
V 236             isInitedDefaultRef.value = true;
237           }
56a966 238         },
0b6110 239       );
V 240
f96453 241       watch(
1 242         () => formModel,
243         useDebounceFn(() => {
244           unref(getProps).submitOnChange && handleSubmit();
245         }, 300),
246         { deep: true },
247       );
248
4ff1c4 249       async function setProps(formProps: Partial<FormProps>): Promise<void> {
V 250         propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
251       }
252
c7639c 253       function setFormModel(key: string, value: any, schema: FormSchema) {
4ff1c4 254         formModel[key] = value;
3de5b5 255         emit('field-value-change', key, value);
L 256         // TODO 优化验证,这里如果是autoLink=false手动关联的情况下才会再次触发此函数
257         if (schema && schema.itemProps && !schema.itemProps.autoLink) {
571f28 258           validateFields([key]).catch((_) => {});
f84401 259         }
2f6253 260       }
261
9b2d41 262       function handleEnterPress(e: KeyboardEvent) {
N 263         const { autoSubmitOnEnter } = unref(getProps);
264         if (!autoSubmitOnEnter) return;
265         if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
266           const target: HTMLElement = e.target as HTMLElement;
267           if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
268             handleSubmit();
269           }
270         }
271       }
272
1d4561 273       const formActionType: Partial<FormActionType> = {
2f6253 274         getFieldsValue,
275         setFieldsValue,
276         resetFields,
277         updateSchema,
c639e4 278         resetSchema,
2f6253 279         setProps,
768fad 280         removeSchemaByField,
2f6253 281         appendSchemaByField,
282         clearValidate,
4ff1c4 283         validateFields,
V 284         validate,
285         submit: handleSubmit,
286         scrollToField: scrollToField,
2f6253 287       };
1c075a 288
2f6253 289       onMounted(() => {
1c075a 290         initDefault();
1d4561 291         emit('register', formActionType);
2f6253 292       });
1c075a 293
2f6253 294       return {
4f20d4 295         getBindValue,
2f6253 296         handleToggleAdvanced,
9b2d41 297         handleEnterPress,
2f6253 298         formModel,
1c075a 299         defaultValueRef,
2f6253 300         advanceState,
785732 301         getRow,
2f6253 302         getProps,
303         formElRef,
304         getSchema,
e15b4f 305         formActionType: formActionType as any,
4ff1c4 306         setFormModel,
ac1a36 307         getFormClass,
30b3ee 308         getFormActionBindProps: computed(
B 309           () =>
310             ({ ...getProps.value, ...advanceState }) as InstanceType<typeof FormAction>['$props'],
311         ),
58b30a 312         fieldsIsAdvancedMap,
1d4561 313         ...formActionType,
2f6253 314       };
315     },
316   });
317 </script>
ac1a36 318 <style lang="less">
V 319   @prefix-cls: ~'@{namespace}-basic-form';
320
321   .@{prefix-cls} {
322     .ant-form-item {
323       &-label label::after {
324         margin: 0 6px 0 2px;
325       }
326
327       &-with-help {
328         margin-bottom: 0;
329       }
330
331       &:not(.ant-form-item-with-help) {
332         margin-bottom: 20px;
333       }
334
335       &.suffix-item {
336         .ant-form-item-children {
337           display: flex;
338         }
339
67a7a7 340         .ant-form-item-control {
V 341           margin-top: 4px;
342         }
343
ac1a36 344         .suffix {
67a7a7 345           display: inline-flex;
V 346           align-items: center;
ba2415 347           margin-top: 1px;
V 348           padding-left: 6px;
349           line-height: 1;
ac1a36 350         }
V 351       }
352     }
353
354     .ant-form-explain {
355       font-size: 14px;
356     }
357
358     &--compact {
359       .ant-form-item {
08df19 360         margin-bottom: 8px !important;
ac1a36 361       }
V 362     }
363   }
364 </style>