背景
最近项目中一个页面要用到很多个对话框(7-8个左右), 而且每个对话框的结构是不一样的, 有的底部需要确定-取消按钮,有的只要确定按钮, 有的啥都不要, 样式都不同, 对话框也有自己的内部逻辑, 要控制对话框的显示隐藏visible.sync
,一个对话框就要声明一个控制显示的变量,管理这些变量会增多不少脏代码。某些场景对话框要顺序弹出关闭,比如dialog1
填完一些表单数据后,拿到接口数据后,关闭当前对话框,弹出dialog2
,并获取dialog1
的接口数据。
*按照一开始在data声明变量控制对话框的显示隐藏, A组件这样写:
<template>
<div> <el-dialog :visible.sync="dialogVisible.dialog1" >...html结构</el-dialog> <el-dialog :visible.sync="dialogVisible.dialog2" >...html结构</el-dialog> <el-dialog :visible.sync="dialogVisible.dialog3" >...html结构</el-dialog> <el-dialog :visible.sync="dialogVisible.dialog4" >...html结构</el-dialog> <el-dialog :visible.sync="dialogVisible.dialog5" >...html结构</el-dialog> ...other code </div>
</template>
<script> export default { data() { return { dialogVisible: { dialog1: false, dialog2: false, dialog3: false, dialog4: false, dialog5: false, dialog6: false, dialog7: false, dialog8: false, dialog9: false, } }, method: { showDialog1() { this.dialog1 = true; }, showDialog2() { this.dialog2 = true; }, } } } </script>
// 然后整个组件加起来1000多行, 有时候一个el-dialog单html结构就100多行, 维护是十分痛苦的
要解决的问题
- 不需要关注对话框显示隐藏
- 通过指令的方式唤起一个对话框
- 对话框按顺序显示和数据传递
解决过程
我的做法是基于el-dialog
组件把对话框封装成全局弹窗组件, 提供了一个方法invok
e呼出对话框并返回promise
, 在对话框的内部可以resolve
或reject
该promise
, 这样就可以链式then
呼出下一个对话框,并且通过promise
传值。
下面是根据我的业务场景实现的全局对话框:
目录结构
.dialog
|____dialog.js
|____dialog.vue
// dialog.vue
<template>
<el-dialog :visible.sync="visible" :title="title" @close="close" @open="open":width="width"> <functionalComponent :params="params" :render="render" ></functionalComponent> </el-dialog>
</template>
<script> const functionalComponent = { functional: true, props: { render: Function, params: Object }, render(h, ctx) { const params = { ...ctx.props.params }; return ctx.props.render(h, params); } } const noop = () => {}; export default { data() { return { title: '', width: '50%', render: noop, cancel: noop, open: noop, // '目前业务只用到了el-dialog中的close&open事件,可以扩展 close: noop, visible: false, resolver: {}, params: {}, }; }, methods: { invoke(config) { this.initParams(config); return new Promise((resolve, reject) => { this.resolver = { resolve, reject }; }) }, initParams(config) { this.visible = true; this.title = config.title; this.width = config.width; this.render = config.render; this.open = config.open; this.close = () => { this.close(); this.hide(); }; this.cancel = () => { this.canel(); this.hide(); } this.params = config.params; } hide() { this.visible = false; }, resolve(v) { this.resolver.resolve(v); }, reject(err) { this.resolver.reject(new Error(err)); }, }, components: { functionalComponent, } } </script>
// dialog.js
import Dialog from './dialog.vue';
import router from '@/router';
import store from '@/store'
import Vue from 'vue';
const noop = () => {};
let dialogInstance = null;
// 挂载Dialog并获得实例
const newInstance = properties => {
const props = properties || {};
const Instance = new Vue({
data: props,
store, // 因为这个是一个单独的Vue实例,跟root实例是隔离的,所以这里需要把store和router上去
router,
render: h => h(Dialog, {
props
})
});
const component = Instance.$mount();
document.body.appendChild(component.$el);
return Instance.$children[0];
};
// 全局只有一个实例, 每次invoke的时候只是在更换functionalComponent的内容,且返回新的promise(pending态)
const getDialogInstance = () => {
dialogInstance = dialogInstance || newInstance();
return dialogInstance;
}
export default {
invoke({ title = '', render = noop, close = noop, open = noop, params = {}, width = '50%' } = {}) {
return getDialogInstance().invoke({title, render, open, close, params, width})
},
hide() {
getDialogInstance().hide();
},
resolve(v) {
getDialogInstance().resolve(v);
},
reject(err) {
getDialogInstance().reject(err);
},
}
使用方式
全局引入:
import Dialog from '@/components/dialog';
Vue.prototype.$dialog = Dialog;
在views文件目录下:
.dialog
|____dialog1.vue
|____dialog2.vue
|____dialog3.vue
|____index.vue
// dialog1.vue
<template>
<div> ...假设很多行代码 <button @click="showNextDialog">显示下一个对话框并传递数据</button> </div>
</template>
<script> export default { data() { return { name: 'jiaxin' } }, methods: { showNextDialog() { // 使用定时器模拟http请求,2秒后显示下一个对话框 setTimeout(() => { this.$dialog.resolve(this.name); }, 2000); } }, } </script>
// dialog2.vue
<template> <div> ...假设很多行代码 <button @click="showNextDialog">显示下一个对话框并传递数据</button> </div> </template>
<script> export default { props: { name: String }, methods: { showNextDialog() { // 使用定时器模拟http请求,2秒后显示下一个对话框 setTimeout(() => { this.$dialog.resolve(10); }, 2000); } }, } </script>
// dialog3.vue
<template> <div> ...假设很多行代码 <button @click="closeDialog">关闭对话框</button> </div> </template>
<script> export default { props: { hobby: String, age: String, }, methods: { closeDialog () { setTimeout(() => { console.log(this.body); this.$dialog.hide(); // 关闭对话框 }, 1000); } }, } </script>
// index.vue
<template> <div> ...假设这里有几百行结构 <button @click="showDialog1">显示对话框1</button> </div> </template>
<script> import Dialog1 from './dialog1'; import Dialog2 from './dialog2'; import Dialog3 from './dialog3'; export default { data() { return { hobby: 'eat' } }, methods: { async showDialog1() { const name = await this.$dialog.invoke({ title: '对话框1', render: (h) => h(Dialog1), }); const age = await this.$dialog.invoke({ title: '对话框2', render: (h) => <Dialog2 name={name}></Dialog2>, }); this.$dialog.invoke({ title: '对话框3', params: { hobby: this.hobby, }, render: (h, { hobby }) => <Dialog3 hobby={hobby} age={age} /> }); }, } } </script>
// 顺序是显示对话框1,对话框2,对话框3,通过promise的then链式调用
使用自定义指令美化this.$dialog.resolve()
当我们在http请求完成成功后才会显示下一个对话框,并且把请求数据带给下一个对话框,如果返回一个promise, this.$dialog.resolve()在promise成功后才会执行, 否则直接把binding的值传递给下个对话框
Vue.directive('dialog', {
inserted: (el, binding, vnode) => {
el.onclick = () => {
const bind = binding.value;
const bindingVal = typeof bind === 'function' ? bind() : bind;
const resolve = vnode.context.$dialog.resolve;
if (bindingVal instanceof Promise) {
bindingVal.then(resolve);
} else {
resolve(bindingVal);
}
}
}
})
// dialog1.vue
<template>
<div> ...假设很多行代码 <button v-dialog="showNextDialog">显示下一个对话框并传递数据</button> </div>
</template>
<script> export default { data() { return { name: 'jiaxin' } }, methods: { showNextDialog() { return new Promise(rs => setTimeout(rs, 2000)); } }, } // 此时效果是一样, 当然可以对this.$dialog.hide()做同样的处理,这里就不说了 </script>
总结
上面封装的dialog全局方法并不适用于所有场景,主要是针对我这边的业务,不过方向是对的,主要是用promise来实现实现异步呼出对话框和数据之间的通信。解决了上面所说的问题。
今天的文章vue页面中处理大量对话框分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/15821.html