



组件使用
<CircleProgress
:value="50"
color="#444999"
:size="40"
/>
组件代码
<template>
<div class="circle-progress-wrapper">
<div class="circle-progress" :style="{ width: size + 'px', height: size + 'px' }">
<svg :width="size" :height="size" class="circle-svg">
<!-- 绘制10个正梯形 -->
<g v-for="(dot, index) in dots" :key="index">
<defs v-if="dot.clipPercent < 1">
<clipPath :id="`clip-${index}`">
<rect
:x="dot.x - dotTopWidth / 2"
:y="dot.y - dotHeight / 2"
:width="dotTopWidth * dot.clipPercent"
:height="dotHeight"
/>
</clipPath>
</defs>
<polygon
:points="getTrapezoidPoints(dot.x, dot.y)"
:fill="dot.fillColor"
:transform="`rotate(${dot.angle} ${dot.x} ${dot.y})`"
:clip-path="dot.clipPercent < 1 ? `url(#clip-${index})` : undefined"
class="dot"
/>
</g>
</svg>
</div>
<!-- 百分比文字在右侧 -->
<span class="percentage-text" :style="{ color: textColor, fontSize: textSize + 'px' }">
{{ value }}%
</span>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
interface Props {
value: number // 百分比值 0-100
color?: string // 激活点的颜色
backgroundColor?: string // 未激活点的颜色
textColor?: string // 文字颜色
size?: number // 组件大小
dotCount?: number // 总点数
}
const props = withDefaults(defineProps<Props>(), {
value: 0,
color: '#3b82f6',
backgroundColor: '#e5e7eb',
textColor: '#1f2937',
size: 120,
dotCount: 10
})
// 计算属性
const center = computed(() => props.size / 2)
const radius = computed(() => props.size * 0.32) // 调整半径让圆圈更紧凑
// 正梯形的尺寸(上宽下窄)
const dotTopWidth = computed(() => props.size * 0.12) // 上边宽度
const dotHeight = computed(() => props.size * 0.12) // 高度
const dotBottomWidth = computed(() => props.size * 0.08) // 下边宽度
// 生成正梯形的点坐标
const getTrapezoidPoints = (centerX: number, centerY: number) => {
const topWidth = dotTopWidth.value
const bottomWidth = dotBottomWidth.value
const height = dotHeight.value
// 正梯形的四个点(上宽下窄)
const points = [
[centerX - topWidth / 2, centerY - height / 2], // 左上
[centerX + topWidth / 2, centerY - height / 2], // 右上
[centerX + bottomWidth / 2, centerY + height / 2], // 右下
[centerX - bottomWidth / 2, centerY + height / 2], // 左下
]
return points.map(p => `${p[0]},${p[1]}`).join(' ')
}
// 根据百分比计算应该激活多少个点(支持小数)
const activeDots = computed(() => {
return (props.value / 100) * props.dotCount
})
// 计算每个点的位置和激活状态
const dots = computed(() => {
const result = []
const angleStep = 360 / props.dotCount
const activeCount = activeDots.value
for (let i = 0; i < props.dotCount; i++) {
const angle = i * angleStep - 90 // -90度让第一个点在顶部
const radian = (angle * Math.PI) / 180
const x = center.value + radius.value * Math.cos(radian)
const y = center.value + radius.value * Math.sin(radian)
// 计算当前点的激活状态
let fillColor = 'transparent' // 未激活的点使用透明色,不显示
let clipPercent = 1 // 默认显示完整的梯形
if (i < Math.floor(activeCount)) {
// 完全激活的点
fillColor = props.color
clipPercent = 1
} else if (i === Math.floor(activeCount) && activeCount % 1 !== 0) {
// 部分激活的点(半点)- 裁剪显示部分梯形
fillColor = props.color
clipPercent = activeCount % 1
}
result.push({
x,
y,
angle: angle + 90, // 让梯形指向圆心
fillColor,
clipPercent
})
}
return result
})
// 文字大小
const textSize = computed(() => {
return Math.max(props.size * 0.25, 18)
})
</script>
<style scoped lang="scss">
.circle-progress-wrapper {
display: inline-flex;
align-items: center;
gap: 16px;
}
.circle-progress {
position: relative;
display: inline-block;
}
.circle-svg {
display: block;
}
.dot {
transition: fill 0.3s ease;
}
.percentage-text {
font-weight: 600;
line-height: 1;
}
</style>
3231

被折叠的 条评论
为什么被折叠?



