pointsPage.vue 8.61 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">{{ balance }}</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.event_title }}
            </div>
            <div class="flex justify-between items-center">
              <div class="flex flex-col">
                <span class="text-gray-400 text-xs mb-1">{{ item.event_time }}</span>
                <span class="text-gray-400 text-xs">{{ item.event_type_desc }}</span>
              </div>
              <div class="text-lg font-bold"
                :class="String(item.change).includes('-') ? 'text-[#FF4D4F]' : 'text-[#2E85FF]'">
                {{ item.change }}
              </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 } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTitle } from '@vueuse/core'
import { showToast } from 'vant'
import dayjs from 'dayjs'
import { getPointsListAPI } from '@/api/points'

// 导入图片
const headerBg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/962@2x.png'
const bottleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/pz02@2x.png'
const indicatorImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/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(2015, 0, 1)
const maxDate = new Date(2035, 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)
const page = ref(0)
const limit = ref(10)
const balance = ref('0')

// 切换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 onLoad = async () => {
  const nextPage = page.value + 1

  let direction = ''
  if (activeTab.value === 1) direction = 'add'
  if (activeTab.value === 2) direction = 'subtract'

  try {
    const res = await getPointsListAPI({
      direction,
      begin_date: startDate.value,
      end_date: endDate.value,
      keyword: searchKeyword.value,
      page: nextPage,
      limit: limit.value
    })

    if (refreshing.value) {
      list.value = []
      refreshing.value = false
    }

    if (res && res.data) {
      if (res.data.balance !== undefined) {
        balance.value = res.data.balance
      }

      const newItems = res.data.point_list || []
      list.value.push(...newItems)

      page.value = nextPage

      if (newItems.length < limit.value) {
        finished.value = true
      }
    } else {
      finished.value = true
    }
  } catch (error) {
    console.error('Failed to load points:', error)
    finished.value = true
    if (refreshing.value) {
      refreshing.value = false
    }
  } finally {
    loading.value = false
  }
}

const onRefresh = () => {
  finished.value = false
  loading.value = true
  page.value = 0
  refreshing.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>