|
- <template>
- <view class="invite-list-page">
- <view class="summary-card">
- <view>
- <text class="eyebrow">INVITE RECORDS</text>
- <text class="summary-title">我的邀请</text>
- <text class="summary-desc">好友完成注册后,奖励点数会自动入账。</text>
- </view>
- <view class="summary-count">
- <text class="count-num">{{ total || list.length }}</text>
- <text class="count-label">记录</text>
- </view>
- </view>
-
- <view class="stats-row">
- <view class="stat-card">
- <text class="stat-num">{{ successCount }}</text>
- <text class="stat-label">已奖励</text>
- </view>
- <view class="stat-card">
- <text class="stat-num">{{ rewardPoints }}</text>
- <text class="stat-label">累计点数</text>
- </view>
- <view class="stat-card">
- <text class="stat-num">{{ pendingCount }}</text>
- <text class="stat-label">待处理</text>
- </view>
- </view>
-
- <view v-if="list.length" class="record-list">
- <view v-for="item in list" :key="item.id" class="record-card">
- <view class="avatar-wrap">
- <image v-if="item.avatar" class="avatar" :src="item.avatar" mode="aspectFill" />
- <text v-else class="avatar-text">{{ getInitial(item.user_name) }}</text>
- </view>
- <view class="record-main">
- <view class="record-head">
- <text class="name">{{ item.user_name || '好友' }}</text>
- <text class="status" :class="statusClass(item.status)">{{ item.status_text || statusText(item.status) }}</text>
- </view>
- <view class="reward-line">
- <text v-if="Number(item.inviter_points) > 0" class="reward-chip">+{{ item.inviter_points }} 点</text>
- <text class="reward-desc">{{ getRecordDesc(item) }}</text>
- </view>
- <text class="time">{{ formatTime(item.reward_time || item.add_time) }}</text>
- </view>
- </view>
- </view>
-
- <view v-if="!list.length && !loading" class="empty-state">
- <view class="empty-mark">邀</view>
- <text class="empty-title">还没有邀请记录</text>
- <text class="empty-desc">分享给好友,好友注册成功后这里会展示奖励明细。</text>
- <view class="empty-action" @click="goInvite">去邀请好友</view>
- </view>
-
- <view v-if="loading" class="loading">{{ list.length ? '加载更多...' : '加载中...' }}</view>
- <view v-if="list.length && list.length >= total && !loading" class="end-text">没有更多记录了</view>
- </view>
- </template>
-
- <script setup>
- import { computed, ref } from 'vue'
- import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
- import dayjs from 'dayjs'
- import { inviteApi } from '@/api/index.js'
- import { useUserStore } from '@/store/user.js'
- import { useApp } from '@/utils/useApp.js'
-
- const userStore = useUserStore()
- const { toast } = useApp()
- const list = ref([])
- const stats = ref({})
- const page = ref(1)
- const pageSize = 10
- const total = ref(0)
- const loading = ref(false)
-
- const successCount = computed(() => Number(stats.value.success !== undefined ? stats.value.success : list.value.filter((item) => Number(item.status) === 1).length))
- const pendingCount = computed(() => list.value.filter((item) => Number(item.status) === 0).length)
- const rewardPoints = computed(() => {
- if (stats.value.inviter_points !== undefined) return Number(stats.value.inviter_points || 0)
- return list.value.reduce((sum, item) => {
- if (Number(item.status) !== 1) return sum
- return sum + Number(item.inviter_points || 0)
- }, 0)
- })
-
- onLoad(() => {
- userStore.restore()
- if (!userStore.isLogin) {
- uni.navigateTo({ url: '/pages/login/login' })
- return
- }
- refreshSilent()
- })
-
- onPullDownRefresh(() => refreshSilent())
- onReachBottom(() => loadMore())
-
- async function fetchList(reset = false) {
- if (loading.value) {
- uni.stopPullDownRefresh()
- return
- }
- loading.value = true
- if (reset) page.value = 1
- try {
- const res = await inviteApi.getList({
- page_no: page.value,
- page_size: pageSize
- })
- const rows = res.list || []
- const data = res.data || {}
- list.value = reset ? rows : list.value.concat(rows)
- total.value = Number(data.total || rows.length)
- stats.value = data.stats || {}
- } catch (e) {
- toast(e.msg || '邀请记录加载失败')
- } finally {
- loading.value = false
- uni.stopPullDownRefresh()
- }
- }
-
- function refreshSilent() {
- fetchList(true)
- }
-
- function loadMore() {
- if (loading.value || list.value.length >= total.value) return
- page.value += 1
- fetchList()
- }
-
- function formatTime(value) {
- if (!value) return ''
- return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm')
- }
-
- function getInitial(name = '') {
- return String(name || '友').slice(0, 1)
- }
-
- function statusClass(status) {
- status = Number(status)
- if (status === 1) return 'success'
- if (status === 0) return 'pending'
- return 'muted'
- }
-
- function statusText(status) {
- status = Number(status)
- if (status === 1) return '已奖励'
- if (status === 2) return '已记录'
- if (status < 0) return '失败'
- return '待发放'
- }
-
- function getRecordDesc(item) {
- const status = Number(item.status)
- if (status === 1) return '好友注册成功,奖励已到账'
- if (status === 2) return '已记录,本次未触发奖励'
- if (status < 0) return item.remark || '奖励发放失败'
- return '等待奖励发放'
- }
-
- function goInvite() {
- const pages = getCurrentPages()
- const index = pages.findIndex((page) => page.route === 'pages/invite/invite')
- if (index >= 0 && pages.length - 1 - index > 0) {
- uni.navigateBack({ delta: pages.length - 1 - index })
- return
- }
- uni.navigateTo({ url: '/pages/invite/invite' })
- }
- </script>
-
- <style lang="scss" scoped>
- .invite-list-page {
- min-height: 100vh;
- padding: 24rpx 24rpx calc(52rpx + env(safe-area-inset-bottom));
- background: #f4f8fc;
- box-sizing: border-box;
- }
-
- .summary-card {
- padding: 28rpx 28rpx;
- border-radius: 28rpx;
- color: #ffffff;
- background: linear-gradient(135deg, #0ea5e9 0%, #1f8cff 100%);
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 24rpx;
- box-shadow: 0 16rpx 34rpx rgba(14, 165, 233, 0.18);
- }
-
- .eyebrow,
- .summary-title,
- .summary-desc,
- .count-num,
- .count-label,
- .stat-num,
- .stat-label,
- .name,
- .reward-desc,
- .time,
- .empty-title,
- .empty-desc {
- display: block;
- }
-
- .eyebrow {
- opacity: 0.72;
- font-size: 20rpx;
- font-weight: 900;
- line-height: 1;
- }
-
- .summary-title {
- margin-top: 14rpx;
- font-size: 40rpx;
- font-weight: 900;
- line-height: 1.18;
- }
-
- .summary-desc {
- margin-top: 12rpx;
- opacity: 0.86;
- font-size: 24rpx;
- line-height: 1.45;
- }
-
- .summary-count {
- width: 126rpx;
- height: 126rpx;
- border-radius: 28rpx;
- background: rgba(255, 255, 255, 0.16);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
-
- .count-num {
- font-size: 42rpx;
- font-weight: 900;
- line-height: 1;
- }
-
- .count-label {
- margin-top: 10rpx;
- opacity: 0.82;
- font-size: 22rpx;
- }
-
- .stats-row {
- margin-top: 18rpx;
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 14rpx;
- }
-
- .stat-card {
- padding: 20rpx 8rpx;
- border-radius: 22rpx;
- background: #ffffff;
- text-align: center;
- box-shadow: 0 8rpx 22rpx rgba(31, 52, 86, 0.05);
- }
-
- .stat-num {
- color: #172033;
- font-size: 34rpx;
- font-weight: 900;
- line-height: 1;
- }
-
- .stat-label {
- margin-top: 10rpx;
- color: #7c8a9b;
- font-size: 22rpx;
- }
-
- .record-list {
- margin-top: 22rpx;
- }
-
- .record-card {
- margin-bottom: 18rpx;
- padding: 24rpx;
- border-radius: 24rpx;
- background: #ffffff;
- display: flex;
- gap: 20rpx;
- box-shadow: 0 8rpx 22rpx rgba(31, 52, 86, 0.045);
- }
-
- .avatar-wrap {
- width: 86rpx;
- height: 86rpx;
- border-radius: 50%;
- background: #ecf7ff;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- flex-shrink: 0;
- }
-
- .avatar {
- width: 100%;
- height: 100%;
- }
-
- .avatar-text {
- color: #0b8cff;
- font-size: 30rpx;
- font-weight: 900;
- }
-
- .record-main {
- flex: 1;
- min-width: 0;
- }
-
- .record-head {
- display: flex;
- align-items: center;
- gap: 16rpx;
- }
-
- .name {
- flex: 1;
- min-width: 0;
- color: #172033;
- font-size: 30rpx;
- font-weight: 900;
- line-height: 1.25;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .status {
- height: 40rpx;
- padding: 0 16rpx;
- border-radius: 999rpx;
- font-size: 22rpx;
- font-weight: 800;
- line-height: 40rpx;
- flex-shrink: 0;
-
- &.success {
- color: #13a86b;
- background: #e9fff4;
- }
-
- &.pending {
- color: #f97316;
- background: #fff4e8;
- }
-
- &.muted {
- color: #7f8896;
- background: #f2f5f9;
- }
- }
-
- .reward-line {
- margin-top: 14rpx;
- display: flex;
- align-items: center;
- gap: 12rpx;
- }
-
- .reward-chip {
- height: 38rpx;
- padding: 0 14rpx;
- border-radius: 999rpx;
- color: #f97316;
- background: #fff3e8;
- font-size: 22rpx;
- font-weight: 900;
- line-height: 38rpx;
- flex-shrink: 0;
- }
-
- .reward-desc {
- min-width: 0;
- color: #64748b;
- font-size: 24rpx;
- line-height: 1.35;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .time {
- margin-top: 12rpx;
- color: #9aa4b2;
- font-size: 22rpx;
- }
-
- .empty-state {
- margin-top: 72rpx;
- padding: 48rpx 34rpx;
- border-radius: 28rpx;
- background: #ffffff;
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- box-shadow: 0 8rpx 22rpx rgba(31, 52, 86, 0.045);
- }
-
- .empty-mark {
- width: 96rpx;
- height: 96rpx;
- border-radius: 30rpx;
- color: #0b8cff;
- background: #eef8ff;
- font-size: 40rpx;
- font-weight: 900;
- line-height: 96rpx;
- }
-
- .empty-title {
- margin-top: 26rpx;
- color: #172033;
- font-size: 32rpx;
- font-weight: 900;
- line-height: 1;
- }
-
- .empty-desc {
- margin-top: 16rpx;
- color: #7f8896;
- font-size: 25rpx;
- line-height: 1.45;
- }
-
- .empty-action {
- width: 230rpx;
- height: 72rpx;
- margin-top: 28rpx;
- border-radius: 999rpx;
- color: #ffffff;
- background: #0b8cff;
- font-size: 26rpx;
- font-weight: 900;
- line-height: 72rpx;
- box-shadow: 0 12rpx 24rpx rgba(11, 140, 255, 0.2);
- }
-
- .loading,
- .end-text {
- padding: 26rpx 0 0;
- color: #9aa4b2;
- font-size: 24rpx;
- text-align: center;
- }
- </style>
|