2025-12-16 12:33:02 +08:00
|
|
|
|
<script setup>
|
2026-05-13 14:43:38 +08:00
|
|
|
|
import { ref } from 'vue'
|
2025-12-16 12:33:02 +08:00
|
|
|
|
import { useRoute } from 'vue-router'
|
|
|
|
|
|
import useApiFetch from '@/composables/useApiFetch'
|
2026-05-13 14:43:38 +08:00
|
|
|
|
import EmptyState from '@/components/EmptyState.vue'
|
2025-12-16 12:33:02 +08:00
|
|
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const refreshing = ref(false)
|
|
|
|
|
|
const finished = ref(false)
|
|
|
|
|
|
const page = ref(1)
|
|
|
|
|
|
const pageSize = 8
|
|
|
|
|
|
const activeTab = ref('order') // 'order' 或 'invite'
|
|
|
|
|
|
|
|
|
|
|
|
// 数据
|
|
|
|
|
|
const userInfo = ref({})
|
|
|
|
|
|
const orderStats = ref({})
|
|
|
|
|
|
const rebateStats = ref({})
|
|
|
|
|
|
const inviteStats = ref({})
|
|
|
|
|
|
const orderList = ref([])
|
|
|
|
|
|
const inviteList = ref([])
|
|
|
|
|
|
const orderListTotal = ref(0)
|
|
|
|
|
|
const inviteListTotal = ref(0)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取详情数据
|
|
|
|
|
|
const fetchDetail = async () => {
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
const tabType = activeTab.value
|
|
|
|
|
|
const { data, error } = await useApiFetch(
|
|
|
|
|
|
`/agent/subordinate/contribution/detail?subordinate_id=${route.params.id}&page=${page.value}&page_size=${pageSize}&tab_type=${tabType}`
|
|
|
|
|
|
)
|
|
|
|
|
|
.get()
|
|
|
|
|
|
.json()
|
|
|
|
|
|
|
|
|
|
|
|
if (data.value && !error.value) {
|
|
|
|
|
|
if (data.value.code === 200) {
|
|
|
|
|
|
if (page.value === 1) {
|
|
|
|
|
|
// 更新用户信息
|
|
|
|
|
|
userInfo.value = {
|
|
|
|
|
|
createTime: data.value.data.create_time,
|
|
|
|
|
|
level: data.value.data.level_name || '普通',
|
|
|
|
|
|
mobile: data.value.data.mobile,
|
|
|
|
|
|
}
|
|
|
|
|
|
// 更新统计数据
|
|
|
|
|
|
orderStats.value = data.value.data.order_stats || {}
|
|
|
|
|
|
rebateStats.value = data.value.data.rebate_stats || {}
|
|
|
|
|
|
inviteStats.value = data.value.data.invite_stats || {}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空列表
|
|
|
|
|
|
if (tabType === 'order') {
|
|
|
|
|
|
orderList.value = []
|
|
|
|
|
|
} else {
|
|
|
|
|
|
inviteList.value = []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理列表数据
|
|
|
|
|
|
if (tabType === 'order') {
|
|
|
|
|
|
if (data.value.data.order_list) {
|
|
|
|
|
|
if (page.value === 1) {
|
|
|
|
|
|
orderList.value = data.value.data.order_list
|
|
|
|
|
|
} else {
|
|
|
|
|
|
orderList.value.push(...data.value.data.order_list)
|
|
|
|
|
|
}
|
|
|
|
|
|
orderListTotal.value = data.value.data.order_list_total || 0
|
|
|
|
|
|
finished.value = data.value.data.order_list.length < pageSize
|
|
|
|
|
|
if (!finished.value) {
|
|
|
|
|
|
page.value++
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
finished.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (data.value.data.invite_list) {
|
|
|
|
|
|
if (page.value === 1) {
|
|
|
|
|
|
inviteList.value = data.value.data.invite_list
|
|
|
|
|
|
} else {
|
|
|
|
|
|
inviteList.value.push(...data.value.data.invite_list)
|
|
|
|
|
|
}
|
|
|
|
|
|
inviteListTotal.value = data.value.data.invite_list_total || 0
|
|
|
|
|
|
finished.value = data.value.data.invite_list.length < pageSize
|
|
|
|
|
|
if (!finished.value) {
|
|
|
|
|
|
page.value++
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
finished.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换标签页
|
|
|
|
|
|
const switchTab = (tab) => {
|
|
|
|
|
|
if (activeTab.value === tab) return
|
|
|
|
|
|
activeTab.value = tab
|
|
|
|
|
|
page.value = 1
|
|
|
|
|
|
finished.value = false
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
// 延迟一下确保tab切换完成
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
fetchDetail()
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 下拉刷新
|
|
|
|
|
|
const onRefresh = () => {
|
|
|
|
|
|
finished.value = false
|
|
|
|
|
|
page.value = 1
|
|
|
|
|
|
fetchDetail().finally(() => {
|
|
|
|
|
|
refreshing.value = false
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取等级标签样式
|
|
|
|
|
|
const getLevelClass = (level) => {
|
|
|
|
|
|
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
|
|
|
|
|
switch (levelNum) {
|
|
|
|
|
|
case 3:
|
|
|
|
|
|
return 'bg-purple-100 text-purple-600'
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
return 'bg-yellow-100 text-yellow-600'
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 'bg-gray-100 text-gray-600'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取等级显示名称
|
|
|
|
|
|
const getLevelName = (level) => {
|
|
|
|
|
|
const levelNum = typeof level === 'number' ? level : parseInt(level)
|
|
|
|
|
|
const levelMap = {
|
|
|
|
|
|
1: '普通',
|
|
|
|
|
|
2: '黄金',
|
|
|
|
|
|
3: '钻石'
|
|
|
|
|
|
}
|
|
|
|
|
|
return levelMap[levelNum] || '普通'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
|
|
const formatTime = timeStr => {
|
|
|
|
|
|
if (!timeStr) return '-'
|
|
|
|
|
|
return timeStr.split(' ')[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化金额
|
|
|
|
|
|
const formatNumber = num => {
|
|
|
|
|
|
if (!num) return '0.00'
|
|
|
|
|
|
return Number(num).toFixed(2)
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="subordinate-detail">
|
|
|
|
|
|
<!-- 用户信息卡片 -->
|
|
|
|
|
|
<div class="p-4">
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-sm p-5 mb-4">
|
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
|
<div class="flex items-center space-x-3">
|
|
|
|
|
|
<div class="text-xl font-semibold text-gray-800">{{ userInfo.mobile }}</div>
|
|
|
|
|
|
<span class="px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-600">
|
|
|
|
|
|
{{ userInfo.level }}代理
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-sm text-gray-500 mb-4">成为下级代理时间:{{ formatTime(userInfo.createTime) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 订单统计卡片 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
|
|
|
|
|
<div class="text-base font-medium text-gray-800 mb-3">订单统计(仅统计有返佣的订单)</div>
|
|
|
|
|
|
<div class="grid grid-cols-3 gap-3">
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">总订单量</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-blue-600">{{ orderStats.total_orders || 0 }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">月订单</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-green-600">{{ orderStats.month_orders || 0 }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">今日订单</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-orange-600">{{ orderStats.today_orders || 0 }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 返佣统计卡片 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
|
|
|
|
|
<div class="text-base font-medium text-gray-800 mb-3">返佣统计</div>
|
|
|
|
|
|
<div class="grid grid-cols-3 gap-3">
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">总返佣金额</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-blue-600">¥{{ formatNumber(rebateStats.total_rebate_amount) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">月返佣金额</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-green-600">¥{{ formatNumber(rebateStats.month_rebate_amount) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">今日返佣金额</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-orange-600">¥{{ formatNumber(rebateStats.today_rebate_amount) }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 邀请统计卡片 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
|
|
|
|
|
<div class="text-base font-medium text-gray-800 mb-3">邀请统计</div>
|
|
|
|
|
|
<div class="grid grid-cols-3 gap-3">
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">总邀请</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-blue-600">{{ inviteStats.total_invites || 0 }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">月邀请</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-green-600">{{ inviteStats.month_invites || 0 }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-center">
|
|
|
|
|
|
<div class="text-gray-500 text-sm mb-1">今日邀请</div>
|
|
|
|
|
|
<div class="text-xl font-semibold text-orange-600">{{ inviteStats.today_invites || 0 }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Tab标签页 -->
|
|
|
|
|
|
<div class="bg-white rounded-xl shadow-sm p-4 mb-4">
|
|
|
|
|
|
<van-tabs v-model:active="activeTab" @change="switchTab">
|
|
|
|
|
|
<van-tab title="订单列表" name="order">
|
|
|
|
|
|
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
2026-05-13 14:43:38 +08:00
|
|
|
|
<van-list v-model:loading="loading" :finished="finished" :finished-text="orderList.length > 0 ? '没有更多了' : ''" @load="fetchDetail">
|
2025-12-16 12:33:02 +08:00
|
|
|
|
<div class="p-2">
|
2026-05-13 14:43:38 +08:00
|
|
|
|
<div v-for="item in orderList" :key="item.order_no"
|
2025-12-16 12:33:02 +08:00
|
|
|
|
class="order-item mb-3 border-b border-gray-200 pb-3">
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
<div class="font-medium text-gray-800 mb-1">{{ item.product_name || '未知产品' }}</div>
|
|
|
|
|
|
<div class="text-xs text-gray-500 mb-1">订单号:{{ item.order_no }}</div>
|
|
|
|
|
|
<div class="text-xs text-gray-500">{{ formatTime(item.create_time) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-right ml-4">
|
|
|
|
|
|
<div class="text-sm text-gray-500 mb-1">订单金额</div>
|
|
|
|
|
|
<div class="text-base font-semibold text-blue-600 mb-2">¥{{ formatNumber(item.order_amount) }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-sm text-gray-500 mb-1">返佣金额</div>
|
|
|
|
|
|
<div class="text-base font-semibold text-green-600">¥{{ formatNumber(item.rebate_amount) }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</van-list>
|
|
|
|
|
|
</van-pull-refresh>
|
2026-05-13 14:43:38 +08:00
|
|
|
|
<EmptyState v-if="!loading && orderList.length === 0" text="暂无订单记录" />
|
2025-12-16 12:33:02 +08:00
|
|
|
|
</van-tab>
|
|
|
|
|
|
<van-tab title="邀请列表" name="invite">
|
|
|
|
|
|
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
2026-05-13 14:43:38 +08:00
|
|
|
|
<van-list v-model:loading="loading" :finished="finished" :finished-text="inviteList.length > 0 ? '没有更多了' : ''" @load="fetchDetail">
|
2025-12-16 12:33:02 +08:00
|
|
|
|
<div class="p-2">
|
2026-05-13 14:43:38 +08:00
|
|
|
|
<div v-for="item in inviteList" :key="item.agent_id"
|
2025-12-16 12:33:02 +08:00
|
|
|
|
class="invite-item mb-3 border-b border-gray-200 pb-3">
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<div class="flex items-center space-x-3 flex-1">
|
|
|
|
|
|
<div class="text-lg font-semibold text-gray-800">{{ item.mobile }}</div>
|
|
|
|
|
|
<span :class="['px-3 py-1 rounded-full text-sm font-medium', getLevelClass(item.level)]">
|
|
|
|
|
|
{{ item.level_name }}代理
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-xs text-gray-500">{{ formatTime(item.create_time) }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</van-list>
|
|
|
|
|
|
</van-pull-refresh>
|
2026-05-13 14:43:38 +08:00
|
|
|
|
<EmptyState v-if="!loading && inviteList.length === 0" text="暂无邀请记录" />
|
2025-12-16 12:33:02 +08:00
|
|
|
|
</van-tab>
|
|
|
|
|
|
</van-tabs>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.subordinate-detail {
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.order-item,
|
|
|
|
|
|
.invite-item {
|
|
|
|
|
|
transition: transform 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.order-item:active,
|
|
|
|
|
|
.invite-item:active {
|
|
|
|
|
|
transform: scale(0.98);
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|