BasicForm.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <template>
  2. <Form v-bind="{ ...$attrs, ...$props }" :class="getFormClass" ref="formElRef" :model="formModel">
  3. <Row :style="getRowWrapStyle">
  4. <slot name="formHeader"></slot>
  5. <template v-for="schema in getSchema" :key="schema.field">
  6. <FormItem
  7. :tableAction="tableAction"
  8. :formActionType="formActionType"
  9. :schema="schema"
  10. :formProps="getProps"
  11. :allDefaultValues="defaultValueRef"
  12. :formModel="formModel"
  13. :setFormModel="setFormModel"
  14. >
  15. <template #[item]="data" v-for="item in Object.keys($slots)">
  16. <slot :name="item" v-bind="data"></slot>
  17. </template>
  18. </FormItem>
  19. </template>
  20. <FormAction v-bind="{ ...getProps, ...advanceState }" @toggle-advanced="handleToggleAdvanced">
  21. <template
  22. #[item]="data"
  23. v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
  24. >
  25. <slot :name="item" v-bind="data"></slot>
  26. </template>
  27. </FormAction>
  28. <slot name="formFooter"></slot>
  29. </Row>
  30. </Form>
  31. </template>
  32. <script lang="ts">
  33. import type { FormActionType, FormProps, FormSchema } from './types/form';
  34. import type { AdvanceState } from './types/hooks';
  35. import type { CSSProperties, Ref } from 'vue';
  36. import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
  37. import { Form, Row } from 'ant-design-vue';
  38. import FormItem from './components/FormItem.vue';
  39. import FormAction from './components/FormAction.vue';
  40. import { dateItemType } from './helper';
  41. import { dateUtil } from '/@/utils/dateUtil';
  42. // import { cloneDeep } from 'lodash-es';
  43. import { deepMerge } from '/@/utils';
  44. import { useFormValues } from './hooks/useFormValues';
  45. import useAdvanced from './hooks/useAdvanced';
  46. import { useFormEvents } from './hooks/useFormEvents';
  47. import { createFormContext } from './hooks/useFormContext';
  48. import { useAutoFocus } from './hooks/useAutoFocus';
  49. import { useModalContext } from '/@/components/Modal';
  50. import { basicProps } from './props';
  51. import { useDesign } from '/@/hooks/web/useDesign';
  52. export default defineComponent({
  53. name: 'BasicForm',
  54. components: { FormItem, Form, Row, FormAction },
  55. props: basicProps,
  56. emits: ['advanced-change', 'reset', 'submit', 'register'],
  57. setup(props, { emit }) {
  58. const formModel = reactive<Recordable>({});
  59. const modalFn = useModalContext();
  60. const advanceState = reactive<AdvanceState>({
  61. isAdvanced: true,
  62. hideAdvanceBtn: false,
  63. isLoad: false,
  64. actionSpan: 6,
  65. });
  66. const defaultValueRef = ref<Recordable>({});
  67. const isInitedDefaultRef = ref(false);
  68. const propsRef = ref<Partial<FormProps>>({});
  69. const schemaRef = ref<Nullable<FormSchema[]>>(null);
  70. const formElRef = ref<Nullable<FormActionType>>(null);
  71. const { prefixCls } = useDesign('basic-form');
  72. // Get the basic configuration of the form
  73. const getProps = computed(
  74. (): FormProps => {
  75. return { ...props, ...unref(propsRef) } as FormProps;
  76. }
  77. );
  78. const getFormClass = computed(() => {
  79. return [
  80. prefixCls,
  81. {
  82. [`${prefixCls}--compact`]: unref(getProps).compact,
  83. },
  84. ];
  85. });
  86. // Get uniform row style
  87. const getRowWrapStyle = computed(
  88. (): CSSProperties => {
  89. const { baseRowStyle = {} } = unref(getProps);
  90. return baseRowStyle;
  91. }
  92. );
  93. const getSchema = computed((): FormSchema[] => {
  94. const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
  95. for (const schema of schemas) {
  96. const { defaultValue, component } = schema;
  97. // handle date type
  98. if (defaultValue && dateItemType.includes(component)) {
  99. if (!Array.isArray(defaultValue)) {
  100. schema.defaultValue = dateUtil(defaultValue);
  101. } else {
  102. const def: moment.Moment[] = [];
  103. defaultValue.forEach((item) => {
  104. def.push(dateUtil(item));
  105. });
  106. schema.defaultValue = def;
  107. }
  108. }
  109. }
  110. return schemas as FormSchema[];
  111. });
  112. const { handleToggleAdvanced } = useAdvanced({
  113. advanceState,
  114. emit,
  115. getProps,
  116. getSchema,
  117. formModel,
  118. defaultValueRef,
  119. });
  120. const { handleFormValues, initDefault } = useFormValues({
  121. getProps,
  122. defaultValueRef,
  123. getSchema,
  124. formModel,
  125. });
  126. useAutoFocus({
  127. getSchema,
  128. getProps,
  129. isInitedDefault: isInitedDefaultRef,
  130. formElRef: formElRef as Ref<FormActionType>,
  131. });
  132. const {
  133. handleSubmit,
  134. setFieldsValue,
  135. clearValidate,
  136. validate,
  137. validateFields,
  138. getFieldsValue,
  139. updateSchema,
  140. resetSchema,
  141. appendSchemaByField,
  142. removeSchemaByFiled,
  143. resetFields,
  144. scrollToField,
  145. } = useFormEvents({
  146. emit,
  147. getProps,
  148. formModel,
  149. getSchema,
  150. defaultValueRef,
  151. formElRef: formElRef as Ref<FormActionType>,
  152. schemaRef: schemaRef as Ref<FormSchema[]>,
  153. handleFormValues,
  154. });
  155. createFormContext({
  156. resetAction: resetFields,
  157. submitAction: handleSubmit,
  158. });
  159. watch(
  160. () => unref(getProps).model,
  161. () => {
  162. const { model } = unref(getProps);
  163. if (!model) return;
  164. setFieldsValue(model);
  165. },
  166. {
  167. immediate: true,
  168. }
  169. );
  170. watch(
  171. () => getSchema.value,
  172. (schema) => {
  173. nextTick(() => {
  174. // Solve the problem of modal adaptive height calculation when the form is placed in the modal
  175. modalFn?.redoModalHeight?.();
  176. });
  177. if (unref(isInitedDefaultRef)) {
  178. return;
  179. }
  180. if (schema?.length) {
  181. initDefault();
  182. isInitedDefaultRef.value = true;
  183. }
  184. }
  185. );
  186. async function setProps(formProps: Partial<FormProps>): Promise<void> {
  187. propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
  188. }
  189. function setFormModel(key: string, value: any) {
  190. formModel[key] = value;
  191. }
  192. const formActionType: Partial<FormActionType> = {
  193. getFieldsValue,
  194. setFieldsValue,
  195. resetFields,
  196. updateSchema,
  197. resetSchema,
  198. setProps,
  199. removeSchemaByFiled,
  200. appendSchemaByField,
  201. clearValidate,
  202. validateFields,
  203. validate,
  204. submit: handleSubmit,
  205. scrollToField: scrollToField,
  206. };
  207. onMounted(() => {
  208. initDefault();
  209. emit('register', formActionType);
  210. });
  211. return {
  212. handleToggleAdvanced,
  213. formModel,
  214. defaultValueRef,
  215. advanceState,
  216. getRowWrapStyle,
  217. getProps,
  218. formElRef,
  219. getSchema,
  220. formActionType,
  221. setFormModel,
  222. prefixCls,
  223. getFormClass,
  224. ...formActionType,
  225. };
  226. },
  227. });
  228. </script>
  229. <style lang="less">
  230. @prefix-cls: ~'@{namespace}-basic-form';
  231. .@{prefix-cls} {
  232. .ant-form-item {
  233. &-label label::after {
  234. margin: 0 6px 0 2px;
  235. }
  236. &-with-help {
  237. margin-bottom: 0;
  238. }
  239. &:not(.ant-form-item-with-help) {
  240. margin-bottom: 20px;
  241. }
  242. &.suffix-item {
  243. .ant-form-item-children {
  244. display: flex;
  245. }
  246. .ant-form-item-control {
  247. margin-top: 4px;
  248. }
  249. .suffix {
  250. display: inline-flex;
  251. padding-left: 6px;
  252. margin-top: 1px;
  253. line-height: 1;
  254. align-items: center;
  255. }
  256. }
  257. }
  258. .ant-form-explain {
  259. font-size: 14px;
  260. }
  261. &--compact {
  262. .ant-form-item {
  263. margin-bottom: 8px !important;
  264. }
  265. }
  266. }
  267. </style>