This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,239 @@
<script setup lang="ts">
import { computed, defineComponent, onBeforeMount, reactive, watch } from 'vue'
import { INPUT_EVENT, PREFIX, UPDATE_MODEL_EVENT } from '../_constants'
import { getMainClass, getTimeStamp, isH5, padZero } from '../_utils'
import requestAniFrame from '../_utils/raf'
import { countdownEmits, countdownProps } from './countdown'
const props = defineProps(countdownProps)
const emits = defineEmits(countdownEmits)
defineExpose({ start, pause, reset })
const state = reactive({
restTime: 0, // 倒计时剩余时间时间
timer: null,
counting: !props.paused && props.autoStart, // 是否处于倒计时中
handleEndTime: Date.now(), // 最终截止时间
diffTime: 0, // 设置了 startTime 时,与 date.now() 的差异
})
const classes = computed(() => {
return getMainClass(props, componentName)
})
// 将倒计时剩余时间格式化 参数: t 时间戳 type custom 自定义类型
function formatRemainTime(t: number, type?: string) {
const ts = t
const rest = {
d: 0,
h: 0,
m: 0,
s: 0,
ms: 0,
}
const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
if (ts > 0) {
rest.d = ts >= SECOND ? Math.floor(ts / DAY) : 0
rest.h = Math.floor((ts % DAY) / HOUR)
rest.m = Math.floor((ts % HOUR) / MINUTE)
rest.s = Math.floor((ts % MINUTE) / SECOND)
rest.ms = Math.floor(ts % SECOND)
}
return type === 'custom' ? rest : parseFormat({ ...rest })
}
function parseFormat(time: { d: number, h: number, m: number, s: number, ms: number }) {
let { d, h, m, s, ms } = time
let format = props.format
if (format.includes('DD'))
format = format.replace('DD', padZero(d))
else
h += Number(d) * 24
if (format.includes('HH'))
format = format.replace('HH', padZero(h))
else
m += Number(h) * 60
if (format.includes('mm'))
format = format.replace('mm', padZero(m))
else
s += Number(m) * 60
if (format.includes('ss'))
format = format.replace('ss', padZero(s))
else
ms += Number(s) * 1000
if (format.includes('S')) {
const msC = padZero(ms, 3).toString()
if (format.includes('SSS'))
format = format.replace('SSS', msC)
else if (format.includes('SS'))
format = format.replace('SS', msC.slice(0, 2))
else if (format.includes('S'))
format = format.replace('S', msC.slice(0, 1))
}
return format
}
// 倒计时 interval
function initTime() {
state.handleEndTime = props.endTime as number
state.diffTime = Date.now() - getTimeStamp(props.startTime) // 时间差
if (!state.counting)
state.counting = true
tick()
}
function tick() {
function countdown() {
const currentTime = Date.now() - state.diffTime
const remainTime = Math.max(state.handleEndTime - currentTime, 0)
state.restTime = remainTime
if (!remainTime) {
state.counting = false
pause()
emits('onEnd')
}
if (remainTime > 0)
tick()
}
if (isH5) {
(state.timer as any) = requestAnimationFrame(() => {
if (state.counting)
countdown()
})
}
else {
(state.timer as any) = requestAniFrame(() => {
if (state.counting)
countdown()
})
}
}
/**
* @description 开始倒计时
*/
function start() {
if (!state.counting && !props.autoStart) {
state.counting = true
state.handleEndTime = Date.now() + Number(state.restTime)
tick()
emits('onRestart', state.restTime)
}
}
/**
* @description 暂停倒计时
*/
function pause() {
if (isH5)
cancelAnimationFrame(state.timer as any)
else
clearTimeout(state.timer as any)
state.counting = false
emits('onPaused', state.restTime)
}
/**
* @description 重设倒计时,若 `auto-start` 为 `true`,重设后会自动开始倒计时
*/
function reset() {
if (!props.autoStart) {
pause()
state.restTime = props.time as number
}
}
const renderTime = computed(() => {
return formatRemainTime(state.restTime)
})
onBeforeMount(() => {
if (props.autoStart)
initTime()
else
state.restTime = props.time as number
})
watch(
() => state.restTime,
(value) => {
const tranTime = formatRemainTime(value, 'custom')
emits(UPDATE_MODEL_EVENT, tranTime)
emits(INPUT_EVENT, tranTime)
},
)
watch(
() => props.paused,
(v, ov) => {
if (!ov) {
if (state.counting)
pause()
}
else {
if (!state.counting) {
state.counting = true
state.handleEndTime = Date.now() + Number(state.restTime)
tick()
}
emits('onRestart', state.restTime)
}
},
)
watch(
() => props.endTime,
() => {
initTime()
},
)
watch(
() => props.startTime,
() => {
initTime()
},
)
</script>
<script lang="ts">
const componentName = `${PREFIX}-countdown`
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<view :class="classes" :style="customStyle">
<template v-if="$slots.default">
<slot />
</template>
<template v-else>
<rich-text class="nut-countdown__content" :nodes="renderTime as string" />
</template>
</view>
</template>
<style lang="scss">
@import './index';
</style>