一、功能清单(已全部实现)
✅ 2️⃣ v-model 控制开始 / 暂停 / 继续
v-model:running控制进度条运行状态- 外部随时暂停 / 恢复
✅ 3️⃣ 可作为公共组件(uniapp / Vue2)
- props + emit 规范
- 可直接放到
components/ProgressBar/index.vue - 也可无痛迁移到
uni_modules
✅ 4️⃣ 主题化(高度 / 颜色 / icon 可配置)
- 渐变色可配置
- 危险阈值可配置
- icon 可配置
二、组件使用方式示例(先看用法)
vue
<ProgressBar
v-model:running="running"
:totalTime="180"
icon="⚡"
:dangerTime="15"
:height="20"
/>
<button @click="running = !running">
{{ running ? '暂停' : '继续' }}
</button>
三、完整公共组件(每一行都有注释)
📁 components/ProgressBar/index.vue
vue
<template>
<!-- 整体容器,作为相对定位基准 -->
<view class="progress-wrapper">
<!-- 跟随进度移动的图标 -->
<view
class="progress-icon"
:style="{ left: iconLeft + '%' }"
>
{{ icon }}
</view>
<!-- 进度条轨道 -->
<view
class="progress-track"
:style="{ height: height + 'rpx' }"
>
<!-- 实际进度条 -->
<view
class="progress-bar"
:class="{ danger: isDanger }"
:style="{ width: progress + '%', background: barGradient }"
>
<!-- 高光流动效果 -->
<view class="progress-shine"></view>
</view>
</view>
<!-- 倒计时文本 -->
<view class="time-text">
{{ formatTime(remainTime) }}
</view>
</view>
</template>
<script>
export default {
name: "ProgressBar",
/* ================= props ================= */
props: {
// 总时长(秒)
totalTime: {
type: Number,
default: 180,
},
// v-model:running 控制运行状态
running: {
type: Boolean,
default: false,
},
// 危险时间阈值(秒)
dangerTime: {
type: Number,
default: 15,
},
// 进度条高度(rpx)
height: {
type: Number,
default: 20,
},
// 图标内容
icon: {
type: String,
default: "⚡",
},
// 正常渐变色
gradient: {
type: String,
default: "linear-gradient(90deg,#ff6a00,#ff9f00,#ff6a00)",
},
// 危险状态渐变色
dangerGradient: {
type: String,
default: "linear-gradient(90deg,#ff3b30,#ff6a00)",
},
},
/* ================= emits ================= */
emits: ["update:running", "finish"],
data() {
return {
// 剩余时间(毫秒)
remainTime: this.totalTime * 1000,
// requestAnimationFrame id
rafId: null,
// 开始时间戳
startTime: 0,
};
},
/* ================= 计算属性 ================= */
computed: {
// 当前进度百分比(0 → 100)
progress() {
const elapsed = this.totalTime * 1000 - this.remainTime;
return Math.min((elapsed / (this.totalTime * 1000)) * 100, 100);
},
// 图标位置(防止超出边界)
iconLeft() {
return Math.min(Math.max(this.progress, 0), 100);
},
// 是否进入危险状态
isDanger() {
return this.remainTime / 1000 <= this.dangerTime;
},
// 当前进度条渐变色
barGradient() {
return this.isDanger ? this.dangerGradient : this.gradient;
},
},
/* ================= 监听 ================= */
watch: {
// 外部控制 running
running(val) {
val ? this.start() : this.stop();
},
},
mounted() {
// 初始 running 为 true 则直接启动
if (this.running) {
this.start();
}
},
beforeDestroy() {
// 组件销毁时清理动画
cancelAnimationFrame(this.rafId);
},
/* ================= 方法 ================= */
methods: {
// 启动倒计时
start() {
// 防止重复启动
if (this.rafId) return;
// 记录开始时间
this.startTime = Date.now();
// 帧循环
const tick = () => {
const elapsed = Date.now() - this.startTime;
// 计算剩余时间
this.remainTime = Math.max(
this.remainTime - elapsed,
0
);
// 更新时间基准
this.startTime = Date.now();
// 继续 or 结束
if (this.remainTime > 0 && this.running) {
this.rafId = requestAnimationFrame(tick);
} else {
this.finish();
}
};
this.rafId = requestAnimationFrame(tick);
},
// 暂停倒计时
stop() {
cancelAnimationFrame(this.rafId);
this.rafId = null;
},
// 结束
finish() {
this.stop();
this.$emit("update:running", false);
this.$emit("finish");
},
// 格式化时间 mm:ss
formatTime(ms) {
const s = Math.floor(ms / 1000);
const m = Math.floor(s / 60);
const sec = s % 60;
return `${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
},
},
};
</script>
<style scoped lang="scss">
/* 外层容器 */
.progress-wrapper {
position: relative;
padding-top: 40rpx;
}
/* 进度条轨道 */
.progress-track {
background: rgba(255, 255, 255, 0.15);
border-radius: 999rpx;
overflow: hidden;
}
/* 主进度条 */
.progress-bar {
height: 100%;
border-radius: 999rpx;
position: relative;
transition: width 0.08s linear;
}
/* 流动条纹 */
.progress-bar::after {
content: "";
position: absolute;
inset: 0;
background-size: 40rpx 40rpx;
background-image: linear-gradient(
-45deg,
rgba(255,255,255,.25) 25%,
transparent 25%,
transparent 50%,
rgba(255,255,255,.25) 50%,
rgba(255,255,255,.25) 75%,
transparent 75%
);
animation: stripe 2.5s linear infinite;
}
/* 危险状态加速 */
.progress-bar.danger::after {
animation-duration: 1.2s;
}
/* 高光 */
.progress-shine {
position: absolute;
left: -40%;
width: 40%;
height: 100%;
background: linear-gradient(
120deg,
transparent,
rgba(255,255,255,.6),
transparent
);
animation: shine 1.2s infinite;
}
/* 图标 */
.progress-icon {
position: absolute;
top: 0;
transform: translate(-50%, -100%);
font-size: 36rpx;
transition: left 0.08s linear;
animation: float 1.6s ease-in-out infinite;
}
/* 时间文字 */
.time-text {
margin-top: 20rpx;
text-align: center;
font-size: 26rpx;
color: #ff7a18;
}
/* 动画定义 */
@keyframes stripe {
to { background-position: 80rpx 80rpx; }
}
@keyframes shine {
to { left: 120%; }
}
@keyframes float {
50% { transform: translate(-50%, -120%); }
}
</style>