这是基于vue-vben-admin 模板适用于abp Vnext的前端管理项目
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

407 lines
11 KiB

<template>
<Form
v-bind="getBindValue"
:class="getFormClass"
ref="formElRef"
:model="formModel"
@keypress.enter="handleEnterPress"
>
<Tabs
v-model:activeKey="activedTabKey"
:style="tabsStyle.style"
:tabBarStyle="tabsStyle.tabBarStyle"
>
<!-- fix bug: forceRender 必须强制渲染,否则form验证会失效 -->
<TabPane
v-for="tabSchema in getTabSchema"
:key="tabSchema.key"
:tab="tabSchema.key"
:forceRender="true"
>
<Row v-bind="getRow">
<slot name="formHeader"></slot>
<template v-for="schema in tabSchema.schemas" :key="schema.field">
<FormItem
:tableAction="tableAction"
:formActionType="formActionType"
:schema="schema"
:formProps="getProps"
:allDefaultValues="defaultValueRef"
:formModel="formModel"
:setFormModel="setFormModel"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</FormItem>
</template>
</Row>
</TabPane>
</Tabs>
<Row v-bind="getRow">
<FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
<template
#[item]="data"
v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
>
<slot :name="item" v-bind="data || {}"></slot>
</template>
</FormAction>
<slot name="formFooter"></slot>
</Row>
</Form>
</template>
<script lang="ts">
import type { TabFormActionType, TabFormProps, TabFormSchema } from './types/form';
import type { AdvanceState } from './types/hooks';
import type { Ref } from 'vue';
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
import { Form, Row, Tabs } from 'ant-design-vue';
import FormItem from './components/FormItem.vue';
import FormAction from './components/FormAction.vue';
import { dateItemType } from './helper';
import { dateUtil } from '/@/utils/dateUtil';
import { groupBy } from '/@/utils/array';
// import { cloneDeep } from 'lodash-es';
import { deepMerge } from '/@/utils';
import { useFormValues } from './hooks/useFormValues';
import useAdvanced from './hooks/useAdvanced';
import { useFormEvents } from './hooks/useFormEvents';
import { createFormContext } from './hooks/useFormContext';
import { useAutoFocus } from './hooks/useAutoFocus';
import { useTabsStyle } from '/@/hooks/component/useStyles';
import { useModalContext } from '/@/components/Modal';
import { useDebounceFn } from '@vueuse/core';
import { tabProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'TabForm',
components: {
Form,
FormItem,
FormAction,
Row,
Tabs,
TabPane: Tabs.TabPane,
},
props: tabProps,
emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
setup(props, { emit, attrs }) {
const formModel = reactive<Recordable>({});
const modalFn = useModalContext();
const tabsStyle = useTabsStyle();
const advanceState = reactive<AdvanceState>({
isAdvanced: true,
hideAdvanceBtn: false,
isLoad: false,
actionSpan: 6,
});
const defaultValueRef = ref<Recordable>({});
const isInitedDefaultRef = ref(false);
const activedTabKey = ref('');
const propsRef = ref<Partial<TabFormProps>>({});
const schemaRef = ref<Nullable<TabFormSchema[]>>(null);
const formElRef = ref<Nullable<TabFormActionType>>(null);
const { prefixCls } = useDesign('basic-form');
// Get the basic configuration of the form
const getProps = computed((): TabFormProps => {
return { ...props, ...unref(propsRef) } as TabFormProps;
});
const getFormClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--compact`]: unref(getProps).compact,
},
];
});
// Get uniform row style and Row configuration for the entire form
const getRow = computed((): Recordable => {
const { baseRowStyle = {}, rowProps } = unref(getProps);
return {
style: baseRowStyle,
...rowProps,
};
});
const getBindValue = computed(
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable),
);
const getSchema = computed((): TabFormSchema[] => {
const schemas: TabFormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
for (const schema of schemas) {
const { defaultValue, component } = schema;
// handle date type
if (defaultValue && dateItemType.includes(component)) {
if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue);
} else {
const def: any[] = [];
defaultValue.forEach((item) => {
def.push(dateUtil(item));
});
schema.defaultValue = def;
}
}
}
if (unref(getProps).showAdvancedButton) {
return schemas.filter((schema) => schema.component !== 'Divider');
} else {
return schemas;
}
});
const getTabSchema = computed((): { key: string; schemas: TabFormSchema[] }[] => {
// const schemas = unref(getSchema);
const tabSchemas: { key: string; schemas: TabFormSchema[] }[] = [];
const group = groupBy(getSchema.value, 'tab');
Object.keys(group).forEach((key) => {
tabSchemas.push({
key: key,
schemas: group[key],
});
});
return tabSchemas;
});
const { handleToggleAdvanced } = useAdvanced({
advanceState,
emit,
getProps,
getSchema,
formModel,
defaultValueRef,
});
const { handleFormValues, initDefault } = useFormValues({
getProps,
defaultValueRef,
getSchema,
formModel,
});
useAutoFocus({
getSchema,
getProps,
isInitedDefault: isInitedDefaultRef,
formElRef: formElRef as Ref<TabFormActionType>,
});
const {
handleSubmit,
setFieldsValue,
clearValidate,
validate,
validateFields,
getFieldsValue,
updateSchema,
resetSchema,
appendSchemaByField,
removeSchemaByFiled,
resetFields,
scrollToField,
} = useFormEvents({
emit,
getProps,
formModel,
getSchema,
defaultValueRef,
formElRef: formElRef as Ref<TabFormActionType>,
schemaRef: schemaRef as Ref<TabFormSchema[]>,
handleFormValues,
});
createFormContext({
resetAction: resetFields,
submitAction: handleSubmit,
});
watch(
() => unref(getProps).model,
() => {
const { model, schemas } = unref(getProps);
if (schemas?.length) {
activedTabKey.value = schemas[0].tab;
}
if (!model) return;
setFieldsValue(model);
},
{
immediate: true,
},
);
watch(
() => unref(getProps).schemas,
(schemas) => {
resetSchema(schemas ?? []);
if (schemas?.length) {
activedTabKey.value = schemas[0].tab;
}
},
);
watch(
() => getSchema.value,
(schema) => {
nextTick(() => {
// Solve the problem of modal adaptive height calculation when the form is placed in the modal
modalFn?.redoModalHeight?.();
});
if (unref(isInitedDefaultRef)) {
return;
}
if (schema?.length) {
initDefault();
isInitedDefaultRef.value = true;
activedTabKey.value = schema[0].tab;
}
},
);
watch(
() => formModel,
useDebounceFn(() => {
unref(getProps).submitOnChange && handleSubmit();
}, 300),
{ deep: true },
);
async function setProps(formProps: Partial<TabFormProps>): Promise<void> {
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
}
function setFormModel(key: string, value: any) {
formModel[key] = value;
const { validateTrigger } = unref(getBindValue);
if (!validateTrigger || validateTrigger === 'change') {
validateFields([key]).catch((_) => {});
}
emit('field-value-change', key, value);
}
function handleEnterPress(e: KeyboardEvent) {
const { autoSubmitOnEnter } = unref(getProps);
if (!autoSubmitOnEnter) return;
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
const target: HTMLElement = e.target as HTMLElement;
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
handleSubmit();
}
}
}
function changeTab(tab: string) {
activedTabKey.value = tab;
}
const formActionType: Partial<TabFormActionType> = {
getFieldsValue,
setFieldsValue,
resetFields,
updateSchema,
resetSchema,
setProps,
removeSchemaByFiled,
appendSchemaByField,
clearValidate,
validateFields,
validate,
submit: handleSubmit,
scrollToField: scrollToField,
changeTab: changeTab,
};
onMounted(() => {
initDefault();
emit('register', formActionType);
});
return {
getBindValue,
handleToggleAdvanced,
handleEnterPress,
activedTabKey,
formModel,
defaultValueRef,
advanceState,
getRow,
getProps,
formElRef,
getSchema,
getTabSchema,
tabsStyle,
formActionType: formActionType as any,
setFormModel,
getFormClass,
getFormActionBindProps: computed(
(): Recordable => ({ ...getProps.value, ...advanceState }),
),
...formActionType,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-form';
.@{prefix-cls} {
.ant-form-item {
&-label label::after {
margin: 0 6px 0 2px;
}
&-with-help {
margin-bottom: 0;
}
&:not(.ant-form-item-with-help) {
margin-bottom: 20px;
}
&.suffix-item {
.ant-form-item-children {
display: flex;
}
.ant-form-item-control {
margin-top: 4px;
}
.suffix {
display: inline-flex;
padding-left: 6px;
margin-top: 1px;
line-height: 1;
align-items: center;
}
}
}
.ant-form-explain {
font-size: 14px;
}
&--compact {
.ant-form-item {
margin-bottom: 8px !important;
}
}
}
</style>