Modal目录结构
components包含modal的内容组件,头部组件和底部组件等,
hooks中是方法具体实现的集合,
BasciModal.vue集成了components的组件文件,
props.ts为传入属性类型定义和初始化赋值,
typing.ts为弹窗对外暴露方法的声明和初始化。
BasciModal.vue 封装-template
<template>
<Modal v-bind="getBindValue" @cancel="handleCancel">
<template #closeIcon v-if="!$slots.closeIcon">
<ModalClose
:canFullscreen="getProps.canFullscreen"
:fullScreen="fullScreenRef"
@cancel="handleCancel"
@fullscreen="handleFullScreen"
/>
</template>
<template #title v-if="!$slots.title">
<ModalHeader
:helpMessage="getProps.helpMessage"
:title="getMergeProps.title"
@dblclick="handleTitleDbClick"
/>
</template>
<template #footer v-if="!$slots.footer">
<ModalFooter v-bind="getBindValue" @ok="handleOk" @cancel="handleCancel">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</ModalFooter>
</template>
<ModalWrapper
:useWrapper="getProps.useWrapper"
:footerOffset="wrapperFooterOffset"
:fullScreen="fullScreenRef"
ref="modalWrapperRef"
:loading="getProps.loading"
:loading-tip="getProps.loadingTip"
:minHeight="getProps.minHeight"
:height="getWrapperHeight"
:visible="visibleRef"
:modalFooterHeight="footer !== undefined && !footer ? 0 : undefined"
v-bind="omit(getProps.wrapperProps, 'visible', 'height', 'modalFooterHeight')"
@ext-height="handleExtHeight"
@height-change="handleHeightChange"
>
<slot></slot>
</ModalWrapper>
<template #[item]="data" v-for="item in Object.keys(omit($slots, 'default'))">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</Modal>
</template>
从 template可以看到,Modal 的 dom 结构,有遮罩层、标题组件、内容组件、和底部组件几部分。这几块都可以定义并接收对应 props进行不同的样式或行为配置。 看下内容组件modalWrapper这块:
<ModalWrapper
:useWrapper="getProps.useWrapper"
:footerOffset="wrapperFooterOffset"
:fullScreen="fullScreenRef"
ref="modalWrapperRef"
:loading="getProps.loading"
:loading-tip="getProps.loadingTip"
:minHeight="getProps.minHeight"
:height="getWrapperHeight"
:visible="visibleRef"
:modalFooterHeight="footer !== undefined && !footer ? 0 : undefined"
v-bind="omit(getProps.wrapperProps, 'visible', 'height', 'modalFooterHeight')"
@ext-height="handleExtHeight"
@height-change="handleHeightChange"
>
<slot></slot>
</ModalWrapper>
该组件获取从父组件传递的参数后通过传统的调用组件方式再传回给子组件ModalWrapper
<template>
<ScrollContainer ref="wrapperRef">
<div ref="spinRef" :style="spinStyle" v-loading="loading" :loading-tip="loadingTip">
<slot></slot>
</div>
</ScrollContainer>
</template>
通过slot可以自定义传入modal内容
组件API
属性api:
事件api:
在vben中扩展了拖拽,全屏,自适应高度等功能。
属性是根据组件之间的传值进行监听和设置的,方法也是通过组件通信进行传递的。
举例如下:
在封装的basicalModal.vue 时,已经写好了对应的「确定」「取消」事件:
function handleOk(e: Event) {
emit('ok', e);
}
// 取消事件
as1ync function handleCancel(e: Event) {
e?.stopPropagation();
if (props.closeFunc && isFunction(props.closeFunc)) {
const isClose: boolean = await props.closeFunc();
visibleRef.value = !isClose;
return;
}
visibleRef.value = false;
emit('cancel', e);
}
即在父组件中调用组件时进行监听,点击按钮后触发自定义取消/确实事件。
emit传回的Ok事件直接绑定handleSubmit进行后续的数据处理
modal组件的使用
由于弹窗内代码一般都是作为单文件组件挂载,故推荐将弹窗用单文件组件进行进一步封装 如:
// ModalExp.vue
<template>
<BasicModal v-bind="$attrs" title="Modal Title" :helpMessage="['提示1', '提示2']">
Modal Info.
</BasicModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { BasicModal } from '/@/components/Modal';
export default defineComponent({
components: { BasicModal },
setup() {
return {};
},
});
</script>
页面引用该ModalExp弹窗
// Page.vue
<template>
<div class="px-10">
<ModalExp @register="register" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useModal } from '/@/components/Modal';
import Modal from './Modal.vue';
export default defineComponent({
components: { Modal },
setup() {
const [register, { openModal }] = useModal();
return {
register,
openModal,
};
},
});
</script>
注意:独立封装的组件需要将 attrs绑定到BasicModal组件。
解析useModal及其相关方法
调用组件必须通过外部调用useModal方法用于注册和调用事件操作组件。
const [register, { openModal, setModalProps }] = useModal();
register
register 用于注册 useModal,如果需要使用 useModal 提供的 api,必须将 register 传入组件的 onRegister进行注册。
<ModalExp @register="register" />
实现这一步依赖于vue的子传父组件通信,通过emit('register', modalInstance)实现;
const register = (modalInstance: ModalMethods, uuid: string) =>
isProdMode() &&
tryOnUnmounted(() => {
modalInstanceRef.value = null;
});
uidRef.value = uuid;
modalInstanceRef.value = modalInstance;
currentInstance?.emit('register', modalInstance, uuid);
};
openModal/closeModal
openModal: <T = any>(visible = true, data?: T, openOnSet = true): void => {
getInstance()?.setModalProps({
visible: visible,
});
if (!data) return;
const id = unref(uid);
if (openOnSet) {
dataTransfer[id] = null;
dataTransfer[id] = toRaw(data);
return;
}
const equal = isEqual(toRaw(dataTransfer[id]), toRaw(data));
if (!equal) {
dataTransfer[id] = toRaw(data);
}
},
该方法传入true或false打开/关闭弹窗
几乎所有api属性均是如此传值,通过setModalProps进行更新。 也可直接用colseModal关闭弹窗
// true/false: 打开关闭弹窗
// data: 传递到子组件的数据
openModal(true, data);
closeModal();