pointsPage.vue 8.74 KB
<template>
  <div class="points-page min-h-screen bg-gray-50 flex flex-col">
    <!-- 头部区域 -->
    <div class="header relative w-full h-48 bg-cover bg-center" :style="{ backgroundImage: `url(${headerBg})` }">
      <div class="absolute top-12 left-6">
        <div class="text-white text-sm opacity-90 mb-1">当前星球币</div>
        <div class="text-[#FFDD01] text-4xl font-bold tracking-wider">15,800</div>
      </div>

      <!-- 右侧瓶子 -->
      <img :src="bottleImg" class="absolute right-4 top-4 w-32 object-contain animate-float" alt="bottle" />

      <!-- 底部提示条 -->
      <div class="absolute bottom-0 left-0 w-full h-8 bg-white/20 flex items-center px-4">
        <span class="text-white text-xs scale-90 origin-left">可用于兑换活动优惠券或实物奖励,敬请期待!</span>
      </div>
    </div>

    <!-- Tab 标签页 -->
    <div class="bg-white pt-2 sticky top-0 z-10 shadow-sm">
      <div class="grid grid-cols-3 items-center border-gray-100 pb-0 relative">
        <div v-for="(tab, index) in tabs" :key="index"
          class="relative py-3 flex flex-col items-center cursor-pointer z-10" @click="handleTabChange(index)">
          <span class="text-base font-medium transition-colors duration-300"
            :class="activeTab === index ? 'text-gray-900 font-bold' : 'text-gray-400'">
            {{ tab }}
          </span>
        </div>
        <!-- 移动指示器 -->
        <div class="absolute bottom-0 h-1.5 w-1/3 transition-transform duration-300 ease-in-out z-10"
          :style="{ transform: `translateX(${activeTab * 100}%)` }">
          <div class="w-full h-full flex justify-center">
            <img :src="indicatorImg" class="w-8 h-1.5" alt="indicator" />
          </div>
        </div>
      </div>

      <!-- 筛选区域 -->
      <div class="p-3 bg-white space-y-3">
        <!-- 日期选择 -->
        <div class="flex items-center justify-between space-x-2">
          <div class="flex-1 bg-white border border-gray-200 rounded-lg h-9 flex items-center px-3 cursor-pointer"
            @click="showStartDatePicker = true">
            <van-icon name="calendar-o" class="text-gray-400 mr-2" />
            <span :class="startDate ? 'text-gray-800' : 'text-gray-400'" class="text-sm truncate">
              {{ startDate || '开始日期' }}
            </span>
          </div>
          <span class="text-gray-400 text-sm">至</span>
          <div class="flex-1 bg-white border border-gray-200 rounded-lg h-9 flex items-center px-3 cursor-pointer"
            @click="showEndDatePicker = true">
            <van-icon name="calendar-o" class="text-gray-400 mr-2" />
            <span :class="endDate ? 'text-gray-800' : 'text-gray-400'" class="text-sm truncate">
              {{ endDate || '结束日期' }}
            </span>
          </div>
        </div>

        <!-- 搜索框 -->
        <div class="bg-white border border-gray-200 rounded-lg h-9 flex items-center px-3">
          <van-icon name="search" class="text-gray-400 mr-2" size="16" />
          <input v-model="searchKeyword" type="text" placeholder="搜索活动或课程名称"
            class="flex-1 bg-transparent text-sm text-gray-800 placeholder-gray-400 outline-none"
            @keyup.enter="onRefresh" />
        </div>
      </div>
    </div>

    <!-- 列表区域 -->
    <div class="flex-1 overflow-y-auto p-3">
      <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
        <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
          <div v-for="item in list" :key="item.id" class="bg-white rounded-xl p-4 mb-3 shadow-sm">
            <div class="text-gray-900 text-sm font-medium leading-relaxed mb-3 line-clamp-2">
              {{ item.title }}
            </div>
            <div class="flex justify-between items-center">
              <div class="flex flex-col">
                <span class="text-gray-400 text-xs mb-1">{{ item.date }}</span>
                <span class="text-gray-400 text-xs">{{ item.type }}</span>
              </div>
              <div class="text-lg font-bold" :class="item.isIncome ? 'text-[#2E85FF]' : 'text-[#FF4D4F]'">
                {{ item.isIncome ? '+' : '' }}{{ item.amount }}
              </div>
            </div>
            <!-- 分割线 -->
            <div v-if="item.id !== list[list.length - 1].id" class="mt-3 border-t border-gray-50 border-dashed"></div>
          </div>
        </van-list>
      </van-pull-refresh>
    </div>

    <!-- 日期选择弹窗 -->
    <van-popup v-model:show="showStartDatePicker" position="bottom">
      <van-date-picker v-model="currentStartDate" title="选择开始日期" :min-date="minDate" :max-date="maxDate"
        @confirm="onConfirmStartDate" @cancel="showStartDatePicker = false" />
    </van-popup>

    <van-popup v-model:show="showEndDatePicker" position="bottom">
      <van-date-picker v-model="currentEndDate" title="选择结束日期" :min-date="minDate" :max-date="maxDate"
        @confirm="onConfirmEndDate" @cancel="showEndDatePicker = false" />
    </van-popup>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTitle } from '@vueuse/core'
import { showToast } from 'vant'
import dayjs from 'dayjs'

// 导入图片
import headerBg from '@/assets/images/recall/962@2x.png'
import bottleImg from '@/assets/images/recall/pz02@2x.png'
import indicatorImg from '@/assets/images/recall/xian@2x.png'

useTitle('我的积分')
const route = useRoute()
const router = useRouter()

// 状态
const tabs = ['全部', '已获得', '已消耗']
const activeTab = ref(0)
const searchKeyword = ref('')
const startDate = ref('')
const endDate = ref('')
const showStartDatePicker = ref(false)
const showEndDatePicker = ref(false)

const now = new Date()
const minDate = new Date(2020, 0, 1)
const maxDate = new Date(2030, 11, 31)
const currentStartDate = ref([now.getFullYear().toString(), (now.getMonth() + 1).toString().padStart(2, '0'), now.getDate().toString().padStart(2, '0')])
const currentEndDate = ref([now.getFullYear().toString(), (now.getMonth() + 1).toString().padStart(2, '0'), now.getDate().toString().padStart(2, '0')])

// 列表相关
const list = ref([])
const loading = ref(false)
const finished = ref(false)
const refreshing = ref(false)

// 切换Tab
const handleTabChange = (index) => {
  activeTab.value = index
  onRefresh()
}

// 日期确认
const onConfirmStartDate = ({ selectedValues }) => {
  const dateStr = selectedValues.join('-')
  if (endDate.value && dayjs(dateStr).isAfter(dayjs(endDate.value))) {
    showToast('开始日期不能晚于结束日期')
    return
  }
  startDate.value = dateStr
  showStartDatePicker.value = false
  onRefresh()
}

const onConfirmEndDate = ({ selectedValues }) => {
  const dateStr = selectedValues.join('-')
  if (startDate.value && dayjs(dateStr).isBefore(dayjs(startDate.value))) {
    showToast('结束日期不能早于开始日期')
    return
  }
  endDate.value = dateStr
  showEndDatePicker.value = false
  onRefresh()
}

// 模拟数据
const mockData = [
  {
    id: 1,
    title: '2025.11月3日-10日江苏东台养生营,邀您一起进入童话世界!',
    date: '2025-11-03',
    type: '活动参与奖励',
    amount: 6400,
    isIncome: true
  },
  {
    id: 2,
    title: '【自然的恩典】青少年成长营-贵阳百花湖3(小学初中专场)',
    date: '2025-10-03',
    type: '活动参与奖励',
    amount: 7998,
    isIncome: true
  },
  {
    id: 3,
    title: '2024年4月22-25日浙江义乌【中华智慧商业应用论坛】',
    date: '2024-04-20',
    type: '活动参与奖励',
    amount: 3200,
    isIncome: true
  },
  {
    id: 4,
    title: '2023.7.6-7.11【自然的恩典】“爱我中华”优秀传统文化夏令营-天津场',
    date: '2023-07-01',
    type: '活动参与奖励',
    amount: 3990,
    isIncome: true
  },
  {
    id: 5,
    title: '兑换活动优惠券',
    date: '2023-07-01',
    type: '兑换奖励',
    amount: 200,
    isIncome: false
  }
]

// 加载列表
const onLoad = () => {
  setTimeout(() => {
    if (refreshing.value) {
      list.value = []
      refreshing.value = false
    }

    // 模拟数据加载
    const newData = mockData.map(item => ({ ...item, id: item.id + list.value.length }))
    list.value.push(...newData)

    loading.value = false

    // 模拟数据加载完毕
    if (list.value.length >= 20) {
      finished.value = true
    }
  }, 1000)
}

const onRefresh = () => {
  finished.value = false
  loading.value = true
  onLoad()
}
</script>

<style lang="less" scoped>
.animate-float {
  animation: float 3s ease-in-out infinite;
}

@keyframes float {

  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-5px);
  }
}

:deep(.van-picker__toolbar) {
  border-bottom: 1px solid #f5f5f5;
}
</style>