Skip to content

Vue2 插件化开发

开发一个 Message 插件, 在屏幕中显示一个信息

目录结构

代码实现

html
<!-- Message/Message.vue -->
<template>
  <div v-show="visible" class="message-box" :class="type">{{ msg }}</div>
</template>

<script>
  export default {
    data: () => ({
      type: "",
      msg: "",
      onClose: null,
      visible: true,
      duration: 1000,
    }),
    mounted() {
      this.timer = null;
      this.startTimer();
    },
    methods: {
      close() {
        this.visible = false;
        this.clearTimer();
        typeof this.onClose === "function" && this.onClose();
      },
      startTimer() {
        this.timer = setTimeout(this.close, this.duration);
      },
      clearTimer() {
        this.timer && clearTimeout(this.timer);
      },
    },
  };
</script>

<style lang="scss" scoped>
  .message-box {
    position: fixed;
    top: 30px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 200;
    min-width: 200px;
    text-align: center;
    padding: 5px 10px;
    color: #fff;
    border-radius: 3px;
    font-size: 14px;
    &.info {
      background: #c6e2ff;
    }
    &.error {
      background: #f56c6c;
    }
    &.success {
      background: #67c23a;
    }
  }
</style>
js
// Message/index.js
"use strict";
import Vue from "vue";
import MessageVue from "./Message.vue";

// Vue.extend 继承 Vue 类
const MessageClass = Vue.extend(MessageVue);
const MessageTypes = ["success", "warning", "info", "error"];

// MessageComponent 的 vm 实例
let instance;

function Message(options = { type: "info", msg: "" }) {
  options.onClose = Message.close;
  instance = new MessageClass({
    data: options,
  });
  instance.$mount(); // 手动挂载
  document.body.append(instance.$el); // 将 $el 添加到 body
}

// 可以直接 Message() 也可以 Message.success() 调用
MessageTypes.forEach((item) => {
  Message[item] = function (msg) {
    if (typeof msg !== "string") {
      throw new TypeError("[Message]: msg must be a string");
    }
    return Message({ type: item, msg });
  };
});

// 销毁实例
Message.close = function () {
  instance.$el.remove();
  instance.$destroy();
  instance = null;
};

export default Message;

Vue3 插件化开发

目录结构

源码实现

html
<!-- ConfirmMsg/index.vue -->
<template>
  <teleport to="body">
    <div v-show="visible" class="layer-mask">
      <div class="layer-main">
        <div class="layer-header">{{ data.title }}</div>
        <div class="layer-content">{{ data.content }}</div>
        <div class="layer-footer">
          <button class="btn cancel" @click="cancel">取消</button>
          <button class="btn confirm" @click="confirm">确定</button>
        </div>
      </div>
    </div>
  </teleport>
</template>

<script setup>
  import { reactive, ref, defineExpose } from "vue";

  const visible = ref(true);
  const data = reactive({
    title: "",
    content: "",
  });

  // onConfirm(resolve), onCancel(reject), onClose(ConfirmMsg.close)
  let onConfirm, onCancel, onClose;

  function init({ resolve, reject, close, options }) {
    onConfirm = resolve;
    onCancel = reject;
    onClose = close;
    data.title = options.title;
    data.content = options.content;
  }
  defineExpose({ init });

  const cancel = () => exec(onCancel);
  const confirm = () => exec(onConfirm);
  function exec(fn) {
    visible.value = false;
    typeof fn === "function" && fn.call(this);
    typeof onClose === "function" && onClose(this);
  }
</script>

<style lang="scss" scoped>
  .layer-mask {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 100%;
    right: 100%;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    .layer-main {
      min-width: 300px;
      background: #fff;
      padding: 10px 20px;
      .layer-header {
        font-weight: 500;
      }
      .layer-content {
        padding: 30px 0;
        text-align: center;
      }
      .layer-footer {
        display: flex;
        justify-content: flex-end;
        .btn {
          border: none;
          color: #fff;
          padding: 5px 15px;
          &.confirm {
            background: #3f9eff;
          }
          &.cancel {
            margin-right: 20px;
            background: #f42c2e;
          }
        }
      }
    }
  }
</style>
js
// ConfirmMsg/index.js
"use strict";

import { createApp } from "vue";
import ConfirmMsgVue from "./index.vue";

let instance; // ConfirmMsg 实例, 一个 Proxy 对象
let appCtx; // 应用实例可以理解为 getCurrentInstnace() 返回的结果,
// 注意这个 appCtx 和 setup(ctx) 这个参数是不同的
// appCtx 是组件的执行上下文, 而 setup(ctx) 只是 setup 函数
// 执行是时的上下文

function ConfirmMsg(options = { title, content }) {
  return new Promise((resolve, reject) => {
    const frag = document.createDocumentFragment();
    appCtx = createApp(ConfirmMsgVue);
    instance = appCtx.mount(frag);
    instance.init({
      resolve,
      reject,
      close: ConfirmMsg.close,
      options,
    });
  });
}

// 卸载组件 && 销毁实例
ConfirmMsg.close = function () {
  instance.$el.remove();
  appCtx.unmount();
  instance = null;
};

export default ConfirmMsg;

Released under the MIT License.