1.使用方法
<script setup lang="ts">
import { ElButton } from 'element-plus';
import MyDialog from 'components/MyDialog.vue';
const myDialog = useDialog(MyDialog);
const handleOpenDialog = () => {
// 打开弹窗
myDialog({
// 传入一些myDialog 需要的 props
title: "弹窗标题",
onSubmit: () => {
// 弹窗的回调处理,如果有的话
},
onClose: () =>{
// close 回调 , 如果需要的话
}
})
// myDialog.close()可以关闭弹窗
};
</script>
<template>
<div>
<ElButton @click="handleOpenDialog"> 打开弹窗 </ElButton>
<div>其他内容。。。 </div>
</div>
// 不需要将MyDialog声明到template中
</template>
2.组件
<script setup lang="ts" name="MyDialog">
const props = defineProps<{
visible: boolean;
title?: string;
onConfirm: (imgSrc: string, imgId: number) => void;
}>()
const emits = defineEmits<{
close: [],
}>()
const dialogVisible = ref(false)
// 取消选择
const cancel = () => {
dialogVisible.value = false
}
// 确认选择
const confirm = () => {
// 其他逻辑
props?.onConfirm()
cancel()
}
const dialogVisible = computed<boolean>({
get() {
return props.visible;
},
set(visible) {
emits('update:visible', visible);
if (!visible) {
emits('close');
}
},
});
</script>
<template>
<el-dialog
v-model="dialogVisible"
:title="props.title"
width="800"
:before-close="cancel"
>
<div>
弹窗内容
</div>
<template #footer>
<el-button @click="cancel">
取消
</el-button>
<el-button type="primary" @click="confirm">
确定
</el-button>
</template>
</el-dialog>
</template>
3.源码
import { AppContext, Component, ComponentPublicInstance, createVNode, getCurrentInstance, render, VNode } from 'vue';
export interface Options {
visible?: boolean;
onClose?: () => void;
appendTo?: HTMLElement | string;
[key: string]: unknown;
}
export interface DialogComponent {
(options: Options): VNode;
close: () => void;
}
const getAppendToElement = (props: Options): HTMLElement => {
let appendTo: HTMLElement | null = document.body;
if (props.appendTo) {
if (typeof props.appendTo === 'string') {
appendTo = document.querySelector<HTMLElement>(props.appendTo);
}
if (props.appendTo instanceof HTMLElement) {
appendTo = props.appendTo;
}
if (!(appendTo instanceof HTMLElement)) {
appendTo = document.body;
}
}
return appendTo;
};
const initInstance = <T extends Component>(
Component: T,
props: Options,
container: HTMLElement,
appContext: AppContext | null = null
) => {
const vNode = createVNode(Component, props);
vNode.appContext = appContext;
render(vNode, container);
getAppendToElement(props).appendChild(container);
return vNode;
};
export const useDialog = <T extends Component>(Component: T): DialogComponent => {
const appContext = getCurrentInstance()?.appContext;
if (appContext) {
const currentProvides = (getCurrentInstance() as any)?.provides;
Reflect.set(appContext, 'provides', { ...appContext.provides, ...currentProvides });
}
const container = document.createElement('div');
const close = () => {
render(null, container);
container.parentNode?.removeChild(container);
};
const DialogComponent = (options: Options): VNode => {
if (!Reflect.has(options, 'visible')) {
options.visible = true;
}
if (typeof options.onClose !== 'function') {
options.onClose = close;
} else {
const originOnClose = options.onClose;
options.onClose = () => {
originOnClose();
close();
};
}
const vNode = initInstance<T>(Component, options, container, appContext);
const vm = vNode.component?.proxy as ComponentPublicInstance<Options>;
for (const prop in options) {
if (Reflect.has(options, prop) && !Reflect.has(vm.$props, prop)) {
vm[prop as keyof ComponentPublicInstance] = options[prop];
}
}
return vNode;
};
DialogComponent.close = close;
return DialogComponent;
};
export default useDialog;