489 lines
11 KiB
Vue
489 lines
11 KiB
Vue
<template>
|
|
<view class="revenue-container">
|
|
<!-- 顶部导航 -->
|
|
<!-- <Header :show-back="true" back-icon-color="#e6e6e6" :show-guide-icon="false" :show-task-icon="false" :show-star-activity-icon="false" /> -->
|
|
|
|
<!-- 页面标题 -->
|
|
<view class="page-title">展示收益</view>
|
|
|
|
<!-- Tab 切换 -->
|
|
<view class="tab-bar">
|
|
<view
|
|
class="tab-item"
|
|
:class="{ 'is-active': currentTab === 'claimable' }"
|
|
@click="switchTab('claimable')"
|
|
>
|
|
可领取
|
|
<view v-if="claimableCount > 0" class="tab-badge">{{ claimableCount }}</view>
|
|
</view>
|
|
<view
|
|
class="tab-item"
|
|
:class="{ 'is-active': currentTab === 'claimed' }"
|
|
@click="switchTab('claimed')"
|
|
>
|
|
已领取
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 加载状态 -->
|
|
<view v-if="loading" class="loading-state">
|
|
<view class="loading-spinner"></view>
|
|
<text class="loading-text">加载中...</text>
|
|
</view>
|
|
|
|
<!-- 错误状态 -->
|
|
<view v-else-if="errorMessage" class="error-state">
|
|
<text class="error-text">{{ errorMessage }}</text>
|
|
<button class="retry-btn" @click="loadRevenue">重试</button>
|
|
</view>
|
|
|
|
<!-- 收益列表 -->
|
|
<view v-else class="revenue-list">
|
|
<!-- 空状态 -->
|
|
<view v-if="items.length === 0" class="empty-state">
|
|
<text class="empty-text">{{ currentTab === 'claimable' ? '暂无可领取的收益' : '暂无已领取记录' }}</text>
|
|
</view>
|
|
|
|
<!-- 收益项 -->
|
|
<view v-for="item in items" :key="item.id" class="revenue-item">
|
|
<view class="revenue-info">
|
|
<!-- 占位图 -->
|
|
<view class="exhibition-thumb">
|
|
<text class="thumb-placeholder">🎨</text>
|
|
</view>
|
|
|
|
<view class="revenue-detail">
|
|
<view class="revenue-title">
|
|
<text class="slot-type-badge" :class="item.slot_type">
|
|
{{ item.slot_type === 'own' ? '自己的展位' : '好友展位' }}
|
|
</text>
|
|
</view>
|
|
<view class="revenue-cycle">
|
|
{{ formatTime(item.cycle_start_time) }} - {{ formatTime(item.cycle_end_time) }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="revenue-action">
|
|
<view class="crystal-amount">
|
|
<text class="crystal-icon">💎</text>
|
|
<text class="crystal-value">{{ item.crystal_amount }}</text>
|
|
</view>
|
|
|
|
<!-- 领取按钮 -->
|
|
<button
|
|
v-if="currentTab === 'claimable' && item.can_claim"
|
|
class="claim-btn"
|
|
:loading="claimingId === item.id"
|
|
@click="handleClaim(item)"
|
|
>
|
|
领取
|
|
</button>
|
|
<view v-else-if="currentTab === 'claimed'" class="claimed-tag">
|
|
已领取
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 加载更多 -->
|
|
<view v-if="hasMore && !loadingMore" class="load-more" @click="loadMore">
|
|
<text class="load-more-text">加载更多</text>
|
|
</view>
|
|
<view v-if="loadingMore" class="loading-more">
|
|
<text class="loading-more-text">加载中...</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 一键领取按钮 -->
|
|
<view v-if="!loading && !errorMessage && currentTab === 'claimable' && claimableCount > 0" class="claim-all-bar">
|
|
<button class="claim-all-btn" :loading="claimingAll" @click="handleClaimAll">
|
|
一键领取 ({{ claimableCount }})
|
|
</button>
|
|
</view>
|
|
|
|
<!-- 下拉刷新 -->
|
|
<view v-if="!loading" class="pull-to-refresh" @click="handleRefresh">
|
|
<!-- <text class="refresh-text">{{ refreshing ? '刷新中...' : '下拉刷新' }}</text> -->
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import Header from '../components/Header.vue'
|
|
import { getExhibitionRevenue, claimExhibitionRevenue, claimAllExhibitionRevenue } from '@/utils/task-api.js'
|
|
|
|
const loading = ref(true)
|
|
const loadingMore = ref(false)
|
|
const refreshing = ref(false)
|
|
const errorMessage = ref('')
|
|
const currentTab = ref('claimable')
|
|
const items = ref([])
|
|
const total = ref(0)
|
|
const page = ref(1)
|
|
const pageSize = ref(20)
|
|
const claimingId = ref('')
|
|
const claimingAll = ref(false)
|
|
const starId = ref(1)
|
|
|
|
const hasMore = computed(() => items.value.length < total.value)
|
|
const claimableCount = computed(() => items.value.filter(i => i.can_claim).length)
|
|
|
|
function formatTime(timestamp) {
|
|
if (!timestamp) return ''
|
|
const date = new Date(timestamp)
|
|
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
}
|
|
|
|
async function loadRevenue(append = false) {
|
|
try {
|
|
if (!append) {
|
|
loading.value = true
|
|
errorMessage.value = ''
|
|
} else {
|
|
loadingMore.value = true
|
|
}
|
|
|
|
const fanProfile = uni.getStorageSync('fan_profile')
|
|
if (fanProfile && fanProfile.star_id) {
|
|
starId.value = fanProfile.star_id
|
|
}
|
|
|
|
const status = currentTab.value === 'claimable' ? 'claimable' : 'claimed'
|
|
const res = await getExhibitionRevenue(starId.value, status, page.value, pageSize.value)
|
|
const data = res.data || {}
|
|
|
|
if (append) {
|
|
items.value = [...items.value, ...(data.items || [])]
|
|
} else {
|
|
items.value = data.items || []
|
|
}
|
|
total.value = data.total || 0
|
|
} catch (err) {
|
|
console.error('loadRevenue error:', err)
|
|
if (!append) {
|
|
errorMessage.value = err.message || '加载失败'
|
|
}
|
|
} finally {
|
|
loading.value = false
|
|
loadingMore.value = false
|
|
refreshing.value = false
|
|
}
|
|
}
|
|
|
|
async function switchTab(tab) {
|
|
if (currentTab.value === tab) return
|
|
currentTab.value = tab
|
|
page.value = 1
|
|
items.value = []
|
|
total.value = 0
|
|
await loadRevenue()
|
|
}
|
|
|
|
async function loadMore() {
|
|
if (loadingMore.value || !hasMore.value) return
|
|
page.value++
|
|
await loadRevenue(true)
|
|
}
|
|
|
|
async function handleClaim(item) {
|
|
if (claimingId.value) return
|
|
try {
|
|
claimingId.value = item.id
|
|
await claimExhibitionRevenue(item.id, starId.value)
|
|
await loadRevenue()
|
|
uni.showToast({ title: '领取成功', icon: 'success' })
|
|
} catch (err) {
|
|
console.error('handleClaim error:', err)
|
|
uni.showToast({ title: err.message || '领取失败', icon: 'none' })
|
|
} finally {
|
|
claimingId.value = ''
|
|
}
|
|
}
|
|
|
|
async function handleClaimAll() {
|
|
if (claimingAll.value) return
|
|
try {
|
|
claimingAll.value = true
|
|
await claimAllExhibitionRevenue(starId.value)
|
|
await loadRevenue()
|
|
uni.showToast({ title: '领取成功', icon: 'success' })
|
|
} catch (err) {
|
|
console.error('handleClaimAll error:', err)
|
|
uni.showToast({ title: err.message || '领取失败', icon: 'none' })
|
|
} finally {
|
|
claimingAll.value = false
|
|
}
|
|
}
|
|
|
|
function handleRefresh() {
|
|
refreshing.value = true
|
|
page.value = 1
|
|
loadRevenue()
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadRevenue()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.revenue-container {
|
|
min-height: 100vh;
|
|
background-color: #f5f5f5;
|
|
padding-bottom: 120rpx;
|
|
}
|
|
|
|
.page-title {
|
|
font-size: 36rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
text-align: center;
|
|
padding: 20rpx 0;
|
|
}
|
|
|
|
.tab-bar {
|
|
display: flex;
|
|
background: #fff;
|
|
padding: 0 30rpx;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.tab-item {
|
|
flex: 1;
|
|
text-align: center;
|
|
padding: 20rpx 0;
|
|
font-size: 28rpx;
|
|
color: #666;
|
|
position: relative;
|
|
}
|
|
|
|
.tab-item.is-active {
|
|
color: #6c5ce7;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.tab-item.is-active::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 60rpx;
|
|
height: 4rpx;
|
|
background: #6c5ce7;
|
|
border-radius: 2rpx;
|
|
}
|
|
|
|
.tab-badge {
|
|
position: absolute;
|
|
top: 10rpx;
|
|
right: 30rpx;
|
|
min-width: 32rpx;
|
|
height: 32rpx;
|
|
background: #ff4d4f;
|
|
color: #fff;
|
|
border-radius: 16rpx;
|
|
font-size: 22rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 8rpx;
|
|
}
|
|
|
|
.loading-state,
|
|
.error-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 100rpx 0;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
border: 4rpx solid #e0e0e0;
|
|
border-top-color: #6c5ce7;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.loading-text,
|
|
.error-text {
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
.retry-btn {
|
|
margin-top: 20rpx;
|
|
padding: 16rpx 40rpx;
|
|
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
|
|
color: #fff;
|
|
border-radius: 50rpx;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
.empty-state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 100rpx 0;
|
|
}
|
|
|
|
.empty-text {
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
.revenue-list {
|
|
padding: 0 20rpx;
|
|
}
|
|
|
|
.revenue-item {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
padding: 30rpx;
|
|
margin-bottom: 20rpx;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.revenue-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
flex: 1;
|
|
}
|
|
|
|
.exhibition-thumb {
|
|
width: 100rpx;
|
|
height: 100rpx;
|
|
background: #f5f5f5;
|
|
border-radius: 12rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.thumb-placeholder {
|
|
font-size: 48rpx;
|
|
}
|
|
|
|
.revenue-detail {
|
|
flex: 1;
|
|
}
|
|
|
|
.slot-type-badge {
|
|
padding: 4rpx 12rpx;
|
|
border-radius: 12rpx;
|
|
font-size: 22rpx;
|
|
}
|
|
|
|
.slot-type-badge.own {
|
|
background: #e8f4ff;
|
|
color: #1890ff;
|
|
}
|
|
|
|
.slot-type-badge.friend {
|
|
background: #fff7e6;
|
|
color: #fa8c16;
|
|
}
|
|
|
|
.revenue-cycle {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
margin-top: 8rpx;
|
|
}
|
|
|
|
.revenue-action {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 10rpx;
|
|
}
|
|
|
|
.crystal-amount {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6rpx;
|
|
}
|
|
|
|
.crystal-icon {
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
.crystal-value {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #6c5ce7;
|
|
}
|
|
|
|
.claim-btn {
|
|
padding: 12rpx 30rpx;
|
|
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
|
|
color: #fff;
|
|
border-radius: 30rpx;
|
|
font-size: 26rpx;
|
|
min-width: 120rpx;
|
|
}
|
|
|
|
.claimed-tag {
|
|
padding: 12rpx 30rpx;
|
|
background: #f5f5f5;
|
|
color: #999;
|
|
border-radius: 30rpx;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.load-more,
|
|
.loading-more {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 30rpx 0;
|
|
}
|
|
|
|
.load-more-text,
|
|
.loading-more-text {
|
|
color: #999;
|
|
font-size: 26rpx;
|
|
}
|
|
|
|
.claim-all-bar {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
padding: 20rpx 30rpx;
|
|
background: #fff;
|
|
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.claim-all-btn {
|
|
width: 100%;
|
|
padding: 24rpx 0;
|
|
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
|
|
color: #fff;
|
|
border-radius: 50rpx;
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.pull-to-refresh {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 30rpx 0;
|
|
}
|
|
|
|
.refresh-text {
|
|
color: #999;
|
|
font-size: 24rpx;
|
|
}
|
|
</style>
|