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


目录
相关文章
|
25天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
27天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
1天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
25天前
|
JavaScript 前端开发 开发者
Vue是如何劫持响应式对象的
Vue是如何劫持响应式对象的
22 1
|
25天前
|
JavaScript 前端开发 API
介绍一下Vue中的响应式原理
介绍一下Vue中的响应式原理
27 1
|
25天前
|
JavaScript 前端开发 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
25天前
|
存储 JavaScript 前端开发
介绍一下Vue的核心功能
介绍一下Vue的核心功能
|
JavaScript 测试技术 容器
Vue2+VueRouter2+webpack 构建项目
1). 安装Node环境和npm包管理工具 检测版本 node -v npm -v 图1.png 2). 安装vue-cli(vue脚手架) npm install -g vue-cli --registry=https://registry.
1059 0
|
28天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
32 1
vue学习第一章
|
28天前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
26 1
vue学习第三章