CircularProgress

This commit is contained in:
何德超 2025-08-12 21:21:53 +08:00
parent 9d6086726d
commit 79bd72febb
1 changed files with 157 additions and 0 deletions

View File

@ -0,0 +1,157 @@
<template>
<div class="circular-progress-container">
<div class="circular-progress" :style="{ width: size + 'px', height: size + 'px' }">
<svg class="circular-progress-svg" :width="size" :height="size">
<!-- 背景圆环 -->
<circle
class="circular-progress-bg"
:cx="center"
:cy="center"
:r="radius"
:stroke-width="strokeWidth"
fill="none"
/>
<!-- 进度圆环 -->
<circle
class="circular-progress-fill"
:cx="center"
:cy="center"
:r="radius"
:stroke-width="strokeWidth"
fill="none"
:stroke-dasharray="circumference"
:stroke-dashoffset="strokeDashoffset"
:style="{ stroke: progressColor }"
/>
</svg>
<!-- 中心文本 -->
<div class="circular-progress-text" v-if="showText">
<span class="progress-percent">{{ percent }}%</span>
<span class="progress-label" v-if="label">{{ label }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
interface Props {
percent: number
size?: number
strokeWidth?: number
showText?: boolean
label?: string
color?: string
}
const props = withDefaults(defineProps<Props>(), {
size: 60,
strokeWidth: 4,
showText: true,
label: '',
color: ''
})
//
const center = computed(() => props.size / 2)
const radius = computed(() => (props.size - props.strokeWidth) / 2)
const circumference = computed(() => 2 * Math.PI * radius.value)
const strokeDashoffset = computed(() => circumference.value - (props.percent / 100) * circumference.value)
//
const progressColor = computed(() => {
if (props.color) return props.color
if (props.percent >= 80) return '#52c41a'
if (props.percent >= 60) return '#1890ff'
if (props.percent >= 40) return '#faad14'
if (props.percent > 0) return '#ff4d4f'
return '#d9d9d9'
})
</script>
<style scoped lang="scss">
.circular-progress-container {
display: inline-block;
}
.circular-progress {
position: relative;
display: inline-block;
}
.circular-progress-svg {
transform: rotate(-90deg);
}
.circular-progress-bg {
stroke: #f0f2f5;
}
.circular-progress-fill {
transition: stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1);
stroke-linecap: round;
}
.circular-progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
}
.progress-percent {
font-size: 14px;
font-weight: 600;
color: #1d2129;
line-height: 1;
}
.progress-label {
font-size: 10px;
color: #86909c;
margin-top: 2px;
line-height: 1;
}
//
.circular-progress[style*="width: 40px"],
.circular-progress[style*="width: 40px;"] {
.progress-percent {
font-size: 12px;
}
.progress-label {
font-size: 9px;
}
}
.circular-progress[style*="width: 80px"],
.circular-progress[style*="width: 80px;"] {
.progress-percent {
font-size: 16px;
}
.progress-label {
font-size: 11px;
}
}
.circular-progress[style*="width: 100px"],
.circular-progress[style*="width: 100px;"] {
.progress-percent {
font-size: 18px;
}
.progress-label {
font-size: 12px;
}
}
</style>