From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目
---
src/components/CountTo/src/CountTo.vue | 182 +++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 182 insertions(+), 0 deletions(-)
diff --git a/src/components/CountTo/src/CountTo.vue b/src/components/CountTo/src/CountTo.vue
new file mode 100644
index 0000000..7a19bec
--- /dev/null
+++ b/src/components/CountTo/src/CountTo.vue
@@ -0,0 +1,182 @@
+<script lang="ts" setup>
+import { PropType } from 'vue'
+import { isNumber } from '@/utils/is'
+import { propTypes } from '@/utils/propTypes'
+import { useDesign } from '@/hooks/web/useDesign'
+
+defineOptions({ name: 'CountTo' })
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('count-to')
+
+const props = defineProps({
+ startVal: propTypes.number.def(0), // 寮�濮嬫挱鏀惧��
+ endVal: propTypes.number.def(2021), // 鏈�缁堝��
+ duration: propTypes.number.def(3000), // 鍔ㄧ敾鏃堕暱
+ autoplay: propTypes.bool.def(true), // 鏄惁鑷姩鎾斁鍔ㄧ敾, 榛樿鎾斁
+ decimals: propTypes.number.validate((value: number) => value >= 0).def(0), // 鏄剧ず鐨勫皬鏁颁綅鏁�, 榛樿涓嶆樉绀哄皬鏁�
+ decimal: propTypes.string.def('.'), // 灏忔暟鍒嗛殧绗﹀彿, 榛樿涓虹偣
+ separator: propTypes.string.def(','), // 鏁板瓧姣忎笁浣嶇殑鍒嗛殧绗�, 榛樿涓洪�楀彿
+ prefix: propTypes.string.def(''), // 鍓嶇紑, 鏁板�煎墠闈㈡樉绀虹殑鍐呭
+ suffix: propTypes.string.def(''), // 鍚庣紑, 鏁板�煎悗闈㈡樉绀虹殑鍐呭
+ useEasing: propTypes.bool.def(true), // 鏄惁浣跨敤缂撳姩鏁堟灉, 榛樿鍚敤
+ easingFn: {
+ type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
+ default(t: number, b: number, c: number, d: number) {
+ return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
+ } // 缂撳姩鍑芥暟
+ }
+})
+
+const emit = defineEmits(['mounted', 'callback'])
+
+const formatNumber = (num: number | string) => {
+ const { decimals, decimal, separator, suffix, prefix } = props
+ num = Number(num).toFixed(decimals)
+ num += ''
+ const x = num.split('.')
+ let x1 = x[0]
+ const x2 = x.length > 1 ? decimal + x[1] : ''
+ const rgx = /(\d+)(\d{3})/
+ if (separator && !isNumber(separator)) {
+ while (rgx.test(x1)) {
+ x1 = x1.replace(rgx, '$1' + separator + '$2')
+ }
+ }
+ return prefix + x1 + x2 + suffix
+}
+
+const state = reactive<{
+ localStartVal: number
+ printVal: number | null
+ displayValue: string
+ paused: boolean
+ localDuration: number | null
+ startTime: number | null
+ timestamp: number | null
+ rAF: any
+ remaining: number | null
+}>({
+ localStartVal: props.startVal,
+ displayValue: formatNumber(props.startVal),
+ printVal: null,
+ paused: false,
+ localDuration: props.duration,
+ startTime: null,
+ timestamp: null,
+ remaining: null,
+ rAF: null
+})
+
+const displayValue = toRef(state, 'displayValue')
+
+onMounted(() => {
+ if (props.autoplay) {
+ start()
+ }
+ emit('mounted')
+})
+
+const getCountDown = computed(() => {
+ return props.startVal > props.endVal
+})
+
+watch([() => props.startVal, () => props.endVal], () => {
+ if (props.autoplay) {
+ start()
+ }
+})
+
+const start = () => {
+ const { startVal, duration } = props
+ state.localStartVal = startVal
+ state.startTime = null
+ state.localDuration = duration
+ state.paused = false
+ state.rAF = requestAnimationFrame(count)
+}
+
+const pauseResume = () => {
+ if (state.paused) {
+ resume()
+ state.paused = false
+ } else {
+ pause()
+ state.paused = true
+ }
+}
+
+const pause = () => {
+ cancelAnimationFrame(state.rAF)
+}
+
+const resume = () => {
+ state.startTime = null
+ state.localDuration = +(state.remaining as number)
+ state.localStartVal = +(state.printVal as number)
+ requestAnimationFrame(count)
+}
+
+const reset = () => {
+ state.startTime = null
+ cancelAnimationFrame(state.rAF)
+ state.displayValue = formatNumber(props.startVal)
+}
+
+const count = (timestamp: number) => {
+ const { useEasing, easingFn, endVal } = props
+ if (!state.startTime) state.startTime = timestamp
+ state.timestamp = timestamp
+ const progress = timestamp - state.startTime
+ state.remaining = (state.localDuration as number) - progress
+ if (useEasing) {
+ if (unref(getCountDown)) {
+ state.printVal =
+ state.localStartVal -
+ easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
+ } else {
+ state.printVal = easingFn(
+ progress,
+ state.localStartVal,
+ endVal - state.localStartVal,
+ state.localDuration as number
+ )
+ }
+ } else {
+ if (unref(getCountDown)) {
+ state.printVal =
+ state.localStartVal -
+ (state.localStartVal - endVal) * (progress / (state.localDuration as number))
+ } else {
+ state.printVal =
+ state.localStartVal +
+ (endVal - state.localStartVal) * (progress / (state.localDuration as number))
+ }
+ }
+ if (unref(getCountDown)) {
+ state.printVal = state.printVal < endVal ? endVal : state.printVal
+ } else {
+ state.printVal = state.printVal > endVal ? endVal : state.printVal
+ }
+ state.displayValue = formatNumber(state.printVal!)
+ if (progress < (state.localDuration as number)) {
+ state.rAF = requestAnimationFrame(count)
+ } else {
+ emit('callback')
+ }
+}
+
+defineExpose({
+ pauseResume,
+ reset,
+ start,
+ pause
+})
+</script>
+
+<template>
+ <span :class="prefixCls">
+ {{ displayValue }}
+ </span>
+</template>
--
Gitblit v1.8.0