Commit a305e59124f4cc88aaf6ec85a13fc998a18b9471

Authored by vben
1 parent 08df1987

fix(form): form validate error

CHANGELOG.zh_CN.md
@@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
11 - form: 新增`suffix`属性,用于配置后缀内容 11 - form: 新增`suffix`属性,用于配置后缀内容
12 - form: 新增远程下拉`ApiSelect`及示例 12 - form: 新增远程下拉`ApiSelect`及示例
13 - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框 13 - form: 新增`autoFocusFirstItem`配置。用于配置是否聚焦表单第一个输入框
  14 +- useForm: 支持动态改变参数。可以传入`Ref`类型与`Computed`类型进行动态更改
14 15
15 ### ⚡ Performance Improvements 16 ### ⚡ Performance Improvements
16 17
@@ -18,7 +19,7 @@ @@ -18,7 +19,7 @@
18 19
19 ### 🎫 Chores 20 ### 🎫 Chores
20 21
21 -- 升级`ant-design-vue`到`2.0.0-rc.6` 22 +- 升级`ant-design-vue`到`2.0.0-rc.7`
22 23
23 ### 🐛 Bug Fixes 24 ### 🐛 Bug Fixes
24 25
package.json
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 "dependencies": { 23 "dependencies": {
24 "@iconify/iconify": "^2.0.0-rc.4", 24 "@iconify/iconify": "^2.0.0-rc.4",
25 "@vueuse/core": "^4.0.1", 25 "@vueuse/core": "^4.0.1",
26 - "ant-design-vue": "^2.0.0-rc.6", 26 + "ant-design-vue": "^2.0.0-rc.7",
27 "apexcharts": "^3.23.0", 27 "apexcharts": "^3.23.0",
28 "axios": "^0.21.1", 28 "axios": "^0.21.1",
29 "crypto-es": "^1.2.6", 29 "crypto-es": "^1.2.6",
src/components/Form/src/BasicForm.vue
@@ -31,14 +31,24 @@ @@ -31,14 +31,24 @@
31 import type { AdvanceState } from './types/hooks'; 31 import type { AdvanceState } from './types/hooks';
32 import type { CSSProperties, Ref, WatchStopHandle } from 'vue'; 32 import type { CSSProperties, Ref, WatchStopHandle } from 'vue';
33 33
34 - import { defineComponent, reactive, ref, computed, unref, onMounted, watch, toRefs } from 'vue'; 34 + import {
  35 + defineComponent,
  36 + reactive,
  37 + ref,
  38 + computed,
  39 + unref,
  40 + onMounted,
  41 + watch,
  42 + toRefs,
  43 + toRaw,
  44 + } from 'vue';
35 import { Form, Row } from 'ant-design-vue'; 45 import { Form, Row } from 'ant-design-vue';
36 import FormItem from './components/FormItem'; 46 import FormItem from './components/FormItem';
37 import FormAction from './components/FormAction.vue'; 47 import FormAction from './components/FormAction.vue';
38 48
39 import { dateItemType } from './helper'; 49 import { dateItemType } from './helper';
40 import moment from 'moment'; 50 import moment from 'moment';
41 - import { cloneDeep } from 'lodash-es'; 51 + // import { cloneDeep } from 'lodash-es';
42 import { deepMerge } from '/@/utils'; 52 import { deepMerge } from '/@/utils';
43 53
44 import { useFormValues } from './hooks/useFormValues'; 54 import { useFormValues } from './hooks/useFormValues';
@@ -76,7 +86,7 @@ @@ -76,7 +86,7 @@
76 // Get the basic configuration of the form 86 // Get the basic configuration of the form
77 const getProps = computed( 87 const getProps = computed(
78 (): FormProps => { 88 (): FormProps => {
79 - return deepMerge(cloneDeep(props), unref(propsRef)); 89 + return { ...props, ...unref(propsRef) } as FormProps;
80 } 90 }
81 ); 91 );
82 92
src/components/Form/src/components/ApiSelect.vue
@@ -38,7 +38,7 @@ @@ -38,7 +38,7 @@
38 type: String as PropType<string>, 38 type: String as PropType<string>,
39 }, 39 },
40 api: { 40 api: {
41 - type: Function as PropType<(arg: Recordable) => Promise<OptionsItem[]>>, 41 + type: Function as PropType<(arg?: Recordable) => Promise<OptionsItem[]>>,
42 default: null, 42 default: null,
43 }, 43 },
44 params: { 44 params: {
src/components/Form/src/hooks/useForm.ts
1 -import { ref, onUnmounted, unref, nextTick } from 'vue'; 1 +import { ref, onUnmounted, unref, nextTick, watchEffect } from 'vue';
2 2
3 import { isInSetup } from '/@/utils/helper/vueHelper'; 3 import { isInSetup } from '/@/utils/helper/vueHelper';
4 import { isProdMode } from '/@/utils/env'; 4 import { isProdMode } from '/@/utils/env';
5 import { error } from '/@/utils/log'; 5 import { error } from '/@/utils/log';
  6 +import { getDynamicProps } from '/@/utils';
6 7
7 import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'; 8 import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
8 import type { NamePath } from 'ant-design-vue/lib/form/interface'; 9 import type { NamePath } from 'ant-design-vue/lib/form/interface';
  10 +import type { DynamicProps } from '/@/types/utils';
9 11
10 export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>; 12 export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;
11 13
12 -export function useForm(props?: Partial<FormProps>): UseFormReturnType { 14 +type Props = Partial<DynamicProps<FormProps>>;
  15 +
  16 +export function useForm(props?: Props): UseFormReturnType {
13 isInSetup(); 17 isInSetup();
14 18
15 const formRef = ref<Nullable<FormActionType>>(null); 19 const formRef = ref<Nullable<FormActionType>>(null);
@@ -25,6 +29,7 @@ export function useForm(props?: Partial&lt;FormProps&gt;): UseFormReturnType { @@ -25,6 +29,7 @@ export function useForm(props?: Partial&lt;FormProps&gt;): UseFormReturnType {
25 await nextTick(); 29 await nextTick();
26 return form as FormActionType; 30 return form as FormActionType;
27 } 31 }
  32 +
28 function register(instance: FormActionType) { 33 function register(instance: FormActionType) {
29 isProdMode() && 34 isProdMode() &&
30 onUnmounted(() => { 35 onUnmounted(() => {
@@ -34,8 +39,12 @@ export function useForm(props?: Partial&lt;FormProps&gt;): UseFormReturnType { @@ -34,8 +39,12 @@ export function useForm(props?: Partial&lt;FormProps&gt;): UseFormReturnType {
34 if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return; 39 if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
35 40
36 formRef.value = instance; 41 formRef.value = instance;
37 - props && instance.setProps(props); 42 +
38 loadedRef.value = true; 43 loadedRef.value = true;
  44 +
  45 + watchEffect(() => {
  46 + props && instance.setProps(getDynamicProps(props));
  47 + });
39 } 48 }
40 49
41 const methods: FormActionType = { 50 const methods: FormActionType = {
src/components/Form/src/hooks/useFormEvents.ts
@@ -178,12 +178,10 @@ export function useFormEvents({ @@ -178,12 +178,10 @@ export function useFormEvents({
178 } 178 }
179 179
180 async function validateFields(nameList?: NamePath[] | undefined) { 180 async function validateFields(nameList?: NamePath[] | undefined) {
181 - const res = await unref(formElRef)?.validateFields(nameList || []);  
182 - return res; 181 + return unref(formElRef)?.validateFields(nameList);
183 } 182 }
184 -  
185 async function validate(nameList?: NamePath[] | undefined) { 183 async function validate(nameList?: NamePath[] | undefined) {
186 - return await unref(formElRef)?.validate(nameList || []); 184 + return await unref(formElRef)?.validate(nameList);
187 } 185 }
188 186
189 async function clearValidate(name?: string | string[]) { 187 async function clearValidate(name?: string | string[]) {
src/components/Form/src/hooks/useLabelWidth.ts
@@ -18,6 +18,9 @@ export function useItemLabelWidth(schemaItemRef: Ref&lt;FormSchema&gt;, propsRef: Ref&lt; @@ -18,6 +18,9 @@ export function useItemLabelWidth(schemaItemRef: Ref&lt;FormSchema&gt;, propsRef: Ref&lt;
18 18
19 // If labelWidth is set globally, all items setting 19 // If labelWidth is set globally, all items setting
20 if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) { 20 if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
  21 + labelCol.style = {
  22 + textAlign: 'left',
  23 + };
21 return { labelCol, wrapperCol }; 24 return { labelCol, wrapperCol };
22 } 25 }
23 let width = labelWidth || globalLabelWidth; 26 let width = labelWidth || globalLabelWidth;
@@ -27,6 +30,7 @@ export function useItemLabelWidth(schemaItemRef: Ref&lt;FormSchema&gt;, propsRef: Ref&lt; @@ -27,6 +30,7 @@ export function useItemLabelWidth(schemaItemRef: Ref&lt;FormSchema&gt;, propsRef: Ref&lt;
27 if (width) { 30 if (width) {
28 width = isNumber(width) ? `${width}px` : width; 31 width = isNumber(width) ? `${width}px` : width;
29 } 32 }
  33 +
30 return { 34 return {
31 labelCol: { style: { width }, ...col }, 35 labelCol: { style: { width }, ...col },
32 wrapperCol: { style: { width: `calc(100% - ${width})` }, ...wrapCol }, 36 wrapperCol: { style: { width: `calc(100% - ${width})` }, ...wrapCol },
src/components/Table/src/BasicTable.vue
@@ -68,8 +68,9 @@ @@ -68,8 +68,9 @@
68 import { useEventListener } from '/@/hooks/event/useEventListener'; 68 import { useEventListener } from '/@/hooks/event/useEventListener';
69 import { basicProps } from './props'; 69 import { basicProps } from './props';
70 import { ROW_KEY } from './const'; 70 import { ROW_KEY } from './const';
71 - import './style/index.less';  
72 import { useExpose } from '/@/hooks/core/useExpose'; 71 import { useExpose } from '/@/hooks/core/useExpose';
  72 +
  73 + import './style/index.less';
73 export default defineComponent({ 74 export default defineComponent({
74 props: basicProps, 75 props: basicProps,
75 components: { Table, BasicForm }, 76 components: { Table, BasicForm },
@@ -87,6 +88,12 @@ @@ -87,6 +88,12 @@
87 } as BasicTableProps; 88 } as BasicTableProps;
88 }); 89 });
89 90
  91 + // const getProps = computed(
  92 + // (): FormProps => {
  93 + // return deepMerge(toRaw(props), unref(innerPropsRef));
  94 + // }
  95 + // );
  96 +
90 const { loadingRef } = useLoading(getMergeProps); 97 const { loadingRef } = useLoading(getMergeProps);
91 const { getPaginationRef, setPagination } = usePagination(getMergeProps); 98 const { getPaginationRef, setPagination } = usePagination(getMergeProps);
92 const { getColumnsRef, setColumns } = useColumns(getMergeProps, getPaginationRef); 99 const { getColumnsRef, setColumns } = useColumns(getMergeProps, getPaginationRef);
@@ -299,8 +306,8 @@ @@ -299,8 +306,8 @@
299 loadingRef.value = loading; 306 loadingRef.value = loading;
300 }, 307 },
301 setProps, 308 setProps,
302 - getSize: (): SizeType => {  
303 - return unref(getBindValues).size; 309 + getSize: () => {
  310 + return unref(getBindValues).size as SizeType;
304 }, 311 },
305 }; 312 };
306 313
src/components/Table/src/components/EditTableHeaderIcon.vue
@@ -16,6 +16,5 @@ @@ -16,6 +16,5 @@
16 default: '', 16 default: '',
17 }, 17 },
18 }, 18 },
19 - setup() {},  
20 }); 19 });
21 </script> 20 </script>
src/components/Table/src/components/TableSetting.vue
@@ -90,7 +90,6 @@ @@ -90,7 +90,6 @@
90 SettingOutlined, 90 SettingOutlined,
91 } from '@ant-design/icons-vue'; 91 } from '@ant-design/icons-vue';
92 import { useFullscreen } from '/@/hooks/web/useFullScreen'; 92 import { useFullscreen } from '/@/hooks/web/useFullScreen';
93 -  
94 import type { SizeType, TableSetting } from '../types/table'; 93 import type { SizeType, TableSetting } from '../types/table';
95 import { useI18n } from '/@/hooks/web/useI18n'; 94 import { useI18n } from '/@/hooks/web/useI18n';
96 95
@@ -150,6 +149,7 @@ @@ -150,6 +149,7 @@
150 init(); 149 init();
151 } 150 }
152 }); 151 });
  152 +
153 function init() { 153 function init() {
154 let ret: Options[] = []; 154 let ret: Options[] = [];
155 table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => { 155 table.getColumns({ ignoreIndex: true, ignoreAction: true }).forEach((item) => {
src/components/Table/src/components/TableTitle.vue
@@ -13,10 +13,10 @@ @@ -13,10 +13,10 @@
13 components: { BasicTitle }, 13 components: { BasicTitle },
14 props: { 14 props: {
15 title: { 15 title: {
16 - type: [Function, String] as PropType<string | ((data: any) => string)>, 16 + type: [Function, String] as PropType<string | ((data: Recordable) => string)>,
17 }, 17 },
18 getSelectRows: { 18 getSelectRows: {
19 - type: Function as PropType<() => any[]>, 19 + type: Function as PropType<() => Recordable[]>,
20 }, 20 },
21 helpMessage: { 21 helpMessage: {
22 type: [String, Array] as PropType<string | string[]>, 22 type: [String, Array] as PropType<string | string[]>,
src/components/Table/src/components/renderExpandIcon.tsx
1 import { BasicArrow } from '/@/components/Basic'; 1 import { BasicArrow } from '/@/components/Basic';
2 2
3 export default () => { 3 export default () => {
4 - return (props: any) => { 4 + return (props: Recordable) => {
5 return ( 5 return (
6 <BasicArrow 6 <BasicArrow
7 onClick={(e: Event) => { 7 onClick={(e: Event) => {
src/components/Table/src/hooks/useTable.ts
1 import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table'; 1 import type { BasicTableProps, TableActionType, FetchParams, BasicColumn } from '../types/table';
2 import type { PaginationProps } from '../types/pagination'; 2 import type { PaginationProps } from '../types/pagination';
3 3
4 -import { ref, getCurrentInstance, onUnmounted, unref } from 'vue'; 4 +import { ref, onUnmounted, unref } from 'vue';
5 import { isProdMode } from '/@/utils/env'; 5 import { isProdMode } from '/@/utils/env';
  6 +import { isInSetup } from '/@/utils/helper/vueHelper';
6 7
7 export function useTable( 8 export function useTable(
8 tableProps?: Partial<BasicTableProps> 9 tableProps?: Partial<BasicTableProps>
9 ): [(instance: TableActionType) => void, TableActionType] { 10 ): [(instance: TableActionType) => void, TableActionType] {
10 - if (!getCurrentInstance()) {  
11 - throw new Error('Please put useTable function in the setup function!');  
12 - } 11 + isInSetup();
13 12
14 - const tableRef = ref<TableActionType | null>(null);  
15 - const loadedRef = ref<boolean | null>(false); 13 + const tableRef = ref<Nullable<TableActionType>>(null);
  14 + const loadedRef = ref<Nullable<boolean>>(false);
16 15
17 function register(instance: TableActionType) { 16 function register(instance: TableActionType) {
18 onUnmounted(() => { 17 onUnmounted(() => {
19 tableRef.value = null; 18 tableRef.value = null;
20 loadedRef.value = null; 19 loadedRef.value = null;
21 }); 20 });
  21 +
22 if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) { 22 if (unref(loadedRef) && isProdMode() && instance === unref(tableRef)) {
23 return; 23 return;
24 } 24 }
src/layouts/default/header/components/lock/LockAction.tsx
@@ -57,7 +57,7 @@ export default defineComponent({ @@ -57,7 +57,7 @@ export default defineComponent({
57 <p class={`${prefixCls}__header-name`}>{userStore.getUserInfoState.realName}</p> 57 <p class={`${prefixCls}__header-name`}>{userStore.getUserInfoState.realName}</p>
58 </div> 58 </div>
59 59
60 - <BasicForm onRegister={registerForm} layout="vertical" /> 60 + <BasicForm onRegister={registerForm} />
61 61
62 <div class={`${prefixCls}__footer`}> 62 <div class={`${prefixCls}__footer`}>
63 <Button type="primary" block class="mt-2" onClick={lock}> 63 <Button type="primary" block class="mt-2" onClick={lock}>
src/layouts/default/header/components/lock/LockModal.vue
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 <p :class="`${prefixCls}__header-name`">{{ getRealName }}</p> 12 <p :class="`${prefixCls}__header-name`">{{ getRealName }}</p>
13 </div> 13 </div>
14 14
15 - <BasicForm @register="registerForm" layout="vertical" /> 15 + <BasicForm @register="registerForm" />
16 16
17 <div :class="`${prefixCls}__footer`"> 17 <div :class="`${prefixCls}__footer`">
18 <a-button type="primary" block class="mt-2" @click="handleLock"> 18 <a-button type="primary" block class="mt-2" @click="handleLock">
src/types/utils.ts 0 → 100644
  1 +import type { ComputedRef, Ref } from 'vue';
  2 +
  3 +export type DynamicProps<T> = {
  4 + [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>;
  5 +};
src/utils/index.ts
1 export const timestamp = () => +Date.now(); 1 export const timestamp = () => +Date.now();
  2 +import { unref } from 'vue';
2 import { isObject } from '/@/utils/is'; 3 import { isObject } from '/@/utils/is';
3 export const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n)); 4 export const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));
4 export const noop = () => {}; 5 export const noop = () => {};
@@ -76,3 +77,14 @@ export function openWindow( @@ -76,3 +77,14 @@ export function openWindow(
76 77
77 window.open(url, target, feature.join(',')); 78 window.open(url, target, feature.join(','));
78 } 79 }
  80 +
  81 +// dynamic use hook props
  82 +export function getDynamicProps<T, U>(props: T): Partial<U> {
  83 + const ret: Recordable = {};
  84 +
  85 + Object.keys(props).map((key) => {
  86 + ret[key] = unref((props as Recordable)[key]);
  87 + });
  88 +
  89 + return ret as Partial<U>;
  90 +}
src/views/demo/form/UseForm.vue
@@ -162,6 +162,7 @@ @@ -162,6 +162,7 @@
162 components: { BasicForm, CollapseContainer }, 162 components: { BasicForm, CollapseContainer },
163 setup() { 163 setup() {
164 const { createMessage } = useMessage(); 164 const { createMessage } = useMessage();
  165 +
165 const [register, { setProps }] = useForm({ 166 const [register, { setProps }] = useForm({
166 labelWidth: 120, 167 labelWidth: 120,
167 schemas, 168 schemas,
@@ -172,7 +173,7 @@ @@ -172,7 +173,7 @@
172 return { 173 return {
173 register, 174 register,
174 schemas, 175 schemas,
175 - handleSubmit: (values: any) => { 176 + handleSubmit: (values: Recordable) => {
176 createMessage.success('click search,values:' + JSON.stringify(values)); 177 createMessage.success('click search,values:' + JSON.stringify(values));
177 }, 178 },
178 setProps, 179 setProps,
src/views/demo/page/form/high/index.vue
@@ -6,10 +6,10 @@ @@ -6,10 +6,10 @@
6 6
7 <div class="m-5"> 7 <div class="m-5">
8 <a-card title="仓库管理" :bordered="false"> 8 <a-card title="仓库管理" :bordered="false">
9 - <BasicForm @register="register" layout="vertical" /> 9 + <BasicForm @register="register" />
10 </a-card> 10 </a-card>
11 <a-card title="任务管理" :bordered="false" class="mt-5"> 11 <a-card title="任务管理" :bordered="false" class="mt-5">
12 - <BasicForm @register="registerTask" layout="vertical" /> 12 + <BasicForm @register="registerTask" />
13 </a-card> 13 </a-card>
14 <a-card title="成员管理" :bordered="false" class="mt-5"> 14 <a-card title="成员管理" :bordered="false" class="mt-5">
15 <PersonTable ref="tableRef" /> 15 <PersonTable ref="tableRef" />
yarn.lock
@@ -1956,10 +1956,10 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: @@ -1956,10 +1956,10 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
1956 dependencies: 1956 dependencies:
1957 color-convert "^2.0.1" 1957 color-convert "^2.0.1"
1958 1958
1959 -ant-design-vue@^2.0.0-rc.6:  
1960 - version "2.0.0-rc.6"  
1961 - resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.0-rc.6.tgz#f25f61cde1c75c32a78b536751731c0b223b6590"  
1962 - integrity sha512-NRxzIC4CSM56MXYHdg3K2oTc+pkcSJd6BJtIBCxUsbFfbBGp+F7ei7C1bQDdHHos3o/Oe2iqGwzfrZ7+Ot2Uew== 1959 +ant-design-vue@^2.0.0-rc.7:
  1960 + version "2.0.0-rc.7"
  1961 + resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.0-rc.7.tgz#5d83a7f13275574ec1fc1ea8c1fe8d9aa6de067c"
  1962 + integrity sha512-QMStvwaLfV1Q3RaQ8D926aCkW6iqWBHXlNv7dBdTPvU8eeFXPaPKenLu1OTpSi+wpCncJqgumFOEcENPvh0nKw==
1963 dependencies: 1963 dependencies:
1964 "@ant-design-vue/use" "^0.0.1-0" 1964 "@ant-design-vue/use" "^0.0.1-0"
1965 "@ant-design/icons-vue" "^5.1.7" 1965 "@ant-design/icons-vue" "^5.1.7"