'use client'; import { useState, useCallback, useEffect } from 'react'; import { useParams, useRouter } from 'next/navigation'; import Link from 'next/link'; import { Button } from '@/components/common'; import { PageContainer } from '@/components/layout'; import { cn } from '@/utils/helpers'; import { formatNumber, formatRanking } from '@/utils/formatters'; import { useUserFullDetail, useReferralTree, usePlantingLedger, useWalletLedger, useAuthorizationDetail, } from '@/hooks/useUserDetailPage'; import { userDetailService } from '@/services/userDetailService'; // [2026-02-05] 新增:合同服务 import { contractService, CONTRACT_STATUS_LABELS, type ContractsListResponse } from '@/services/contractService'; // 获取合同状态颜色(内联样式) const getContractStatusStyle = (status: string): React.CSSProperties => { switch (status) { case 'SIGNED': return { color: '#16a34a' }; case 'PENDING': case 'SCROLLED': case 'ACKNOWLEDGED': return { color: '#ca8a04' }; case 'EXPIRED': case 'TIMEOUT': return { color: '#dc2626' }; default: return { color: '#4b5563' }; } }; import type { ReferralNode } from '@/types/userDetail.types'; import { PROVINCE_CODE_NAMES, CITY_CODE_NAMES } from '@/types'; import styles from './user-detail.module.scss'; // Tab 类型 // [2026-02-05] 更新:新增 contracts Tab type TabType = 'referral' | 'planting' | 'wallet' | 'authorization' | 'contracts'; const tabs: { key: TabType; label: string }[] = [ { key: 'referral', label: '引荐关系' }, { key: 'planting', label: '认种信息' }, { key: 'wallet', label: '钱包信息' }, { key: 'authorization', label: '授权信息' }, // [2026-02-05] 新增:合同信息 Tab { key: 'contracts', label: '合同信息' }, ]; // 流水类型标签 const entryTypeLabels: Record = { DEPOSIT: '充值', DEPOSIT_USDT: '绿积分充值', DEPOSIT_BNB: 'BNB充值', WITHDRAW: '提现', WITHDRAW_FROZEN: '提现冻结', WITHDRAW_CONFIRMED: '提现确认', WITHDRAW_CANCELLED: '提现取消', PLANTING_PAYMENT: '认种支付', PLANTING_FROZEN: '认种冻结', PLANTING_DEDUCT: '认种扣款', REWARD_PENDING: '收益待领取', REWARD_SETTLED: '收益结算', REWARD_EXPIRED: '收益过期', TRANSFER_OUT: '转出', TRANSFER_IN: '转入', INTERNAL_TRANSFER: '内部转账', ADMIN_ADJUSTMENT: '管理员调整', SYSTEM_DEDUCT: '系统扣款', FEE: '手续费', }; const assetTypeLabels: Record = { USDT: '绿积分', DST: 'DST', BNB: 'BNB', OG: 'OG', RWAD: 'RWAD', HASHPOWER: '算力', }; const plantingStatusLabels: Record = { CREATED: '已创建', PAID: '已支付', FUND_ALLOCATED: '资金已分配', MINING_ENABLED: '已开始挖矿', CANCELLED: '已取消', EXPIRED: '已过期', }; const roleTypeLabels: Record = { COMMUNITY: '部门权益', COMMUNITY_PARTNER: '部门权益', AUTH_PROVINCE_COMPANY: '省团队', // 授权省公司 = 省团队权益 PROVINCE_COMPANY: '省区域', // 正式省公司 = 省区域权益 AUTH_CITY_COMPANY: '市团队', // 授权市公司 = 市团队权益(40U) CITY_COMPANY: '市区域', // 正式市公司 = 市区域权益(35U+2%算力) }; // 判断是否为社区/部门类型角色 const isCommunityRole = (roleType: string): boolean => { return roleType === 'COMMUNITY' || roleType === 'COMMUNITY_PARTNER'; }; // 判断是否为市级角色 const isCityRole = (roleType: string): boolean => { return roleType === 'AUTH_CITY_COMPANY' || roleType === 'CITY_COMPANY'; }; // 获取区域标签名称 const getRegionLabel = (roleType: string): string => { if (isCommunityRole(roleType)) return '部门名称:'; if (isCityRole(roleType)) return '城市名称:'; return '区域:'; }; const authStatusLabels: Record = { PENDING: '待授权', AUTHORIZED: '已授权', REVOKED: '已撤销', EXPIRED: '已过期', }; const assessmentResultLabels: Record = { NOT_ASSESSED: '未考核', PASSED: '通过', FAILED: '未通过', BYPASSED: '豁免', }; // 权益操作类型标签 const benefitActionLabels: Record = { ACTIVATED: '已激活', RENEWED: '已续期', DEACTIVATED: '已停用', NO_CHANGE: '无变化', }; /** * 将区域代码转换为名称 * @param code 区域代码,如 450000(省)或 451200(市) * @returns 区域名称 */ const getRegionName = (code: string | null | undefined): string => { if (!code) return '-'; // 先尝试查找城市 if (CITY_CODE_NAMES[code]) { return CITY_CODE_NAMES[code]; } // 再尝试查找省份 if (PROVINCE_CODE_NAMES[code]) { return PROVINCE_CODE_NAMES[code]; } // 尝试去掉后4位的0(如 450000 -> 45)查找省份 if (code.length === 6 && code.endsWith('0000')) { const shortCode = code.substring(0, 2); if (PROVINCE_CODE_NAMES[shortCode]) { return PROVINCE_CODE_NAMES[shortCode]; } } return code; }; /** * 用户详情页面 */ export default function UserDetailPage() { const params = useParams(); const router = useRouter(); const accountSequence = params.id as string; const [activeTab, setActiveTab] = useState('referral'); const [treeRootUser, setTreeRootUser] = useState(accountSequence); const [plantingPage, setPlantingPage] = useState(1); const [walletPage, setWalletPage] = useState(1); // 存储已展开节点的状态:key 是节点 accountSequence,value 是其子节点数组(null 表示未加载) const [expandedNodes, setExpandedNodes] = useState>({}); // [2026-02-05] 新增:合同信息状态 const [contractsPage, setContractsPage] = useState(1); const [contractsData, setContractsData] = useState(null); const [contractsLoading, setContractsLoading] = useState(false); // 获取用户完整信息 const { data: userDetail, isLoading: detailLoading, error: detailError } = useUserFullDetail(accountSequence); // 获取推荐关系树(以当前选中的用户为根) const { data: referralTree, isLoading: treeLoading } = useReferralTree(treeRootUser, 'both', 1); // 获取认种分类账 const { data: plantingData, isLoading: plantingLoading } = usePlantingLedger(accountSequence, { page: plantingPage, pageSize: 10, }); // 获取钱包分类账 const { data: walletData, isLoading: walletLoading } = useWalletLedger(accountSequence, { page: walletPage, pageSize: 10, }); // 获取授权信息 const { data: authData, isLoading: authLoading } = useAuthorizationDetail(accountSequence); // 当 referralTree 数据加载完成后,自动展开当前用户的直推下级 useEffect(() => { if (referralTree && referralTree.directReferrals.length > 0) { setExpandedNodes((prev) => ({ ...prev, [referralTree.currentUser.accountSequence]: referralTree.directReferrals, })); } }, [referralTree]); // [2026-02-05] 新增:加载合同数据 useEffect(() => { if (activeTab === 'contracts') { setContractsLoading(true); contractService.getUserContracts(accountSequence, { page: contractsPage, pageSize: 10, }) .then((data) => { setContractsData(data); }) .catch((error) => { console.error('获取合同列表失败:', error); setContractsData(null); }) .finally(() => { setContractsLoading(false); }); } }, [activeTab, accountSequence, contractsPage]); // 切换推荐关系树的根节点 const handleTreeNodeClick = useCallback((node: ReferralNode) => { setTreeRootUser(node.accountSequence); }, []); // 展开/收起节点的下级 const handleToggleNode = useCallback(async (nodeSeq: string, hasChildren: boolean) => { if (!hasChildren) return; // 如果已展开,则收起 if (expandedNodes[nodeSeq] !== undefined) { setExpandedNodes(prev => { const newState = { ...prev }; delete newState[nodeSeq]; return newState; }); return; } // 展开:先标记为 null(加载中),然后获取数据 setExpandedNodes(prev => ({ ...prev, [nodeSeq]: null })); try { const treeData = await userDetailService.getReferralTree(nodeSeq, 'down', 1); setExpandedNodes(prev => ({ ...prev, [nodeSeq]: treeData.directReferrals })); } catch (error) { console.error('获取下级失败:', error); // 加载失败时移除展开状态 setExpandedNodes(prev => { const newState = { ...prev }; delete newState[nodeSeq]; return newState; }); } }, [expandedNodes]); // 返回列表 const handleBack = useCallback(() => { router.push('/users'); }, [router]); // [2026-02-05] 新增:下载合同 PDF const handleDownloadContract = useCallback((orderNo: string) => { const downloadUrl = contractService.getDownloadUrl(orderNo); window.open(downloadUrl, '_blank'); }, []); // 格式化日期 const formatDate = (dateStr: string | null) => { if (!dateStr) return '-'; return new Date(dateStr).toLocaleString('zh-CN'); }; // 格式化金额 const formatAmount = (amount: string | null) => { if (!amount) return '-'; const num = parseFloat(amount); return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 8 }); }; if (detailLoading) { return (
加载中...
); } if (detailError || !userDetail) { return (

加载失败: {(detailError as Error)?.message || '用户不存在'}

); } return (
{/* 返回按钮 */}
{/* 用户基本信息卡片 */}

{userDetail.nickname || '未设置昵称'} {userDetail.status === 'active' ? '正常' : userDetail.status === 'frozen' ? '冻结' : '停用'}

账户序号: {userDetail.accountSequence} 手机号: {userDetail.phoneNumberMasked || '未绑定'} KYC: {userDetail.kycStatus}
注册时间: {formatDate(userDetail.registeredAt)} 最后活跃: {formatDate(userDetail.lastActiveAt)}
{/* 统计卡片 */}
个人认种量 {formatNumber(userDetail.personalAdoptions)}
团队认种量 {formatNumber(userDetail.teamAdoptions)}
团队地址数 {formatNumber(userDetail.teamAddresses)}
龙虎榜排名 {userDetail.ranking ? formatRanking(userDetail.ranking) : '-'}
引荐人数 {formatNumber(userDetail.referralInfo.directReferralCount)}
{/* 引荐人信息 */} {userDetail.referralInfo.referrerSequence && (
引荐人: {userDetail.referralInfo.referrerSequence} {userDetail.referralInfo.referrerNickname && ` (${userDetail.referralInfo.referrerNickname})`} 邀请码: {userDetail.referralInfo.usedReferralCode || '-'} 层级深度: {userDetail.referralInfo.depth}
)}
{/* Tab 切换 */}
{tabs.map((tab) => ( ))}
{/* Tab 内容 */}
{/* 引荐关系 Tab */} {activeTab === 'referral' && (

引荐关系树

{treeRootUser !== accountSequence && ( )}
{treeLoading ? (
加载中...
) : referralTree ? (
{/* 向上的引荐人链 */}
引荐人链 (向上)
{referralTree.ancestors.length > 0 ? ( <> {/* 有上级时显示祖先节点列表 */}
{referralTree.ancestors.map((ancestor, index) => (
{index < referralTree.ancestors.length - 1 && (
)}
))}
) : ( <> {/* 没有上级时显示总部节点 */}
总部
)}
{/* 当前用户及其递归下级 */}
) : (
暂无引荐关系数据
)}
)} {/* 认种信息 Tab */} {activeTab === 'planting' && (
{plantingLoading ? (
加载中...
) : plantingData ? ( <> {/* 认种汇总 */}

认种汇总

总订单数 {formatNumber(plantingData.summary.totalOrders)}
总认种量 {formatNumber(plantingData.summary.totalTreeCount)}
总金额 (绿积分) {formatAmount(plantingData.summary.totalAmount)}
有效认种量 {formatNumber(plantingData.summary.effectiveTreeCount)}
首次认种 {formatDate(plantingData.summary.firstPlantingAt)}
最近认种 {formatDate(plantingData.summary.lastPlantingAt)}
{/* 认种分类账 */}

认种分类账明细

订单号
认种数量
金额
省市
创建时间
支付时间
{plantingData.items.length === 0 ? (
暂无认种记录
) : ( plantingData.items.map((item) => (
{item.orderNo}
{formatNumber(item.treeCount)}
{formatAmount(item.totalAmount)}
{getRegionName(item.selectedProvince)} / {getRegionName(item.selectedCity)}
{formatDate(item.createdAt)}
{formatDate(item.paidAt)}
)) )}
{/* 分页 */} {plantingData.totalPages > 1 && (
第 {plantingPage} / {plantingData.totalPages} 页
)}
) : (
暂无认种数据
)}
)} {/* 钱包信息 Tab */} {activeTab === 'wallet' && (
{walletLoading ? (
加载中...
) : walletData ? ( <> {/* 钱包汇总 */}

钱包汇总

绿积分 可用 {formatAmount(walletData.summary.usdtAvailable)}
绿积分 冻结 {formatAmount(walletData.summary.usdtFrozen)}
待领取收益 {formatAmount(walletData.summary.pendingUsdt)}
可结算收益 {formatAmount(walletData.summary.settleableUsdt)}
已结算收益 {formatAmount(walletData.summary.settledTotalUsdt)}
过期收益 {formatAmount(walletData.summary.expiredTotalUsdt)}
{/* 钱包分类账 */}

钱包分类账明细

流水ID
类型
资产
金额
余额快照
关联订单
备注
时间
{walletData.items.length === 0 ? (
暂无钱包流水
) : ( walletData.items.map((item) => (
{item.entryId}
{entryTypeLabels[item.entryType] || item.entryType}
{assetTypeLabels[item.assetType] || item.assetType}
= 0 ? styles['ledgerTable__cell--positive'] : styles['ledgerTable__cell--negative'] )}> {parseFloat(item.amount) >= 0 ? '+' : ''}{formatAmount(item.amount)}
{formatAmount(item.balanceAfter)}
{item.refOrderId || item.refTxHash || '-'}
{item.memo || '-'}
{formatDate(item.createdAt)}
)) )}
{/* 分页 */} {walletData.totalPages > 1 && (
第 {walletPage} / {walletData.totalPages} 页
)}
) : (
暂无钱包数据
)}
)} {/* 授权信息 Tab */} {activeTab === 'authorization' && (
{authLoading ? (
加载中...
) : authData ? ( <> {/* 授权角色列表 */}

授权角色

{authData.roles.length === 0 ? (
暂无授权角色
) : (
{authData.roles.map((role) => (
{roleTypeLabels[role.roleType] || role.roleType} {authStatusLabels[role.status] || role.status}

{getRegionLabel(role.roleType)} {role.regionName} ({role.regionCode})

显示头衔: {role.displayTitle}

权益状态: {role.benefitActive ? ( 已激活 ) : ( 未激活 )}

初始目标: {formatNumber(role.initialTargetTreeCount)} 棵

月度目标类型: {role.monthlyTargetType}

授权时间: {formatDate(role.authorizedAt)}

))}
)}
{/* 月度考核记录 */}

月度考核记录

{(() => { // 只显示用户实际拥有且未撤销角色的考核记录(按 authorization_id 匹配) const activeRoleIds = new Set( authData.roles .filter(r => r.status !== 'REVOKED') .map(r => r.id) ); const filteredAssessments = authData.assessments.filter( a => activeRoleIds.has(a.authorizationId) ); // 创建角色ID到区域名称的映射,用于显示角色的区域信息 const roleIdToRegion = new Map( authData.roles.map(r => [r.id, r.regionName]) ); return filteredAssessments.length === 0 ? (
暂无考核记录
) : (
考核月份
角色
区域
月度目标/完成
累计目标/完成
结果
区域排名
{filteredAssessments.map((assessment) => (
{assessment.assessmentMonth}
{roleTypeLabels[assessment.roleType] || assessment.roleType}
{roleIdToRegion.get(assessment.authorizationId) || getRegionName(assessment.regionCode)}
{formatNumber(assessment.monthlyCompleted)} / {formatNumber(assessment.monthlyTarget)}
{formatNumber(assessment.cumulativeCompleted)} / {formatNumber(assessment.cumulativeTarget)}
{assessmentResultLabels[assessment.result] || assessment.result}
{assessment.rankingInRegion || '-'} {assessment.isFirstPlace && ' 🥇'}
))}
); })()}
{/* 权益考核记录 */}

权益考核记录

{(() => { // 只显示用户实际拥有且未撤销角色的权益考核记录 const activeRoleIds = new Set( authData.roles .filter(r => r.status !== 'REVOKED') .map(r => r.id) ); const filteredBenefitAssessments = (authData.benefitAssessments || []).filter( a => activeRoleIds.has(a.authorizationId) ); // 创建角色ID到区域名称的映射 const roleIdToRegion = new Map( authData.roles.map(r => [r.id, r.regionName]) ); return filteredBenefitAssessments.length === 0 ? (
暂无权益考核记录
) : (
考核月份
角色
区域
完成/需求
权益操作
权益状态
有效期至
结果
{filteredBenefitAssessments.map((assessment) => (
{assessment.assessmentMonth}
{roleTypeLabels[assessment.roleType] || assessment.roleType}
{roleIdToRegion.get(assessment.authorizationId) || assessment.regionName || getRegionName(assessment.regionCode)}
{formatNumber(assessment.treesCompleted)} / {formatNumber(assessment.treesRequired)}
{benefitActionLabels[assessment.benefitActionTaken] || assessment.benefitActionTaken}
{assessment.previousBenefitStatus ? '有效' : '无效'} → {assessment.newBenefitStatus ? '有效' : '无效'}
{assessment.newValidUntil ? formatDate(assessment.newValidUntil) : '-'}
{assessmentResultLabels[assessment.result] || assessment.result}
))}
); })()}
{/* 系统账户流水(如果有) */} {authData.systemAccountLedger.length > 0 && (

系统账户流水

流水ID
账户类型
流水类型
金额
余额
时间
{authData.systemAccountLedger.map((ledger) => (
{ledger.ledgerId}
{ledger.accountType}
{ledger.entryType}
= 0 ? styles['ledgerTable__cell--positive'] : styles['ledgerTable__cell--negative'] )}> {parseFloat(ledger.amount) >= 0 ? '+' : ''}{formatAmount(ledger.amount)}
{formatAmount(ledger.balanceAfter)}
{formatDate(ledger.createdAt)}
))}
)} ) : (
暂无授权数据
)}
)} {/* [2026-02-05] 新增:合同信息 Tab */} {activeTab === 'contracts' && (
{contractsLoading ? (
加载中...
) : contractsData ? ( <> {/* 合同汇总 */}

合同汇总

合同总数 {formatNumber(contractsData.total)}
已签署 {formatNumber(contractsData.items.filter(c => c.status === 'SIGNED').length)}
待签署 {formatNumber(contractsData.items.filter(c => ['PENDING', 'SCROLLED', 'ACKNOWLEDGED'].includes(c.status)).length)}
{/* 合同列表 */}

合同列表

合同编号
订单号
认种数量
金额
省市
状态
签署时间
操作
{contractsData.items.length === 0 ? (
暂无合同记录
) : ( contractsData.items.map((contract) => (
{contract.contractNo}
{contract.orderNo}
{formatNumber(contract.treeCount)}
{formatAmount(contract.totalAmount.toString())}
{contract.provinceName} / {contract.cityName}
{CONTRACT_STATUS_LABELS[contract.status] || contract.status}
{contract.signedAt ? formatDate(contract.signedAt) : '-'}
{contract.status === 'SIGNED' && contract.signedPdfUrl && ( )}
)) )}
{/* 分页 */} {contractsData.totalPages > 1 && (
第 {contractsPage} / {contractsData.totalPages} 页
)}
) : (
暂无合同数据
)}
)}
); } /** * 递归渲染引荐节点 */ interface ReferralNodeItemProps { node: ReferralNode; isCurrentUser?: boolean; isHighlight?: boolean; expandedNodes: Record; onToggle: (nodeSeq: string, hasChildren: boolean) => void; onClick: (node: ReferralNode) => void; } function ReferralNodeItem({ node, isCurrentUser = false, isHighlight = false, expandedNodes, onToggle, onClick, }: ReferralNodeItemProps) { const hasChildren = node.directReferralCount > 0; const isExpanded = expandedNodes[node.accountSequence] !== undefined; const isLoading = expandedNodes[node.accountSequence] === null; const children = expandedNodes[node.accountSequence] || []; return (
{/* 展开/收起按钮 */} {hasChildren && ( )}
{/* 递归渲染子节点 */} {isExpanded && children.length > 0 && (
{children.map((child) => ( ))}
)}
); }