Files
rent/apps/miniapp/src/pages/invite/records.vue
T
2026-05-27 00:12:45 +08:00

717 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page-records">
<!-- 顶部统计卡片 -->
<view class="stats-banner">
<view class="stats-item">
<text class="stats-value">{{ records.length }}</text>
<text class="stats-label">累计邀请</text>
</view>
<view class="stats-divider"></view>
<view class="stats-item">
<text class="stats-value">{{ totalOrders }}</text>
<text class="stats-label">产生订单</text>
</view>
<view class="stats-divider"></view>
<view class="stats-item">
<text class="stats-value">{{ activeUsers }}</text>
<text class="stats-label">活跃用户</text>
</view>
</view>
<!-- 筛选标签 -->
<view class="filter-tabs">
<view
v-for="tab in filterTabs"
:key="tab.value"
class="filter-tab"
:class="{ active: currentFilter === tab.value }"
@tap="changeFilter(tab.value)"
>
<text class="tab-text">{{ tab.label }}</text>
<view v-if="currentFilter === tab.value" class="tab-indicator"></view>
</view>
</view>
<!-- 记录列表 -->
<scroll-view
scroll-y
class="record-list"
@scrolltolower="loadMore"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
>
<view class="list-container">
<view
v-for="item in filteredRecords"
:key="item.id"
class="record-item"
@tap="viewDetail(item)"
>
<view class="record-main">
<view class="user-section">
<view class="avatar-container">
<image
class="avatar"
:src="item.inviteeAvatar || '/static/default-avatar.png'"
mode="aspectFill"
/>
<view v-if="item.orderCount > 0" class="avatar-badge">
<u-icon name="checkmark" :size="10" color="#ffffff" />
</view>
</view>
<view class="user-info">
<view class="name-row">
<text class="user-name">{{ item.inviteeNickname || '神秘用户' }}</text>
<view v-if="item.isActive" class="active-tag">
<text class="tag-text">活跃</text>
</view>
</view>
<view class="time-row">
<u-icon name="clock" :size="12" color="#999" />
<text class="join-time">{{ formatTime(item.createdAt) }}</text>
</view>
</view>
</view>
<view class="order-section">
<view class="order-badge" :class="getOrderBadgeClass(item.orderCount)">
<text class="order-number">{{ item.orderCount }}</text>
<text class="order-unit"></text>
</view>
<text class="order-label">已下单</text>
</view>
</view>
</view>
<!-- 加载状态 -->
<view v-if="loading" class="loading-wrapper">
<view class="loading-spinner">
<view class="spinner-dot"></view>
<view class="spinner-dot"></view>
<view class="spinner-dot"></view>
</view>
<text class="loading-text">加载中...</text>
</view>
<!-- 没有更多 -->
<view v-if="!hasMore && records.length > 0" class="no-more">
<view class="no-more-line"></view>
<text class="no-more-text">已经到底了</text>
<view class="no-more-line"></view>
</view>
<!-- 空状态 -->
<view v-if="records.length === 0 && !loading" class="empty-state">
<view class="empty-icon">
<u-icon name="account" :size="80" color="#E5E5E5" />
</view>
<text class="empty-title">还没有邀请好友</text>
<text class="empty-desc">邀请好友下单即可获得丰厚返现奖励</text>
<view class="empty-action" @tap="goInvite">
<u-icon name="share-fill" :size="16" color="#ffffff" />
<text class="action-text">立即邀请</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { getInviteRecords } from '@/api/user/invite';
const records = ref<any[]>([]);
const page = ref(1);
const loading = ref(false);
const hasMore = ref(true);
const refreshing = ref(false);
const currentFilter = ref('all');
const filterTabs = [
{ label: '全部', value: 'all' },
{ label: '已下单', value: 'ordered' },
{ label: '未下单', value: 'not-ordered' },
];
// 计算统计数据
const totalOrders = computed(() => {
return records.value.reduce((sum, item) => sum + (item.orderCount || 0), 0);
});
const activeUsers = computed(() => {
return records.value.filter(item => item.orderCount > 0).length;
});
// 筛选记录
const filteredRecords = computed(() => {
console.log('计算筛选记录, 当前筛选:', currentFilter.value);
if (currentFilter.value === 'ordered') {
return records.value.filter(item => item.orderCount > 0);
} else if (currentFilter.value === 'not-ordered') {
return records.value.filter(item => item.orderCount === 0);
}
return records.value;
});
onMounted(() => {
fetchRecords(true);
});
async function fetchRecords(reset = false) {
if (loading.value) return;
if (!reset && !hasMore.value) return;
loading.value = true;
if (reset) {
page.value = 1;
hasMore.value = true;
}
try {
const res = await getInviteRecords({ page: page.value, pageSize: 20 });
const list = res.data?.list || [];
// 模拟添加活跃状态和收益数据(实际应从后端返回)
const enrichedList = list.map((item: any) => ({
...item,
isActive: item.orderCount > 0,
totalCashback: item.totalCashback || (item.orderCount * 5.5).toFixed(2),
}));
records.value = reset ? enrichedList : [...records.value, ...enrichedList];
hasMore.value = list.length >= 20;
page.value++;
} catch (e) {
console.error('获取邀请记录失败:', e);
uni.showToast({ title: '加载失败,请重试', icon: 'none' });
} finally {
loading.value = false;
refreshing.value = false;
}
}
function loadMore() {
fetchRecords();
}
function onRefresh() {
refreshing.value = true;
fetchRecords(true);
}
function changeFilter(value: string) {
console.log('切换筛选:', value);
currentFilter.value = value;
console.log('当前筛选值:', currentFilter.value);
console.log('筛选后记录数:', filteredRecords.value.length);
}
function formatTime(dateStr: string) {
if (!dateStr) return '';
const d = new Date(dateStr);
const now = new Date();
const diff = now.getTime() - d.getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) {
const hours = Math.floor(diff / (1000 * 60 * 60));
if (hours === 0) {
const minutes = Math.floor(diff / (1000 * 60));
return minutes <= 0 ? '刚刚' : `${minutes}分钟前`;
}
return `${hours}小时前`;
} else if (days === 1) {
return '昨天';
} else if (days < 7) {
return `${days}天前`;
} else {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}
}
function getOrderBadgeClass(count: number) {
if (count === 0) return 'badge-gray';
if (count >= 5) return 'badge-gold';
if (count >= 3) return 'badge-orange';
return 'badge-blue';
}
function viewDetail(item: any) {
if (item.orderCount > 0) {
uni.showToast({
title: `该用户已下${item.orderCount}单,为您带来¥${item.totalCashback}收益`,
icon: 'none',
duration: 2000,
});
}
}
function goInvite() {
uni.navigateBack();
}
function goBack() {
uni.navigateBack();
}
</script>
<style lang="scss" scoped>
@import '@/static/styles/design-tokens.scss';
@import '@/static/styles/mixins.scss';
.page-records {
min-height: 100vh;
background: linear-gradient(180deg, #FFF5F0 0%, #F5F7FA 100%);
display: flex;
flex-direction: column;
}
/* ========== 顶部统计横幅 ========== */
.stats-banner {
margin: 24rpx 24rpx 16rpx;
background: linear-gradient(135deg, #FF8C5A 0%, #FF6B35 100%);
border-radius: 20rpx;
padding: 32rpx 24rpx;
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.25);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: -40rpx;
right: -40rpx;
width: 160rpx;
height: 160rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
}
}
.stats-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
position: relative;
z-index: 1;
}
.stats-value {
font-size: 48rpx;
font-weight: 700;
color: #ffffff;
line-height: 1;
}
.stats-label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.85);
}
.stats-divider {
width: 2rpx;
height: 48rpx;
background: rgba(255, 255, 255, 0.3);
}
/* ========== 筛选标签 ========== */
.filter-tabs {
margin: 0 24rpx 16rpx;
background: #ffffff;
border-radius: 16rpx;
padding: 8rpx;
display: flex;
gap: 8rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.filter-tab {
flex: 1;
padding: 16rpx 24rpx;
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
transition: all 0.3s ease;
position: relative;
&:active {
transform: scale(0.96);
}
&.active {
background: linear-gradient(135deg, #FFF5F0 0%, #FFE8DD 100%);
}
}
.tab-text {
font-size: 28rpx;
color: #666;
font-weight: 500;
transition: all 0.3s ease;
.filter-tab.active & {
color: #FF6B35;
font-weight: 600;
}
}
.tab-indicator {
width: 32rpx;
height: 6rpx;
background: linear-gradient(90deg, #FF8C5A 0%, #FF6B35 100%);
border-radius: 3rpx;
}
/* ========== 记录列表 ========== */
.record-list {
flex: 1;
overflow-y: auto;
}
.list-container {
padding: 0 24rpx 24rpx;
}
.record-item {
background: #ffffff;
border-radius: 20rpx;
margin-bottom: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
}
}
.record-main {
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
/* ========== 用户信息区 ========== */
.user-section {
flex: 1;
display: flex;
align-items: center;
gap: 20rpx;
}
.avatar-container {
position: relative;
flex-shrink: 0;
}
.avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
border: 3rpx solid #FFF5F0;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.avatar-badge {
position: absolute;
bottom: -2rpx;
right: -2rpx;
width: 28rpx;
height: 28rpx;
background: linear-gradient(135deg, #52C41A 0%, #73D13D 100%);
border-radius: 50%;
border: 3rpx solid #ffffff;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(82, 196, 26, 0.3);
}
.user-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 12rpx;
min-width: 0;
}
.name-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.user-name {
font-size: 30rpx;
font-weight: 600;
color: #1A1A1A;
max-width: 280rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.active-tag {
padding: 4rpx 12rpx;
background: linear-gradient(135deg, #E6F7FF 0%, #BAE7FF 100%);
border-radius: 8rpx;
border: 1rpx solid #91D5FF;
}
.tag-text {
font-size: 20rpx;
color: #1890FF;
font-weight: 600;
}
.time-row {
display: flex;
align-items: center;
gap: 6rpx;
}
.join-time {
font-size: 24rpx;
color: #999;
}
/* ========== 订单统计区 ========== */
.order-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
flex-shrink: 0;
}
.order-badge {
min-width: 88rpx;
height: 88rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
}
.badge-gray {
background: linear-gradient(135deg, #F5F5F5 0%, #E8E8E8 100%);
}
.badge-blue {
background: linear-gradient(135deg, #91D5FF 0%, #69C0FF 100%);
}
.badge-orange {
background: linear-gradient(135deg, #FFD591 0%, #FFC069 100%);
}
.badge-gold {
background: linear-gradient(135deg, #FFE58F 0%, #FFD666 100%);
}
.order-number {
font-size: 40rpx;
font-weight: 700;
line-height: 1;
}
.order-unit {
font-size: 20rpx;
font-weight: 500;
}
.badge-gray .order-number,
.badge-gray .order-unit {
color: #999;
}
.badge-blue .order-number,
.badge-blue .order-unit {
color: #0050B3;
}
.badge-orange .order-number,
.badge-orange .order-unit {
color: #D46B08;
}
.badge-gold .order-number,
.badge-gold .order-unit {
color: #D48806;
}
.order-label {
font-size: 22rpx;
color: #999;
}
/* ========== 收益信息区 ========== */
.record-footer {
padding: 20rpx 24rpx;
background: linear-gradient(135deg, #FFF7E6 0%, #FFF1E0 100%);
border-top: 1rpx solid #FFE8CC;
display: flex;
align-items: center;
justify-content: space-between;
}
.earnings-info {
display: flex;
align-items: center;
gap: 8rpx;
}
.earnings-text {
font-size: 24rpx;
color: #999;
}
.earnings-amount {
font-size: 28rpx;
font-weight: 700;
color: #FF6B35;
}
.arrow-icon {
opacity: 0.5;
}
/* ========== 加载状态 ========== */
.loading-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
padding: 48rpx 0;
}
.loading-spinner {
display: flex;
align-items: center;
gap: 12rpx;
}
.spinner-dot {
width: 12rpx;
height: 12rpx;
background: #FF6B35;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
&:nth-child(1) {
animation-delay: -0.32s;
}
&:nth-child(2) {
animation-delay: -0.16s;
}
}
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0.6);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
.loading-text {
font-size: 24rpx;
color: #999;
}
/* ========== 没有更多 ========== */
.no-more {
display: flex;
align-items: center;
justify-content: center;
gap: 24rpx;
padding: 48rpx 0;
}
.no-more-line {
width: 80rpx;
height: 2rpx;
background: linear-gradient(90deg, transparent 0%, #E5E5E5 50%, transparent 100%);
}
.no-more-text {
font-size: 24rpx;
color: #CCCCCC;
}
/* ========== 空状态 ========== */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 120rpx 48rpx;
}
.empty-icon {
width: 160rpx;
height: 160rpx;
background: linear-gradient(135deg, #F5F5F5 0%, #E8E8E8 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32rpx;
}
.empty-title {
font-size: 32rpx;
font-weight: 600;
color: #666;
margin-bottom: 16rpx;
}
.empty-desc {
font-size: 26rpx;
color: #999;
text-align: center;
line-height: 1.6;
margin-bottom: 48rpx;
}
.empty-action {
display: flex;
align-items: center;
gap: 12rpx;
padding: 24rpx 48rpx;
background: linear-gradient(135deg, #FF8C5A 0%, #FF6B35 100%);
border-radius: 48rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.3);
transition: all 0.3s ease;
&:active {
transform: scale(0.96);
box-shadow: 0 4rpx 16rpx rgba(255, 107, 53, 0.4);
}
}
.action-text {
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
}
</style>