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