Skip to content
📈0️⃣

全局 loading 效果

要实现的功能

语法

window.$bus.emit('spin', param, callback)

第一参数

固定字符串 'spin'

第二参数

  1. 不传值:默认开启 loading
  2. 传值 true:开启 loading
  3. 传值 false:关闭 loading
  4. 传值字符串:修改 loading 文本
  5. 传值对象:包含 show 默认为 true;text 默认为 '加载中...';delay 默认为 5000 毫秒

第三参数

callback 是关闭 loading 后的回调

实战效果演示

window.$bus.emit('spin')

window.$bus.emit('spin', 'loading')

window.$bus.emit('spin', '加载中')

window.$bus.emit('spin', { delay: 8000 })

window.$bus.emit('spin', false)

window.$bus.emit('spin', { show: false })

利用 mitt 实现全局 loading 效果

1. 编写 loading.vue

新建文件 loading.vue,在其中编写一个全局 loading 效果组件。

vue
<script setup lang="ts">
import { ref, onMounted } from "vue";

const spinShow = ref(false);
const spinTxt = ref("加载中...");
type Timeout = ReturnType<typeof setTimeout> | null;
const timer = ref<Timeout>(null);

onMounted(() => {
  const bus = (window as any).$bus;
  bus.off("spin");
  interface Iparam {
    text?: string;
    show?: boolean;
    delay?: number;
    callback?: () => void;
  }
  // 通过调用 window.$bus.emit('spin') 来触发加载中效果,参数为可选
  bus.on("spin", (data: string | boolean | Iparam = "加载中...") => {
    if (typeof data === "boolean") {
      spinShow.value = data;
    } else if (typeof data === "string") {
      spinShow.value = true;
      spinTxt.value = data;
    } else if (typeof data === "object") {
      const { text = "加载中...", show = true } = data;
      spinShow.value = show;
      spinTxt.value = text;
    }
    const callback =
      (typeof data === "object" ? data.callback : "") || (() => {});
    const delay = (typeof data === "object" ? data.delay : "") || 5000;
    if (data) {
      clearTimeout(timer.value!);
      timer.value = setTimeout(() => {
        spinShow.value = false;
        callback();
      }, delay);
    } else {
      clearTimeout(timer.value!);
      timer.value = setTimeout(() => {
        spinShow.value = false;
        callback();
      }, 500);
    }
  });
});
</script>
vue
<template>
  <div class="zc-spin" v-if="spinShow">
    <div class="zc-spin__content">
      <div class="zc-spin--loading">
        <svg class="circular" viewBox="0 0 50 50">
          <circle class="path" cx="25" cy="25" r="20" fill="none"></circle>
        </svg>
      </div>
      <div class="zc-spin--data">{{ spinTxt }}</div>
    </div>
  </div>
</template>
vue
<style lang="scss">
@keyframes loading-rotate {
  to {
    transform: rotate(360deg);
  }
}

@keyframes loading-dash {
  0% {
    stroke-dasharray: 1, 200;
    stroke-dashoffset: 0;
  }

  50% {
    stroke-dasharray: 90, 150;
    stroke-dashoffset: -40px;
  }

  to {
    stroke-dasharray: 90, 150;
    stroke-dashoffset: -120px;
  }
}

// 定义变量, 主要是用于自定义主题, 默认浅色主题, 即 color-scheme: light;
:root {
  --zc-color-primary: #65b1ff;
  --zc-mask-color: rgba(255, 255, 255, 0.9);
}
// 深色主题, 即 color-scheme: dark;
html.dark {
  --zc-color-primary: #3e73ac;
  --zc-mask-color: rgba(0, 0, 0, 0.8);
}

.zc-spin {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: var(--zc-mask-color);
  z-index: 99;

  .zc-spin__content {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 300px;
    height: 200px;
    margin-left: -150px;
    margin-top: -100px;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.3);
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    text-align: center;
    transition: opacity 0.2s;
  }

  .zc-spin--loading {
    top: 50%;
    margin-top: calc(-40px / 2);
    width: 100%;
    text-align: center;
    position: absolute;

    .circular {
      display: inline;
      height: 40px;
      width: 40px;
      animation: loading-rotate 2s linear infinite;

      .path {
        animation: loading-dash 1.5s ease-in-out infinite;
        stroke-dasharray: 90, 150;
        stroke-dashoffset: 0;
        stroke-width: 2;
        stroke: #65b1ff;
        stroke-linecap: round;
      }
    }
  }

  .zc-spin--data {
    margin-top: 125px;
    color: var(--zc-color-primary);
  }
}
</style>

2. 在 App.vue 中引入 loading.vue

vue
<script setup>
import Loading from "./components/loading.vue";
</script>

<template>
  <div>
    <Loading></Loading>
  </div>
</template>

3. 测试 loading

在需要使用 loading 的页面中,引入 Loading 组件,并调用其 show() 方法即可。

vue
<script setup lang="ts">
const doLoading = (text?: string | boolean | object) => {
  const btns = document.querySelectorAll(".test-box button");
  btns.forEach((e) => {
    e.style.zIndex = "9999";
    e.style.position = "relative";
  });
  let param: { [key: string]: any } = {};
  if (typeof text === "boolean") param.show = text;
  if (typeof text === "string") param.text = text;
  if (typeof text === "object") param = text;
  window.$bus.emit("spin", {
    ...param,
    callback: () => {
      btns.forEach((e) => (e.style.zIndex = ""));
    },
  });
};
</script>
html
<template>
  <div class="test-box">
    <p>
      <el-button type="primary" @click="doLoading()">spin</el-button
      >window.$bus.emit('spin')
    </p>
    <p>
      <el-button type="primary" @click="doLoading('loading')">loading</el-button
      >window.$bus.emit('spin', 'loading')
    </p>
    <p>
      <el-button type="primary" @click="doLoading('加载中')">加载中</el-button
      >window.$bus.emit('spin', '加载中')
    </p>
    <p>
      <el-button type="primary" @click="doLoading({ delay: 8000 })"
        >{ delay: 8000 }</el-button
      >window.$bus.emit('spin', { delay: 8000 })
    </p>
    <p>
      <el-button type="danger" @click="doLoading(false)">false</el-button
      >window.$bus.emit('spin', false)
    </p>
    <p>
      <el-button type="danger" @click="doLoading({ show: false })"
        >{ show: false }</el-button
      >window.$bus.emit('spin', { show: false })
    </p>
  </div>
</template>