vue实现全局消息提醒功能(vue-extend)

发布时间 2023-12-08 17:13:45作者: 柯基与佩奇

1.需求背景

(1)在一般的管理系统或者 H5 应用中,需要交互反馈提醒。这种交互反馈,往往需要在多个组件中使用到,那么是否可以将其抽离出来,封装一个组件呢?答案是肯定的,可以根据日常的业务,对消息提醒功能进行封装,那么问题来了,如何实现一次注册,多次使用呢,关键时刻,vue.extend API 就派上用场了

2. vue.extend({组件选项})的用法

(1)官方的解释是:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。 按照理解是extend方法可以把一个组件选项作为参数,使用此方法,可以获得该组件的构造函数,然后通过 new 方法创建一个组件出来。最后通过 amount 方法挂载到对应的结点上。废话不多说,直接上代码。

// 创建构造器
var Profile = Vue.extend({
  template: "<p>{{firstName}} {{lastName}} aka {{alias}}</p>",
  data: function () {
    return {
      firstName: "Walter",
      lastName: "White",
      alias: "Heisenberg",
    };
  },
});
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount("#mount-point");

(2)疑惑:这么方法何种场景适用? 解释一下,对比正常的组件挂载流程,需要在 .vue 文件中的component进行注册,然后在 template 文件中,编写相应的代码。 但很多时候,需要在组件之外的地方,使用到这个组件的一些 api,比如在 axios 拦截响应请求时,对异常消息提示,做出反馈,这个时候无法使用普通的组件注册方式调用消息提醒组件的 api。因为这个是一个 js 文件,因此,通过这个extend API,提供了一个可以在.vue 文件之外,创建组件,并挂载,调用其 api 实现相应功能的能力。

3 封装消息提醒组件

<template>
  <div class="toast" v-show="showToast">
    <div class="info" v-if="type == 'info'">{{ message }}</div>
    <div class="loading" v-if="type == 'loading'">
      <div class="circle">
        <div class="line" style="--line:1"></div>
        <div class="line" style="--line:2"></div>
        <div class="line" style="--line:3"></div>
      </div>
      <div class="message">{{ message }}</div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "",
      duration: 3000,
      timer: null,
      type: "info",
      showToast: false,
      forbidClick: true, //加载时禁止点击
      lock: false,
    };
  },
  watch: {
    showToast(val) {
      if (val) {
        if (this.type == "info") {
          this.startTime();
        } else {
          clearTimeout(this.timer);
          this.loading();
        }
      }
    },
  },
  methods: {
    startTime() {
      clearTimeout(this.timer); //之前的未关闭,需要清除,保障全局唯一性
      this.timer = setTimeout(() => {
        this.clear();
      }, this.duration);
    },
    clear() {
      this.showToast = false;
      this.lock = false;
      this.lockClick();
    },
    loading() {
      this.lock = true;
      this.lockClick();
    },
    lockClick() {
      if (this.lock && this.forbidClick) {
        document.body.classList.add("unclickable");
      } else {
        document.body.classList.remove("unclickable");
      }
    },
  },
};
</script>

<style lang="less">
:root {
  --dw: min(calc(100vw / 750), 750px/750);
}
.px(@ww, @att) {
  @{att}: calc(@ww * var(--dw));
}

.padding(@top, @left, @att) {
  @{att}-top: calc(@top * var(--dw));
  @{att}-bottom: calc(@top * var(--dw));
  @{att}-left: calc(@left * var(--dw));
  @{att}-right: calc(@left * var(--dw));
}

.toast {
  position: fixed;
  left: 50%;
  top: 45%;
  transition: all 0.5s;
  transform: translateX(-50%) translateY(-50%);
  color: #fff;
  text-align: center;
  background: rgba(17, 17, 17, 0.6);
  max-width: 80%;
  z-index: 9999;
  .px(200, min-width);
  .padding(16, 32, padding);
  .px(24, border-radius);
  .px(24, font-size);
}

.loading {
  min-height: calc(200 * var(--dw));
  display: flex;
  flex-direction: column;
  justify-content: center;

  .circle {
    .line {
      display: inline-block;
      .px(15, width);
      .px(15, height);
      .px(15, border-radius);
      background-color: #fe0033;
      animation: loadingA 0.6s linear infinite;
      animation-delay: calc(0.1s * var(--line));
    }
  }

  .message {
    margin-top: calc(32 * var(--dw));
    font-weight: 400;
    color: white;
  }

  @keyframes loadingA {
    0% {
      transform: translate(0, 0);
    }

    50% {
      transform: translate(0, calc(15 * var(--dw)));
    }

    100% {
      transform: translate(0, 0);
    }
  }
}

.unclickable {
  overflow: hidden;

  * {
    pointer-events: none;
  }
}
</style>

(1)组件提供两种类型消息,一种是普通的消息提醒 info,另外一种是 loading 加载提示。默认是 info 类型。 (2) 在消息提醒没有消失的情况下,默认是不可点击页面的任何按钮。通过pointer-events: none; 控制 (3)info 消息提示默认显示是三秒,通过 duration 字段控制时长,loading 加载是没有消失时间,如果没有手动关闭,则会一直显示。

4 全局注册

toast 组件为上面封装的消息提醒组件。

import toast from "./index.vue";

function install(Vue) {
  const ToastConstructor = Vue.extend(toast); //使用基础 Vue 构造器,创建一个“子类 ,可以通过js语法实例化组件并挂载到对应地方
  const instance = new ToastConstructor();
  instance.$mount(document.createElement("div"));
  document.body.appendChild(instance.$el);
  Vue.prototype.$Toast = {
    info(message, duration = 3000) {
      instance.showToast = false;
      Vue.nextTick(() => {
        instance.message = message;
        instance.duration = duration;
        instance.type = "info";
        instance.showToast = true;
      });
    },
    loading(message) {
      instance.showToast = false;
      Vue.nextTick(() => {
        instance.message = message;
        instance.type = "loading";
        instance.showToast = true;
      });
    },
    clear() {
      instance.clear();
    },
  };
}

export default install;

(1)全局注册,主要通过把对应的方法挂载到 vue 的原型上,这样便可以实现任何组件可以使用到了。 接下来便是在 main.js 中,进行操作。 initShowToast 为上面代码对外暴露的 index 脚本

import initShowToast from "./views/components/toast/index";
initShowToast(Vue);

5 使用方式

(1)组件使用 直接通过 this.$toast 访问对应的api,this.$toast 需要根据实际在 vue 原型挂载的命名来确定。上面是 Toast,因此需要对应。

getPop(id) {
  this.$Toast.loading("正在领取...")
  getCoupop({ stockId: id }).then(res => {
    console.log(res)
    //领取成功后,需要刷新
    this.refreshCoupon()
  }).catch(e => {
    console.log(e)
    this.$Toast.clear()
  })
},

(2)axios 拦截器使用 需要注意的是 js 文件无法通过 this 访问到 vue,因此需要以下方法才能实现调用。

import Vue from "vue";
import initShowToast from "../views/components/toast/index";
initShowToast(Vue);
const Toast = Vue.prototype.$Toast;
//下面是使用示例

//loading加载与消息提醒关闭
if (config.showLoading == false) {
  Toast.clear();
} else {
  Toast.loading("加载中...");
}
//普通消息提示
Toast.info(response.data.err_msg);