user-coupon-list.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. <template>
  2. <view class="page">
  3. <NavBar title="用户优惠券" :showBack="true" />
  4. <view class="search-bar"><input class="search-input" v-model="keyword" placeholder="搜索优惠券码" placeholder-class="search-ph" @confirm="handleSearch" /></view>
  5. <scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
  6. <view class="card-set">
  7. <view class="card" v-for="item in list" :key="item.id">
  8. <view class="card-head">
  9. <view class="card-left"><text class="card-name">{{ item.couponName || '优惠券' }}</text><text class="card-user">用户ID: {{ item.userId }}</text></view>
  10. <view class="card-amount">${{ formatMoney(item.amount || 0) }}</view>
  11. <view class="card-status" :class="statusClass(item.status)"><text>{{ statusText(item.status) }}</text></view>
  12. </view>
  13. <view class="card-foot"><text class="foot-time">领取: {{ item.receiveTime || '-' }}</text><text class="foot-time" v-if="item.useTime">使用: {{ item.useTime }}</text></view>
  14. </view>
  15. </view>
  16. <view class="load-tip" v-if="loading"><view class="dot-row"><view class="pulse-dot"></view><view class="pulse-dot"></view><view class="pulse-dot"></view></view></view>
  17. <view class="end-tip" v-if="!hasMore && list.length > 0"><text>没有更多了</text></view>
  18. <view class="empty-state" v-if="!loading && list.length === 0"><text class="empty-text">暂无数据</text></view>
  19. </scroll-view>
  20. </view>
  21. </template>
  22. <script setup lang="ts">
  23. import { ref, onMounted } from 'vue';
  24. import NavBar from '@/components/NavBar.vue';
  25. import { getUserCouponList } from '@/api/userCoupon';
  26. import { formatMoney } from '@/utils/common';
  27. import { DEFAULT_PAGE_SIZE } from '@/utils/constants';
  28. const list = ref<any[]>([]); const loading = ref(false); const hasMore = ref(true); const page = ref(1); const keyword = ref('');
  29. const statusText = (status: number) => ({ 0: '未使用', 1: '已使用', 2: '已过期' } as Record<number,string>)[status] || '未知';
  30. const statusClass = (status: number) => status === 0 ? 'on' : status === 1 ? 'used' : 'off';
  31. const loadData = async () => { loading.value = true; try { const params: any = { page: page.value, pageSize: DEFAULT_PAGE_SIZE }; if (keyword.value) params.couponCode = keyword.value; const res = await getUserCouponList(params); if (page.value === 1) list.value = res.list || []; else list.value = [...list.value, ...(res.list || [])]; hasMore.value = list.value.length < (res.total || 0); } catch (e) { console.error('加载用户优惠券列表失败', e); } loading.value = false; };
  32. const loadMore = () => { if (loading.value || !hasMore.value) return; page.value++; loadData(); };
  33. const handleSearch = () => { page.value = 1; hasMore.value = true; list.value = []; loadData(); };
  34. onMounted(() => loadData());
  35. </script>
  36. <style lang="scss" scoped>
  37. .page { height: 100vh; background: $bg-color-page; display: flex; flex-direction: column; padding-bottom: env(safe-area-inset-bottom); }
  38. .search-bar { padding: $spacing-2 $spacing-3; background: $bg-color-card; }
  39. .search-input { width: 100%; height: 68rpx; background: $bg-color-page; border-radius: 40rpx; padding: 0 24rpx; font-size: $font-size-base; box-sizing: border-box; }
  40. .search-ph { color: $text-color-muted; font-size: 26rpx; }
  41. .list-scroll { flex: 1; height: 0; }
  42. .card-set { padding: 20rpx 24rpx; display: flex; flex-direction: column; gap: 16rpx; }
  43. .card { background: $bg-color-card; border-radius: $radius-md; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05); }
  44. .card-head { display: flex; justify-content: space-between; align-items: flex-start; }
  45. .card-left { flex: 1; min-width: 0; }
  46. .card-name { font-size: 30rpx; font-weight: 600; color: $text-color-primary; display: block; margin-bottom: 6rpx; }
  47. .card-user { font-size: 24rpx; color: $text-color-muted; display: block; }
  48. .card-amount { font-size: 32rpx; font-weight: 700; color: $primary-color-dark; margin: 0 24rpx; }
  49. .card-status { padding: 4rpx 14rpx; border-radius: 8rpx; font-size: 22rpx; font-weight: 500; flex-shrink: 0; }
  50. .card-status.on { background: $success-color-bg; color: $success-color; }
  51. .card-status.used { background: $info-color-bg; color: $info-color; }
  52. .card-status.off { background: $bg-color-page; color: $text-color-muted; }
  53. .card-foot { display: flex; gap: 24rpx; margin-top: 14rpx; padding-top: 14rpx; border-top: 1rpx solid $border-color-light; }
  54. .foot-time { font-size: 22rpx; color: $text-color-muted; }
  55. .load-tip { display: flex; justify-content: center; padding: 36rpx 0; }
  56. .dot-row { display: flex; gap: 8rpx; }
  57. .pulse-dot { width: 9rpx; height: 9rpx; background: $text-color-placeholder; border-radius: 50%; animation: pulse 1.2s ease-in-out infinite; }
  58. .pulse-dot:nth-child(2) { animation-delay: 0.2s; } .pulse-dot:nth-child(3) { animation-delay: 0.4s; }
  59. @keyframes pulse { 0%,80%,100% { transform: scale(0.5); opacity: 0.4; } 40% { transform: scale(1); opacity: 1; } }
  60. .end-tip { text-align: center; padding: 36rpx; font-size: 24rpx; color: $text-color-muted; }
  61. .empty-state { display: flex; align-items: center; justify-content: center; padding: 100rpx 0; }
  62. .empty-text { font-size: 28rpx; color: $text-color-muted; }
  63. </style>