main.ts中
//引入并注册
import { createApp } from "vue";
import SubDialog from '@/components/sub.ts'
let instance: any = null;
instance = createApp(App);
instance.use(SubDialog);
components下的sub.ts文件
import { h, render } from 'vue'
import iLawDialog from '@/components/iLawDialog.vue'
let mountNode
let app
let NodeArray = []
let createMount = (opts) => {
// doubleDialog 是否需要二级弹窗
if (NodeArray.length>0 && !opts.doubleDialog) {//确保只存在一个弹窗
// 执行组件生命周期
render(null, mountNode);
// 移除dom
document.body.removeChild(NodeArray.pop())
}
mountNode = document.createElement('div')
NodeArray.push(mountNode)
document.body.appendChild(mountNode)
let vnode = h(iLawDialog, {
options: {
...opts,
remove() { //传入remove函数,组件内可移除dom 组件内通过props接收x
// 执行组件生命周期
render(null, mountNode);
// 移除dom
NodeArray.length > 0 && document.body.removeChild(NodeArray.pop());
// 清空NodeArray
NodeArray.length === 0 && (mountNode = null);
}
},
})
vnode.appContext = app
render(vnode, mountNode);
}
const V3Popup = (options = { id: Number }) => {
options.id = options.id || 'v3popup_' + 1
createMount(options)
}
export default {
install(Vue: any) {
/**
* @description: 将公共的工具类 注册在vue 实例上
* @param {*}
* @return {*}
*/
app = Vue._context
Vue.config.globalProperties.$subDialog = V3Popup;
},
};
components下的iLawDialog.vue
<template>
<el-dialog
v-model="dialogVisible"
:custom-class="`subDialog ${props.options.hideHeader}`"
v-bind="props.options"
@opened="handleCallback('opened')"
@close="handleCallback('close')"
@closed="handleCallback('closed')"
@open-auto-focus="handleCallback('openAutoFocus')"
@close-auto-focus="handleCallback('closeAutoFocus')"
>
<template #header>
<component
:is="props.options?.headerDom"
v-bind="props.options.contentComponent?.attrs"
@onClick="_handleToolbarClick($event)"
></component>
</template>
<template #default>
<p
v-html="props.options.contentText"
v-if="props.options.contentText"
></p>
<div v-if="props.options.contentComponent">
<component
:is="componentId"
v-bind="props.options.contentComponent.attrs"
@onClick="_handleToolbarClick($event)"
></component>
</div>
</template>
<template #footer v-if="is_toolbar">
<div class="dialog-footer reset-element">
<iLawButtons
:toolbar="props.options.toolbar"
@onClick="_handleToolbarClick($event)"
></iLawButtons>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import iLawButtons from "@/components/iLawButtons.vue";
import { ref, Ref, watch, computed, getCurrentInstance } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
/**
* @description: 获取 函数 实例
* @return {*}
*/
// 注册 绑定 父组件 v-model change 事件
// 获取 父组件传值 v-model
const props = defineProps({
options: {
type: Object,
default: () => {},
},
});
const handleCallback = (val) => {
props.options[val] && props.options[val]();
};
// 定义 dialog v-model 传值
let dialogVisible: Ref<boolean> = ref(true);
/**
* @description: 计算属性判断 底部按钮是否默认还是没有
* @return {*}
*/
const is_toolbar = computed(() => {
return props.options?.toolbar ? true : false;
});
const componentId = computed(() => {
return props.options.contentComponent.is;
});
// 监听 dialogVisible
watch(
() => dialogVisible.value,
(val) => {
if (!val) {
props.options.remove();
}
}
);
watch(
() => route.path,
(val) => {
props.options.remove();
}
);
/**
* 处理按钮回调
* @param item
* @param data
* @param $event
*/
const _handleToolbarClick = (item?: any, $event?: any) => {
if (item.click == "cancel") {
props.options.remove();
return;
}
// 将 移除dom 的当 callback 传出
props.options[item.click](props.options.remove, item.options);
};
/**
* @description: 将属性方法首字母大写
* @param {*} str
* @return {*}
*/
const titleCase = (str) => {
let newStr = str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase();
return newStr;
};
</script>
<style lang="scss">
.subDialog {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&.el-dialog {
z-index: 99;
background-color: #fff;
}
.el-dialog__header {
border-bottom: 1px solid rgb(0 0 0 / 6%);
padding: 0 25px;
width: 100%;
height: 50px;
text-align: left;
line-height: 50px;
box-sizing: border-box;
}
.el-dialog__title {
font-size: 18px;
font-family: "PingFangSC-Medium", "PingFang SC";
font-weight: 500;
color: rgb(0 0 0 / 85%);
}
.el-dialog__footer {
display: flex;
justify-content: center;
align-items: center;
margin-right: 20px;
border-top: 0;
padding: 0;
height: 60px;
line-height: 60px;
.el-button {
border-radius: 22px;
padding: 0;
width: 120px;
height: 32px;
line-height: 32px;
}
.color-info {
border-color: #999;
background: #999 !important;
}
}
}
.hideHeader .el-dialog__header {
display: none;
}
</style>
components下的 iLawButtons.vue
<template>
<div :class="`iLaw-buttons-${align}`">
<component
:is="componentName"
v-for="(item, index) of props.toolbar"
:key="index"
v-bind="item"
@click.stop="handleToolbarClick(item, $event)"
>
<template v-if="item.name"
><i :class="`icon iconName ${item.fontIcon}`" v-if="item.fontIcon"></i>
{{ item.name }}</template
>
<template v-if="item.tip"
><i :class="`icon ${item.fontIcon}`" v-if="item.fontIcon"></i
></template>
</component>
</div>
</template>
<script setup lang="ts">
import { ElButton } from "element-plus";
import { ref, Ref, computed, getCurrentInstance } from "vue";
const { proxy }: any = getCurrentInstance();
/**
* @description: 获取 函数 实例
* @return {*}
*/
const emits = defineEmits(["onClick"]);
// 按钮组
// @group Z.私有组件
const Types = {
button: ElButton,
text: "ge-textButton",
};
const props = defineProps({
// 按钮对齐方式
align: {
type: String,
default: (): string => "center",
validator: (value: string) => ["left", "center", "right"].includes(value),
},
// 按钮数组
toolbar: {
// type: Array,
default: (): any => [{}],
required: true,
},
// 按钮类型
type: {
type: String,
default: (): string => "button",
// validator: (value: string) => Types[value],
},
});
let timer: Ref<any> = ref(null);
const componentName = computed(() => {
return Types[props.type] || Types.button;
});
const handleToolbarClick = (item, event) => {
timer.value && clearTimeout(timer.value);
const { click, ...rest } = item;
timer.value = setTimeout(() => {
if (typeof click === "function") {
click(rest, event);
} else {
const func = proxy.$attrs[click] ? click : "onClick";
emits(func, item);
}
}, 300);
};
</script>
<style lang="scss" scoped>
.iLaw-buttons {
&-left {
text-align: left;
}
&-center {
text-align: center;
}
&-right {
text-align: right;
}
}
:deep(.iconName::before) {
font-size: 16px;
margin-right: 5px;
font-weight: 900;
}
</style>
创建组件
<template>
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submit">确认</el-button>
</template>
<script setup lang="ts">
import { defineProps, ref, defineEmits } from "vue";
const props: any = defineProps({
data: {
type: Object,
default: () => {},
},
});
const emits = defineEmits(["onClick"]);
const form = ref('');
const cancel = () => {
emits("onClick", {
click: "cancel",
});
};
const submit = () => {
emits("onClick", {
click: "confirm",
options: form.value,
});
};
</script>
<style scoped lang="scss">
</style>
如何使用
import { getCurrentInstance, markRaw } from 'vue'
const {proxy}:any = getCurrentInstance()
//执行
proxy.$subDialog({
width: 383,
hideHeader: "hideHeader",
"show-close": false,
contentComponent: {
is: markRaw(DownloadContent),//创建的自定义的组件
attrs: {
data: '',//传值给自定义组件
toolbar: [
{ text: "取消", click: "cancel" },
{ text: "确认", type: "primary", click: "confirm" },
],
},
},
confirm: (remove, form) => {
// 自定义组件传回来的值 form
remove();//弹框
},
});