diff --git a/src/admin/users/index.tsx b/src/admin/users/index.tsx index 19222bc..58ac8ab 100644 --- a/src/admin/users/index.tsx +++ b/src/admin/users/index.tsx @@ -1,444 +1,241 @@ -import React, {useState, useEffect, useCallback} from 'react' +import {useEffect, useRef, useState} from 'react' +import Taro, {useDidShow} from '@tarojs/taro' 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"; +import { + Avatar, + Button, + Empty, + InfiniteLoading, + Loading, + PullToRefresh, + SearchBar, + Tag +} from '@nutui/nutui-react-taro' +import type {User} from '@/api/system/user/model' +import {pageUsers} from '@/api/system/user' +import {listUserRole, updateUserRole} from '@/api/system/userRole' -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; -} +const PAGE_SIZE = 10 -// 层级信息接口 -interface LevelInfo { - dealerId: number - dealerName?: string - level: number -} +const AdminUsers = () => { + const [searchValue, setSearchValue] = useState('') -const DealerTeam: React.FC = () => { - const [teamMembers, setTeamMembers] = useState([]) - const {dealerUser} = useDealerUser() - const [dealerId, setDealerId] = useState() - // 层级栈,用于支持返回上一层 - const [levelStack, setLevelStack] = useState([]) + const [users, setUsers] = useState([]) const [loading, setLoading] = useState(false) - // 当前查看的用户名称 - const [currentDealerName, setCurrentDealerName] = useState('') + const [hasMore, setHasMore] = useState(true) + const [page, setPage] = useState(1) + const [total, setTotal] = useState(0) - // 异步加载成员统计数据 - 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 rolesLoadedRef = useRef(false) - 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 - }) - ]) + const reload = async (isRefresh = false, overrideKeywords?: string) => { + if (loading) return - 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)) - } + if (isRefresh) { + setPage(1) + setUsers([]) + setHasMore(true) } - } - - // 获取团队数据 - const fetchTeamData = useCallback(async () => { - if (!dealerUser?.userId && !dealerId) return + setLoading(true) try { - setLoading(true) - console.log(dealerId, 'dealerId>>>>>>>>>') - // 获取团队成员关系 - const refereeResult = await listShopDealerReferee({ - dealerId: dealerId ? dealerId : dealerUser?.userId + const currentPage = isRefresh ? 1 : page + const res = await pageUsers({ + page: currentPage, + limit: PAGE_SIZE, + keywords: overrideKeywords ?? searchValue }) - 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) - + if (res?.list) { + const nextUsers = isRefresh ? res.list : [...users, ...res.list] + setUsers(nextUsers) + setTotal(res.count || 0) + setHasMore(res.list.length === PAGE_SIZE) + setPage(isRefresh ? 2 : currentPage + 1) + } else { + setUsers([]) + setTotal(0) + setHasMore(false) } } catch (error) { - console.error('获取团队数据失败:', error) - Taro.showToast({ - title: '获取团队数据失败', - icon: '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 getUserRoleCodes = (target: User): string[] => { + const fromRoles = target.roles?.map(r => r.roleCode).filter(Boolean) as string[] | undefined + const fromSingle = target.roleCode ? [target.roleCode] : [] + const merged = [...(fromRoles || []), ...fromSingle].filter(Boolean) + return Array.from(new Set(merged)) } - // 一键拨打 - const makePhoneCall = (phone: string) => { - Taro.makePhoneCall({ - phoneNumber: phone, - fail: () => { - Taro.showToast({ - title: '拨打取消', - icon: 'error' - }); + const getPrimaryRoleCode = (target: User): string | undefined => { + const codes = getUserRoleCodes(target) + if (codes.includes('superAdmin')) return 'superAdmin' + if (codes.includes('admin')) return 'admin' + if (codes.includes('dealer')) return 'dealer' + if (codes.includes('user')) return 'user' + return codes[0] + } + + const renderRoleTag = (target: User) => { + const code = getPrimaryRoleCode(target) + if (code === 'superAdmin') return 超级管理员 + if (code === 'admin') return 管理员 + if (code === 'dealer') return 业务员 + if (code === 'user') return 注册会员 + return 未知 + } + + const toggleRole = async (target: User) => { + const current = getPrimaryRoleCode(target) + const nextRoleName = current === 'dealer' ? '注册会员' : '业务员' + + const confirmRes = await Taro.showModal({ + title: '确认切换角色', + content: `确定将该用户切换为「${nextRoleName}」吗?` + }) + if (!confirmRes.confirm) return + + try { + const userId = target.userId + if (!userId) return + + const roles = await listUserRole({userId}) + const candidate = roles?.find(r => r.roleCode === 'dealer' || r.roleCode === 'user') || roles?.[0] + + if (candidate) { + await updateUserRole({ + ...candidate, + roleId: 1935 + }) + } else { + await updateUserRole({ + userId, + roleId: 1915 + }) } - }); - }; - // 别名备注 - 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' - }); - } - } + Taro.showToast({title: '切换成功', icon: 'success'}) + await reload(true) + } catch (error) { + console.error('切换角色失败:', error) + Taro.showToast({title: '切换失败', icon: 'error'}) + } + } + + const handleSearch = (value: string) => { + setSearchValue(value) + reload(true, undefined).then() + } + + const loadMore = async () => { + if (!loading && hasMore) { + await reload(false) + } + } + + useDidShow(() => { + const init = async () => { + try { + await reload(true) + } 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]) + init().then() + }) - // 初始化当前用户名称 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)}]} - /> - - ) - } + if (!rolesLoadedRef.current) return + reload(true).then() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [searchValue]) return ( - <> - {renderHeader()} + - {loading ? ( - - 加载中... - - ) : teamMembers.length > 0 ? ( - renderOverview() - ) : ( - - + + + + + {total > 0 && ( + + 共找到 {total} 个成员 )} - navTo(`/dealer/qrcode/index`, true)}/> - + reload(true)} headHeight={60}> + + {users.length === 0 && !loading ? ( + + + + ) : ( + + + 加载中... + + } + loadMoreText={ + + {users.length === 0 ? '暂无数据' : '没有更多了'} + + } + > + {users.map((item, index) => { + const displayName = item.alias || item.nickname || item.realName || item.username || `用户${item.userId || ''}` + const phone = item.phone || item.mobile || '-' + const primaryRole = getPrimaryRoleCode(item) + const toggleText = primaryRole === 'dealer' ? '设为注册会员' : '设为业务员' + + return ( + + + + + + {displayName} + {renderRoleTag(item)} + + + UID:{item.userId || '-'} · 手机:{phone} + + + + + + + + + ) + })} + + )} + + + ) } -export default DealerTeam; +export default AdminUsers diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts index fa36f86..730a699 100644 --- a/src/api/system/role/index.ts +++ b/src/api/system/role/index.ts @@ -10,7 +10,7 @@ import {SERVER_API_URL} from "@/utils/server"; export async function pageRoles(params: RoleParam) { const res = await request.get>>( SERVER_API_URL + '/system/role/page', - { params } + params ); if (res.code === 0) { return res.data; @@ -24,9 +24,7 @@ export async function pageRoles(params: RoleParam) { export async function listRoles(params?: RoleParam) { const res = await request.get>( SERVER_API_URL + '/system/role', - { - params - } + params ); if (res.code === 0 && res.data) { return res.data;