index.vue 7.53 KB
<template>
  <view class="min-h-screen flex flex-col bg-white">
    <!-- Blue header background -->
    <view class="bg-blue-500 h-52 absolute w-full top-0 left-0 z-0"></view>
    <!-- <AppHeader title="积分明细" /> -->
    <!-- Content -->
    <view class="relative z-10 flex-1 pb-20">
      <!-- Points display -->
      <view class="pt-4 pb-6 flex flex-col items-center">
        <h2 class="text-4xl font-bold text-white mb-1">{{ totalPoints }}</h2>
        <p class="text-white text-opacity-80">当前可用积分</p>
      </view>
      <!-- Points strategy section -->
      <view class="bg-white rounded-t-3xl px-4 pt-5">
        <view class="flex justify-between items-center mb-4">
          <h3 class="text-lg font-medium">积分攻略</h3>
          <view @tap="handleViewAll" class="text-blue-500 text-sm flex items-center">
            查看全部
            <!-- <Right size="16" /> -->
          </view>
        </view>
        <!-- Strategy cards -->
        <view class="space-y-3 mb-6">
          <view class="bg-white border border-gray-100 p-4 rounded-lg flex items-start shadow-sm">
            <view class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3">
              <My size="20" class="text-blue-500" />
            </view>
            <view>
              <h4 class="font-medium">每日同步步数可获得积分</h4>
              <p class="text-sm text-gray-600">
                每100步可兑换1积分,每日上限200积分
              </p>
            </view>
          </view>
          <view class="bg-white border border-gray-100 p-4 rounded-lg flex items-start shadow-sm">
            <view class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3">
              <My size="20" class="text-blue-500" />
            </view>
            <view>
              <h4 class="font-medium">家人陪伴健步有奖励积分</h4>
              <p class="text-sm text-gray-600">
                一起健步晒合影每天可获得额外100积分
              </p>
            </view>
          </view>
          <view class="bg-white border border-gray-100 p-4 rounded-lg flex items-start shadow-sm">
            <view class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3">
              <My size="20" class="text-blue-500" />
            </view>
            <view>
              <h4 class="font-medium">邀请家人加入家庭,人数达标奖励</h4>
              <p class="text-sm text-gray-600">
                邀请的家人达到5位,获得500奖励积分
              </p>
            </view>
          </view>
        </view>
        <!-- Tabs -->
        <!-- <nut-sticky top="0"> -->
        <view class="border-b border-gray-200 bg-white">
          <view class="flex space-x-8">
            <view :class="['py-3 font-medium', activeTab === 'all' ? 'text-blue-500 border-b-2 border-blue-500' : 'text-gray-500']" @click="activeTab = 'all'">
              全部
            </view>
            <view :class="['py-3 font-medium', activeTab === 'earned' ? 'text-blue-500 border-b-2 border-blue-500' : 'text-gray-500']" @click="activeTab = 'earned'">
              已发放
            </view>
            <view :class="['py-3 font-medium', activeTab === 'spent' ? 'text-blue-500 border-b-2 border-blue-500' : 'text-gray-500']" @click="activeTab = 'spent'">
              已消耗
            </view>
          </view>
        </view>
        <!-- </nut-sticky> -->
        <!-- Points history list -->
        <view class="pt-4">
          <view v-if="loading">
            <view class="py-8 text-center text-gray-500">
              加载中...
            </view>
          </view>
          <view v-else>
            <view v-if="pointsHistory && pointsHistory.length > 0">
              <view v-for="item in pointsHistory" :key="item.id" class="py-4 border-b border-gray-100 flex justify-between">
                <view>
                  <h4 class="font-medium">{{ item.title }}</h4>
                  <p class="text-sm text-gray-500">{{ item.date }}</p>
                </view>
                <span :class="['font-medium', item.type === 'earned' ? 'text-green-500' : 'text-red-500']">
                  {{ item.type === 'earned' ? '+' : '-' }}
                  {{ item.points }}
                </span>
              </view>
            </view>
            <view v-else class="py-8 text-center text-gray-500">
              暂无积分记录
            </view>
          </view>
        </view>
      </view>
    </view>
    <!-- <BottomNav /> -->
        <!-- 返回顶部组件 -->
    <BackToTop :distance="200" />
  </view>
</template>

<script setup>
import { ref, computed, onMounted, watch } from 'vue';
import Taro, { useDidShow } from '@tarojs/taro';
import AppHeader from '../../components/AppHeader.vue';
import BottomNav from '../../components/BottomNav.vue';
import BackToTop from '../../components/BackToTop.vue';
import { Right, My } from '@nutui/icons-vue-taro';
import { getPointListAPI } from '../../api/points';

const activeTab = ref('all');
const pointsHistory = ref([]);
const totalPoints = ref(0);
const loading = ref(false);
const page = ref(0);
const limit = ref(9999);
const hasMore = ref(true);

/**
 * 获取积分列表数据
 */
const fetchPointsList = async (isRefresh = false) => {
  if (loading.value) return;

  try {
    loading.value = true;

    if (isRefresh) {
      page.value = 0;
      pointsHistory.value = [];
      hasMore.value = true;
    }

    const params = {
      page: page.value.toString(),
      limit: limit.value.toString()
    };

    if (activeTab.value === 'earned') {
      params.points_status = 'issued';
    } else if (activeTab.value === 'spent') {
      params.points_status = 'used';
    }

    const response = await getPointListAPI(params);

    if (response.code && response.data) {
      totalPoints.value = response.data.total_points || 0;

      const logs = response.data.logs || [];

      // 转换API数据格式为页面需要的格式
      const formattedLogs = logs.map(log => ({
        id: log.id,
        title: log.note || '积分变动',
        date: formatDate(new Date(log.created_time)),
        points: Math.abs(parseInt(log.points_change) || 0),
        type: parseInt(log.points_change) > 0 ? 'earned' : 'spent',
        log_type: log.log_type,
        source_type: log.source_type,
        balance_after: log.balance_after
      }));

      if (isRefresh) {
        pointsHistory.value = formattedLogs;
      } else {
        pointsHistory.value = [...pointsHistory.value, ...formattedLogs];
      }

      // 判断是否还有更多数据
      hasMore.value = logs.length >= limit.value;

      if (logs.length > 0) {
        page.value += 1;
      }
    }
  } catch (error) {
    console.error('获取积分列表失败:', error);
    Taro.showToast({
      title: '获取数据失败',
      icon: 'none'
    });
  } finally {
    loading.value = false;
  }
};

/**
 * 格式化日期
 */
const formatDate = (date) => {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  return `${year}-${month}-${day} ${hours}:${minutes}`;
};

watch(activeTab, () => {
  fetchPointsList(true);
});

const handleViewAll = () => {
  Taro.navigateTo({
    url: '/pages/PointsList/index'
  })
};

// 页面显示时获取数据
useDidShow(() => {
  fetchPointsList(true);
});

// 组件挂载时获取数据
onMounted(() => {
  fetchPointsList(true);
});
</script>