Vue-Ellipse-Progress 完整使用指南
🌟 前言
打造视觉震撼的数据展示体验,从一个完美的进度条开始!
在现代前端开发中,数据可视化已经成为用户体验的重要组成部分。一个优雅的进度条不仅能清晰地传达信息,更能为你的应用增添专业感和科技感。
为什么选择 vue-ellipse-progress?
🎯 零学习成本 - 简单易用的 API 设计,5 分钟即可上手
🎨 无限创意 - 支持渐变色、多重进度、动画效果等高级特性
⚡ 性能卓越 - 轻量级设计,对性能敏感的项目完全友好
🔧 高度定制 - 从简单的百分比显示到复杂的仪表盘,满足所有需求
📱 响应式 - 完美适配移动端和桌面端
这份指南将带你:
✅ 掌握从基础到高级的所有用法
✅ 学会在实际项目中的最佳实践
✅ 了解性能优化的核心技巧
✅ 获得完整的 TypeScript 支持方案
无论你是 Vue 新手还是资深开发者,这份指南都将成为你的得力助手。让我们一起探索 vue-ellipse-progress 的强大功能,为你的项目打造令人印象深刻的数据展示效果!
📚 目录
📖 库介绍
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: 检查以下几点:
- 确保设置了
animation
属性 - 检查
progress
值是否正确更新 - 确认组件已正确挂载
<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 是一个功能强大、高度可定制的圆形进度条组件库。通过本指南,你已经学会了:
- 基础使用:安装、配置和基本使用方法
- 高级特性:多重进度条、渐变色、动画效果
- 实际应用:仪表盘、文件上传、学习进度等场景
- 性能优化:懒加载、防抖、减少重渲染
- 最佳实践:组件封装、组合式 API、TypeScript 支持
关键要点
- 合理使用动画效果,避免过度动画影响性能
- 根据实际需求选择合适的配置参数
- 在大量数据展示时考虑性能优化
- 保持组件的可复用性和可维护性
- 注意移动端适配和响应式设计
下一步建议
- 实践本指南中的示例代码
- 根据项目需求定制自己的进度条组件
- 结合其他 UI 库创建完整的数据可视化方案
- 关注库的更新和新特性
希望这份指南能帮助你更好地使用 vue-ellipse-progress 库,提升项目的用户体验!