|
|
@@ -0,0 +1,192 @@
|
|
|
+<template>
|
|
|
+ <div class="circle_progress">
|
|
|
+ <canvas
|
|
|
+ ref="canvasRef"
|
|
|
+ :width="canvasWidth"
|
|
|
+ :height="canvasHeight"
|
|
|
+ :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
|
|
+ ></canvas>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup>
|
|
|
+import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
|
|
+
|
|
|
+/**
|
|
|
+ * 圆形进度条组件
|
|
|
+ * @description 使用 Canvas 绘制的圆形进度条,支持高分辨率屏幕适配
|
|
|
+ */
|
|
|
+
|
|
|
+// ==================== Props 定义 ====================
|
|
|
+interface Props {
|
|
|
+ /** 进度值 (0-100) */
|
|
|
+ process?: number
|
|
|
+ /** 线条宽度 */
|
|
|
+ lineWidth?: number
|
|
|
+ /** 背景圆颜色 */
|
|
|
+ backgroundColor?: string
|
|
|
+ /** 进度条颜色 */
|
|
|
+ lineColor?: string
|
|
|
+ /** Canvas 显示宽度(CSS像素) */
|
|
|
+ canvasWidth?: number
|
|
|
+ /** Canvas 显示高度(CSS像素) */
|
|
|
+ canvasHeight?: number
|
|
|
+ /** 圆形半径 */
|
|
|
+ circleRadius?: number
|
|
|
+}
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<Props>(), {
|
|
|
+ process: 0,
|
|
|
+ lineWidth: 3,
|
|
|
+ backgroundColor: '#ffffff',
|
|
|
+ lineColor: '#2E64FA',
|
|
|
+ canvasWidth: 20,
|
|
|
+ canvasHeight: 20,
|
|
|
+ circleRadius: 5
|
|
|
+})
|
|
|
+
|
|
|
+// ==================== 响应式数据 ====================
|
|
|
+/** Canvas DOM 引用 */
|
|
|
+const canvasRef = ref<HTMLCanvasElement | null>(null)
|
|
|
+
|
|
|
+/** 防抖定时器 */
|
|
|
+let resizeTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
+
|
|
|
+// ==================== 核心方法 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * 绘制圆形进度条
|
|
|
+ * @description 包含背景圆和进度圆弧的绘制,支持高清屏适配
|
|
|
+ */
|
|
|
+const drawCircle = (): void => {
|
|
|
+ const canvas = canvasRef.value
|
|
|
+ if (!canvas) return
|
|
|
+
|
|
|
+ // 获取设备像素比
|
|
|
+ const dpr = window.devicePixelRatio || 1
|
|
|
+
|
|
|
+ // 设置canvas的实际物理尺寸(像素)- 这会重置 Canvas 状态
|
|
|
+ canvas.width = props.canvasWidth * dpr
|
|
|
+ canvas.height = props.canvasHeight * dpr
|
|
|
+
|
|
|
+ // 设置canvas的显示尺寸(CSS像素)
|
|
|
+ canvas.style.width = props.canvasWidth + 'px'
|
|
|
+ canvas.style.height = props.canvasHeight + 'px'
|
|
|
+
|
|
|
+ const ctx = canvas.getContext('2d')
|
|
|
+ if (!ctx) return
|
|
|
+
|
|
|
+ // 缩放绘制上下文以匹配设备像素比
|
|
|
+ ctx.scale(dpr, dpr)
|
|
|
+
|
|
|
+ // 启用高质量渲染
|
|
|
+ ctx.imageSmoothingEnabled = true
|
|
|
+ ctx.imageSmoothingQuality = 'high'
|
|
|
+
|
|
|
+ // 设置线条样式
|
|
|
+ ctx.lineCap = 'round'
|
|
|
+ ctx.lineJoin = 'round'
|
|
|
+ ctx.lineWidth = props.lineWidth
|
|
|
+
|
|
|
+ // 清除画布
|
|
|
+ ctx.clearRect(0, 0, props.canvasWidth, props.canvasHeight)
|
|
|
+
|
|
|
+ // 计算圆心坐标
|
|
|
+ const centerX = props.canvasWidth / 2
|
|
|
+ const centerY = props.canvasHeight / 2
|
|
|
+
|
|
|
+ // 绘制背景圆(灰色)
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(centerX, centerY, props.circleRadius, 0, 2 * Math.PI)
|
|
|
+ ctx.strokeStyle = '#cccccc'
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ // 绘制进度圆弧(仅当进度大于 0 时绘制)
|
|
|
+ if (props.process > 0) {
|
|
|
+ ctx.beginPath()
|
|
|
+
|
|
|
+ // 起始角度:从顶部开始(-90度)
|
|
|
+ const startAngle = -90 * Math.PI / 180
|
|
|
+
|
|
|
+ // 结束角度:根据进度计算
|
|
|
+ const endAngle = ((props.process / 100) * 360 - 90) * Math.PI / 180
|
|
|
+
|
|
|
+ console.log('进度值:', props.process, '起始角度:', startAngle, '结束角度:', endAngle)
|
|
|
+
|
|
|
+ // 绘制圆弧
|
|
|
+ ctx.arc(centerX, centerY, props.circleRadius, startAngle, endAngle, false)
|
|
|
+ ctx.strokeStyle = props.lineColor
|
|
|
+ ctx.stroke()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 窗口大小变化处理函数
|
|
|
+ * @description 使用防抖优化,避免频繁重绘
|
|
|
+ */
|
|
|
+const handleResize = (): void => {
|
|
|
+ if (resizeTimer) {
|
|
|
+ clearTimeout(resizeTimer)
|
|
|
+ }
|
|
|
+
|
|
|
+ resizeTimer = setTimeout(() => {
|
|
|
+ drawCircle()
|
|
|
+ }, 100)
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 生命周期钩子 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * 组件挂载时执行
|
|
|
+ */
|
|
|
+onMounted(() => {
|
|
|
+ drawCircle()
|
|
|
+ window.addEventListener('resize', handleResize)
|
|
|
+})
|
|
|
+
|
|
|
+/**
|
|
|
+ * 组件卸载前执行
|
|
|
+ */
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ window.removeEventListener('resize', handleResize)
|
|
|
+
|
|
|
+ if (resizeTimer) {
|
|
|
+ clearTimeout(resizeTimer)
|
|
|
+ resizeTimer = null
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// ==================== 监听器 ====================
|
|
|
+
|
|
|
+/**
|
|
|
+ * 监听进度变化,自动重绘
|
|
|
+ */
|
|
|
+watch(
|
|
|
+ () => props.process,
|
|
|
+ () => {
|
|
|
+ drawCircle()
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+/**
|
|
|
+ * 监听 Canvas 尺寸变化,自动重绘
|
|
|
+ */
|
|
|
+watch(
|
|
|
+ () => [props.canvasWidth, props.canvasHeight, props.circleRadius],
|
|
|
+ () => {
|
|
|
+ drawCircle()
|
|
|
+ }
|
|
|
+)
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.circle_progress {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ canvas {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|