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" :model="formModel"
@keypress.enter="handleEnterPress" @keypress.enter="handleEnterPress"
> >
<Row v-bind="{ ...getRow }"> <Tabs
<slot name="formHeader"></slot> v-model:activeKey="activedTabKey"
<Tabs v-model="activedTabKey" style="width: 100%"> :style="tabsStyle.style"
<!-- fix bug: forceRender 必须强制渲染否则form验证会失效 --> :tabBarStyle="tabsStyle.tabBarStyle"
<TabPane >
v-for="tabSchema in getTabSchema" <!-- fix bug: forceRender 必须强制渲染否则form验证会失效 -->
:key="tabSchema.key" <TabPane
:tab="tabSchema.key" v-for="tabSchema in getTabSchema"
:forceRender="true" :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"> <template v-for="schema in tabSchema.schemas" :key="schema.field">
<FormItem <FormItem
:tableAction="tableAction" :tableAction="tableAction"
@ -27,19 +31,21 @@
:setFormModel="setFormModel" :setFormModel="setFormModel"
> >
<template #[item]="data" v-for="item in Object.keys($slots)"> <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> </template>
</FormItem> </FormItem>
</template> </template>
</TabPane> </Row>
</Tabs> </TabPane>
</Tabs>
<Row v-bind="getRow">
<FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced"> <FormAction v-bind="getFormActionBindProps" @toggle-advanced="handleToggleAdvanced">
<template <template
#[item]="data" #[item]="data"
v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']" v-for="item in ['resetBefore', 'submitBefore', 'advanceBefore', 'advanceAfter']"
> >
<slot :name="item" v-bind="data"></slot> <slot :name="item" v-bind="data || {}"></slot>
</template> </template>
</FormAction> </FormAction>
<slot name="formFooter"></slot> <slot name="formFooter"></slot>
@ -48,8 +54,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import dayjs from 'dayjs'; import type { TabFormActionType, TabFormProps, TabFormSchema } from './types/form';
import type { FormActionType, TabFormProps, FormSchema, TabFormSchema } from './types/form';
import type { AdvanceState } from './types/hooks'; import type { AdvanceState } from './types/hooks';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
@ -70,11 +75,12 @@
import { useFormEvents } from './hooks/useFormEvents'; import { useFormEvents } from './hooks/useFormEvents';
import { createFormContext } from './hooks/useFormContext'; import { createFormContext } from './hooks/useFormContext';
import { useAutoFocus } from './hooks/useAutoFocus'; import { useAutoFocus } from './hooks/useAutoFocus';
import { useTabsStyle } from '/@/hooks/component/useStyles';
import { useModalContext } from '/@/components/Modal'; import { useModalContext } from '/@/components/Modal';
import { useDebounceFn } from '@vueuse/core';
import { tabProps } from './props'; import { tabProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { mergeWith } from 'lodash-es';
export default defineComponent({ export default defineComponent({
name: 'TabForm', name: 'TabForm',
@ -87,10 +93,11 @@
TabPane: Tabs.TabPane, TabPane: Tabs.TabPane,
}, },
props: tabProps, props: tabProps,
emits: ['advanced-change', 'reset', 'submit', 'register'], emits: ['advanced-change', 'reset', 'submit', 'register', 'field-value-change'],
setup(props, { emit, attrs }) { setup(props, { emit, attrs }) {
const formModel = reactive<Recordable>({}); const formModel = reactive<Recordable>({});
const modalFn = useModalContext(); const modalFn = useModalContext();
const tabsStyle = useTabsStyle();
const advanceState = reactive<AdvanceState>({ const advanceState = reactive<AdvanceState>({
isAdvanced: true, isAdvanced: true,
@ -104,17 +111,13 @@
const activedTabKey = ref(''); const activedTabKey = ref('');
const propsRef = ref<Partial<TabFormProps>>({}); const propsRef = ref<Partial<TabFormProps>>({});
const schemaRef = ref<Nullable<TabFormSchema[]>>(null); const schemaRef = ref<Nullable<TabFormSchema[]>>(null);
const formElRef = ref<Nullable<FormActionType>>(null); const formElRef = ref<Nullable<TabFormActionType>>(null);
const { prefixCls } = useDesign('basic-form'); const { prefixCls } = useDesign('basic-form');
const getBindValue = computed(
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable),
);
// Get the basic configuration of the form // Get the basic configuration of the form
const getProps = computed((): TabFormProps => { const getProps = computed((): TabFormProps => {
return mergeWith(props, unref(propsRef)) as TabFormProps; return { ...props, ...unref(propsRef) } as TabFormProps;
}); });
const getFormClass = computed(() => { const getFormClass = computed(() => {
@ -135,6 +138,10 @@
}; };
}); });
const getBindValue = computed(
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable),
);
const getSchema = computed((): TabFormSchema[] => { const getSchema = computed((): TabFormSchema[] => {
const schemas: TabFormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any); const schemas: TabFormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
for (const schema of schemas) { for (const schema of schemas) {
@ -144,7 +151,7 @@
if (!Array.isArray(defaultValue)) { if (!Array.isArray(defaultValue)) {
schema.defaultValue = dateUtil(defaultValue); schema.defaultValue = dateUtil(defaultValue);
} else { } else {
const def: dayjs.Dayjs[] = []; const def: any[] = [];
defaultValue.forEach((item) => { defaultValue.forEach((item) => {
def.push(dateUtil(item)); def.push(dateUtil(item));
}); });
@ -153,15 +160,15 @@
} }
} }
if (unref(getProps).showAdvancedButton) { if (unref(getProps).showAdvancedButton) {
return schemas.filter((schema) => schema.component !== 'Divider') as TabFormSchema[]; return schemas.filter((schema) => schema.component !== 'Divider');
} else { } 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 schemas = unref(getSchema);
const tabSchemas: { key: string; schemas: FormSchema[] }[] = []; const tabSchemas: { key: string; schemas: TabFormSchema[] }[] = [];
const group = groupBy(getSchema.value, 'tab'); const group = groupBy(getSchema.value, 'tab');
Object.keys(group).forEach((key) => { Object.keys(group).forEach((key) => {
tabSchemas.push({ tabSchemas.push({
@ -192,7 +199,7 @@
getSchema, getSchema,
getProps, getProps,
isInitedDefault: isInitedDefaultRef, isInitedDefault: isInitedDefaultRef,
formElRef: formElRef as Ref<FormActionType>, formElRef: formElRef as Ref<TabFormActionType>,
}); });
const { const {
@ -214,7 +221,7 @@
formModel, formModel,
getSchema, getSchema,
defaultValueRef, defaultValueRef,
formElRef: formElRef as Ref<FormActionType>, formElRef: formElRef as Ref<TabFormActionType>,
schemaRef: schemaRef as Ref<TabFormSchema[]>, schemaRef: schemaRef as Ref<TabFormSchema[]>,
handleFormValues, handleFormValues,
}); });
@ -227,10 +234,12 @@
watch( watch(
() => unref(getProps).model, () => unref(getProps).model,
() => { () => {
const { model } = unref(getProps); const { model, schemas } = unref(getProps);
if (schemas?.length) {
activedTabKey.value = schemas[0].tab;
}
if (!model) return; if (!model) return;
setFieldsValue(model); setFieldsValue(model);
activedTabKey.value = '';
}, },
{ {
immediate: true, immediate: true,
@ -241,6 +250,9 @@
() => unref(getProps).schemas, () => unref(getProps).schemas,
(schemas) => { (schemas) => {
resetSchema(schemas ?? []); resetSchema(schemas ?? []);
if (schemas?.length) {
activedTabKey.value = schemas[0].tab;
}
}, },
); );
@ -257,16 +269,30 @@
if (schema?.length) { if (schema?.length) {
initDefault(); initDefault();
isInitedDefaultRef.value = true; 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> { async function setProps(formProps: Partial<TabFormProps>): Promise<void> {
propsRef.value = deepMerge(unref(propsRef) || {}, formProps); propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
} }
function setFormModel(key: string, value: any) { function setFormModel(key: string, value: any) {
formModel[key] = value; formModel[key] = value;
const { validateTrigger } = unref(getBindValue);
if (!validateTrigger || validateTrigger === 'change') {
validateFields([key]).catch((_) => {});
}
emit('field-value-change', key, value);
} }
function handleEnterPress(e: KeyboardEvent) { function handleEnterPress(e: KeyboardEvent) {
@ -280,7 +306,11 @@
} }
} }
const formActionType: Partial<FormActionType> = { function changeTab(tab: string) {
activedTabKey.value = tab;
}
const formActionType: Partial<TabFormActionType> = {
getFieldsValue, getFieldsValue,
setFieldsValue, setFieldsValue,
resetFields, resetFields,
@ -294,6 +324,7 @@
validate, validate,
submit: handleSubmit, submit: handleSubmit,
scrollToField: scrollToField, scrollToField: scrollToField,
changeTab: changeTab,
}; };
onMounted(() => { onMounted(() => {
@ -314,6 +345,7 @@
formElRef, formElRef,
getSchema, getSchema,
getTabSchema, getTabSchema,
tabsStyle,
formActionType: formActionType as any, formActionType: formActionType as any,
setFormModel, setFormModel,
getFormClass, getFormClass,
@ -325,3 +357,51 @@
}, },
}); });
</script> </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 { CSSProperties, PropType } from 'vue';
import type { ColEx } from './types'; import type { ColEx } from './types';
import type { TableActionType } from '/@/components/Table'; import type { TableActionType } from '/@/components/Table';
@ -120,7 +120,7 @@ export const tabProps = {
compact: propTypes.bool, compact: propTypes.bool,
// 表单配置规则 // 表单配置规则
schemas: { schemas: {
type: [Array] as PropType<FormSchema[]>, type: [Array] as PropType<TabFormSchema[]>,
default: () => [], default: () => [],
}, },
mergeDynamicData: { 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>; scrollToField: (name: NamePath, options?: ScrollOptions) => Promise<void>;
} }
export interface TabFormActionType extends FormActionType {
changeTab: (tab: string) => void;
}
export type RegisterFn = (formInstance: FormActionType) => void; export type RegisterFn = (formInstance: FormActionType) => void;
export type UseFormReturnType = [RegisterFn, FormActionType]; export type UseFormReturnType = [RegisterFn, FormActionType];
@ -127,6 +131,7 @@ export interface FormProps {
export interface TabFormProps extends FormProps { export interface TabFormProps extends FormProps {
tab: string; tab: string;
schemas?: TabFormSchema[];
} }
export interface FormSchema { export interface FormSchema {

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

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

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

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

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

@ -23,10 +23,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, ref } from 'vue'; import { nextTick, ref, unref } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { useLocalization } from '/@/hooks/abp/useLocalization'; 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 { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import { basicProps } from './props'; import { basicProps } from './props';
import { Menu } from '/@/api/platform/model/menuModel'; import { Menu } from '/@/api/platform/model/menuModel';
@ -39,7 +39,7 @@
const { L } = useLocalization(['AppPlatform', 'AbpUi']); const { L } = useLocalization(['AppPlatform', 'AbpUi']);
const menu = ref<Menu>({} as Menu); const menu = ref<Menu>({} as Menu);
const framework = ref<string | undefined>(''); const framework = ref<string | undefined>('');
const formElRef = ref<Nullable<FormActionType>>(null); const formElRef = ref<Nullable<TabFormActionType>>(null);
const { formTitle, getFormSchemas, handleFormSubmit, fetchLayoutResource } = const { formTitle, getFormSchemas, handleFormSubmit, fetchLayoutResource } =
useMenuFormContext({ useMenuFormContext({
menuModel: menu, menuModel: menu,
@ -53,6 +53,8 @@
nextTick(() => { nextTick(() => {
setDrawerProps({ confirmLoading: false }); setDrawerProps({ confirmLoading: false });
fetchLayoutResource(dataVal.layoutId); 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 { 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 { unref, computed, watch, createVNode } from 'vue';
import { Checkbox } from 'ant-design-vue'; import { Checkbox } from 'ant-design-vue';
@ -14,7 +14,7 @@ import { Menu, UpdateMenu, CreateMenu } from '/@/api/platform/model/menuModel';
interface UseMenuFormContext { interface UseMenuFormContext {
menuModel: Ref<Menu>; menuModel: Ref<Menu>;
formElRef: Ref<Nullable<FormActionType>>; formElRef: Ref<Nullable<TabFormActionType>>;
framework: Ref<string | undefined>, framework: Ref<string | undefined>,
} }

Loading…
Cancel
Save