首先 声明
这篇文章时基于 作者: theMuseCatcher 写的 Vue3倒计时组件(Countdown) 进行改写的.
因为在使用的过程中发现原作者的组件存在以下问题:
1.不能主动停止倒计时.
2.在倒计时countdown赋值为0的时候,还会在次执行一次渲染方法,导致出现00:0-1的情况
3.在countdown再次赋值后不能主动监听值变化进行渲染
所以我改写了部分代码逻辑实现了以上的问题
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import type { Ref } from 'vue'
interface Props {
countdown: number, // 倒计时数值(countdown),必传,支持设置未来某时刻的时间戳(ms) 或 相对剩余时间(s)
title?: string, // 倒计时标题 string | v-slot
format?: string, // 格式化倒计时展示,(Y:年,M:月,D:天,H:小时,m:分钟,s:秒)
prefix?: string, // 倒计时数值的前缀 string | v-slot
suffix?: string, // 倒计时数值的后缀 string | v-slot
finishedText?: string // 完成后的展示文本 string | v-slot
isStop?: boolean // 是否停止倒计时
}
const props = withDefaults(defineProps<Props>(), {
countdown: 0,
title: 'Countdown',
format: 'HH:mm:ss',
prefix: '',
suffix: '',
finishedText: '',
isStop: false
})
const restTime = ref(props.countdown)
let timer = ref()
let cd = toRefs(props).countdown; // 倒计时重新赋值为0 结束倒计时
watch(cd, (newValue, oldValue) => {
clearInterval(timer.value);
restTime.value = newValue > Date.now() ? Math.floor((newValue - Date.now()) / 1000) : newValue;
countDown();
});
const countDown = () => {
timer.value = setInterval(() => {
if (restTime.value < 1) { // js中Boolean(非0)都是true
clearInterval(timer.value);
emit('finish');
return;
}
restTime.value--;
},1000)
}
const showTime = computed(() => { // 展示的倒计时 监听restTime的每一次变化来达到重新渲染的目的
return timeFormat(restTime.value)
})
const emit = defineEmits(['finish','stop'])
onMounted(() => {
if (restTime.value > Date.now()) {
restTime.value = Math.floor((restTime.value - Date.now()) / 1000)
}
countDown();
})
onUnmounted(() => {
clearInterval(timer.value);
})
let stop = toRefs(props).isStop; // 倒计时重新赋值为0 结束倒计时
watch(stop, (newValue, oldValue) => {
if (newValue) {
emit('stop')
clearInterval(timer.value);
} else {
countDown()
}
});
function fixedTwo (value: number): string {
return value < 10 ? '0' + value : String(value)
}
function timeFormat (time: number): string {
let showTime = props.format
if (showTime.includes('s')) {
var s = time
} else {
var s = 0
}
if (showTime.includes('m')) {
s = s % 60 || 0
var m = Math.floor((time - s) / 60)
} else {
var m = 0
}
if (showTime.includes('H')) {
m = m % 60
var H = Math.floor((time - s - m * 60) / 60 / 60)
} else {
var H = 0
}
if (showTime.includes('D')) {
H = H % 24
var D = Math.floor((time - s - m * 60 - H * 60 * 60) / 60 / 60 / 24)
} else {
var D = 0
}
if (showTime.includes('M')) {
D = D % 30
var M = Math.floor((time - s - m * 60 - H * 60 * 60 - D * 24 * 60 * 60) / 60 / 60 / 24 / 30)
} else {
var M = 0
}
if (showTime.includes('Y')) {
M = M % 12
var Y = Math.floor((time - s - m * 60 - H * 60 * 60 - D * 24 * 60 * 60 - M * 30 * 24 * 60 * 60) / 60 / 60 / 24 / 30 / 12)
} else {
var Y = 0
}
showTime = showTime.includes('ss') ? showTime.replace('ss', fixedTwo(s)) : showTime.replace('s', String(s))
showTime = showTime.includes('mm') ? showTime.replace('mm', fixedTwo(m)) : showTime.replace('m', String(m))
showTime = showTime.includes('HH') ? showTime.replace('HH', fixedTwo(H)) : showTime.replace('H', String(H))
showTime = showTime.includes('DD') ? showTime.replace('DD', fixedTwo(D)) : showTime.replace('D', String(D))
showTime = showTime.includes('MM') ? showTime.replace('MM', fixedTwo(M)) : showTime.replace('M', String(M))
showTime = showTime.includes('YY') ? showTime.replace('YY', fixedTwo(Y)) : showTime.replace('Y', String(Y))
return showTime
}
</script>
<template>
<div class="m-countdown">
<slot name="title">
<p class="u-title">{{ props.title }}</p>
</slot>
<div class="u-time">
<slot name="prefix" v-if="restTime > 0">{{ prefix }}</slot>
<slot v-if="finishedText && restTime === 0" name="finish">{{ finishedText }}</slot>
<span v-else>{{ showTime }}</span>
<slot name="suffix" v-if="restTime > 0"> {{ suffix }}</slot>
</div>
</div>
</template>
<style lang="scss" scoped>
.m-countdown {
display: inline-block;
.u-title {
margin-bottom: 4px;
color: #00000073;
font-size: 14px;
}
.u-time {
color: #000000d9;
font-size: 24px;
line-height: 1.5;
}
}
</style>
组件使用
<script setup lang="ts">
import Countdown from './Countdown.vue'
function onFinish () {
console.log('countdown finished')
}
</script>
<template>
<count-down
:countdown="countDownTime"
:is-stop="isStop"
format="m:ss"
finishedText="倒计时结束"
@finish="onFinish">
<template #prefix>There's only </template>
<!-- <template #finish>< FinishedText slot ></template> -->
<template #suffix> left for the end.</template>
</count-down>
</template>
如果要主动停止倒计时,对组件的props.isStop设置为true进行了,恢复计时设置为false