Vue-Ellipse-Progress 使用指南

发布于:2025-09-13 ⋅ 阅读:(20) ⋅ 点赞:(0)

Vue-Ellipse-Progress 完整使用指南

🌟 前言

打造视觉震撼的数据展示体验,从一个完美的进度条开始!

在现代前端开发中,数据可视化已经成为用户体验的重要组成部分。一个优雅的进度条不仅能清晰地传达信息,更能为你的应用增添专业感和科技感。

为什么选择 vue-ellipse-progress?

🎯 零学习成本 - 简单易用的 API 设计,5 分钟即可上手
🎨 无限创意 - 支持渐变色、多重进度、动画效果等高级特性
性能卓越 - 轻量级设计,对性能敏感的项目完全友好
🔧 高度定制 - 从简单的百分比显示到复杂的仪表盘,满足所有需求
📱 响应式 - 完美适配移动端和桌面端

这份指南将带你:

✅ 掌握从基础到高级的所有用法
✅ 学会在实际项目中的最佳实践
✅ 了解性能优化的核心技巧
✅ 获得完整的 TypeScript 支持方案

无论你是 Vue 新手还是资深开发者,这份指南都将成为你的得力助手。让我们一起探索 vue-ellipse-progress 的强大功能,为你的项目打造令人印象深刻的数据展示效果!


📚 目录

  1. 库介绍
  2. 安装与配置
  3. 基础用法
  4. 高级特性
  5. 配置选项详解
  6. 实际项目应用
  7. 样式定制
  8. 性能优化
  9. 常见问题
  10. 最佳实践

📖 库介绍

vue-ellipse-progress 是一个功能强大的 Vue.js 圆形进度条组件库,支持以下特性:

  • 🎨 高度可定制的圆形进度条
  • 📊 支持多种数据展示格式
  • 🎬 平滑的动画效果
  • 🎯 支持多个进度线
  • 🌈 丰富的颜色配置
  • 📱 响应式设计
  • ⚡ 轻量级,性能优秀

主要用途

  • 数据可视化
  • 加载进度显示
  • KPI 指标展示
  • 仪表盘组件
  • 统计图表

🚀 安装与配置

NPM 安装

npm install vue-ellipse-progress

Yarn 安装

yarn add vue-ellipse-progress

全局注册

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import VueEllipseProgress from 'vue-ellipse-progress';

const app = createApp(App);
app.use(VueEllipseProgress);
app.mount('#app');

局部注册

<script>
import VueEllipseProgress from 'vue-ellipse-progress';

export default {
  components: {
    VueEllipseProgress,
  },
};
</script>

TypeScript 支持

// vue-ellipse-progress.d.ts
declare module 'vue-ellipse-progress' {
  import { DefineComponent } from 'vue';
  const VueEllipseProgress: DefineComponent<{}, {}, any>;
  export default VueEllipseProgress;
}

🎯 基础用法

简单进度条

<template>
  <div class="progress-container">
    <!-- 基础圆形进度条 -->
    <vue-ellipse-progress :progress="60" :size="200" color="blue" />
  </div>
</template>

<script>
export default {
  name: 'BasicProgress',
};
</script>

带标签的进度条

<template>
  <vue-ellipse-progress
    :progress="75"
    :size="180"
    color="#42b883"
    :thickness="8"
    empty-thickness="4"
    empty-color="#f0f0f0"
  >
    <span class="progress-text">75%</span>
  </vue-ellipse-progress>
</template>

<style scoped>
.progress-text {
  font-size: 24px;
  font-weight: bold;
  color: #42b883;
}
</style>

动态进度更新

<template>
  <div class="dynamic-progress">
    <vue-ellipse-progress
      :progress="currentProgress"
      :size="200"
      color="#ff6b6b"
      :animation="{ duration: 1000 }"
    >
      <div class="progress-content">
        <div class="progress-value">{{ currentProgress }}%</div>
        <div class="progress-label">加载中...</div>
      </div>
    </vue-ellipse-progress>

    <div class="controls">
      <button @click="updateProgress">更新进度</button>
      <button @click="resetProgress">重置</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentProgress: 0,
    };
  },
  methods: {
    updateProgress() {
      this.currentProgress = Math.floor(Math.random() * 100);
    },
    resetProgress() {
      this.currentProgress = 0;
    },
  },
  mounted() {
    // 自动递增示例
    this.autoIncrement();
  },
  methods: {
    autoIncrement() {
      const interval = setInterval(() => {
        if (this.currentProgress < 100) {
          this.currentProgress += 1;
        } else {
          clearInterval(interval);
        }
      }, 50);
    },
  },
};
</script>

<style scoped>
.dynamic-progress {
  text-align: center;
}

.progress-content {
  text-align: center;
}

.progress-value {
  font-size: 28px;
  font-weight: bold;
  color: #ff6b6b;
}

.progress-label {
  font-size: 14px;
  color: #666;
  margin-top: 5px;
}

.controls {
  margin-top: 20px;
}

.controls button {
  margin: 0 10px;
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  background: #42b883;
  color: white;
  cursor: pointer;
}

.controls button:hover {
  background: #369870;
}
</style>

🎨 高级特性

多重进度条

<template>
  <vue-ellipse-progress
    :data="progressData"
    :size="220"
    :thickness="12"
    :empty-thickness="4"
    empty-color="#f5f5f5"
  >
    <div class="multi-progress-content">
      <div class="main-value">{{ totalProgress }}%</div>
      <div class="sub-values">
        <div
          class="sub-item"
          v-for="(item, index) in progressData"
          :key="index"
        >
          <span class="color-dot" :style="{ background: item.color }"></span>
          <span>{{ item.label }}: {{ item.progress }}%</span>
        </div>
      </div>
    </div>
  </vue-ellipse-progress>
</template>

<script>
export default {
  data() {
    return {
      progressData: [
        { progress: 40, color: '#ff6b6b', label: 'CPU' },
        { progress: 65, color: '#4ecdc4', label: '内存' },
        { progress: 30, color: '#45b7d1', label: '磁盘' },
      ],
    };
  },
  computed: {
    totalProgress() {
      const total = this.progressData.reduce(
        (sum, item) => sum + item.progress,
        0
      );
      return Math.round(total / this.progressData.length);
    },
  },
};
</script>

<style scoped>
.multi-progress-content {
  text-align: center;
  padding: 10px;
}

.main-value {
  font-size: 32px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
}

.sub-values {
  font-size: 12px;
}

.sub-item {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 3px 0;
}

.color-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 6px;
}
</style>

渐变色进度条

<template>
  <vue-ellipse-progress
    :progress="progress"
    :size="200"
    :color="gradientColors"
    :thickness="10"
    line-cap="round"
  >
    <div class="gradient-content">
      <div class="progress-number">{{ progress }}</div>
      <div class="progress-unit">分</div>
    </div>
  </vue-ellipse-progress>
</template>

<script>
export default {
  data() {
    return {
      progress: 85,
      gradientColors: {
        radial: false,
        colors: [
          { color: '#ff9a9e', offset: '0%' },
          { color: '#fecfef', offset: '50%' },
          { color: '#fecfef', offset: '100%' },
        ],
      },
    };
  },
};
</script>

<style scoped>
.gradient-content {
  text-align: center;
}

.progress-number {
  font-size: 36px;
  font-weight: bold;
  background: linear-gradient(45deg, #ff9a9e, #fecfef);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.progress-unit {
  font-size: 14px;
  color: #666;
  margin-top: -5px;
}
</style>

仪表盘样式

<template>
  <vue-ellipse-progress
    :progress="dashboardData.value"
    :size="250"
    :thickness="8"
    :color="getDashboardColor(dashboardData.value)"
    :angle="-180"
    :loading="false"
    empty-color="#e6e6e6"
    :dot="dotConfig"
  >
    <div class="dashboard-content">
      <div class="dashboard-value">{{ dashboardData.value }}</div>
      <div class="dashboard-unit">{{ dashboardData.unit }}</div>
      <div class="dashboard-label">{{ dashboardData.label }}</div>
      <div class="dashboard-range">
        {{ dashboardData.min }} - {{ dashboardData.max }}
      </div>
    </div>
  </vue-ellipse-progress>
</template>

<script>
export default {
  data() {
    return {
      dashboardData: {
        value: 42,
        min: 0,
        max: 100,
        unit: '°C',
        label: '温度',
      },
      dotConfig: {
        size: 6,
        color: 'white',
        border: { width: 2, color: '#333' },
      },
    };
  },
  methods: {
    getDashboardColor(value) {
      if (value < 30) return '#4ecdc4'; // 绿色
      if (value < 70) return '#f7b731'; // 黄色
      return '#ff5252'; // 红色
    },
  },
};
</script>

<style scoped>
.dashboard-content {
  text-align: center;
  padding-top: 30px;
}

.dashboard-value {
  font-size: 48px;
  font-weight: bold;
  color: #333;
  line-height: 1;
}

.dashboard-unit {
  font-size: 18px;
  color: #666;
  margin-top: -8px;
}

.dashboard-label {
  font-size: 16px;
  color: #333;
  margin: 10px 0 5px;
  font-weight: 500;
}

.dashboard-range {
  font-size: 12px;
  color: #999;
}
</style>

🔧 配置选项详解

基础配置

const basicConfig = {
  // 进度值 (0-100)
  progress: 50,

  // 组件大小
  size: 200,

  // 进度条厚度
  thickness: 4,

  // 空白部分厚度
  emptyThickness: 4,

  // 进度条颜色
  color: 'blue',

  // 空白部分颜色
  emptyColor: '#e6e6e6',

  // 线条端点样式 ("round" | "butt" | "square")
  lineCap: 'round',
};

动画配置

const animationConfig = {
  animation: {
    // 动画持续时间 (毫秒)
    duration: 1000,

    // 缓动函数
    easing: 'ease-out',

    // 延迟时间
    delay: 0,
  },
};

高级配置

const advancedConfig = {
  // 起始角度 (度)
  angle: -90,

  // 进度条方向 (1: 顺时针, -1: 逆时针)
  clockwise: 1,

  // 加载状态
  loading: false,

  // 点标记配置
  dot: {
    size: 5,
    color: 'white',
    border: {
      width: 2,
      color: '#333',
    },
  },

  // 图例配置
  legend: {
    display: true,
    formatter: '{0}%',
  },
};

💼 实际项目应用

1. 仪表盘 KPI 组件

<template>
  <div class="kpi-dashboard">
    <div class="kpi-grid">
      <div class="kpi-item" v-for="kpi in kpiData" :key="kpi.id">
        <vue-ellipse-progress
          :progress="kpi.value"
          :size="160"
          :thickness="6"
          :color="kpi.color"
          :animation="{ duration: 1500 }"
        >
          <div class="kpi-content">
            <div class="kpi-value">{{ kpi.value }}%</div>
            <div class="kpi-title">{{ kpi.title }}</div>
            <div class="kpi-target">目标: {{ kpi.target }}%</div>
          </div>
        </vue-ellipse-progress>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'KpiDashboard',
  data() {
    return {
      kpiData: [
        {
          id: 1,
          title: '销售完成率',
          value: 85,
          target: 100,
          color: '#42b883',
        },
        {
          id: 2,
          title: '客户满意度',
          value: 92,
          target: 90,
          color: '#ff6b6b',
        },
        {
          id: 3,
          title: '项目进度',
          value: 67,
          target: 80,
          color: '#4ecdc4',
        },
        {
          id: 4,
          title: '团队效率',
          value: 78,
          target: 85,
          color: '#f7b731',
        },
      ],
    };
  },
};
</script>

<style scoped>
.kpi-dashboard {
  padding: 20px;
}

.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 30px;
}

.kpi-item {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  text-align: center;
  transition: transform 0.3s ease;
}

.kpi-item:hover {
  transform: translateY(-5px);
}

.kpi-content {
  padding: 10px;
}

.kpi-value {
  font-size: 28px;
  font-weight: bold;
  color: #333;
}

.kpi-title {
  font-size: 16px;
  color: #666;
  margin: 8px 0 4px;
}

.kpi-target {
  font-size: 12px;
  color: #999;
}
</style>

2. 文件上传进度

<template>
  <div class="file-upload">
    <div
      class="upload-area"
      @click="triggerFileInput"
      @drop="handleDrop"
      @dragover.prevent
    >
      <input
        ref="fileInput"
        type="file"
        multiple
        @change="handleFileSelect"
        style="display: none"
      />
      <div v-if="!uploading">
        <i class="upload-icon">📁</i>
        <p>点击或拖拽文件到这里上传</p>
      </div>
    </div>

    <div v-if="uploading" class="upload-progress">
      <vue-ellipse-progress
        :progress="uploadProgress"
        :size="120"
        :thickness="8"
        color="#42b883"
        :animation="{ duration: 300 }"
      >
        <div class="upload-status">
          <div class="progress-value">{{ uploadProgress }}%</div>
          <div class="upload-speed">{{ uploadSpeed }}</div>
        </div>
      </vue-ellipse-progress>

      <div class="file-list">
        <div v-for="file in uploadFiles" :key="file.name" class="file-item">
          <span class="file-name">{{ file.name }}</span>
          <span class="file-status" :class="file.status">{{
            file.statusText
          }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'FileUpload',
  data() {
    return {
      uploading: false,
      uploadProgress: 0,
      uploadSpeed: '0 KB/s',
      uploadFiles: [],
    };
  },
  methods: {
    triggerFileInput() {
      this.$refs.fileInput.click();
    },

    handleFileSelect(event) {
      const files = Array.from(event.target.files);
      this.startUpload(files);
    },

    handleDrop(event) {
      event.preventDefault();
      const files = Array.from(event.dataTransfer.files);
      this.startUpload(files);
    },

    async startUpload(files) {
      this.uploading = true;
      this.uploadFiles = files.map((file) => ({
        name: file.name,
        status: 'pending',
        statusText: '等待中...',
      }));

      // 模拟上传进度
      for (let i = 0; i <= 100; i++) {
        await new Promise((resolve) => setTimeout(resolve, 50));
        this.uploadProgress = i;
        this.uploadSpeed = `${Math.floor(Math.random() * 100 + 50)} KB/s`;

        // 更新文件状态
        if (i === 100) {
          this.uploadFiles.forEach((file) => {
            file.status = 'success';
            file.statusText = '上传完成';
          });
        }
      }

      setTimeout(() => {
        this.uploading = false;
        this.uploadProgress = 0;
        this.uploadFiles = [];
      }, 1000);
    },
  },
};
</script>

<style scoped>
.file-upload {
  max-width: 500px;
  margin: 20px auto;
}

.upload-area {
  border: 2px dashed #ddd;
  border-radius: 8px;
  padding: 40px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.3s ease;
}

.upload-area:hover {
  border-color: #42b883;
}

.upload-icon {
  font-size: 48px;
  display: block;
  margin-bottom: 10px;
}

.upload-progress {
  text-align: center;
  margin-top: 20px;
}

.upload-status {
  text-align: center;
}

.progress-value {
  font-size: 20px;
  font-weight: bold;
  color: #42b883;
}

.upload-speed {
  font-size: 12px;
  color: #666;
  margin-top: 4px;
}

.file-list {
  margin-top: 20px;
  text-align: left;
}

.file-item {
  display: flex;
  justify-content: space-between;
  padding: 8px 0;
  border-bottom: 1px solid #f0f0f0;
}

.file-name {
  font-size: 14px;
  color: #333;
}

.file-status {
  font-size: 12px;
}

.file-status.success {
  color: #42b883;
}

.file-status.pending {
  color: #f7b731;
}
</style>

3. 学习进度跟踪

<template>
  <div class="learning-progress">
    <h2>学习进度跟踪</h2>

    <div class="course-grid">
      <div class="course-card" v-for="course in courses" :key="course.id">
        <div class="course-header">
          <h3>{{ course.title }}</h3>
          <span class="course-type">{{ course.type }}</span>
        </div>

        <div class="progress-section">
          <vue-ellipse-progress
            :progress="course.progress"
            :size="100"
            :thickness="6"
            :color="getProgressColor(course.progress)"
            empty-color="#f0f0f0"
          >
            <div class="course-progress">
              <div class="progress-percent">{{ course.progress }}%</div>
            </div>
          </vue-ellipse-progress>

          <div class="course-stats">
            <div class="stat-item">
              <span class="stat-label">已完成</span>
              <span class="stat-value"
                >{{ course.completed }}/{{ course.total }}</span
              >
            </div>
            <div class="stat-item">
              <span class="stat-label">剩余时间</span>
              <span class="stat-value">{{ course.timeLeft }}</span>
            </div>
          </div>
        </div>

        <div class="course-actions">
          <button @click="continueLearning(course)" class="continue-btn">
            {{ course.progress === 100 ? '回顾' : '继续学习' }}
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'LearningProgress',
  data() {
    return {
      courses: [
        {
          id: 1,
          title: 'Vue.js 进阶',
          type: '前端开发',
          progress: 75,
          completed: 15,
          total: 20,
          timeLeft: '2小时',
        },
        {
          id: 2,
          title: 'Node.js 实战',
          type: '后端开发',
          progress: 45,
          completed: 9,
          total: 20,
          timeLeft: '4小时',
        },
        {
          id: 3,
          title: 'TypeScript 基础',
          type: '编程语言',
          progress: 100,
          completed: 12,
          total: 12,
          timeLeft: '已完成',
        },
        {
          id: 4,
          title: 'React Hooks',
          type: '前端开发',
          progress: 20,
          completed: 3,
          total: 15,
          timeLeft: '6小时',
        },
      ],
    };
  },
  methods: {
    getProgressColor(progress) {
      if (progress === 100) return '#42b883';
      if (progress >= 70) return '#f7b731';
      if (progress >= 40) return '#4ecdc4';
      return '#ff6b6b';
    },

    continueLearning(course) {
      console.log(`继续学习: ${course.title}`);
      // 实际项目中这里会跳转到学习页面
    },
  },
};
</script>

<style scoped>
.learning-progress {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
}

.course-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
  margin-top: 20px;
}

.course-card {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease;
}

.course-card:hover {
  transform: translateY(-3px);
}

.course-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.course-header h3 {
  margin: 0;
  color: #333;
  font-size: 18px;
}

.course-type {
  background: #f0f0f0;
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 12px;
  color: #666;
}

.progress-section {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 20px;
}

.course-progress {
  text-align: center;
}

.progress-percent {
  font-size: 14px;
  font-weight: bold;
  color: #333;
}

.course-stats {
  flex: 1;
}

.stat-item {
  display: flex;
  justify-content: space-between;
  margin-bottom: 8px;
}

.stat-label {
  font-size: 14px;
  color: #666;
}

.stat-value {
  font-size: 14px;
  font-weight: 500;
  color: #333;
}

.course-actions {
  text-align: center;
}

.continue-btn {
  background: #42b883;
  color: white;
  border: none;
  padding: 8px 20px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  transition: background 0.3s ease;
}

.continue-btn:hover {
  background: #369870;
}
</style>

🎨 样式定制

CSS 变量定制

/* 全局样式变量 */
:root {
  --progress-primary-color: #42b883;
  --progress-secondary-color: #f0f0f0;
  --progress-text-color: #333;
  --progress-background: white;
  --progress-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* 自定义进度条样式 */
.custom-progress {
  filter: drop-shadow(var(--progress-shadow));
}

.custom-progress .ep-circle {
  transition: all 0.3s ease;
}

.custom-progress:hover .ep-circle {
  transform: scale(1.05);
}

主题切换

<template>
  <div class="theme-progress" :class="currentTheme">
    <div class="theme-switcher">
      <button
        v-for="theme in themes"
        :key="theme.name"
        @click="setTheme(theme.name)"
        :class="{ active: currentTheme === theme.name }"
      >
        {{ theme.label }}
      </button>
    </div>

    <vue-ellipse-progress
      :progress="75"
      :size="200"
      :color="themeConfig.color"
      :thickness="themeConfig.thickness"
      :empty-color="themeConfig.emptyColor"
    >
      <div class="themed-content">
        <div class="value">75%</div>
        <div class="label">完成度</div>
      </div>
    </vue-ellipse-progress>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentTheme: 'light',
      themes: [
        { name: 'light', label: '浅色' },
        { name: 'dark', label: '深色' },
        { name: 'colorful', label: '彩色' },
      ],
    };
  },
  computed: {
    themeConfig() {
      const configs = {
        light: {
          color: '#42b883',
          thickness: 8,
          emptyColor: '#f0f0f0',
        },
        dark: {
          color: '#64ffda',
          thickness: 6,
          emptyColor: '#424242',
        },
        colorful: {
          color: {
            radial: false,
            colors: [
              { color: '#ff9a9e', offset: '0%' },
              { color: '#fecfef', offset: '50%' },
              { color: '#fecfef', offset: '100%' },
            ],
          },
          thickness: 10,
          emptyColor: '#f5f5f5',
        },
      };
      return configs[this.currentTheme];
    },
  },
  methods: {
    setTheme(theme) {
      this.currentTheme = theme;
    },
  },
};
</script>

<style scoped>
.theme-progress {
  padding: 20px;
  border-radius: 12px;
  transition: all 0.3s ease;
}

.theme-progress.light {
  background: #ffffff;
  color: #333;
}

.theme-progress.dark {
  background: #1e1e1e;
  color: #fff;
}

.theme-progress.colorful {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: #fff;
}

.theme-switcher {
  margin-bottom: 20px;
  text-align: center;
}

.theme-switcher button {
  margin: 0 5px;
  padding: 6px 12px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.light .theme-switcher button {
  background: #f0f0f0;
  color: #333;
}

.dark .theme-switcher button {
  background: #424242;
  color: #fff;
}

.colorful .theme-switcher button {
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
}

.theme-switcher button.active {
  background: #42b883;
  color: white;
}

.themed-content {
  text-align: center;
}

.value {
  font-size: 32px;
  font-weight: bold;
}

.label {
  font-size: 14px;
  opacity: 0.8;
  margin-top: 5px;
}
</style>

⚡ 性能优化

1. 懒加载组件

<template>
  <div class="lazy-progress-container">
    <vue-ellipse-progress
      v-if="isVisible"
      :progress="progress"
      :size="200"
      :color="color"
      :animation="animationConfig"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: false,
      progress: 75,
      color: '#42b883',
      animationConfig: {
        duration: 1000,
        delay: 0,
      },
    };
  },
  mounted() {
    // 使用 Intersection Observer 实现懒加载
    this.initIntersectionObserver();
  },
  methods: {
    initIntersectionObserver() {
      const observer = new IntersectionObserver(
        (entries) => {
          entries.forEach((entry) => {
            if (entry.isIntersecting) {
              this.isVisible = true;
              observer.unobserve(this.$el);
            }
          });
        },
        { threshold: 0.1 }
      );

      observer.observe(this.$el);
    },
  },
};
</script>

2. 防抖更新

<script>
import { debounce } from 'lodash-es';

export default {
  data() {
    return {
      progress: 0,
      targetProgress: 0,
    };
  },
  created() {
    // 防抖更新进度
    this.debouncedUpdateProgress = debounce(this.updateProgress, 100);
  },
  methods: {
    updateProgress() {
      this.progress = this.targetProgress;
    },

    setProgress(value) {
      this.targetProgress = value;
      this.debouncedUpdateProgress();
    },
  },
};
</script>

3. 减少重渲染

<template>
  <vue-ellipse-progress
    :progress="memoizedProgress"
    :size="size"
    :color="color"
    :key="progressKey"
  />
</template>

<script>
export default {
  props: {
    value: Number,
    size: {
      type: Number,
      default: 200,
    },
    color: {
      type: String,
      default: '#42b883',
    },
  },
  computed: {
    memoizedProgress() {
      // 只有当变化超过 1% 时才更新
      return Math.floor(this.value / 1) * 1;
    },

    progressKey() {
      // 使用 key 强制重新渲染关键变化
      return `${this.memoizedProgress}-${this.size}-${this.color}`;
    },
  },
};
</script>

❓ 常见问题

Q1: 进度条不显示动画?

A: 检查以下几点:

  1. 确保设置了 animation 属性
  2. 检查 progress 值是否正确更新
  3. 确认组件已正确挂载
<vue-ellipse-progress
  :progress="progress"
  :animation="{ duration: 1000, easing: 'ease-out' }"
/>

Q2: 如何实现逆时针进度?

A: 使用 clockwise 属性:

<vue-ellipse-progress :progress="75" :clockwise="-1" />

Q3: 如何自定义起始位置?

A: 使用 angle 属性(以度为单位):

<vue-ellipse-progress
  :progress="75"
  :angle="-90"  <!-- 从顶部开始 -->
/>

Q4: 进度条在移动端显示异常?

A: 添加响应式处理:

<template>
  <vue-ellipse-progress
    :size="responsiveSize"
    :thickness="responsiveThickness"
    :progress="progress"
  />
</template>

<script>
export default {
  computed: {
    responsiveSize() {
      return window.innerWidth < 768 ? 150 : 200;
    },
    responsiveThickness() {
      return window.innerWidth < 768 ? 4 : 6;
    },
  },
};
</script>

Q5: 如何处理大量进度条的性能问题?

A: 使用虚拟滚动或分页加载:

<template>
  <div class="progress-list">
    <vue-ellipse-progress
      v-for="(item, index) in visibleItems"
      :key="`progress-${index}`"
      :progress="item.progress"
      :size="80"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      allItems: [], // 所有数据
      pageSize: 10,
      currentPage: 1,
    };
  },
  computed: {
    visibleItems() {
      const start = (this.currentPage - 1) * this.pageSize;
      const end = start + this.pageSize;
      return this.allItems.slice(start, end);
    },
  },
};
</script>

🌟 最佳实践

1. 组件封装

<!-- ProgressIndicator.vue -->
<template>
  <div class="progress-indicator">
    <vue-ellipse-progress
      :progress="normalizedProgress"
      :size="size"
      :thickness="thickness"
      :color="computedColor"
      :empty-color="emptyColor"
      :animation="animationConfig"
      :line-cap="lineCap"
    >
      <div class="progress-content">
        <slot :progress="normalizedProgress" :status="status">
          <div class="default-content">
            <div class="progress-value">{{ displayValue }}</div>
            <div class="progress-label" v-if="label">{{ label }}</div>
          </div>
        </slot>
      </div>
    </vue-ellipse-progress>
  </div>
</template>

<script>
export default {
  name: 'ProgressIndicator',
  props: {
    value: {
      type: Number,
      required: true,
      validator: (value) => value >= 0 && value <= 100,
    },
    size: {
      type: Number,
      default: 120,
    },
    thickness: {
      type: Number,
      default: 6,
    },
    color: {
      type: [String, Object],
      default: '#42b883',
    },
    emptyColor: {
      type: String,
      default: '#f0f0f0',
    },
    lineCap: {
      type: String,
      default: 'round',
      validator: (value) => ['round', 'butt', 'square'].includes(value),
    },
    label: String,
    animated: {
      type: Boolean,
      default: true,
    },
    duration: {
      type: Number,
      default: 1000,
    },
    colorThresholds: {
      type: Object,
      default: () => ({
        danger: 30,
        warning: 70,
        success: 100,
      }),
    },
  },
  computed: {
    normalizedProgress() {
      return Math.max(0, Math.min(100, this.value));
    },

    status() {
      const { danger, warning } = this.colorThresholds;
      if (this.normalizedProgress < danger) return 'danger';
      if (this.normalizedProgress < warning) return 'warning';
      return 'success';
    },

    computedColor() {
      if (typeof this.color === 'string') {
        const colors = {
          danger: '#ff5252',
          warning: '#f7b731',
          success: '#42b883',
        };
        return colors[this.status] || this.color;
      }
      return this.color;
    },

    displayValue() {
      return `${this.normalizedProgress}%`;
    },

    animationConfig() {
      return this.animated
        ? {
            duration: this.duration,
            easing: 'ease-out',
          }
        : null;
    },
  },
};
</script>

<style scoped>
.progress-indicator {
  display: inline-block;
}

.progress-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
}

.default-content {
  text-align: center;
}

.progress-value {
  font-size: 1.2em;
  font-weight: bold;
  color: #333;
}

.progress-label {
  font-size: 0.8em;
  color: #666;
  margin-top: 4px;
}
</style>

2. 使用组合式 API (Vue 3)

// useProgress.js
import { ref, computed, watch } from 'vue';

export function useProgress(initialValue = 0, options = {}) {
  const {
    min = 0,
    max = 100,
    step = 1,
    animated = true,
    duration = 1000,
  } = options;

  const currentValue = ref(initialValue);
  const targetValue = ref(initialValue);
  const isAnimating = ref(false);

  const progress = computed(() => {
    const range = max - min;
    const current = Math.max(min, Math.min(max, currentValue.value));
    return ((current - min) / range) * 100;
  });

  const setValue = (value) => {
    targetValue.value = Math.max(min, Math.min(max, value));

    if (animated) {
      animateToTarget();
    } else {
      currentValue.value = targetValue.value;
    }
  };

  const increment = (amount = step) => {
    setValue(currentValue.value + amount);
  };

  const decrement = (amount = step) => {
    setValue(currentValue.value - amount);
  };

  const reset = () => {
    setValue(initialValue);
  };

  const animateToTarget = () => {
    if (isAnimating.value) return;

    isAnimating.value = true;
    const startValue = currentValue.value;
    const endValue = targetValue.value;
    const startTime = Date.now();

    const animate = () => {
      const elapsed = Date.now() - startTime;
      const progress = Math.min(elapsed / duration, 1);

      // 缓动函数
      const easeOutQuart = 1 - Math.pow(1 - progress, 4);

      currentValue.value = startValue + (endValue - startValue) * easeOutQuart;

      if (progress < 1) {
        requestAnimationFrame(animate);
      } else {
        currentValue.value = endValue;
        isAnimating.value = false;
      }
    };

    requestAnimationFrame(animate);
  };

  return {
    currentValue: readonly(currentValue),
    progress: readonly(progress),
    isAnimating: readonly(isAnimating),
    setValue,
    increment,
    decrement,
    reset,
  };
}

3. TypeScript 类型定义

// types/progress.ts
export interface ProgressConfig {
  progress: number;
  size?: number;
  thickness?: number;
  emptyThickness?: number;
  color?: string | GradientConfig;
  emptyColor?: string;
  lineCap?: 'round' | 'butt' | 'square';
  angle?: number;
  clockwise?: 1 | -1;
  animation?: AnimationConfig;
  dot?: DotConfig;
}

export interface GradientConfig {
  radial: boolean;
  colors: Array<{
    color: string;
    offset: string;
  }>;
}

export interface AnimationConfig {
  duration: number;
  easing?: string;
  delay?: number;
}

export interface DotConfig {
  size: number;
  color: string;
  border?: {
    width: number;
    color: string;
  };
}

export interface ProgressTheme {
  primary: string;
  secondary: string;
  success: string;
  warning: string;
  danger: string;
  background: string;
}

4. 测试用例

// tests/ProgressIndicator.test.js
import { mount } from '@vue/test-utils';
import ProgressIndicator from '@/components/ProgressIndicator.vue';

describe('ProgressIndicator', () => {
  test('renders with correct progress value', () => {
    const wrapper = mount(ProgressIndicator, {
      props: { value: 75 },
    });

    expect(wrapper.text()).toContain('75%');
  });

  test('applies correct color based on status', () => {
    const wrapper = mount(ProgressIndicator, {
      props: {
        value: 25,
        colorThresholds: { danger: 30, warning: 70, success: 100 },
      },
    });

    // 应该是危险状态的颜色
    const progressElement = wrapper.findComponent({
      name: 'VueEllipseProgress',
    });
    expect(progressElement.props('color')).toBe('#ff5252');
  });

  test('emits events on value change', async () => {
    const wrapper = mount(ProgressIndicator, {
      props: { value: 50 },
    });

    await wrapper.setProps({ value: 75 });

    // 检查是否触发了相应的事件
    expect(wrapper.emitted()).toHaveProperty('change');
  });
});

📝 总结

vue-ellipse-progress 是一个功能强大、高度可定制的圆形进度条组件库。通过本指南,你已经学会了:

  1. 基础使用:安装、配置和基本使用方法
  2. 高级特性:多重进度条、渐变色、动画效果
  3. 实际应用:仪表盘、文件上传、学习进度等场景
  4. 性能优化:懒加载、防抖、减少重渲染
  5. 最佳实践:组件封装、组合式 API、TypeScript 支持

关键要点

  • 合理使用动画效果,避免过度动画影响性能
  • 根据实际需求选择合适的配置参数
  • 在大量数据展示时考虑性能优化
  • 保持组件的可复用性和可维护性
  • 注意移动端适配和响应式设计

下一步建议

  1. 实践本指南中的示例代码
  2. 根据项目需求定制自己的进度条组件
  3. 结合其他 UI 库创建完整的数据可视化方案
  4. 关注库的更新和新特性

希望这份指南能帮助你更好地使用 vue-ellipse-progress 库,提升项目的用户体验!


网站公告

今日签到

点亮在社区的每一天
去签到