TotalPointsDisplay.vue 2.72 KB
<template>
  <view class="total-points-display" :style="{ height: height }">
    <!-- 中心圆形显示总积分 -->
    <view class="center-circle1">
      <view class="total-points" @tap="handleGoToRewards">
        <text class="points-number">{{ animatedTotalPoints }}分</text>
        <text class="points-label">去兑换</text>
      </view>
    </view>
  </view>
</template>

<script setup>
import { ref, watch, onMounted, defineProps } from 'vue'
import Taro from '@tarojs/taro'

// 定义props
const props = defineProps({
  totalPoints: {
    type: Number,
    required: true,
    default: 0
  },
  height: {
    type: String,
    default: '30vh'
  }
})

// 响应式数据
const animatedTotalPoints = ref(0)

/**
 * 数字滚动动画
 * @param {number} start - 动画开始值
 * @param {number} end - 动画结束值
 */
const animateNumber = (start, end) => {
  if (start === end) {
    animatedTotalPoints.value = end;
    return;
  }
  const duration = 800;
  const startTime = Date.now();
  const difference = end - start;

  const animate = () => {
    const elapsed = Date.now() - startTime;
    const progress = Math.min(elapsed / duration, 1);
    const easeOut = 1 - Math.pow(1 - progress, 3);
    animatedTotalPoints.value = Math.floor(start + difference * easeOut);

    if (progress < 1) {
      requestAnimationFrame(animate);
    } else {
      animatedTotalPoints.value = end;
    }
  };
  animate();
}

// 监听totalPoints变化,并执行动画
watch(() => props.totalPoints, (newValue, oldValue) => {
  animateNumber(oldValue, newValue);
})

onMounted(() => {
    animateNumber(0, props.totalPoints)
})

/**
 * 处理去兑换点击事件
 */
const handleGoToRewards = () => {
  Taro.navigateTo({
    url: '/pages/RewardCategories/index',
  })
}
</script>

<style lang="less">
.total-points-display {
  position: relative;
  width: 100vw;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
}

.center-circle1 {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 170rpx;
  height: 170rpx;
  background: linear-gradient(135deg, #4A90E2, #4d96ea);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8rpx 32rpx rgba(53, 144, 255, 0.3);
  z-index: 10;
}

.total-points {
  text-align: center;
  color: white;
}

.points-number {
  display: block;
  font-size: 48rpx;
  font-weight: bold;
  line-height: 1;
}

.points-label {
  display: block;
  font-size: 24rpx;
  margin-top: 8rpx;
  opacity: 0.9;
}

// 响应式适配
@media screen and (max-width: 750px) {
  .center-circle {
    width: 160rpx;
    height: 160rpx;
  }

  .points-number {
    font-size: 36rpx;
  }

  .points-label {
    font-size: 20rpx;
  }
}
</style>