diff --git a/src/admin/users/index.config.ts b/src/admin/users/index.config.ts new file mode 100644 index 0000000..610a31b --- /dev/null +++ b/src/admin/users/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '成员管理' +}) diff --git a/src/admin/users/index.tsx b/src/admin/users/index.tsx index e69de29..19222bc 100644 --- a/src/admin/users/index.tsx +++ b/src/admin/users/index.tsx @@ -0,0 +1,444 @@ +import React, {useState, useEffect, useCallback} from 'react' +import {View, Text} from '@tarojs/components' +import {Phone, Edit, Message} 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' +import {pageShopDealerOrder} from '@/api/shop/shopDealerOrder' +import type {ShopDealerReferee} from '@/api/shop/shopDealerReferee/model' +import FixedButton from "@/components/FixedButton"; +import navTo from "@/utils/common"; +import {updateUser} from "@/api/system/user"; + +interface TeamMemberWithStats extends ShopDealerReferee { + name?: string + avatar?: string + nickname?: string; + alias?: string; + phone?: string; + orderCount?: number + commission?: string + status?: 'active' | 'inactive' + subMembers?: number + joinTime?: string + dealerAvatar?: string; + dealerName?: string; + dealerPhone?: string; +} + +// 层级信息接口 +interface LevelInfo { + dealerId: number + dealerName?: string + level: number +} + +const DealerTeam: React.FC = () => { + const [teamMembers, setTeamMembers] = useState([]) + const {dealerUser} = useDealerUser() + const [dealerId, setDealerId] = useState() + // 层级栈,用于支持返回上一层 + const [levelStack, setLevelStack] = useState([]) + const [loading, setLoading] = useState(false) + // 当前查看的用户名称 + const [currentDealerName, setCurrentDealerName] = useState('') + + // 异步加载成员统计数据 + 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 + + try { + setLoading(true) + console.log(dealerId, 'dealerId>>>>>>>>>') + // 获取团队成员关系 + const refereeResult = await listShopDealerReferee({ + dealerId: dealerId ? dealerId : dealerUser?.userId + }) + + if (refereeResult) { + console.log('团队成员原始数据:', refereeResult) + // 处理团队成员数据 + const processedMembers: TeamMemberWithStats[] = refereeResult.map(member => ({ + ...member, + name: `${member.userId}`, + orderCount: 0, + commission: '0.00', + status: 'active' as const, + subMembers: 0, + joinTime: member.createTime + })) + + // 先显示基础数据,然后异步加载详细统计 + setTeamMembers(processedMembers) + setLoading(false) + + // 异步加载每个成员的详细统计数据 + loadMemberStats(processedMembers) + + } + } catch (error) { + console.error('获取团队数据失败:', error) + Taro.showToast({ + title: '获取团队数据失败', + icon: 'error' + }) + } finally { + setLoading(false) + } + }, [dealerUser?.userId, dealerId]) + + // 查看下级成员 + const getNextUser = (item: TeamMemberWithStats) => { + // 检查层级限制:最多只能查看2层(levelStack.length >= 1 表示已经是第2层了) + if (levelStack.length >= 1) { + return + } + + // 如果没有下级成员,不允许点击 + if (!item.subMembers || item.subMembers === 0) { + return + } + + console.log('点击用户:', item.userId, item.name) + + // 将当前层级信息推入栈中 + const currentLevel: LevelInfo = { + dealerId: dealerId || dealerUser?.userId || 0, + dealerName: currentDealerName || (dealerId ? '上级' : dealerUser?.realName || '我'), + level: levelStack.length + } + setLevelStack(prev => [...prev, currentLevel]) + + // 切换到下级 + setDealerId(item.userId) + setCurrentDealerName(item.nickname || item.dealerName || `用户${item.userId}`) + } + + // 返回上一层 + const goBack = () => { + if (levelStack.length === 0) { + // 如果栈为空,返回首页或上一页 + Taro.navigateBack() + return + } + + // 从栈中弹出上一层信息 + const prevLevel = levelStack[levelStack.length - 1] + setLevelStack(prev => prev.slice(0, -1)) + + if (prevLevel.dealerId === (dealerUser?.userId || 0)) { + // 返回到根层级 + setDealerId(undefined) + setCurrentDealerName('') + } else { + setDealerId(prevLevel.dealerId) + setCurrentDealerName(prevLevel.dealerName || '') + } + } + + // 一键拨打 + const makePhoneCall = (phone: string) => { + Taro.makePhoneCall({ + phoneNumber: phone, + fail: () => { + Taro.showToast({ + title: '拨打取消', + icon: 'error' + }); + } + }); + }; + + // 别名备注 + const editAlias = (item: any, index: number) => { + Taro.showModal({ + title: '备注', + // @ts-ignore + editable: true, + placeholderText: '真实姓名', + content: item.alias || '', + success: async (res: any) => { + if (res.confirm && res.content !== undefined) { + try { + // 更新跟进情况 + await updateUser({ + userId: item.userId, + alias: res.content.trim() + }); + teamMembers[index].alias = res.content.trim() + setTeamMembers(teamMembers) + } catch (error) { + console.error('备注失败:', error); + Taro.showToast({ + title: '备注失败,请重试', + icon: 'error' + }); + } + } + } + }); + }; + + // 发送消息 + const sendMessage = (item: TeamMemberWithStats) => { + return navTo(`/user/chat/message/add?id=${item.userId}`, true) + } + + // 监听数据变化,获取团队数据 + useEffect(() => { + if (dealerUser?.userId || dealerId) { + fetchTeamData().then() + } + }, [fetchTeamData]) + + // 初始化当前用户名称 + useEffect(() => { + if (!dealerId && dealerUser?.realName && !currentDealerName) { + setCurrentDealerName(dealerUser.realName) + } + }, [dealerUser, dealerId, currentDealerName]) + + const renderMemberItem = (member: TeamMemberWithStats, index: number) => { + // 判断是否可以点击:有下级成员且未达到层级限制 + 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 ( + getNextUser(member)} + > + + + + + + + {member.alias ? {member.alias} : + {member.nickname}} + {/*别名备注*/} + { + e.stopPropagation() + editAlias(member, index) + }}/> + {/*发送消息*/} + { + e.stopPropagation() + sendMessage(member) + }}/> + + + {/* 显示手机号(仅本级可见) */} + {showPhone && member.phone && ( + { + e.stopPropagation(); + makePhoneCall(member.phone || ''); + }}> + {member.phone} + + + )} + + + + UID:{member.userId} + + + 加入时间:{member.joinTime} + + + + + + + + 订单数 + + {isStatsLoading ? '-' : member.orderCount} + + + + 贡献佣金 + + {isStatsLoading ? '-' : `¥${member.commission}`} + + + + 团队成员 + + {isStatsLoading ? '-' : (member.subMembers || 0)} + + + + + ) + } + + const renderOverview = () => ( + + + 我的团队成员 + 成员数:{teamMembers.length} + + {teamMembers.map(renderMemberItem)} + + ) + + // 渲染顶部导航栏 + const renderHeader = () => { + if (levelStack.length === 0) return null + + return ( + + + + + {currentDealerName}的团队成员 + + + + + + ) + } + + if (!dealerUser) { + return ( + + navTo(`/dealer/apply/add`, true)}]} + /> + + ) + } + + return ( + <> + {renderHeader()} + + {loading ? ( + + 加载中... + + ) : teamMembers.length > 0 ? ( + renderOverview() + ) : ( + + + + )} + + navTo(`/dealer/qrcode/index`, true)}/> + + ) +} + +export default DealerTeam;