首先 声明
这篇文章时基于 作者: 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