vue3 + elementPlus 实现自定义进度条指令v-progress

148 阅读1分钟

不想每个地方都重复使用elementplus的 进度条组件,就想着像v-loading一样,封装一个,方便使用。

使用

<div class="result-content" v-progress="progressValue">
//简单说明:progressValuefalse时隐藏,0100 显示进度条

代码

import { ElProgress } from "element-plus";
import { createVNode, render } from "vue";

const vProgress = {
  mounted(el, binding) {
    // 为每个元素创建独立的状态
    el._progressState = {
      mask: null,
      progressContainer: null,
      progressTimer: null,
      progressVNode: null,
      currentProgress: 0,
      randomValue: 0,
      // 将方法绑定到实例
      updateProgress: (newVal) => {
        if (newVal >= 0 && newVal <= 100) {
          el._progressState.currentProgress = newVal;

          // 创建遮罩和容器(如果不存在)
          if (newVal === 0 && !el._progressState.mask) {
            createProgressUI(el);
          }

          // 更新进度条
          if (el._progressState.progressContainer) {
            const progressVNode = createVNode(ElProgress, {
              percentage: newVal,
              "stroke-width": 15,
              striped: true,
              "striped-flow": true,
              duration: 10,
              "text-inside": true,
              width: 200,
              status: newVal >= 95 ? "success" : "",
              format: () => {
                return `${newVal < 95 ? "分析计算中" : "计算完成,结果准备中"} ${newVal}%`;
              },
              style: {
                width: "50%",
                fontWeight: "bold",
                color: "#009265",
              },
            });
            
            render(progressVNode, el._progressState.progressContainer);
          }

          // 当进度为100时,移除进度条和遮罩层
          if (newVal === 100) {
            setTimeout(() => {
              cleanup(el);
            }, 1500);
          } else if (newVal < 100) {
            // 随机延迟时间
            const randomDelay = 0.5 + Math.random() * 1000;
            el._progressState.randomValue += Math.random();
            
            el._progressState.progressTimer = setTimeout(() => {
              const nextVal = Math.floor(Math.min(el._progressState.randomValue, 98));
              el._progressState.updateProgress(nextVal);
            }, randomDelay);
          }
        }
      }
    };

    if (binding.value === false) return;
    el._progressState.updateProgress(binding.value || 0);
  },

  updated(el, binding) {
    if (binding.value === false) {
      cleanup(el);
      return;
    }

    if (!el._progressState) {
      vProgress.mounted(el, binding);
      return;
    }

    // 清除之前的定时器
    if (el._progressState.progressTimer) {
      clearTimeout(el._progressState.progressTimer);
      el._progressState.progressTimer = null;
    }

    el._progressState.updateProgress(binding.value || 0);
  },

  unmounted(el) {
    cleanup(el);
  }
};

// 辅助函数
function cleanup(el) {
  if (!el._progressState) return;
  
  if (el._progressState.progressTimer) {
    clearTimeout(el._progressState.progressTimer);
  }
  
  if (el._progressState.mask && el.contains(el._progressState.mask)) {
    el.removeChild(el._progressState.mask);
  }
  
  if (el._progressState.progressContainer && el.contains(el._progressState.progressContainer)) {
    el.removeChild(el._progressState.progressContainer);
  }
  
  delete el._progressState;
}

function createProgressUI(el) {
  const state = el._progressState;
  if (!state || state.mask) return;

  // 创建遮罩层
  const mask = document.createElement("div");
  mask.style.position = "absolute";
  mask.style.top = "0";
  mask.style.left = "0";
  mask.style.width = "100%";
  mask.style.height = "100%";
  mask.style.backgroundColor = "rgba(215, 215, 215, 0.95)";
  mask.style.display = "flex";
  mask.style.justifyContent = "center";
  mask.style.alignItems = "center";
  mask.style.zIndex = "9999";
  el.style.position = "relative";
  el.style.overflow = "hidden";
  el.appendChild(mask);
  state.mask = mask;

  // 创建进度条容器
  const progressContainer = document.createElement("div");
  progressContainer.style.position = "absolute";
  progressContainer.style.width = "100%";
  progressContainer.style.height = "100%";
  progressContainer.style.zIndex = "10000";
  progressContainer.style.display = "flex";
  progressContainer.style.justifyContent = "center";
  progressContainer.style.alignItems = "center";
  el.appendChild(progressContainer);
  state.progressContainer = progressContainer;
}

export default vProgress;