<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>