<template>
    <span>{{ animatedValue }}</span>
</template>

<script setup lang="ts">

defineOptions({
  name: 'AnimatedNumber'
})
import { ref, watch, onMounted, onUnmounted } from 'vue';

interface Props {
    value: number;
    duration?: number;
    decimals?: number;
    separator?: string;
}

const props = withDefaults(defineProps<Props>(), {
    duration: 1000,
    decimals: 2,
    separator: ','
});

const animatedValue = ref('0.00');
let animationFrameId: number | null = null;
let startTime: number | null = null;
const startValue = ref(0);

// 格式化数字
const formatNumber = (num: number): string => {
    const fixed = num.toFixed(props.decimals);
    const parts = fixed.split('.');
    parts[0] && (parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, props.separator));
    return parts.join('.');
};

// 动画函数
const animate = (timestamp: number) => {
    if (!startTime) startTime = timestamp;
    const elapsed = timestamp - startTime;
    const progress = Math.min(elapsed / props.duration, 1);

    // 使用缓动函数 easeOutQuart
    const easeProgress = 1 - Math.pow(1 - progress, 4);

    const currentValue = startValue.value + (props.value - startValue.value) * easeProgress;
    animatedValue.value = formatNumber(currentValue);

    if (progress < 1) {
        animationFrameId = requestAnimationFrame(animate);
    }
};

// 开始动画
const startAnimation = () => {
    if (animationFrameId) {
        cancelAnimationFrame(animationFrameId);
    }
    startTime = null;
    startValue.value = parseFloat(animatedValue.value.replace(/,/g, '')) || 0;
    animationFrameId = requestAnimationFrame(animate);
};

// 监听值变化
watch(() => props.value, () => {
    startAnimation();
}, { immediate: false });

onMounted(() => {
    startAnimation();
});

onUnmounted(() => {
    if (animationFrameId) {
        cancelAnimationFrame(animationFrameId);
    }
});
</script>
