diff --git a/frontend/mining-admin-web/src/app/(dashboard)/users/[accountSequence]/page.tsx b/frontend/mining-admin-web/src/app/(dashboard)/users/[accountSequence]/page.tsx index 75d43e3e..926a389a 100644 --- a/frontend/mining-admin-web/src/app/(dashboard)/users/[accountSequence]/page.tsx +++ b/frontend/mining-admin-web/src/app/(dashboard)/users/[accountSequence]/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useParams } from 'next/navigation'; +import Link from 'next/link'; import { PageHeader } from '@/components/layout/page-header'; import { useUserDetail } from '@/features/users/hooks/use-users'; import { formatDecimal, formatNumber } from '@/lib/utils/format'; @@ -8,9 +9,15 @@ import { formatDateTime } from '@/lib/utils/date'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Skeleton } from '@/components/ui/skeleton'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Badge } from '@/components/ui/badge'; import { ContributionRecordsList } from '@/features/users/components/contribution-records-list'; import { MiningRecordsList } from '@/features/users/components/mining-records-list'; import { TradeOrdersList } from '@/features/users/components/trade-orders-list'; +import { ReferralTree } from '@/features/users/components/referral-tree'; +import { PlantingLedger } from '@/features/users/components/planting-ledger'; +import { WalletLedger } from '@/features/users/components/wallet-ledger'; +import { Users, TreePine, Wallet, Zap, ShoppingCart, Network, Coins } from 'lucide-react'; function UserDetailSkeleton() { return ( @@ -45,62 +52,128 @@ export default function UserDetailPage() { ); } + // 获取状态徽章 + const getStatusBadge = () => { + if (user?.status === 'frozen') { + return 冻结; + } + if (user?.status === 'deactivated') { + return 停用; + } + if (user?.isOnline) { + return 在线; + } + return 正常; + }; + return (
+ {/* 基本信息卡片 */} 基本信息 -
-
-

账户序列

-

{user?.accountSequence}

+
+
+ + + {user?.nickname?.charAt(0) || 'U'} + + {user?.isOnline && ( + + )}
-
-

昵称

-

{user?.nickname || '-'}

+
+
+

{user?.nickname || '未设置昵称'}

+ {getStatusBadge()} +
+
+
+ 账户序列: + {user?.accountSequence} +
+
+ 手机号: + {user?.phoneNumberMasked || user?.phone} +
+
+ KYC状态: + {user?.kycStatus || '未认证'} +
+
-
-

手机号

-

{user?.phone}

+
+ +
+
+

+ 推荐人 +

+ {user?.referrerAccountSequence ? ( + + {user.referrerAccountSequence} + + ) : ( +

-

+ )}
-
-

认种状态

- - {user?.hasAdopted ? '已认种' : '未认种'} - -
-
-

推荐人

-

{user?.referrerAccountSequence || '-'}

-
-
+

直推人数

{formatNumber(user?.directReferralCount)}

-
+

直推认种

{formatNumber(user?.directReferralAdoptedCount)}

-
+

注册时间

-

{formatDateTime(user?.createdAt)}

+

{formatDateTime(user?.registeredAt || user?.createdAt)}

+
+
+ + {/* 认种统计 */} +
+
+

+ 个人认种 +

+

{formatNumber(user?.personalAdoptions ?? 0)}

+
+
+

+ 团队认种 +

+

{formatNumber(user?.teamAdoptions ?? 0)}

+
+
+

团队地址

+

{formatNumber(user?.teamAddresses ?? 0)}

+
+
+

龙虎榜排名

+

+ {user?.ranking ? `#${user.ranking}` : '-'} +

+ {/* 算力构成卡片 */} - 算力构成 + + + 算力构成 +
@@ -128,45 +201,102 @@ export default function UserDetailPage() { {formatDecimal(user?.contributions?.teamBonus, 4)}
- 有效算力 - {formatDecimal(user?.effectiveContribution, 4)} + 有效算力 (贡献值) + {formatDecimal(user?.effectiveContribution, 4)}
+ {/* 余额卡片 */}
- -

挖矿余额

-

{formatDecimal(user?.miningBalance, 4)}

+ +
+
+

+ 挖矿余额 +

+

+ {formatDecimal(user?.miningBalance, 4)} +

+
+
- -

交易余额

-

{formatDecimal(user?.tradingBalance, 4)}

+ +
+
+

+ 交易余额 +

+

+ {formatDecimal(user?.tradingBalance, 4)} +

+
+
- -

冻结余额

-

{formatDecimal(user?.frozenBalance, 4)}

+ +
+
+

冻结余额

+

+ {formatDecimal(user?.frozenBalance, 4)} +

+
+
+ {/* Tab 区域 */} - - 算力记录 - 挖矿记录 - 交易订单 + + + + 算力记录 + + + + 引荐关系 + + + + 认种信息 + + + + 钱包信息 + + + + 挖矿记录 + + + + 交易订单 + + + + + + + + + + + + + diff --git a/frontend/mining-admin-web/src/app/(dashboard)/users/page.tsx b/frontend/mining-admin-web/src/app/(dashboard)/users/page.tsx index 05b5a1ae..8c1964cb 100644 --- a/frontend/mining-admin-web/src/app/(dashboard)/users/page.tsx +++ b/frontend/mining-admin-web/src/app/(dashboard)/users/page.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import Link from 'next/link'; +import Image from 'next/image'; import { PageHeader } from '@/components/layout/page-header'; import { useUsers } from '@/features/users/hooks/use-users'; import { formatDecimal, formatNumber } from '@/lib/utils/format'; @@ -10,8 +11,10 @@ import { Card, CardContent } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; -import { Search, ChevronLeft, ChevronRight, Eye } from 'lucide-react'; +import { Search, ChevronLeft, ChevronRight, Eye, Users, TreePine } from 'lucide-react'; import { Skeleton } from '@/components/ui/skeleton'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Badge } from '@/components/ui/badge'; export default function UsersPage() { const [keyword, setKeyword] = useState(''); @@ -32,9 +35,23 @@ export default function UsersPage() { } }; + // 获取状态徽章样式 + const getStatusBadge = (status?: string, isOnline?: boolean) => { + if (status === 'frozen') { + return 冻结; + } + if (status === 'deactivated') { + return 停用; + } + if (isOnline) { + return 在线; + } + return null; + }; + return (
- + @@ -56,70 +73,127 @@ export default function UsersPage() { - - - - 账户序列 - 昵称 - 手机号 - 认种状态 - 有效算力 - 挖矿余额 - 交易余额 - 注册时间 - 操作 - - - - {isLoading ? ( - [...Array(10)].map((_, i) => ( - - {[...Array(9)].map((_, j) => ( - - - - ))} - - )) - ) : data?.items.length === 0 ? ( +
+
+ - - 暂无数据 - + 头像 + 账户序列 + 昵称 + 手机号 + 认种状态 + 个人认种 + 团队认种 + 有效算力 + 挖矿余额 + 推荐人 + 注册时间 + 操作 - ) : ( - data?.items.map((user) => ( - - {user.accountSequence} - {user.nickname || '-'} - {user.phone} - - - {user.hasAdopted ? '已认种' : '未认种'} - - - - {formatDecimal(user.effectiveContribution, 4)} - - {formatDecimal(user.miningBalance, 4)} - {formatDecimal(user.tradingBalance, 4)} - {formatDateTime(user.createdAt)} - - - - + + + {isLoading ? ( + [...Array(10)].map((_, i) => ( + + {[...Array(12)].map((_, j) => ( + + + + ))} + + )) + ) : data?.items.length === 0 ? ( + + + 暂无数据 - )) - )} - -
+ ) : ( + data?.items.map((user) => ( + + {/* 头像 */} + +
+ + + + {user.nickname?.charAt(0) || 'U'} + + + {user.isOnline && ( + + )} +
+
+ {/* 账户序列 */} + {user.accountSequence} + {/* 昵称 */} + +
+ {user.nickname || '-'} + {getStatusBadge(user.status, user.isOnline)} +
+
+ {/* 手机号 */} + {user.phoneNumberMasked || user.phone} + {/* 认种状态 */} + + + {user.hasAdopted ? '已认种' : '未认种'} + + + {/* 个人认种 */} + +
+ + + {formatNumber(user.personalAdoptions ?? 0)} + +
+
+ {/* 团队认种 */} + +
+ + + {formatNumber(user.teamAdoptions ?? 0)} + +
+
+ {/* 有效算力 */} + + {formatDecimal(user.effectiveContribution, 4)} + + {/* 挖矿余额 */} + + {formatDecimal(user.miningBalance, 4)} + + {/* 推荐人 */} + + {user.referrerId || user.referrerAccountSequence || '-'} + + {/* 注册时间 */} + + {formatDateTime(user.createdAt)} + + {/* 操作 */} + + + + + +
+ )) + )} + + +
{data && data.totalPages > 1 && (
diff --git a/frontend/mining-admin-web/src/features/users/api/users.api.ts b/frontend/mining-admin-web/src/features/users/api/users.api.ts index 65afc5ec..05e5ac7b 100644 --- a/frontend/mining-admin-web/src/features/users/api/users.api.ts +++ b/frontend/mining-admin-web/src/features/users/api/users.api.ts @@ -1,5 +1,14 @@ import { apiClient } from '@/lib/api/client'; -import type { UserOverview, UserDetail, ContributionRecord, MiningRecord, TradeOrder } from '@/types/user'; +import type { + UserOverview, + UserDetail, + ContributionRecord, + MiningRecord, + TradeOrder, + ReferralTreeData, + PlantingLedgerResponse, + WalletLedgerResponse, +} from '@/types/user'; import type { PaginatedResponse, PaginationParams } from '@/types/api'; export const usersApi = { @@ -36,4 +45,32 @@ export const usersApi = { const response = await apiClient.get(`/users/${accountSequence}/orders`, { params }); return response.data.data; }, + + // 从 admin-web 复用的 API + getReferralTree: async ( + accountSequence: string, + direction: 'up' | 'down' | 'both' = 'both', + depth: number = 1 + ): Promise => { + const response = await apiClient.get(`/users/${accountSequence}/referral-tree`, { + params: { direction, depth }, + }); + return response.data.data; + }, + + getPlantingLedger: async ( + accountSequence: string, + params: PaginationParams + ): Promise => { + const response = await apiClient.get(`/users/${accountSequence}/planting-ledger`, { params }); + return response.data.data; + }, + + getWalletLedger: async ( + accountSequence: string, + params: PaginationParams + ): Promise => { + const response = await apiClient.get(`/users/${accountSequence}/wallet-ledger`, { params }); + return response.data.data; + }, }; diff --git a/frontend/mining-admin-web/src/features/users/components/planting-ledger.tsx b/frontend/mining-admin-web/src/features/users/components/planting-ledger.tsx new file mode 100644 index 00000000..382d076c --- /dev/null +++ b/frontend/mining-admin-web/src/features/users/components/planting-ledger.tsx @@ -0,0 +1,207 @@ +'use client'; + +import { useState } from 'react'; +import { usePlantingLedger } from '../hooks/use-users'; +import { formatDecimal, formatNumber } from '@/lib/utils/format'; +import { formatDateTime } from '@/lib/utils/date'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Badge } from '@/components/ui/badge'; +import { ChevronLeft, ChevronRight, TreePine, Calendar, DollarSign } from 'lucide-react'; + +interface PlantingLedgerProps { + accountSequence: string; +} + +// 认种状态标签 +const plantingStatusLabels: Record = { + CREATED: '已创建', + PAID: '已支付', + FUND_ALLOCATED: '资金已分配', + MINING_ENABLED: '已开始挖矿', + CANCELLED: '已取消', + EXPIRED: '已过期', +}; + +// 状态对应的样式 +const getStatusVariant = (status: string): 'default' | 'secondary' | 'destructive' | 'outline' => { + switch (status) { + case 'MINING_ENABLED': + case 'PAID': + case 'FUND_ALLOCATED': + return 'default'; + case 'CREATED': + return 'secondary'; + case 'CANCELLED': + case 'EXPIRED': + return 'destructive'; + default: + return 'outline'; + } +}; + +export function PlantingLedger({ accountSequence }: PlantingLedgerProps) { + const [page, setPage] = useState(1); + const pageSize = 10; + + const { data, isLoading } = usePlantingLedger(accountSequence, { page, pageSize }); + + if (isLoading) { + return ( +
+ + + 认种汇总 + + +
+ {[...Array(6)].map((_, i) => ( + + ))} +
+
+
+ + + + + +
+ ); + } + + if (!data) { + return ( + + + 认种信息 + + +

暂无认种数据

+
+
+ ); + } + + return ( +
+ {/* 认种汇总 */} + + + + + 认种汇总 + + + +
+
+

总订单数

+

{formatNumber(data.summary.totalOrders)}

+
+
+

总认种量

+

{formatNumber(data.summary.totalTreeCount)}

+
+
+

总金额

+

{formatDecimal(data.summary.totalAmount, 2)}

+
+
+

有效认种量

+

{formatNumber(data.summary.effectiveTreeCount)}

+
+
+

首次认种

+

+ {data.summary.firstPlantingAt ? formatDateTime(data.summary.firstPlantingAt) : '-'} +

+
+
+

最近认种

+

+ {data.summary.lastPlantingAt ? formatDateTime(data.summary.lastPlantingAt) : '-'} +

+
+
+
+
+ + {/* 认种分类账明细 */} + + + 认种分类账明细 + + + + + + 订单号 + 认种数量 + 金额 + 省市 + 状态 + 创建时间 + 支付时间 + + + + {data.items.length === 0 ? ( + + + 暂无认种记录 + + + ) : ( + data.items.map((item) => ( + + {item.orderNo} + {formatNumber(item.treeCount)} + {formatDecimal(item.totalAmount, 2)} + + {item.selectedProvince || '-'} / {item.selectedCity || '-'} + + + + {plantingStatusLabels[item.status] || item.status} + + + + {formatDateTime(item.createdAt)} + + + {item.paidAt ? formatDateTime(item.paidAt) : '-'} + + + )) + )} + +
+ + {data.totalPages > 1 && ( +
+

+ 共 {formatNumber(data.total)} 条,第 {page} / {data.totalPages} 页 +

+
+ + +
+
+ )} +
+
+
+ ); +} diff --git a/frontend/mining-admin-web/src/features/users/components/referral-tree.tsx b/frontend/mining-admin-web/src/features/users/components/referral-tree.tsx new file mode 100644 index 00000000..207f4ddc --- /dev/null +++ b/frontend/mining-admin-web/src/features/users/components/referral-tree.tsx @@ -0,0 +1,289 @@ +'use client'; + +import { useState, useCallback, useEffect } from 'react'; +import Link from 'next/link'; +import { useReferralTree } from '../hooks/use-users'; +import { usersApi } from '../api/users.api'; +import { formatNumber } from '@/lib/utils/format'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { ChevronDown, ChevronRight, Users, TreePine, Building2 } from 'lucide-react'; +import type { ReferralNode } from '@/types/user'; +import { cn } from '@/lib/utils'; + +interface ReferralTreeProps { + accountSequence: string; +} + +export function ReferralTree({ accountSequence }: ReferralTreeProps) { + const [treeRootUser, setTreeRootUser] = useState(accountSequence); + const [expandedNodes, setExpandedNodes] = useState>({}); + + const { data: referralTree, isLoading } = useReferralTree(treeRootUser, 'both', 1); + + // 当 referralTree 数据加载完成后,自动展开当前用户的直推下级 + useEffect(() => { + if (referralTree && referralTree.directReferrals.length > 0) { + setExpandedNodes((prev) => ({ + ...prev, + [referralTree.currentUser.accountSequence]: referralTree.directReferrals, + })); + } + }, [referralTree]); + + // 切换推荐关系树的根节点 + 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 usersApi.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]); + + if (isLoading) { + return ( + + + 引荐关系 + + +
+ {[...Array(3)].map((_, i) => ( + + ))} +
+
+
+ ); + } + + if (!referralTree) { + return ( + + + 引荐关系 + + +

暂无引荐关系数据

+
+
+ ); + } + + return ( + + + 引荐关系 + {treeRootUser !== accountSequence && ( + + )} + + + {/* 向上的引荐人链 */} +
+

引荐人链 (向上)

+ {referralTree.ancestors.length > 0 ? ( +
+ {referralTree.ancestors.map((ancestor, index) => ( +
+ handleTreeNodeClick(ancestor)} + variant="ancestor" + /> + {index < referralTree.ancestors.length - 1 && ( +
+ +
+ )} +
+ ))} +
+ +
+
+ ) : ( +
+ + 总部 +
+ )} +
+ + {/* 当前用户 */} +
+

当前用户

+ +
+ + {/* 直推下级列表 */} + {referralTree.directReferrals.length > 0 && ( +
+

+ 直推下级 ({referralTree.directReferrals.length}) +

+
+ {referralTree.directReferrals.map((child) => ( + + ))} +
+
+ )} +
+
+ ); +} + +interface ReferralNodeCardProps { + node: ReferralNode; + isCurrentUser?: boolean; + isHighlight?: boolean; + variant?: 'ancestor' | 'current' | 'child'; + expandedNodes?: Record; + onToggle?: (nodeSeq: string, hasChildren: boolean) => void; + onClick?: (node: ReferralNode) => void; + showExpandButton?: boolean; +} + +function ReferralNodeCard({ + node, + isCurrentUser = false, + isHighlight = false, + variant, + expandedNodes = {}, + onToggle, + onClick, + showExpandButton = false, +}: ReferralNodeCardProps) { + const hasChildren = node.directReferralCount > 0; + const isExpanded = expandedNodes[node.accountSequence] !== undefined; + const isLoading = expandedNodes[node.accountSequence] === null; + const children = expandedNodes[node.accountSequence] || []; + + return ( +
+
onClick?.(node)} + > + + + {node.nickname?.charAt(0) || 'U'} + + +
+
+ {node.accountSequence} + {node.nickname || '未设置'} +
+
+ + + 个人: {formatNumber(node.personalAdoptions)} + + + + 团队: {formatNumber(node.teamAdoptions)} + + {node.directReferralCount > 0 && ( + 引荐: {formatNumber(node.directReferralCount)} + )} +
+
+ + {showExpandButton && hasChildren && ( + + )} + + e.stopPropagation()} + > + 查看 + +
+ + {/* 展开的子节点 */} + {isExpanded && children.length > 0 && ( +
+ {children.map((child) => ( + + ))} +
+ )} +
+ ); +} diff --git a/frontend/mining-admin-web/src/features/users/components/wallet-ledger.tsx b/frontend/mining-admin-web/src/features/users/components/wallet-ledger.tsx new file mode 100644 index 00000000..96ff8c9f --- /dev/null +++ b/frontend/mining-admin-web/src/features/users/components/wallet-ledger.tsx @@ -0,0 +1,251 @@ +'use client'; + +import { useState } from 'react'; +import { useWalletLedger } from '../hooks/use-users'; +import { formatDecimal, formatNumber } from '@/lib/utils/format'; +import { formatDateTime } from '@/lib/utils/date'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Skeleton } from '@/components/ui/skeleton'; +import { ChevronLeft, ChevronRight, Wallet, TrendingUp, TrendingDown } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface WalletLedgerProps { + accountSequence: string; +} + +// 流水类型标签 +const entryTypeLabels: Record = { + DEPOSIT: '充值', + DEPOSIT_USDT: '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: '手续费', + MINING_REWARD: '挖矿奖励', + TRADE_BUY: '买入', + TRADE_SELL: '卖出', +}; + +// 资产类型标签 +const assetTypeLabels: Record = { + USDT: '绿积分', + DST: 'DST', + BNB: 'BNB', + OG: 'OG', + RWAD: 'RWAD', + HASHPOWER: '算力', + MINING: '挖矿积分', + TRADING: '交易积分', +}; + +export function WalletLedger({ accountSequence }: WalletLedgerProps) { + const [page, setPage] = useState(1); + const pageSize = 10; + + const { data, isLoading } = useWalletLedger(accountSequence, { page, pageSize }); + + if (isLoading) { + return ( +
+ + + 钱包汇总 + + +
+ {[...Array(6)].map((_, i) => ( + + ))} +
+
+
+ + + + + +
+ ); + } + + if (!data) { + return ( + + + 钱包信息 + + +

暂无钱包数据

+
+
+ ); + } + + return ( +
+ {/* 钱包汇总 */} + + + + + 钱包汇总 + + + +
+
+

绿积分 可用

+

+ {formatDecimal(data.summary.usdtAvailable, 4)} +

+
+
+

绿积分 冻结

+

+ {formatDecimal(data.summary.usdtFrozen, 4)} +

+
+
+

待领取收益

+

+ {formatDecimal(data.summary.pendingUsdt, 4)} +

+
+
+

可结算收益

+

+ {formatDecimal(data.summary.settleableUsdt, 4)} +

+
+
+

已结算收益

+

+ {formatDecimal(data.summary.settledTotalUsdt, 4)} +

+
+
+

过期收益

+

+ {formatDecimal(data.summary.expiredTotalUsdt, 4)} +

+
+
+
+
+ + {/* 钱包分类账明细 */} + + + 钱包分类账明细 + + +
+ + + + 流水ID + 类型 + 资产 + 金额 + 余额快照 + 关联订单 + 备注 + 时间 + + + + {data.items.length === 0 ? ( + + + 暂无钱包流水 + + + ) : ( + data.items.map((item) => { + const amount = parseFloat(item.amount); + const isPositive = amount >= 0; + return ( + + {item.entryId} + + {entryTypeLabels[item.entryType] || item.entryType} + + + {assetTypeLabels[item.assetType] || item.assetType} + + + + {isPositive ? ( + + ) : ( + + )} + {isPositive ? '+' : ''} + {formatDecimal(item.amount, 4)} + + + + {formatDecimal(item.balanceAfter, 4)} + + + {item.refOrderId || item.refTxHash || '-'} + + + {item.memo || '-'} + + + {formatDateTime(item.createdAt)} + + + ); + }) + )} + +
+
+ + {data.totalPages > 1 && ( +
+

+ 共 {formatNumber(data.total)} 条,第 {page} / {data.totalPages} 页 +

+
+ + +
+
+ )} +
+
+
+ ); +} diff --git a/frontend/mining-admin-web/src/features/users/hooks/use-users.ts b/frontend/mining-admin-web/src/features/users/hooks/use-users.ts index 2e6139bd..482c3f78 100644 --- a/frontend/mining-admin-web/src/features/users/hooks/use-users.ts +++ b/frontend/mining-admin-web/src/features/users/hooks/use-users.ts @@ -40,3 +40,32 @@ export function useTradeOrders(accountSequence: string, params: PaginationParams enabled: !!accountSequence, }); } + +// 从 admin-web 复用的 hooks +export function useReferralTree( + accountSequence: string, + direction: 'up' | 'down' | 'both' = 'both', + depth: number = 1 +) { + return useQuery({ + queryKey: ['users', accountSequence, 'referral-tree', direction, depth], + queryFn: () => usersApi.getReferralTree(accountSequence, direction, depth), + enabled: !!accountSequence, + }); +} + +export function usePlantingLedger(accountSequence: string, params: PaginationParams) { + return useQuery({ + queryKey: ['users', accountSequence, 'planting-ledger', params], + queryFn: () => usersApi.getPlantingLedger(accountSequence, params), + enabled: !!accountSequence, + }); +} + +export function useWalletLedger(accountSequence: string, params: PaginationParams) { + return useQuery({ + queryKey: ['users', accountSequence, 'wallet-ledger', params], + queryFn: () => usersApi.getWalletLedger(accountSequence, params), + enabled: !!accountSequence, + }); +} diff --git a/frontend/mining-admin-web/src/types/user.ts b/frontend/mining-admin-web/src/types/user.ts index ef716605..d463b23a 100644 --- a/frontend/mining-admin-web/src/types/user.ts +++ b/frontend/mining-admin-web/src/types/user.ts @@ -1,13 +1,32 @@ export interface UserOverview { accountSequence: number; + accountId?: string; nickname: string; phone: string; + phoneNumberMasked?: string; + avatar?: string | null; hasAdopted: boolean; totalContribution: string; effectiveContribution: string; miningBalance: string; tradingBalance: string; frozenBalance: string; + // 从 admin-web 复用的字段 + personalAdoptions?: number; + teamAdoptions?: number; + teamAddresses?: number; + provincialAdoptions?: { + count: number; + percentage: number; + }; + cityAdoptions?: { + count: number; + percentage: number; + }; + referrerId?: string | null; + ranking?: number | null; + status?: 'active' | 'frozen' | 'deactivated'; + isOnline?: boolean; createdAt: string; } @@ -18,6 +37,17 @@ export interface UserDetail extends UserOverview { teamSize: number; teamAdoptedCount: number; contributions: ContributionBreakdown; + // 从 admin-web 复用的字段 + kycStatus?: string; + registeredAt?: string; + lastActiveAt?: string; + referralInfo?: { + referrerSequence?: string; + referrerNickname?: string; + usedReferralCode?: string; + depth?: number; + directReferralCount?: number; + }; } export interface ContributionBreakdown { @@ -65,3 +95,87 @@ export interface TradeOrder { createdAt: string; updatedAt: string; } + +// 引荐关系节点 +export interface ReferralNode { + accountSequence: string; + nickname: string | null; + avatar?: string | null; + personalAdoptions: number; + teamAdoptions: number; + directReferralCount: number; + isOnline?: boolean; +} + +// 引荐关系树 +export interface ReferralTreeData { + currentUser: ReferralNode; + ancestors: ReferralNode[]; + directReferrals: ReferralNode[]; +} + +// 认种订单 +export interface PlantingOrder { + orderId: string; + orderNo: string; + treeCount: number; + totalAmount: string; + selectedProvince?: string; + selectedCity?: string; + status: string; + createdAt: string; + paidAt?: string; +} + +// 认种汇总 +export interface PlantingSummary { + totalOrders: number; + totalTreeCount: number; + totalAmount: string; + effectiveTreeCount: number; + firstPlantingAt?: string; + lastPlantingAt?: string; +} + +// 认种分类账响应 +export interface PlantingLedgerResponse { + summary: PlantingSummary; + items: PlantingOrder[]; + total: number; + page: number; + pageSize: number; + totalPages: number; +} + +// 钱包流水 +export interface WalletLedgerItem { + entryId: string; + entryType: string; + assetType: string; + amount: string; + balanceAfter: string; + refOrderId?: string; + refTxHash?: string; + memo?: string; + createdAt: string; +} + +// 钱包汇总 +export interface WalletSummary { + usdtAvailable: string; + usdtFrozen: string; + pendingUsdt: string; + settleableUsdt: string; + settledTotalUsdt: string; + expiredTotalUsdt: string; +} + +// 钱包分类账响应 +export interface WalletLedgerResponse { + summary: WalletSummary; + items: WalletLedgerItem[]; + total: number; + page: number; + pageSize: number; + totalPages: number; +}