Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
commit
2e2ffcd59e
@ -310,7 +310,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
|||||||
| actionWrapperClass | 表单操作区域class | `any` | - |
|
| actionWrapperClass | 表单操作区域class | `any` | - |
|
||||||
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||||
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||||
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>,) => void` | - |
|
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |
|
||||||
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
|
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
|
||||||
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
@ -325,6 +325,12 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
|||||||
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
|
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
|
||||||
| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |
|
| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |
|
||||||
|
|
||||||
|
::: tip handleValuesChange
|
||||||
|
|
||||||
|
`handleValuesChange` 回调函数的第一个参数`values`装载了表单改变后的当前值对象,第二个参数`fieldsChanged`是一个数组,包含了所有被改变的字段名。注意:第二个参数仅在v5.5.4(不含)以上版本可用,并且传递的是已在schema中定义的字段名。如果你使用了字段映射并且需要检查是哪些字段发生了变化的话,请注意该参数并不会包含映射后的字段名。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
::: tip fieldMappingTime
|
::: tip fieldMappingTime
|
||||||
|
|
||||||
此属性用于将表单内的数组值映射成 2 个字段,它应当传入一个数组,数组的每一项是一个映射规则,规则的第一个成员是一个字符串,表示需要映射的字段名,第二个成员是一个数组,表示映射后的字段名,第三个成员是一个可选的格式掩码,用于格式化日期时间字段;也可以提供一个格式化函数(参数分别为当前值和当前字段名,返回格式化后的值)。如果明确地将格式掩码设为null,则原值映射而不进行格式化(适用于非日期时间字段)。例如:`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]`,`timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime`和`endTime`字段上。每一项的第三个参数是一个可选的格式掩码,
|
此属性用于将表单内的数组值映射成 2 个字段,它应当传入一个数组,数组的每一项是一个映射规则,规则的第一个成员是一个字符串,表示需要映射的字段名,第二个成员是一个数组,表示映射后的字段名,第三个成员是一个可选的格式掩码,用于格式化日期时间字段;也可以提供一个格式化函数(参数分别为当前值和当前字段名,返回格式化后的值)。如果明确地将格式掩码设为null,则原值映射而不进行格式化(适用于非日期时间字段)。例如:`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]`,`timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime`和`endTime`字段上。每一项的第三个参数是一个可选的格式掩码,
|
||||||
|
@ -62,7 +62,7 @@ async function handleReset(e: Event) {
|
|||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
const props = unref(rootProps);
|
const props = unref(rootProps);
|
||||||
|
|
||||||
const values = toRaw(props.formApi?.getValues());
|
const values = toRaw(await props.formApi?.getValues());
|
||||||
|
|
||||||
if (isFunction(props.handleReset)) {
|
if (isFunction(props.handleReset)) {
|
||||||
await props.handleReset?.(values);
|
await props.handleReset?.(values);
|
||||||
|
@ -378,7 +378,10 @@ export interface VbenFormProps<
|
|||||||
/**
|
/**
|
||||||
* 表单值变化回调
|
* 表单值变化回调
|
||||||
*/
|
*/
|
||||||
handleValuesChange?: (values: Record<string, any>) => void;
|
handleValuesChange?: (
|
||||||
|
values: Record<string, any>,
|
||||||
|
fieldsChanged: string[],
|
||||||
|
) => void;
|
||||||
/**
|
/**
|
||||||
* 重置按钮参数
|
* 重置按钮参数
|
||||||
*/
|
*/
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
import type { ExtendedFormApi, VbenFormProps } from './types';
|
import type { ExtendedFormApi, VbenFormProps } from './types';
|
||||||
|
|
||||||
// import { toRaw, watch } from 'vue';
|
// import { toRaw, watch } from 'vue';
|
||||||
import { nextTick, onMounted, watch } from 'vue';
|
import { nextTick, onMounted, watch } from 'vue';
|
||||||
// import { isFunction } from '@vben-core/shared/utils';
|
|
||||||
|
|
||||||
import { useForwardPriorityValues } from '@vben-core/composables';
|
import { useForwardPriorityValues } from '@vben-core/composables';
|
||||||
import { cloneDeep } from '@vben-core/shared/utils';
|
import { cloneDeep, get, isEqual, set } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
|
||||||
@ -61,16 +62,46 @@ function handleKeyDownEnter(event: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleValuesChangeDebounced = useDebounceFn(async () => {
|
const handleValuesChangeDebounced = useDebounceFn(async () => {
|
||||||
forward.value.handleValuesChange?.(
|
|
||||||
cloneDeep(await forward.value.formApi.getValues()),
|
|
||||||
);
|
|
||||||
state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();
|
state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
|
const valuesCache: Recordable<any> = {};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 只在挂载后开始监听,form.values会有一个初始化的过程
|
// 只在挂载后开始监听,form.values会有一个初始化的过程
|
||||||
await nextTick();
|
await nextTick();
|
||||||
watch(() => form.values, handleValuesChangeDebounced, { deep: true });
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -9,7 +9,11 @@ vi.mock('@vben-core/shared/store', () => {
|
|||||||
return {
|
return {
|
||||||
isFunction: (fn: any) => typeof fn === 'function',
|
isFunction: (fn: any) => typeof fn === 'function',
|
||||||
Store: class {
|
Store: class {
|
||||||
|
get state() {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
private _state: DrawerState;
|
private _state: DrawerState;
|
||||||
|
|
||||||
private options: any;
|
private options: any;
|
||||||
|
|
||||||
constructor(initialState: DrawerState, options: any) {
|
constructor(initialState: DrawerState, options: any) {
|
||||||
@ -25,10 +29,6 @@ vi.mock('@vben-core/shared/store', () => {
|
|||||||
this._state = fn(this._state);
|
this._state = fn(this._state);
|
||||||
this.options.onUpdate();
|
this.options.onUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
get state() {
|
|
||||||
return this._state;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -54,7 +54,6 @@ describe('drawerApi', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should close the drawer if onBeforeClose allows it', () => {
|
it('should close the drawer if onBeforeClose allows it', () => {
|
||||||
drawerApi.open();
|
|
||||||
drawerApi.close();
|
drawerApi.close();
|
||||||
expect(drawerApi.store.state.isOpen).toBe(false);
|
expect(drawerApi.store.state.isOpen).toBe(false);
|
||||||
});
|
});
|
||||||
|
@ -86,7 +86,8 @@ export class DrawerApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭弹窗
|
* 关闭抽屉
|
||||||
|
* @description 关闭抽屉时会调用 onBeforeClose 钩子函数,如果 onBeforeClose 返回 false,则不关闭弹窗
|
||||||
*/
|
*/
|
||||||
async close() {
|
async close() {
|
||||||
// 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗
|
// 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗
|
||||||
|
@ -186,7 +186,7 @@ const getAppendTo = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getForceMount = computed(() => {
|
const getForceMount = computed(() => {
|
||||||
return !unref(destroyOnClose);
|
return !unref(destroyOnClose) && unref(firstOpened);
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleClosed() {
|
function handleClosed() {
|
||||||
|
@ -42,6 +42,9 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
fieldMappingTime: [['rangePicker', ['startTime', 'endTime'], 'YYYY-MM-DD']],
|
fieldMappingTime: [['rangePicker', ['startTime', 'endTime'], 'YYYY-MM-DD']],
|
||||||
// 提交函数
|
// 提交函数
|
||||||
handleSubmit: onSubmit,
|
handleSubmit: onSubmit,
|
||||||
|
handleValuesChange(_values, fieldsChanged) {
|
||||||
|
message.info(`表单以下字段发生变化:${fieldsChanged.join(',')}`);
|
||||||
|
},
|
||||||
|
|
||||||
// 垂直布局,label和input在不同行,值为vertical
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
// 水平布局,label和input在同一行
|
// 水平布局,label和input在同一行
|
||||||
|
@ -37,7 +37,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (valid) {
|
if (valid) {
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
const data = formApi.getValues();
|
const data = await formApi.getValues();
|
||||||
try {
|
try {
|
||||||
await (formData.value?.id
|
await (formData.value?.id
|
||||||
? updateDept(formData.value.id, data)
|
? updateDept(formData.value.id, data)
|
||||||
|
Loading…
Reference in New Issue
Block a user