实现效果 将el-dialog
进行封装,便于其他组件进行简单调用,同时简化代码
封装Dialog 过程 主要是参考了 Vue3这样子结合hook写弹窗组件更快更高效 这篇文章里面的内容,但是我的项目里面的脚本使用的是JavaScript,参考文章里面用的是typescript,因此进行了一些翻译(不是)修改。
首先记录一下一些踩坑实况(
问题 点击触发弹窗的按钮后,弹窗并没有弹出来
排查 修改一下openDialog
,使其修改visible
状态后在控制台log
一下,发现状态是成功修改的,但是弹窗确实没有弹出来。
同时能够发现,浏览器的滚动条在点击按钮之前是有的,点击按钮后没有了,虽然也不知道为什么。(此处应有图)
于是我再整一个按钮,调用closeDialog
,随便点点试试。
然后发现,点一次open
,再点一次close
,浏览器的滚动条又出现了,状态也是对应的修改了true/false
。
然后重点来了!!!!
此时再点一次open
,弹窗出来了。
然后我将openDialog
改为先把visible
改true,再false,再改true(搁这搁这呢
发现还是不行
此时我再翻了一下控制台,发现其实是有报错的,只是我之前都忽略了(
这里其实就是我初始化的时候用了defineProps
定义了hiddenFullBtn
,后面直接就调用了,显示没有定义,到底怎么会事呢。
请看VCR:[学习Vue3的defineProps方法](https://zhuanlan.zhihu.com/p/680340457#:~:text=defineProps 方法是在 环境下使用的,这是Vue 3中推荐的语法糖,可以更紧凑地书写组件。 以下是 defineProps,<%2Fscript> 在上述代码中,我们通过 defineProps 定义了两个Props属性: prop1 和 prop2 。)
defineProps
其实是有返回值的,我这里直接参考其他组件的写法,没有拿到返回值,因此这里的hiddenFullBtn
会报错为没有定义。
改了一下,OK了~
附上代码
CommonDialog.vue <script setup> import { computed, ref, useAttrs, useSlots } from "vue"; import { FullScreen, Close } from "@element-plus/icons-vue"; const attrs = useAttrs(); const slots = useSlots(); const props=defineProps({ title:{ type:String }, isDraggable:{ type:Boolean, default:false }, modelValue:{ type:Boolean, default:false }, hiddenFullBtn:{ type:Boolean, default:false }, loading:{ type:Boolean, default:false }, confirmText:{ type:String, default:"确认" }, cancelText:{ type:String, default:"关闭" }, }) const isFullscreen = ref(false); // 是否显示全屏效果图标 const isFullScreenBtn = computed(() => { if (props.hiddenFullBtn) return false; return !attrs?.fullscreen; }); const emits=defineEmits(["update:modelValue","confirm","close"]) // 开启、关闭全屏效果 const handleFullscreen = () => { if (attrs?.fullscreen) return; isFullscreen.value = !isFullscreen.value; }; // 关闭弹窗时向外部发送close事件 const handleClose = () => { // 获取attrs.['before-close']属性,如果类型是函数函数,先执行它 if ( Reflect.has(attrs, "before-close") && typeof attrs["before-close"] === "function" ) { attrs["before-close"](); } emits("close"); }; const handleConfirm = () => { emits("confirm"); }; </script> <template> <div class=""> <el-dialog v-bind="attrs" :model-value="props.modelValue" :show-close="false" :fullscreen="attrs?.fullscreen ?? isFullscreen" :before-close="handleClose" > <template #header> <div> <span class="dialog-title">{{ props.title }}</span> </div> <div class="btns"> <el-icon v-if="isFullScreenBtn" @click="handleFullscreen" ><FullScreen /></el-icon> <el-icon @click="handleClose"><Close /></el-icon> </div> </template> <div class="content" v-loading="props.loading"> <slot></slot> </div> <template #footer> <!-- 如果没有提供其他footer插槽,就使用默认的 --> <span v-if="!slots.footer" class="dialog-footer"> <el-button type="primary" @click="handleConfirm">{{ props.confirmText }}</el-button> <el-button @click="handleClose">{{ props.cancelText }}</el-button> </span> <!-- 使用传入进来的插槽 --> <slot v-else name="footer"></slot> </template> </el-dialog> </div> </template> <style lang="less" scoped> :deep(.el-dialog__header) { border-bottom: 1px solid #eee; display: flex; padding: 12px 16px; align-items: center; justify-content: space-between; margin: 0; } .dialog-title { line-height: 24px; font-size: 18px; color: #303133; } .btns { display: flex; align-items: center; i { margin-right: 8px; font-size: 16px; cursor: pointer; } i:last-child { margin-right: 0; } } </style>
useDialog.js import {ref} from "vue" ;export const useDialog =( )=>{ const visible = ref (false ); const loading = ref (false ); const openDialog =( )=>{ visible.value =true } const closeDialog = ( ) => (visible.value = false ); const openLoading = ( ) => (loading.value = true ); const closeLoading = ( ) => (loading.value = false ); return { visible, loading, openDialog, closeDialog, openLoading, closeLoading } }
使用Dialog script import {useDialog} from "@/composables/useDialog.js" ;import CommonDialog from "@/components/dialog/CommonDialog.vue" ;import {ElMessage } from "element-plus" ;const { visible, openDialog, closeDialog, }=useDialog () const handleConfirm = ( ) => { ElMessage ({ message : "你点击了确定按钮" , type : "success" , }); closeDialog (); }; const handleClose = ( ) => { ElMessage ({ message : "关闭了窗口" , type : "info" , }); closeDialog (); };
template <el-button @click="openDialog">普通弹窗</el-button> <CommonDialog title="test111" :hidden-full-btn="true" v-model="visible" @confirm="handleConfirm" @close="handleClose"></CommonDialog>
首先这里创建一个用于区分表格是添加/编辑/只读状态的js
dialogTypes.js export const MODE = { ADD :'add' , EDIT :'edit' , READONLY :'readonly' , }
然后创建一个管理表格弹窗状态的js以及useFormDialog.js
useDialogState.js import {ref} from "vue" ;import {MODE } from "./dialogTypes.js" export const useDialogState =( )=>{ const mode=ref (MODE .ADD ) const visible=ref (false ) const updateMode =(target )=>{ mode.value =target } return { mode, visible, updateMode } }
import {useDialogState} from "@/composables/useDialogState.js" ;export const useFormDialog =(formInstance )=>{ const { visible, mode, updateMode } = useDialogState (); const closeDialog = ( ) => { formInstance.value ?.resetFields (); visible.value = false ; }; const openDialog = (target ) => { updateMode (target); visible.value = true ; formInstance.value ?.resetFields (); }; const modeText = ( ) => { if (mode.value === 'add' ) return '添加' ; if (mode.value === 'edit' ) return '编辑' ; if (mode.value === 'readonly' ) return '详情' ; } return { visible, mode, openDialog, closeDialog, modeText } }
其中useFormDialog
传入的是表格对象,在关闭和打开弹窗时都要清空一下表格的内容。
script const { visible, mode, closeDialog, openDialog } = useFormDialog (formRef)defineExpose ({ openDialog }) const confirm = ( ) => { if (!formRef.value ) return ; formRef.value .validate ((valid ) => { if (valid) { ElMessage ({ message : "提交成功" , type : "success" , }) console .log (form) closeDialog () } }) } const customClose = ( ) => { closeDialog () }
这里使用了defineExpose
,将useFormDialog
中的openDialog
导出供父组件使用。
template <CommonDialog :before-close="customClose" @confirm="confirm" v-model="visible" title="标题标题" :confirm-text="mode === MODE.ADD ? '添加' : '修改'" > <div class="addressWrapper"> <el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="100px" status-icon> <!-- 此处省略表格内容 --> </el-form> </div> </CommonDialog>
<script setup> import AddressFormDialog from "@/components/dialog/AddressFormDialog.vue"; import {MODE} from "@/composables/dialogTypes.js"; import {ref} from "vue"; const formDialogRef = ref(AddressFormDialog) const openDialog=(mode)=>{ if (!formDialogRef.value) return formDialogRef.value.openDialog(mode) } </script> <template> <el-button @click="openDialog(MODE.EDIT)">普通弹窗</el-button> <AddressFormDialog ref="formDialogRef"/> </template>
大功告成!
参考链接 Vue3这样子结合hook写弹窗组件更快更高效 - 掘金 (juejin.cn)
[学习Vue3的defineProps方法 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/680340457#:~:text=defineProps 方法是在 环境下使用的,这是Vue 3中推荐的语法糖,可以更紧凑地书写组件。 以下是 defineProps,<%2Fscript> 在上述代码中,我们通过 defineProps 定义了两个Props属性: prop1 和 prop2 。)
关于Vue3的defineProps用法 - 掘金 (juejin.cn)
Rean's Blog
Enjoy technology and music