Weex 动态Modal设计
我们在项目中使用了各种自定义的对话框、弹出框等控件,其中各种控件都是在各自的页面中弹出,其中最大的问题就是在tabbar中子页面弹出的modal不能显示全屏。本文的目的即是设计一个动态的Modal放在tabbar外面,通过与子页面的调用方法来开启对应的动态组件。
构建动态组件
Weex中用的组件实际是使用Vue.js生成的代码,由于Vue 2.0之后的代码都是预编译的,以前使用动态template的方法已经不适用,能构建动态组件的方法只有<component>
以及render
方法创建, 本文暂时先采用<component>
来实现, 关于更多的<component>
的知识可以参考Vue动态组件文档。
最基本的动态组件(v-modal.vue)如下:
<template>
<div class="cModal" append="tree">
<component v-bind:is="modalCurrentView"></component>
</div>
</template>
<style>
.cModal{
width: 750;
background-color: transparent;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
</style>
<script>
module.exports = {
computed:{
modalCurrentView() {
return this.$store.state.modalCurrentView
}
},
components:{
"pop-call" : require("../module/personal/mainpage/v-pop-call.vue"),
"pop-head" : require("../module/personal/mainpage/v-pop-head.vue"),
"pop-version" : require("../module/personal/mainpage/v-pop-version.vue"),
"action-sheet" : require("../manage/camera/action_sheet/v-action-sheet.vue"),
}
}
</script>
<component>
主要是通过动态修改is绑定的modalCurrentView
组件来进行切换,可以用此方法来实现类似tabbar切换之类的效果。
其中我们的数据采用的是Vuex进行管理,也就是存储在store.js文件中:
import Vuex from 'vuex'
// Vuex is auto installed on the web
if (WXEnvironment.platform !== 'Web') {
Vue.use(Vuex)
}
var store = new Vuex.Store({
state:{
modalIsShow:false,
modalCurrentView:"pop-call"
},
mutations:{
CHANGE_MODAL_SHOW (state, isShow) {
state.modalIsShow = isShow
},
CHANGE_MODAL_VIEW (state, view) {
state.modalCurrentView = view
}
},
actions:{
changeModalShow(context, state) {
context.commit("CHANGE_MODAL_SHOW", state);
},
changeModalView(context, view) {
context.commit("CHANGE_MODAL_VIEW", view);
}
}
})
export default store
可以从store.js中看出,我们可以用changeModalShow
方法来控制Modal的显示和隐藏,用changeModalView
方法可以控制切换成那一个组件,不过调用store的方法显得有些麻烦,而且在WebStorm上没有任何提示,我们可以在其中再次封装一个modal.js来进行调用:
import store from './store.js'
import Vue from 'vue'
export default {
closeModal () {
store.dispatch("changeModalShow", false)
},
openModal (view) {
store.dispatch("changeModalView", view);
store.dispatch("changeModalShow", true)
},
}
至此一个简单的动态modal架构就出来了, 我们只需要在最外层的页面增加此动态控件即可:
<template>
<div class="cRoot" @androidback="onClickAndroidBack"
@viewappear="viewappear"
@viewdisappear="viewdisappear">
......
// 添加动态modal
<modal v-if="modalIsShow"></modal>
</div>
</template>
<script>
var nav = weex.requireModule('event');
var storage = weex.requireModule("storage");
module.exports = {
......
components:{
......
"modal" : require("./v-modal.vue")
},
computed:{
modalIsShow () {
return this.$store.state.modalIsShow
}
}
}
</script>
我们需要调用显示modal就可以使用:
import vModal from '../modal.js';
vModal.openModal("pop-head"); //其中"pop-head"是我们再v-modal.vue文件中引入的components;
vModal.closeModal(); //需要关闭的时候,只需要调用此方法即可
组件之间传值
Modal的显示和隐藏都没有问题了,但是还有一个问题就是封装的组件点击事件是直接往上传递,也就是传到动态的modal后就直接传到最外面的页面了,类似tabbar子页面就不会接收到事件。此时需要我们使用非父子关系之间的传值,简单的方法有两种, 一种是通过Vuex来实现, 另一种就是使用Vue组件通信, 因为我们定义了单独的modal.js,用第一种方法,调用modal时还要额外多引入文件,因此我们采用第二种方法。
Vue非父子关系之间的通信非常简单,只需要在modal.js加入几行代码即可:
import store from './store.js'
import Vue from 'vue'
export default {
eventBus:new Vue({}), //定义一个通用的Vue对象进行通信
closeModal () {
store.dispatch("changeModalShow", false)
},
openModal (view) {
store.dispatch("changeModalView", view);
store.dispatch("changeModalShow", true)
},
}
不过我们原来的组件点击的时候不再采用原来的this.$emit("eventName", eventData);
,因为此方法只会冒泡到最外面的页面, 而传入不到调用的页面;应该改用如下的方法:
import vModal from '../modal.js'
vModal.eventBus.$emit("eventName", eventData);
而接收事件的组件可以mounted
方法中进行监听:
mounted:function(){
var that= this;
vModal.eventBus.$on("eventName", (eventData)=> {
});
},
通过此方法, 我们就可以在动态modal包括的组件和调用动态modal的组件之间建立了联系。
动态组件的参数传递
动态modal还有一个很大的问题,就是包含的组件如果需要传递参数,怎么办呢?
我们首先定义一个统一的变量作为动态modal的传入参数, 在store.js中进行修改,加入参数:
var store = new Vuex.Store({
state:{
modalIsShow:false,
modalCurrentView:"pop-call",
modalData:{}
},
mutations:{
...
CHANGE_MODAL_DATA (state, data) {
state.modalData = data
}
},
actions:{
...
changeModalData(context, data) {
context.commit("CHANGE_MODAL_DATA", data);
}
}
})
export default store
而在定义的modal.js中修改openModal
方法:
openModal (view, data) {
store.dispatch("changeModalData", data);
store.dispatch("changeModalView", view);
store.dispatch("changeModalShow", true)
},
定义的modalData
可以通过<component>
的参数传入
<template>
<div class="cModal" append="tree">
<component v-bind:is="modalCurrentView" :modalData="modalData"></component>
</div>
</template>
......
<script>
import Vue from 'vue'
module.exports = {
computed:{
modalCurrentView() {
return this.$store.state.modalCurrentView
},
modalData() {
let data = this.$store.state.modalData;
}
},
......
}
</script>
在自定义的弹出显示的组件内容中我们可以使用props来接收这些参数:
module.exports = {
props:{
modalData:{default:{}}
},
}
不过用此方法传递的参数,对子组件的修改比较多, 更好的方法是使用render函数来动态构建一个组件, 通过修改控件的tag来实现,不过在实际使用的时候碰到了一些问题,暂时先用这种不太优雅的方式实现,后续再进行改进吧。