Browse Source

optimize tab form

pull/692/head
cKey 3 years ago
parent
commit
ae451c1673
  1. 148
      apps/vue/src/components/Form/src/TabForm.vue
  2. 4
      apps/vue/src/components/Form/src/props.ts
  3. 5
      apps/vue/src/components/Form/src/types/form.ts
  4. 3
      apps/vue/src/views/platform/dataDic/components/DataItemTable.vue
  5. 45
      apps/vue/src/views/platform/dataDic/components/DataTree.vue
  6. 8
      apps/vue/src/views/platform/menu/components/MenuDrawer.vue
  7. 4
      apps/vue/src/views/platform/menu/hooks/useMenuFormContext.ts

148
apps/vue/src/components/Form/src/TabForm.vue

@ -6,16 +6,20 @@
:model="formModel"
@keypress.enter="handleEnterPress"
>
<Row v-bind="{ ...getRow }">
<slot name="formHeader"></slot>
<Tabs v-model="activedTabKey" style="width: 100%">
<!-- fix bug: forceRender 必须强制渲染否则form验证会失效 -->
<TabPane
v-for="tabSchema in getTabSchema"
:key="tabSchema.key"
:tab="tabSchema.key"
:forceRender="true"
>
<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"
@ -27,19 +31,21 @@
:setFormModel="setFormModel"
>
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data"></slot>
<slot :name="item" v-bind="data || {}"></slot>
</template>
</FormItem>
</template>
</TabPane>
</Tabs>
</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>
<slot :name="item" v-bind="data || {}"></slot>
</template>
</FormAction>
<slot name="formFooter"></slot>
@ -48,8 +54,7 @@
</template>
<script lang="ts">
import dayjs from 'dayjs';
import type { FormActionType, TabFormProps, FormSchema, TabFormSchema } from './types/form';
import type { TabFormActionType, TabFormProps, TabFormSchema } from './types/form';
import type { AdvanceState } from './types/hooks';
import type { Ref } from 'vue';
@ -70,11 +75,12 @@
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';
import { mergeWith } from 'lodash-es';
export default defineComponent({
name: 'TabForm',
@ -87,10 +93,11 @@
TabPane: Tabs.TabPane,
},
props: tabProps,
emits: ['advanced-change', 'reset', 'submit', 'register'],
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,
@ -104,17 +111,13 @@
const activedTabKey = ref('');
const propsRef = ref<Partial<TabFormProps>>({});
const schemaRef = ref<Nullable<TabFormSchema[]>>(null);
const formElRef = ref<Nullable<FormActionType>>(null);
const formElRef = ref<Nullable<TabFormActionType>>(null);
const { prefixCls } = useDesign('basic-form');
const getBindValue = computed(
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable),
);
// Get the basic configuration of the form
const getProps = computed((): TabFormProps => {
return mergeWith(props, unref(propsRef)) as TabFormProps;
return { ...props, ...unref(propsRef) } as TabFormProps;
});
const getFormClass = computed(() => {
@ -135,6 +138,10 @@
};
});
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) {
@ -144,7 +151,7 @@
if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue);
} else {
const def: dayjs.Dayjs[] = [];
const def: any[] = [];
defaultValue.forEach((item) => {
def.push(dateUtil(item));
});
@ -153,15 +160,15 @@
}
}
if (unref(getProps).showAdvancedButton) {
return schemas.filter((schema) => schema.component !== 'Divider') as TabFormSchema[];
return schemas.filter((schema) => schema.component !== 'Divider');
} else {
return schemas as TabFormSchema[];
return schemas;
}
});
const getTabSchema = computed((): { key: string; schemas: FormSchema[] }[] => {
const getTabSchema = computed((): { key: string; schemas: TabFormSchema[] }[] => {
// const schemas = unref(getSchema);
const tabSchemas: { key: string; schemas: FormSchema[] }[] = [];
const tabSchemas: { key: string; schemas: TabFormSchema[] }[] = [];
const group = groupBy(getSchema.value, 'tab');
Object.keys(group).forEach((key) => {
tabSchemas.push({
@ -192,7 +199,7 @@
getSchema,
getProps,
isInitedDefault: isInitedDefaultRef,
formElRef: formElRef as Ref<FormActionType>,
formElRef: formElRef as Ref<TabFormActionType>,
});
const {
@ -214,7 +221,7 @@
formModel,
getSchema,
defaultValueRef,
formElRef: formElRef as Ref<FormActionType>,
formElRef: formElRef as Ref<TabFormActionType>,
schemaRef: schemaRef as Ref<TabFormSchema[]>,
handleFormValues,
});
@ -227,10 +234,12 @@
watch(
() => unref(getProps).model,
() => {
const { model } = unref(getProps);
const { model, schemas } = unref(getProps);
if (schemas?.length) {
activedTabKey.value = schemas[0].tab;
}
if (!model) return;
setFieldsValue(model);
activedTabKey.value = '';
},
{
immediate: true,
@ -241,6 +250,9 @@
() => unref(getProps).schemas,
(schemas) => {
resetSchema(schemas ?? []);
if (schemas?.length) {
activedTabKey.value = schemas[0].tab;
}
},
);
@ -257,16 +269,30 @@
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) {
@ -280,7 +306,11 @@
}
}
const formActionType: Partial<FormActionType> = {
function changeTab(tab: string) {
activedTabKey.value = tab;
}
const formActionType: Partial<TabFormActionType> = {
getFieldsValue,
setFieldsValue,
resetFields,
@ -294,6 +324,7 @@
validate,
submit: handleSubmit,
scrollToField: scrollToField,
changeTab: changeTab,
};
onMounted(() => {
@ -314,6 +345,7 @@
formElRef,
getSchema,
getTabSchema,
tabsStyle,
formActionType: formActionType as any,
setFormModel,
getFormClass,
@ -325,3 +357,51 @@
},
});
</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>

4
apps/vue/src/components/Form/src/props.ts

@ -1,4 +1,4 @@
import type { FieldMapToTime, FormSchema } from './types/form';
import type { FieldMapToTime, FormSchema, TabFormSchema } from './types/form';
import type { CSSProperties, PropType } from 'vue';
import type { ColEx } from './types';
import type { TableActionType } from '/@/components/Table';
@ -120,7 +120,7 @@ export const tabProps = {
compact: propTypes.bool,
// 表单配置规则
schemas: {
type: [Array] as PropType<FormSchema[]>,
type: [Array] as PropType<TabFormSchema[]>,
default: () => [],
},
mergeDynamicData: {

5
apps/vue/src/components/Form/src/types/form.ts

@ -44,6 +44,10 @@ export interface FormActionType {
scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
}
export interface TabFormActionType extends FormActionType {
changeTab: (tab: string) => void;
}
export type RegisterFn = (formInstance: FormActionType) => void;
export type UseFormReturnType = [RegisterFn, FormActionType];
@ -127,6 +131,7 @@ export interface FormProps {
export interface TabFormProps extends FormProps {
tab: string;
schemas?: TabFormSchema[];
}
export interface FormSchema {

3
apps/vue/src/views/platform/dataDic/components/DataItemTable.vue

@ -28,7 +28,7 @@
</template>
</template>
</BasicTable>
<DataItemModal @register="registerModal" @change="fetchItems" />
<DataItemModal @register="registerModal" @change="fetchItems(dataId)" />
</template>
<script lang="ts" setup>
@ -49,6 +49,7 @@
dataId: {
type: String,
retuired: true,
default: '',
},
});

45
apps/vue/src/views/platform/dataDic/components/DataTree.vue

@ -3,22 +3,30 @@
<template #extra>
<a-button type="primary" @click="openDataModal(true, {})">{{ L('Data:AddNew') }}</a-button>
</template>
<BasicTree
:tree-data="treeData"
:field-names="replaceFields"
defaultExpandLevel="1"
:before-right-click="getContentMenus"
@select="handleNodeChange"
/>
<div :style="getContentStyle" ref="contentWrapRef">
<ScrollContainer ref="contentScrollRef">
<BasicTree
:show-line="false"
:show-icon="false"
:block-node="true"
:tree-data="treeData"
:field-names="replaceFields"
defaultExpandLevel="1"
:before-right-click="getContentMenus"
@select="handleNodeChange"
/>
</ScrollContainer>
</div>
<DataModal @register="registerDataModal" @change="onLoadAllDataDic" />
<DataItemModal @register="registerItemModal" @change="(dataId) => handleNodeChange([dataId])" />
</Card>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import type { CSSProperties } from 'vue';
import { computed, ref, onMounted } from 'vue';
import { ScrollContainer } from '/@/components/Container';
import { useContentHeight } from '/@/hooks/web/useContentHeight';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { Card } from 'ant-design-vue';
@ -26,7 +34,6 @@
import { BasicTree, ContextMenuItem } from '/@/components/Tree/index';
import { listToTree } from '/@/utils/helper/treeHelper';
import { getAll, remove } from '/@/api/platform/dataDic';
import { Data } from '/@/api/platform/model/dataModel';
import DataModal from './DataModal.vue';
import DataItemModal from './DataItemModal.vue';
@ -35,7 +42,9 @@
const { createMessage, createConfirm } = useMessage();
const { L } = useLocalization(['AppPlatform', 'AbpUi']);
const title = L('DisplayName:DataDictionary');
const treeData = ref<Data[]>([]);
const contentWrapRef = ref<any>();
const contentScrollRef = ref<any>();
const treeData = ref<any[]>([]);
const replaceFields = ref({
title: 'displayName',
key: 'id',
@ -43,6 +52,16 @@
});
const [registerItemModal, { openModal: openItemModal }] = useModal();
const [registerDataModal, { openModal: openDataModal }] = useModal();
const getFlag = computed(() => true);
const { contentHeight } = useContentHeight(
getFlag, contentWrapRef, [], []
);
const getContentStyle = computed((): CSSProperties => {
return {
width: '100%',
height: `${contentHeight.value}px`,
}
});
onMounted(onLoadAllDataDic);
@ -56,7 +75,7 @@
icon: 'ant-design:edit-outlined',
},
{
label: L('Data:AddNew'),
label: L('Data:AddChildren'),
handler: () => {
openDataModal(true, { parentId: node.eventKey });
},

8
apps/vue/src/views/platform/menu/components/MenuDrawer.vue

@ -23,10 +23,10 @@
</template>
<script lang="ts" setup>
import { nextTick, ref } from 'vue';
import { nextTick, ref, unref } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { TabForm, FormActionType } from '/@/components/Form';
import { TabForm, TabFormActionType } from '/@/components/Form';
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { basicProps } from './props';
import { Menu } from '/@/api/platform/model/menuModel';
@ -39,7 +39,7 @@
const { L } = useLocalization(['AppPlatform', 'AbpUi']);
const menu = ref<Menu>({} as Menu);
const framework = ref<string | undefined>('');
const formElRef = ref<Nullable<FormActionType>>(null);
const formElRef = ref<Nullable<TabFormActionType>>(null);
const { formTitle, getFormSchemas, handleFormSubmit, fetchLayoutResource } =
useMenuFormContext({
menuModel: menu,
@ -53,6 +53,8 @@
nextTick(() => {
setDrawerProps({ confirmLoading: false });
fetchLayoutResource(dataVal.layoutId);
const formEl = unref(formElRef);
formEl?.changeTab(L('DisplayName:Basic'));
});
});

4
apps/vue/src/views/platform/menu/hooks/useMenuFormContext.ts

@ -1,5 +1,5 @@
import type { Ref } from 'vue';
import type { TabFormSchema, FormActionType } from '/@/components/Form/src/types/form';
import type { TabFormSchema, TabFormActionType } from '/@/components/Form/src/types/form';
import { unref, computed, watch, createVNode } from 'vue';
import { Checkbox } from 'ant-design-vue';
@ -14,7 +14,7 @@ import { Menu, UpdateMenu, CreateMenu } from '/@/api/platform/model/menuModel';
interface UseMenuFormContext {
menuModel: Ref<Menu>;
formElRef: Ref<Nullable<FormActionType>>;
formElRef: Ref<Nullable<TabFormActionType>>;
framework: Ref<string | undefined>,
}

Loading…
Cancel
Save