背景:公司组件库之前是使用el-dialog进行二次封装来作为确认弹框组件,但是这样在调用时就会出现一个问题,例如下列模拟删除二次确认场景:
<template>
<button @click="handleDelete('1')">点击删除</button>
<pro-confirm
:is-show="confirmVisible"
@confirm="handleDelConfirm"
content="正在进行删除操作,是否继续?"
/>
<template>
<script setup>
import { ref } from 'vue';
const currentId = ref();
const confirmVisible= ref(false);
function handleDelete(id) {
confirmVisible.value = true;
// 删除操作需要储存id
currentId.value = id;
}
function handleDelConfirm() {
// 调用删除接口传入id删除
api({ id: currentId.value }).then(() => {
// do something
})
}
</script>
可以看到上面二次确认操作和后续的删除调接口操作并不能在同一个执行上下文中执行,固还需存储要删除的id,使用起来比较麻烦,所以使用vue3提供的createVNode + render将二次确认弹框改为函数调用组件来使用,即可解决以上问题,封装代码如下:
import ProConfirmConstructor from './pro-confirm.vue';
import { render, createVNode } from 'vue';
const confirmPromiseInfo = {
resolve: () => {},
reject: () => {}
}
const Confirm = (props) => new Promise((resolve, reject) => {
const _onClose = props.onClose
const _onConfirm = props.onConfirm
confirmPromiseInfo.resolve = resolve
confirmPromiseInfo.reject = reject
props.onClose = () => {
_onClose && _onClose()
confirmInstance.component?.exposed?.close()
// render(null, document.body);
confirmPromiseInfo.reject()
}
props.onConfirm = () => {
_onConfirm && _onConfirm();
// render(null, document.body);
confirmInstance.component?.exposed?.close()
confirmPromiseInfo.resolve();
}
// 此处通过createVNode将el-dialog封装的组件实例转换为虚拟dom节点
const confirmInstance = createVNode(ProConfirmConstructor, props);
// 手动挂载组件实例至body
render(confirmInstance, document.body);
//执行组件中暴露的show方法展示组件
confirmInstance.component?.exposed?.show();
})
// 用于组件库注册全局方法
Confirm.install = (app) => {
app.config.globalProperties.$proConfirm = Confirm
};
export default Confirm;
附上基于el-dialog封装的弹窗组件实例代码 :
// 基于el-dialog封装的弹窗组件实例代码
<template>
<div>
<el-dialog
v-bind="_options"
title=""
v-model="dialogVisible"
:before-close="handleClose"
>
<div class="component-confirm-content">
<slot name="icon">
<img :src="iconMap[props.type].url" alt="" class="icon-sty" />
</slot>
<div class="component-confirm-text">
<div v-if="title" class="title-sty">{{ title }}</div>
<slot>
<div :class="title ? 'sec-sty' : 'fir-sty'">
{{ content }}
</div>
</slot>
</div>
</div>
<template #footer>
<slot name="footer">
<pro-button v-if="isShowCancel" @click="handleClose">{{
cancelName
}}</pro-button>
<pro-button
type="primary"
@click="handleConfirm"
style="margin-left: 10px"
>
{{ confirmName }}
</pro-button>
</slot>
</template>
</el-dialog>
</div>
</template>
<script setup lang="jsx" name="ProConfirm">
import { ref, computed, watch } from "vue";
import { ProButton } from "../../index";
const props = defineProps({
isShow: {
type: Boolean, // 弹窗是否展示
default: false,
},
isShowCancel: {
type: Boolean, // 是否显示取消按钮
default: true,
},
content: {
type: String, // 提示内容
default: "",
},
title: {
type: String, // 提示标题
default: "",
},
type: {
type: String, // icon类型
default: "warn",
},
padding: {
type: String, // 内容的padding
default: "50px 28px 22px",
},
confirmName: {
type: String, // 确认按钮名称
default: "确定",
},
cancelName: {
type: String, // 取消按钮名称
default: "取消",
},
options: {
type: Object, //继承el-dialog自身属性
},
});
const dialogVisible = ref(false);
const iconMap = {
warn: {
url: "https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/component-confirm-warn.png",
},
strongWarn: {
url: "https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/component-confirm-strongWarn.png",
},
success: {
url: "https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/component-confirm-success.png",
},
error: {
url: "https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/component-confirm-error.png",
},
question: {
url: "https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/component-confirm-question.png",
},
};
watch(
() => props.isShow,
(newValues) => {
if (newValues) {
dialogVisible.value = true;
} else {
dialogVisible.value = false;
}
},
{ immediate: true }
);
const emits = defineEmits(["close", "confirm"]);
const _options = computed(() => {
const options = {
closeOnClickModal: false,
showClose: false,
width: "520px",
alignCenter: true,
};
return Object.assign(options, props.options);
});
const handleClose = () => {
emits("close");
};
const handleConfirm = () => {
emits("confirm");
};
const close = () => {
dialogVisible.value = false
}
const show = () => {
dialogVisible.value = true
}
defineExpose({
show,
close
})
</script>
<style lang="less" scoped>
:deep(.el-dialog) {
border-radius: 10px;
padding: 0;
}
:deep(.el-dialog__body) {
padding: v-bind(padding);
}
:deep(.el-dialog__header) {
padding: 0;
}
:deep(.el-dialog__footer) {
text-align: right;
border: none;
padding: 0 28px 32px;
}
.component-confirm-content {
display: flex;
.icon-sty {
height: 20px;
width: 20px;
margin: 2px 10px 0 0;
}
.component-confirm-text {
text-align: left;
.title-sty {
font-weight: 500;
font-size: 15px;
color: #2c2c34;
line-height: 24px;
}
.fir-sty {
font-weight: 500;
font-size: 15px;
color: #2c2c34;
line-height: 24px;
}
.sec-sty {
font-weight: 400;
font-size: 14px;
color: #888888;
line-height: 24px;
margin-top: 6px;
}
}
}
</style>
打开二次确认弹窗方法Confirm 返回一个Promise,在外部调用时在函数返回的.then()中执行确认操作回调回调,此时可以直接将传入的id以闭包的形式传回调函数中,省去了中间存储的一步,具体使用如下:
<template>
<button @click="handleDelete('1')">点击删除</button>
<pro-confirm
:is-show="confirmVisible"
@confirm="handleDelConfirm"
content="正在进行删除操作,是否继续?"
/>
<template>
<script setup>
import { ref } from 'vue';
// 引入组件库方法,也可以直接挂在至globalProperties使用
import { ProConfirmV2 } from 'pro-components'
const currentId = ref();
const confirmVisible= ref(false);
function handleDelete(id) {
ProConfirmV2({
type: 'question',
title: '是否删除?',
confirmName: '确认',
cancelName: '取消',
}).then(() => {
// 直接写用确认删除回调
api({ id }).then(() => {})
})
}
</script>