549 lines
15 KiB
Vue
549 lines
15 KiB
Vue
<template>
|
||
<view class="dist-page">
|
||
<view class="dist-header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="dist-header__nav">
|
||
<u-icon name="arrow-left" color="#fff" size="36" @click="goBack" />
|
||
<text class="dist-header__title">分销中心</text>
|
||
<view style="width: 36rpx" />
|
||
</view>
|
||
|
||
<view class="dist-stats">
|
||
<view class="dist-stats__item">
|
||
<text class="dist-stats__num">{{ distInfo.total_commission || '0.00' }}</text>
|
||
<text class="dist-stats__label">累计佣金</text>
|
||
</view>
|
||
<view class="dist-stats__divider" />
|
||
<view class="dist-stats__item">
|
||
<text class="dist-stats__num">{{ distInfo.available_amount || '0.00' }}</text>
|
||
<text class="dist-stats__label">可提现</text>
|
||
</view>
|
||
<view class="dist-stats__divider" />
|
||
<view class="dist-stats__item">
|
||
<text class="dist-stats__num">{{ teamTotal }}</text>
|
||
<text class="dist-stats__label">团队人数</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="dist-invite">
|
||
<view class="dist-invite__title">
|
||
<text>邀请好友赚佣金</text>
|
||
</view>
|
||
<view class="dist-invite__desc">
|
||
<text>邀请好友注册并消费,您可获得佣金奖励</text>
|
||
</view>
|
||
|
||
<view class="dist-invite__rates">
|
||
<view class="dist-invite__rate-card">
|
||
<text class="dist-invite__rate-num">
|
||
{{ distInfo.commission_rate_1 ? distInfo.commission_rate_1 + '%' : '-' }}
|
||
</text>
|
||
<text class="dist-invite__rate-label">一级佣金</text>
|
||
<text class="dist-invite__rate-desc">
|
||
直接邀请用户消费金额的{{ distInfo.commission_rate_1 ? distInfo.commission_rate_1 + '%' : '-' }}
|
||
</text>
|
||
</view>
|
||
<view class="dist-invite__rate-card">
|
||
<text class="dist-invite__rate-num">
|
||
{{ distInfo.commission_rate_2 ? distInfo.commission_rate_2 + '%' : '-' }}
|
||
</text>
|
||
<text class="dist-invite__rate-label">二级佣金</text>
|
||
<text class="dist-invite__rate-desc">
|
||
间接邀请用户消费金额的{{ distInfo.commission_rate_2 ? distInfo.commission_rate_2 + '%' : '-' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="dist-invite__code">
|
||
<view class="dist-invite__code-box">
|
||
<text class="dist-invite__code-label">我的邀请码</text>
|
||
<text class="dist-invite__code-value">{{ inviteCode || '加载中...' }}</text>
|
||
</view>
|
||
<view class="dist-invite__btns">
|
||
<view class="dist-invite__btn dist-invite__btn--copy" @tap="copyCode">
|
||
<u-icon name="file-text" size="28" color="#005fff" />
|
||
<text>复制邀请码</text>
|
||
</view>
|
||
<view class="dist-invite__btn dist-invite__btn--share" @tap="handleShare">
|
||
<u-icon name="share-square" size="28" color="#fff" />
|
||
<text>分享邀请链接</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="dist-team">
|
||
<view class="dist-team__header">
|
||
<text class="dist-team__title">我的团队 ({{ teamTotal }})</text>
|
||
<view class="dist-team__tabs">
|
||
<text class="dist-team__tab" :class="{ 'dist-team__tab--active': teamLevel === 1 }"
|
||
@tap="switchTeamLevel(1)">
|
||
一级
|
||
</text>
|
||
<text class="dist-team__tab" :class="{ 'dist-team__tab--active': teamLevel === 2 }"
|
||
@tap="switchTeamLevel(2)">
|
||
二级
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="loading" class="dist-team__empty">
|
||
<u-loading mode="circle" size="40" />
|
||
<text class="dist-team__empty-tip">加载中...</text>
|
||
</view>
|
||
|
||
<view v-else-if="teamList.length === 0" class="dist-team__empty">
|
||
<u-empty text="暂无团队成员" mode="data" icon-size="120" />
|
||
<text class="dist-team__empty-tip">快去邀请好友加入吧</text>
|
||
</view>
|
||
|
||
<template v-else>
|
||
<view v-for="item in teamList" :key="item.id" class="dist-team__item">
|
||
<image class="dist-team__avatar" :src="item.avatar || '/static/images/avatar.png'" mode="aspectFill"
|
||
lazy-load />
|
||
<view class="dist-team__info">
|
||
<text class="dist-team__name">{{ item.nickname }}</text>
|
||
<text class="dist-team__time">ID:{{ item.sn || item.id }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="hasMore" class="dist-team__loadmore" @tap="loadMore">
|
||
<text>{{ loadingMore ? '加载中...' : '加载更多' }}</text>
|
||
</view>
|
||
</template>
|
||
|
||
<view class="dist-team__footer">
|
||
<view class="dist-team__footer-item">
|
||
<text class="dist-team__footer-label">总收益 ›</text>
|
||
<text class="dist-team__footer-value">
|
||
{{ distInfo.total_income || '0.00' }}
|
||
</text>
|
||
</view>
|
||
<view class="dist-team__footer-item">
|
||
<text class="dist-team__footer-label">总消费 ›</text>
|
||
<text class="dist-team__footer-value">
|
||
{{ distInfo.total_consume || '0.00' }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<share-popup v-if="showSharePopup" v-model:show="showSharePopup" page-type="invite" path="/pages/index/index" title="邀请你加入我的团队" />
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import { getShareInfo, getInviteList } from '@/api/distribution'
|
||
import type { DistTeamItem } from '@/api/distribution'
|
||
import SharePopup from '@/components/share-popup/share-popup.vue'
|
||
import { checkLogin } from '@/utils/useLoginCheck'
|
||
|
||
const statusBarHeight = ref(0)
|
||
const inviteCode = ref('')
|
||
const h5Domain = ref('')
|
||
const teamList = ref<DistTeamItem[]>([])
|
||
const teamTotal = ref(0)
|
||
const loading = ref(false)
|
||
const loadingMore = ref(false)
|
||
const page = ref(1)
|
||
const hasMore = ref(false)
|
||
const showSharePopup = ref(false)
|
||
const teamLevel = ref(1)
|
||
const distInfo = ref({
|
||
total_commission: '',
|
||
available_amount: '',
|
||
commission_rate_1: '',
|
||
commission_rate_2: '',
|
||
total_income: '',
|
||
total_consume: ''
|
||
})
|
||
|
||
onMounted(async () => {
|
||
const sysInfo = uni.getSystemInfoSync()
|
||
// #ifdef APP-PLUS || MP
|
||
statusBarHeight.value = sysInfo.statusBarHeight || 44
|
||
// #endif
|
||
// #ifdef H5
|
||
statusBarHeight.value = 0
|
||
// #endif
|
||
|
||
const loggedIn = await checkLogin()
|
||
if (!loggedIn) return
|
||
|
||
fetchShareInfo()
|
||
fetchTeamList()
|
||
})
|
||
|
||
const goBack = () => {
|
||
if (getCurrentPages().length > 1) {
|
||
uni.navigateBack()
|
||
} else {
|
||
uni.switchTab({ url: '/pages/user/user' })
|
||
}
|
||
}
|
||
|
||
async function fetchShareInfo() {
|
||
try {
|
||
const res = await getShareInfo()
|
||
inviteCode.value = res.invite_code || ''
|
||
h5Domain.value = res.h5_domain || ''
|
||
teamTotal.value = res.team_count || 0
|
||
} catch (e) {
|
||
console.error('获取分享信息失败', e)
|
||
}
|
||
}
|
||
|
||
async function fetchTeamList() {
|
||
loading.value = true
|
||
try {
|
||
const res = await getInviteList({ page: 1, size: 20, level: teamLevel.value })
|
||
teamList.value = res.list || []
|
||
const listTotal = res.total || 0
|
||
page.value = 1
|
||
hasMore.value = teamList.value.length < listTotal
|
||
} catch (e) {
|
||
console.error('获取团队列表失败', e)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function loadMore() {
|
||
if (loadingMore.value || !hasMore.value) return
|
||
loadingMore.value = true
|
||
try {
|
||
const nextPage = page.value + 1
|
||
const res = await getInviteList({ page: nextPage, size: 20, level: teamLevel.value })
|
||
const list = res.list || []
|
||
teamList.value.push(...list)
|
||
page.value = nextPage
|
||
hasMore.value = teamList.value.length < (res.total || 0)
|
||
} catch (e) {
|
||
console.error('加载更多失败', e)
|
||
} finally {
|
||
loadingMore.value = false
|
||
}
|
||
}
|
||
|
||
const copyCode = () => {
|
||
if (!inviteCode.value) {
|
||
uni.showToast({ title: '邀请码加载中', icon: 'none' })
|
||
return
|
||
}
|
||
uni.setClipboardData({
|
||
data: inviteCode.value,
|
||
success: () => {
|
||
uni.showToast({ title: '邀请码已复制', icon: 'success' })
|
||
}
|
||
})
|
||
}
|
||
|
||
const switchTeamLevel = (level: number) => {
|
||
teamLevel.value = level
|
||
page.value = 1
|
||
fetchTeamList()
|
||
}
|
||
|
||
const handleShare = () => {
|
||
if (!inviteCode.value) {
|
||
uni.showToast({ title: '信息加载中', icon: 'none' })
|
||
return
|
||
}
|
||
showSharePopup.value = true
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.dist-page {
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.dist-header {
|
||
background: linear-gradient(to bottom, #005fff 70%, #f5f5f5 70%);
|
||
padding: 0 0 40rpx;
|
||
|
||
&__nav {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16rpx 32rpx;
|
||
}
|
||
|
||
&__title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
}
|
||
}
|
||
|
||
.dist-stats {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-around;
|
||
margin: 60rpx 40rpx 0;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
border-radius: 20rpx;
|
||
padding: 32rpx 0;
|
||
|
||
&__item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
&__num {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
}
|
||
|
||
&__label {
|
||
font-size: 22rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
&__divider {
|
||
width: 1rpx;
|
||
height: 60rpx;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
}
|
||
}
|
||
|
||
.dist-invite {
|
||
margin: 40rpx 40rpx 0;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 28rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||
|
||
&__title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #005fff;
|
||
}
|
||
|
||
&__desc {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
&__rates {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-top: 24rpx;
|
||
}
|
||
|
||
&__rate-card {
|
||
flex: 1;
|
||
border: 2rpx solid #e8f0fe;
|
||
border-radius: 16rpx;
|
||
padding: 20rpx;
|
||
background: #f7faff;
|
||
}
|
||
|
||
&__rate-num {
|
||
font-size: 36rpx;
|
||
font-weight: 700;
|
||
color: #005fff;
|
||
display: block;
|
||
}
|
||
|
||
&__rate-label {
|
||
font-size: 24rpx;
|
||
color: #333;
|
||
font-weight: 600;
|
||
margin-top: 4rpx;
|
||
display: block;
|
||
}
|
||
|
||
&__rate-desc {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
display: block;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
&__code {
|
||
margin-top: 28rpx;
|
||
padding-top: 24rpx;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
&__code-box {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
background: #f8f8f8;
|
||
border-radius: 12rpx;
|
||
padding: 20rpx 24rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
&__code-label {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
&__code-value {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #005fff;
|
||
letter-spacing: 4rpx;
|
||
}
|
||
|
||
&__btns {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
&__btn {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
padding: 20rpx 0;
|
||
border-radius: 44rpx;
|
||
font-size: 26rpx;
|
||
|
||
&--copy {
|
||
background: #fff;
|
||
border: 2rpx solid #005fff;
|
||
color: #005fff;
|
||
}
|
||
|
||
&--share {
|
||
background: #005fff;
|
||
color: #fff;
|
||
}
|
||
}
|
||
}
|
||
|
||
.dist-team {
|
||
margin: 0 40rpx 20rpx;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 28rpx;
|
||
|
||
&__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
&__title {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
&__tabs {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
&__tab {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
padding: 8rpx 28rpx;
|
||
border-radius: 24rpx;
|
||
border: 2rpx solid #e0e0e0;
|
||
background: #fff;
|
||
|
||
&--active {
|
||
background: #005fff;
|
||
color: #fff;
|
||
border-color: #005fff;
|
||
}
|
||
}
|
||
|
||
&__empty {
|
||
padding: 40rpx 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
&__empty-tip {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
&__item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
}
|
||
|
||
&__avatar {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
&__info {
|
||
flex: 1;
|
||
margin-left: 16rpx;
|
||
}
|
||
|
||
&__name {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
display: block;
|
||
}
|
||
|
||
&__time {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
margin-top: 12rpx;
|
||
display: block;
|
||
}
|
||
|
||
&__loadmore {
|
||
text-align: center;
|
||
padding: 24rpx 0;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
&__footer {
|
||
display: flex;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
margin-top: 20rpx;
|
||
padding-top: 24rpx;
|
||
}
|
||
|
||
&__footer-item {
|
||
flex: 1;
|
||
}
|
||
|
||
&__footer-label {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
display: block;
|
||
}
|
||
|
||
&__footer-value {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
margin-top: 8rpx;
|
||
display: block;
|
||
}
|
||
}
|
||
</style>
|