feat(dealer): 优化业务员申请和团队管理功能
-强制用户手动输入昵称,清空默认微信昵称 - 添加昵称验证逻辑,禁止使用默认昵称 - 优化团队数据加载和展示逻辑 - 添加保存二维码到相册功能 - 调整提现金额门槛为100元
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, {useState, useEffect, useCallback} from 'react'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {Space,Empty, Avatar, Button} from '@nutui/nutui-react-taro'
|
||||
import {Phone} from '@nutui/icons-react-taro'
|
||||
import {Space, Empty, Avatar, Button} from '@nutui/nutui-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {useDealerUser} from '@/hooks/useDealerUser'
|
||||
import {listShopDealerReferee} from '@/api/shop/shopDealerReferee'
|
||||
@@ -41,6 +42,90 @@ const DealerTeam: React.FC = () => {
|
||||
// 当前查看的用户名称
|
||||
const [currentDealerName, setCurrentDealerName] = useState<string>('')
|
||||
|
||||
// 异步加载成员统计数据
|
||||
const loadMemberStats = async (members: TeamMemberWithStats[]) => {
|
||||
// 分批处理,避免过多并发请求
|
||||
const batchSize = 3
|
||||
for (let i = 0; i < members.length; i += batchSize) {
|
||||
const batch = members.slice(i, i + batchSize)
|
||||
|
||||
const batchStats = await Promise.all(
|
||||
batch.map(async (member) => {
|
||||
try {
|
||||
// 并行获取订单统计和下级成员数量
|
||||
const [orderResult, subMembersResult] = await Promise.all([
|
||||
pageShopDealerOrder({
|
||||
page: 1,
|
||||
userId: member.userId
|
||||
}),
|
||||
listShopDealerReferee({
|
||||
dealerId: member.userId,
|
||||
deleted: 0
|
||||
})
|
||||
])
|
||||
|
||||
let orderCount = 0
|
||||
let commission = '0.00'
|
||||
let status: 'active' | 'inactive' = 'inactive'
|
||||
|
||||
if (orderResult?.list) {
|
||||
const orders = orderResult.list
|
||||
orderCount = orders.length
|
||||
commission = orders.reduce((sum, order) => {
|
||||
const levelCommission = member.level === 1 ? order.firstMoney :
|
||||
member.level === 2 ? order.secondMoney :
|
||||
order.thirdMoney
|
||||
return sum + parseFloat(levelCommission || '0')
|
||||
}, 0).toFixed(2)
|
||||
|
||||
// 判断活跃状态(30天内有订单为活跃)
|
||||
const thirtyDaysAgo = new Date()
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
|
||||
const hasRecentOrder = orders.some(order =>
|
||||
new Date(order.createTime || '') > thirtyDaysAgo
|
||||
)
|
||||
status = hasRecentOrder ? 'active' : 'inactive'
|
||||
}
|
||||
|
||||
return {
|
||||
...member,
|
||||
orderCount,
|
||||
commission,
|
||||
status,
|
||||
subMembers: subMembersResult?.length || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`获取成员${member.userId}数据失败:`, error)
|
||||
return {
|
||||
...member,
|
||||
orderCount: 0,
|
||||
commission: '0.00',
|
||||
status: 'inactive' as const,
|
||||
subMembers: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 更新这一批成员的数据
|
||||
setTeamMembers(prevMembers => {
|
||||
const updatedMembers = [...prevMembers]
|
||||
batchStats.forEach(updatedMember => {
|
||||
const index = updatedMembers.findIndex(m => m.userId === updatedMember.userId)
|
||||
if (index !== -1) {
|
||||
updatedMembers[index] = updatedMember
|
||||
}
|
||||
})
|
||||
return updatedMembers
|
||||
})
|
||||
|
||||
// 添加小延迟,避免请求过于密集
|
||||
if (i + batchSize < members.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取团队数据
|
||||
const fetchTeamData = useCallback(async () => {
|
||||
if (!dealerUser?.userId && !dealerId) return
|
||||
@@ -54,6 +139,7 @@ const DealerTeam: React.FC = () => {
|
||||
})
|
||||
|
||||
if (refereeResult) {
|
||||
console.log('团队成员原始数据:', refereeResult)
|
||||
// 处理团队成员数据
|
||||
const processedMembers: TeamMemberWithStats[] = refereeResult.map(member => ({
|
||||
...member,
|
||||
@@ -65,60 +151,12 @@ const DealerTeam: React.FC = () => {
|
||||
joinTime: member.createTime
|
||||
}))
|
||||
|
||||
// 并行获取每个成员的订单统计和下级成员数量
|
||||
const memberStats = await Promise.all(
|
||||
processedMembers.map(async (member) => {
|
||||
try {
|
||||
// 获取订单统计
|
||||
const orderResult = await pageShopDealerOrder({
|
||||
page: 1,
|
||||
userId: member.userId
|
||||
})
|
||||
// 先显示基础数据,然后异步加载详细统计
|
||||
setTeamMembers(processedMembers)
|
||||
setLoading(false)
|
||||
|
||||
// 获取下级成员数量
|
||||
const subMembersResult = await listShopDealerReferee({
|
||||
dealerId: member.userId,
|
||||
deleted: 0
|
||||
})
|
||||
|
||||
let orderCount = 0
|
||||
let commission = '0.00'
|
||||
let status: 'active' | 'inactive' = 'inactive'
|
||||
|
||||
if (orderResult?.list) {
|
||||
const orders = orderResult.list
|
||||
orderCount = orders.length
|
||||
commission = orders.reduce((sum, order) => {
|
||||
const levelCommission = member.level === 1 ? order.firstMoney :
|
||||
member.level === 2 ? order.secondMoney :
|
||||
order.thirdMoney
|
||||
return sum + parseFloat(levelCommission || '0')
|
||||
}, 0).toFixed(2)
|
||||
|
||||
// 判断活跃状态(30天内有订单为活跃)
|
||||
const thirtyDaysAgo = new Date()
|
||||
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
|
||||
const hasRecentOrder = orders.some(order =>
|
||||
new Date(order.createTime || '') > thirtyDaysAgo
|
||||
)
|
||||
status = hasRecentOrder ? 'active' : 'inactive'
|
||||
}
|
||||
|
||||
return {
|
||||
...member,
|
||||
orderCount,
|
||||
commission,
|
||||
status,
|
||||
subMembers: subMembersResult?.length || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`获取成员${member.userId}数据失败:`, error)
|
||||
return member
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
setTeamMembers(memberStats)
|
||||
// 异步加载每个成员的详细统计数据
|
||||
loadMemberStats(processedMembers)
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -198,6 +236,10 @@ const DealerTeam: React.FC = () => {
|
||||
const renderMemberItem = (member: TeamMemberWithStats) => {
|
||||
// 判断是否可以点击:有下级成员且未达到层级限制
|
||||
const canClick = member.subMembers && member.subMembers > 0 && levelStack.length < 1
|
||||
// 判断是否显示手机号:只有本级(levelStack.length === 0)才显示
|
||||
const showPhone = levelStack.length === 0
|
||||
// 判断数据是否还在加载中(初始值都是0或'0.00')
|
||||
const isStatsLoading = member.orderCount === 0 && member.commission === '0.00' && member.subMembers === 0
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -207,47 +249,56 @@ const DealerTeam: React.FC = () => {
|
||||
}`}
|
||||
onClick={() => getNextUser(member)}
|
||||
>
|
||||
<View className="flex items-center mb-3">
|
||||
<Avatar
|
||||
size="40"
|
||||
src={member.avatar}
|
||||
className="mr-3"
|
||||
/>
|
||||
<View className="flex-1">
|
||||
<View className="flex items-center mb-1">
|
||||
<Text className="font-semibold text-gray-800 mr-2">
|
||||
{member.nickname}
|
||||
<View className="flex items-center mb-3">
|
||||
<Avatar
|
||||
size="40"
|
||||
src={member.avatar}
|
||||
className="mr-3"
|
||||
/>
|
||||
<View className="flex-1">
|
||||
<View className="flex items-center justify-between mb-1">
|
||||
<View className="flex items-center">
|
||||
<Text className="font-semibold text-gray-800 mr-2">
|
||||
{member.nickname}
|
||||
</Text>
|
||||
</View>
|
||||
{/* 显示手机号(仅本级可见) */}
|
||||
{showPhone && member.phone && (
|
||||
<Text className="text-sm text-gray-500">
|
||||
{member.phone}
|
||||
<Phone size={12} className="ml-1 text-green-500"/>
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text className="text-xs text-gray-500">
|
||||
加入时间:{member.joinTime}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className="text-xs text-gray-500">
|
||||
加入时间:{member.joinTime}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="grid grid-cols-3 gap-4 text-center">
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">订单数</Text>
|
||||
<Text className="text-sm font-semibold text-blue-600">
|
||||
{isStatsLoading ? '-' : member.orderCount}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">贡献佣金</Text>
|
||||
<Text className="text-sm font-semibold text-green-600">
|
||||
{isStatsLoading ? '-' : `¥${member.commission}`}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">团队成员</Text>
|
||||
<Text className={`text-sm font-semibold ${
|
||||
canClick ? 'text-purple-600' : 'text-gray-400'
|
||||
}`}>
|
||||
{isStatsLoading ? '-' : (member.subMembers || 0)}
|
||||
</Text>
|
||||
</Space>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="grid grid-cols-3 gap-4 text-center">
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">订单数</Text>
|
||||
<Text className="text-sm font-semibold text-blue-600">
|
||||
{member.orderCount}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">贡献佣金</Text>
|
||||
<Text className="text-sm font-semibold text-green-600">
|
||||
¥{member.commission}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<Text className="text-xs text-gray-500">团队成员</Text>
|
||||
<Text className={`text-sm font-semibold ${
|
||||
canClick ? 'text-purple-600' : 'text-gray-400'
|
||||
}`}>
|
||||
{member.subMembers || 0}
|
||||
</Text>
|
||||
</Space>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -287,7 +338,7 @@ const DealerTeam: React.FC = () => {
|
||||
<Space className="flex items-center justify-center">
|
||||
<Empty description="您还不是业务人员" style={{
|
||||
backgroundColor: 'transparent'
|
||||
}} actions={[{ text: '立即申请', onClick: () => navTo(`/dealer/apply/add`,true)}]}
|
||||
}} actions={[{text: '立即申请', onClick: () => navTo(`/dealer/apply/add`, true)}]}
|
||||
/>
|
||||
</Space>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user