vue组件封装 —— 仪表盘(有缺口的环形进度条内显示百分比值)

简介: vue组件封装 —— 仪表盘(有缺口的环形进度条内显示百分比值)

组件 s-progress.vue

<template>
  <div
    class="el-progress"
    :class="[
      'el-progress--' + type,
      status ? 'is-' + status : '',
      {
        'el-progress--without-text': !showText,
        'el-progress--text-inside': textInside,
      },
    ]"
    role="progressbar"
    :aria-valuenow="percentage"
    aria-valuemin="0"
    aria-valuemax="100"
  >
    <div class="el-progress-bar" v-if="type === 'line'">
      <div
        class="el-progress-bar__outer"
        :style="{ height: strokeWidth + 'px' }"
      >
        <div class="el-progress-bar__inner" :style="barStyle">
          <div
            :style="textStyle"
            class="el-progress-bar__innerText  myText"
            v-if="showText && textInside"
          >
            {{ content }}
          </div>
        </div>
      </div>
    </div>
    <div
      class="el-progress-circle"
      :style="{ height: width + 'px', width: width + 'px' }"
      v-else
    >
      <svg viewBox="0 0 100 100">
        <path
          class="el-progress-circle__track"
          :d="trackPath"
          :stroke="bgColor"
          :stroke-width="relativeStrokeWidth"
          fill="none"
          :style="trailPathStyle"
          :stroke-linecap="strokeLinecap"
        ></path>
        <path
          class="el-progress-circle__path"
          :d="trackPath"
          :stroke="stroke"
          fill="none"
          :stroke-linecap="strokeLinecap"
          :stroke-width="percentage ? relativeStrokeWidth : 0"
          :style="circlePathStyle"
        ></path>
      </svg>
    </div>
    <div
      class="el-progress__text myText"
      v-if="showText && !textInside"
      :style="{ fontSize: progressTextSize + 'px' }"
    >
      <template v-if="!status" :style="textStyle">{{ content }}</template>
      <i v-else :class="iconClass"></i>
    </div>
  </div>
</template>
<script>
export default {
  name: "ElProgress",
  props: {
    bgColor: {
      type: String,
      default: "#e5e9f2",
    },
    rate: {
      type: Number,
      default: 1,
    },
    type: {
      type: String,
      default: "line",
      validator: (val) => ["line", "circle", "dashboard"].indexOf(val) > -1,
    },
    percentage: {
      type: Number,
      default: 0,
      required: true,
      validator: (val) => val >= 0 && val <= 100,
    },
    status: {
      type: String,
      validator: (val) => ["success", "exception", "warning"].indexOf(val) > -1,
    },
    strokeWidth: {
      type: Number,
      default: 6,
    },
    strokeLinecap: {
      type: String,
      default: "round",
    },
    textInside: {
      type: Boolean,
      default: false,
    },
    width: {
      type: Number,
      default: 126,
    },
    showText: {
      type: Boolean,
      default: true,
    },
    color: {
      type: [String, Array, Function],
      default: "",
    },
    format: Function,
  },
  computed: {
    barStyle() {
      const style = {};
      style.width = this.percentage + "%";
      style.backgroundColor = this.getCurrentColor(this.percentage);
      return style;
    },
    relativeStrokeWidth() {
      return ((this.strokeWidth / this.width) * 100).toFixed(1);
    },
    radius() {
      if (this.type === "circle" || this.type === "dashboard") {
        return parseInt(50 - parseFloat(this.relativeStrokeWidth) / 2, 10);
      } else {
        return 0;
      }
    },
    trackPath() {
      const radius = this.radius;
      const isDashboard = this.type === "dashboard";
      return `
          M 50 50
          m 0 ${isDashboard ? "" : "-"}${radius}
          a ${radius} ${radius} 0 1 1 0 ${isDashboard ? "-" : ""}${radius * 2}
          a ${radius} ${radius} 0 1 1 0 ${isDashboard ? "" : "-"}${radius * 2}
          `;
    },
    perimeter() {
      return 2 * Math.PI * this.radius;
    },
    strokeDashoffset() {
      const offset = (-1 * this.perimeter * (1 - this.rate)) / 2;
      return `${offset}px`;
    },
    trailPathStyle() {
      return {
        strokeDasharray: `${this.perimeter * this.rate}px, ${this.perimeter}px`,
        strokeDashoffset: this.strokeDashoffset,
      };
    },
    circlePathStyle() {
      return {
        strokeDasharray: `${this.perimeter *
          this.rate *
          (this.percentage / 100)}px, ${this.perimeter}px`,
        strokeDashoffset: this.strokeDashoffset,
        transition: "stroke-dasharray 0.6s ease 0s, stroke 0.6s ease",
      };
    },
    stroke() {
      let ret;
      if (this.color) {
        ret = this.getCurrentColor(this.percentage);
      } else {
        switch (this.status) {
          case "success":
            ret = "#13ce66";
            break;
          case "exception":
            ret = "#ff4949";
            break;
          case "warning":
            ret = "#e6a23c";
            break;
          default:
            ret = "#20a0ff";
        }
      }
      return ret;
    },
    iconClass() {
      if (this.status === "warning") {
        return "el-icon-warning";
      }
      if (this.type === "line") {
        return this.status === "success"
          ? "el-icon-circle-check"
          : "el-icon-circle-close";
      } else {
        return this.status === "success" ? "el-icon-check" : "el-icon-close";
      }
    },
    progressTextSize() {
      return this.type === "line"
        ? 12 + this.strokeWidth * 0.4
        : this.width * 0.111111 + 2;
    },
    content() {
      if (typeof this.format === "function") {
        return this.format(this.percentage) || "";
      } else {
        return `${this.percentage}%`;
      }
    },
  },
  methods: {
    getCurrentColor(percentage) {
      if (typeof this.color === "function") {
        return this.color(percentage);
      } else if (typeof this.color === "string") {
        return this.color;
      } else {
        return this.getLevelColor(percentage);
      }
    },
    getLevelColor(percentage) {
      const colorArray = this.getColorArray().sort(
        (a, b) => a.percentage - b.percentage
      );

      for (let i = 0; i < colorArray.length; i++) {
        if (colorArray[i].percentage > percentage) {
          return colorArray[i].color;
        }
      }
      return colorArray[colorArray.length - 1].color;
    },
    getColorArray() {
      const color = this.color;
      const span = 100 / color.length;
      return color.map((seriesColor, index) => {
        if (typeof seriesColor === "string") {
          return {
            color: seriesColor,
            percentage: (index + 1) * span,
          };
        }
        return seriesColor;
      });
    },
  },
};
</script>
<style scoped>
.myText {
  background-image: linear-gradient(135deg, red, blue);
  -webkit-background-clip: text;
  color: transparent;
}
</style>

使用范例

<template>
  <div class="progressBox">
    <Sprogress
      type="dashboard"
      :width="width"
      :rate="rate"
      :percentage="percentage"
      :bgColor="bgColor"
      :color="colors"
      :stroke-width="strokeWidth"
      :stroke-linecap="strokeLinecap"
    />
    <div class="labelBox">{{ label }}</div>
  </div>
</template>
<script>
import Sprogress from "./s-progress.vue";
export default {
  components: { Sprogress },
  data() {
    return {
      label: "CPU",
      width: 100,
      strokeLinecap: "square",
      strokeWidth: 10,
      rate: 0.75,
      percentage: 90,
      bgColor: "#42567F",
      colors: [
        { color: "#5090FF", percentage: 20 },
        { color: "#5090FF", percentage: 40 },
        { color: "#5090FF", percentage: 60 },
        { color: "#5090FF", percentage: 80 },
        { color: "#5090FF", percentage: 100 },
      ],
    };
  },
};
</script>

<style scoped>
::v-deep .myText {
  font-weight: bolder;
  font-size: 20px !important;
  background-image: linear-gradient(0deg, #498dff, #8bb7fe);
}

.progressBox {
  margin: 130px;
  width: 80px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.labelBox {
  background: #4c8fff;
  width: 50px;
  height: 30px;
  line-height: 30px;
  text-align: center;
  color: white;
  border-radius: 15px;
  margin-top: -20px;
}
</style>

相关参数

参数 说明 类型 可选值 默认值
percentage 百分比(必填) number 0-100 0

type 进度条类型 string line/circle/dashboard line
stroke-width 进度条的宽度,单位 px number 6
text-inside 进度条显示文字内置在进度条内(只在 type=line 时可用) boolean false
status 进度条当前状态 string success/exception/warning
color 进度条背景色(会覆盖 status 状态颜色) string/function/array ''
width 环形进度条画布宽度(只在 type 为 circle 或 dashboard 时可用) number 126
show-text 是否显示进度条文字内容 boolean true
stroke-linecap circle/dashboard 类型路径两端的形状 string butt/round/square round
format 指定进度条文字内容 function(percentage)
rate 进度条比例 number 0-1 1
bgColor 进度条背景色 string 任意颜色值 #e5e9f2


目录
相关文章
|
16天前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
136 2
|
4月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
573 0
|
4月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
3月前
|
JavaScript 安全
在 Vue 中,如何在回调函数中正确使用 this?
在 Vue 中,如何在回调函数中正确使用 this?
112 0
|
3月前
|
人工智能 JSON JavaScript
VTJ.PRO 首发 MasterGo 设计智能识别引擎,秒级生成 Vue 代码
VTJ.PRO发布「AI MasterGo设计稿识别引擎」,成为全球首个支持解析MasterGo原生JSON文件并自动生成Vue组件的AI工具。通过双引擎架构,实现设计到代码全流程自动化,效率提升300%,助力企业降本增效,引领“设计即生产”新时代。
246 1
|
4月前
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
377 17
|
4月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
213 1
|
存储 前端开发 JavaScript
为什么我不再用Vue,改用React?
当我走进现代前端开发行业的时候,我做了一个每位开发人员都要做的决策:选择一个合适的框架。当时正逢 jQuery 被淘汰,前端开发者们不再用它编写难看的、非结构化的老式 JavaScript 程序了。
|
6月前
|
JavaScript
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
770 4
|
5月前
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
548 77