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?.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;
+}