vue3自定义全局confirm插件

5,679 阅读2分钟

背景

公司已经在一些项目上迁移vue3了,由于设计稿的弹窗组件和之前的vue2项目的比较一致,就直接拿过来改了,这里记录一下碰到的一些问题。

插件的定义

首先需要我们了解下插件在vue中的说明

  • 插件用于向Vue添加全局功能
  • 一般是有着install方法作为注册入口的Object或者function
  • install提供两个参数,第一个是app,第二个options

插件实现的能力一般分为以下几种

  1. 添加全局方法或者属性
  2. 添加全局资源:指令/过渡等
  3. 通过插件(使用全局 mixin 方法),添加一些组件选项
  4. 添加全局实例方法,通过把它们添加到 config.globalProperties 上实现
  5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router

常见的全局注册vue2和vue3的区别

实现方式vue2vue3
全局实例方法Vue.prototypeapp.config.globalProperties
全局组件Vue.componentapp.component
全局指令Vue.directiveapp.directive

vue2中插件的写法

main.js

import Vue from "vue";
import App from "./App.vue";
import confirm from "./···/confirm/index";

// 通过Vue.use()装载插件
Vue.use(confirm);
new Vue({
  render: (h) => h(App),
}).$mount("#app");

/components/confirm/index.js

import Comfirm from "./ComfirmModal.vue";
const ComfirmPlugin = {};
/**

* 在main.js中Vue.use时会自动传入vue实例并执行install

*/

ComfirmPlugin.install = function (Vue) {
  //1、Vue.extend拿到构造器constructor
  const pluginConstructor = Vue.extend(Comfirm);

  //2、创建组件对象
  const ComfirmPlugin = new pluginConstructor();

  //3、将组件对象,手动挂载到某一个元素上
  ComfirmPlugin.$mount(document.createElement("div"));

  // 其中3、4也可以合并为下面代码,在创建的时候就指定挂载元素
  // const plugin = new pluginConstructor({
  //   el: document.createElement("div"),
  //   data: {},
  // });

  document.body.appendChild(ComfirmPlugin.$el);

  //5、挂载在 Vue.prototype中实现全局访问
  Vue.prototype.$confirm = ComfirmPlugin;
};
export default ComfirmPlugin; //将ComfirmPlugin导出

vue3中插件的写法

main.js

import { createApp } from "vue";
import App from "./App.vue";
import Confirm from "./components/confirm";

// 通过createApp(App).use()装载插件
const app = createApp(App).use(Confirm);

app.mount("#app");

/components/confirm/index.js

import Comfirm from "./ComfirmModal.vue";
import { createApp } from "vue";

const ComfirmPlugin = {};
ComfirmPlugin.install = function (app) {
  //1、实例化并绑定组件
  const plugin = createApp(Comfirm);
  const instance = plugin.mount(document.createElement("div"));

  //2.将挂载的Node添加到body中
  document.body.appendChild(instance.$el);

  //3、定义全局
  app.config.globalProperties.$confirm = instance;
};
export default ComfirmPlugin;

组件代码

目前想要实现的需求有:

  1. 可以直接将组件导入页面,通过父页面传入数据控制
  2. 直接可以通过全局调用

这里附上最终实现的代码,其中由于之前的组件实现中用到了$on$off。查了官方文档,这两个方法在vue3中已被移除,所以改造中也去掉了这两个方法。最终在博客代码有所删改,有问题可以评论区交流

image.png

ConfirmModal.vue


<template>
  <div class="confirm-modal">
    <transition name="fade">
      <!-- @touchmove.prevent -->
      <div class="modal-dialog" @click="clickMaskToClose ? handleCancel() : null" v-if="visible" @touchmove.prevent>
        <div class="modal">
          <div class="modal-title" v-if="title">
            {{ title }}
          </div>
          <div :class="['modal-content', title ? '' : 'no-title-content']">
            {{ content }}
          </div>
          <div class="split-line-top"></div>
          <div class="modal-footer">
            <div class="btn-cancel" @click="handleCancel" v-if="showCancelButton">
              {{ cancelText }}
            </div>
            <div class="split-line-center" v-if="showCancelButton"></div>
            <div class="btn-confirm" :style="{ color: confirmColor }" @click="handleConfirm">
              <div>{{ confirmText }}</div>
            </div>
          </div>
        </div>
      </div>
    </transition>
  </div>
</template>

<script>
import { ref, defineComponent, reactive, toRefs } from "vue";

export default defineComponent({
  props: {
    visible: {
      type: Boolean,
      default: false,
    },
    showCancelButton: {
      type: Boolean,
      default: false,
    },
    showConfirmButton: {
      type: Boolean,
      default: true,
    },
    title: {
      type: String,
      default: "提示",
    },
    content: {
      type: String,
      default: "内容",
    },
    confirmText: {
      type: String,
      default: "我知道了",
    }, // 确认按钮文本
    confirmColor: {
      type: String,
      default: "#409EFF",
    },
    cancelText: {
      type: String,
      default: "取消",
    }, // 取消按钮文本
    clickMaskToClose: {
      type: Boolean,
      default: false,
    }, // 点击遮罩是否隐藏
  },

  emits: {
    onConfirm: null,
    onCancel: null,
  },
  setup(props, context) {
    let tempData = Object.assign({}, props);
    const propsData = reactive(tempData);
    const handleConfirm = () => {
      propsData.visible = false;
      context.emit("onConfirm");
    };
    const handleCancel = () => {
      propsData.visible = false;
      context.emit("onCancel");
    };

    return {
      ...toRefs(propsData),
      handleCancel,
      handleConfirm,
    };
  },
});
</script>
<style scoped>
@import "../assets/css/animate.css";

.modal-dialog {
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  text-align: center;
  z-index: 999;
  transform: translateZ(9999px);
  letter-spacing: 0;
  background: rgba(0, 0, 0, 0.3);
}
.modal {
  position: absolute;
  top: 40%;
  left: 50%;
  z-index: 1000;
  width: 300px;
  transform: translate(-50%, -50%);
  box-sizing: border-box;
  background: #fff;
  border-radius: 4px;
}

.modal-title {
  padding: 24px 28px 0 28px;
  font-size: 18px;
  line-height: 25px;
  color: #030303;
}

.modal-content {
  font-size: 16px;
  line-height: 21px;
  color: #5e5f64;
  padding: 16px 24px 24px 24px;
}

.no-title-content {
  font-size: 16px;
  padding: 28px;
  color: #333333;
}

.modal-right {
  padding-right: 10px;
  width: 36px;
  background: #f2f2f2;
  color: rgba(0, 16, 38, 0.3);
  font-size: 12px;
  border-radius: 0 4px 4px 0;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
}

.split-line-top {
  height: 1px;
  transform: scale(1, 0.5);
  background: #e8eaef;
}
.modal-footer {
  width: 100%;
  display: flex;
  align-items: center;
  height: 52px;
  font-size: 16px;
  line-height: 52px;
  text-align: center;
}
.split-line-center {
  width: 1px;
  height: 100%;
  transform: scale(0.5, 1);
  background: #e8eaef;
}

.btn-cancel {
  flex: 1;
  color: #696d76;
}
.btn-confirm {
  position: relative;
  flex: 1;
  color: #409eff;
}
</style>

index.js

import { createApp } from "vue";
import ConfirmComponent from "./ConfirmModal.vue";

const ConfirmPlugin = {};
let $vm;

const defaultsOptions = {
  title: "提示",
  content: "内容",
  confirmText: "确定",
  cancelText: "取消",
  confirmColor: "#409EFF",
  showCancelButton: true,
  showConfirmButton: true,
  clickMaskToClose: false,
};

const initInstance = () => {
  const app = createApp(ConfirmComponent);
  const container = document.createElement("div");
  $vm = app.mount(container);
  document.body.appendChild(container);
};

ConfirmPlugin.install = function (app) {
  const confirm = {
    show(options) {
      if (!$vm) initInstance();
      options = Object.assign({}, defaultsOptions, options);
      for (const i in options) {
        $vm[i] = options[i];
      }
      let handleCancel = $vm.handleCancel;
      let handleConfirm = $vm.handleConfirm;
      $vm.handleCancel = () => {
        handleCancel();
        options && options.onCancel && options.onCancel();
      };

      $vm.handleConfirm = () => {
        handleConfirm();
        options && options.onConfirm && options.onConfirm();
      };

      $vm.visible = true;

      return $vm;
    },
    hide() {
      if ($vm) $vm.visible = false;
    },
  };
  app.config.globalProperties.$confirm = confirm;
};

export default ConfirmPlugin;

main.js

import { createApp } from "vue";
import App from "./App.vue";
import Confirm from "./components/confirm";

// 通过createApp(App).use()装载插件
const app = createApp(App).use(Confirm);

app.mount("#app");

最后调用的代码及效果如下

 setup() {
    const { proxy } = getCurrentInstance();
    proxy.$confirm.show({
      title: "温馨提示",
      onConfirm: () => {
        console.log("confirm", proxy.$confirm);
      },
    });
  },

image.png