vue頁面中處理大量對話框

原由

最近項目中一個頁面要用到不少個對話框(7-8個左右), 並且每一個對話框的結構是不同的, 有的底部須要肯定-取消按鈕,有的只要肯定按鈕, 有的啥都不要, 樣式都不一樣, 對話框也有本身的內部邏輯, 要控制對話框的顯示隱藏visible.sync每次都要在data聲明一些標記, 管理這些變量還好,關鍵若是所有對話框都寫在一個父組件裏面,這個組件就好大了(1000行+), 由於對話框能夠嵌套複雜的結構, 維護起來也很難, 原本是想着把每一個對話框寫成一個個子組件,可是發現若是對話框是有前後順序的(好比一個對話框點擊肯定請求數據成功後,才顯示下一個對話框),這時候組件的通訊和數據傳遞又比較困難了, 一開始我使用Vuex管理這些對話框顯示隱藏變量和數據的傳遞, 可是在store中管理這些東西要作的事情不少, 看看element-ui的說明後, 在store中寫了一大坨代碼, 組件中處處都是commit, dispatch, 得不償失, 這確定也不行
html

若是 visible 屬性綁定的變量位於 Vuex 的 store 內,那麼 .sync 不會正常工做。此時須要去除 .sync 修飾符,同時監聽 Dialog 的 open 和 close 事件,在事件回調中執行 Vuex 中對應的 mutation 更新 visible 屬性綁定的變量的值vue

按照一開始在data聲明變量控制對話框的顯示隱藏, A組件這樣寫的:node

<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,
      }
    }
  }
}
</script>
而後整個組件加起來1000多行, 有時候一個el-dialog單html結構就100多行, 閱讀起來很費勁~~~
複製代碼

要解決的問題

  • 對話框顯示隱藏
  • 對話框抽成一個獨立的組件
  • 對話框按順序顯示和數據傳遞

解決過程

個人作法是基於el-dialog組件把對話框封裝成全局彈窗組件, 提供了一個方法invoke呼出對話框並返回promise, 在對話框的內部能夠resolve或reject該promise, 這樣就能夠鏈式then呼出下一個對話框,而且經過promise傳值。 下面是根據個人業務場景實現的全局對話框:element-ui

目錄結構
.dialog
|____dialog.js
|____dialog.vue

------------------------------------------dialog.vue:---------------------------------------
<template>
  <el-dialog :visible.sync="visible" :title="title" @close="close" @open="open":width="width">
    <render :params="params" :render="render" ></render>
  </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,
      open: noop, // '目前業務只用到了el-dialog中的close&open事件,能夠擴展'
      close: noop,
      visible: false,
      resolver: {},
      params: {},
    };
  },
  methods: {
    invoke(config) {
      this.visible = true;
      this.title = config.title;
      this.width = config.width;
      this.render = config.render;
      this.open = config.open;
      this.close = config.close;
      this.params = config.params;
      return new Promise((resolve, reject) => {
        this.resolver = { resolve, reject };
      })
    },
    hide() {
      this.visible = false;
    },
    resolve(v) {
      this.resolver.resolve(v);
    },
    reject(err) {
      this.resolver.reject(new Error(err));
    },
    resolver() {
      return this.resolver;
    }
  },
  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, // '由於dialog是全局組件, 掛載在body尾部, 不在#app容器內,與任何組件沒有父子兄弟關係, 須要從新引入sotre, 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);
    },
    resolver: () => getDialogInstance().resolver(),
}
複製代碼

使用方式

全局引入:
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 {
  data() {
    return {
      age: 33
    }
  },
  methods: {
    showNextDialog() {
    // 使用定時器模擬http請求,2秒後顯示下一個對話框
      setTimeout(() => {
        this.$dialog.resolve(this.age);
      }, 2000);
    }
  },
}
</script>

---------------------------------------------dialog3.vue-------------------------------
<template>
  <div>
    ...假設不少行代碼
    <button @click="closeDialog">關閉對話框</button>
  </div>
</template>
<script>
  export default {
    props: {
      hobby: 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: {
      showDialog1() {
        this.$dialog.invoke({
          title: '對話框1',
          render: h => h(Dialog1)
        }).then(name => {
          console.log(name); // 'jiaxin'
          return this.$dialog.invoke({
            title: '對話框2',
            render: h => h(Dialog2)
          });
        }).then(age => {
          console.log(age); // 33
          this.$dialog.invoke({
            title: '對話框3',
            params: {
              hobby: this.hobby,
            },
            render(h, {hobby}) {
              return h(Dialog3, {props: {hobby}})
              // 若是使用jsx能夠寫成
              return <Dialog3  hobby={hobby} />
            }});
        });
      }
    }
  }
</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
---------------------------------------------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來實現實現異步呼出對話框和數據之間的通訊。promise

相關文章
相關標籤/搜索