刘光辉
9 小时以前 0dfe84494048ce27ba8449831782128412d3eb13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<script setup lang="ts">
import type { Recordable } from '@vben-core/typings';
 
import type { ExtendedFormApi, VbenFormProps } from './types';
 
// import { toRaw, watch } from 'vue';
import { nextTick, onMounted, watch } from 'vue';
 
import { useForwardPriorityValues } from '@vben-core/composables';
import { cloneDeep, get, isEqual, set } from '@vben-core/shared/utils';
 
import { useDebounceFn } from '@vueuse/core';
 
import FormActions from './components/form-actions.vue';
import { COMPONENT_BIND_EVENT_MAP, COMPONENT_MAP, DEFAULT_FORM_COMMON_CONFIG } from './config';
import { Form } from './form-render';
import { provideComponentRefMap, provideFormProps, useFormInitial } from './use-form-context';
// 通过 extends 会导致热更新卡死,所以重复写了一遍
interface Props extends VbenFormProps {
  formApi: ExtendedFormApi;
}
 
const props = defineProps<Props>();
 
const state = props.formApi?.useStore?.();
 
const forward = useForwardPriorityValues(props, state);
 
const componentRefMap = new Map<string, unknown>();
 
const { delegatedSlots, form } = useFormInitial(forward);
 
provideFormProps([forward, form]);
provideComponentRefMap(componentRefMap);
 
props.formApi?.mount?.(form, componentRefMap);
 
const handleUpdateCollapsed = (value: boolean) => {
  props.formApi?.setState({ collapsed: !!value });
};
 
function handleKeyDownEnter(event: KeyboardEvent) {
  if (!state.value.submitOnEnter || !forward.value.formApi?.isMounted) {
    return;
  }
  // 如果是 textarea 不阻止默认行为,否则会导致无法换行。
  // 跳过 textarea 的回车提交处理
  if (event.target instanceof HTMLTextAreaElement) {
    return;
  }
  event.preventDefault();
 
  forward.value.formApi.validateAndSubmitForm();
}
 
const handleValuesChangeDebounced = useDebounceFn(async () => {
  state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();
}, 300);
 
const valuesCache: Recordable<any> = {};
 
onMounted(async () => {
  // 只在挂载后开始监听,form.values会有一个初始化的过程
  await nextTick();
  watch(
    () => form.values,
    async (newVal) => {
      if (forward.value.handleValuesChange) {
        const fields = state.value.schema?.map((item) => {
          return item.fieldName;
        });
 
        if (fields && fields.length > 0) {
          const changedFields: string[] = [];
          fields.forEach((field) => {
            const newFieldValue = get(newVal, field);
            const oldFieldValue = get(valuesCache, field);
            if (!isEqual(newFieldValue, oldFieldValue)) {
              changedFields.push(field);
              set(valuesCache, field, newFieldValue);
            }
          });
 
          if (changedFields.length > 0) {
            // 调用handleValuesChange回调,传入所有表单值的深拷贝和变更的字段列表
            forward.value.handleValuesChange(cloneDeep(await forward.value.formApi.getValues()), changedFields);
          }
        }
      }
      handleValuesChangeDebounced();
    },
    { deep: true },
  );
});
</script>
 
<template>
  <Form
    @keydown.enter="handleKeyDownEnter"
    v-bind="forward"
    :collapsed="state.collapsed"
    :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" :key="slotName" #[slotName]="slotProps">
      <slot :name="slotName" v-bind="slotProps"></slot>
    </template>
    <template #default="slotProps">
      <slot v-bind="slotProps">
        <FormActions v-if="forward.showDefaultActions" :model-value="state.collapsed" @update:model-value="handleUpdateCollapsed">
          <template #reset-before="resetSlotProps">
            <slot name="reset-before" v-bind="resetSlotProps"></slot>
          </template>
          <template #submit-before="submitSlotProps">
            <slot name="submit-before" v-bind="submitSlotProps"></slot>
          </template>
          <template #expand-before="expandBeforeSlotProps">
            <slot name="expand-before" v-bind="expandBeforeSlotProps"></slot>
          </template>
          <template #expand-after="expandAfterSlotProps">
            <slot name="expand-after" v-bind="expandAfterSlotProps"></slot>
          </template>
        </FormActions>
      </slot>
    </template>
  </Form>
</template>