diff --git a/frontend/admin-web/src/app/(dashboard)/users/[id]/page.tsx b/frontend/admin-web/src/app/(dashboard)/users/[id]/page.tsx index 96705ac5..6ce2682f 100644 --- a/frontend/admin-web/src/app/(dashboard)/users/[id]/page.tsx +++ b/frontend/admin-web/src/app/(dashboard)/users/[id]/page.tsx @@ -15,6 +15,7 @@ import { useWalletLedger, useAuthorizationDetail, } from '@/hooks/useUserDetailPage'; +import { userDetailService } from '@/services/userDetailService'; import type { ReferralNode, PlantingLedgerItem, @@ -139,7 +140,8 @@ export default function UserDetailPage() { const [treeRootUser, setTreeRootUser] = useState(accountSequence); const [plantingPage, setPlantingPage] = useState(1); const [walletPage, setWalletPage] = useState(1); - const [showDirectReferrals, setShowDirectReferrals] = useState(false); // 默认收起直推下级 + // 存储已展开节点的状态:key 是节点 accountSequence,value 是其子节点数组(null 表示未加载) + const [expandedNodes, setExpandedNodes] = useState>({}); // 获取用户完整信息 const { data: userDetail, isLoading: detailLoading, error: detailError } = useUserFullDetail(accountSequence); @@ -167,6 +169,37 @@ export default function UserDetailPage() { 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'); @@ -370,72 +403,18 @@ export default function UserDetailPage() { )} - {/* 当前用户 */} -
-
- - {referralTree.currentUser.accountSequence} - - - {referralTree.currentUser.nickname || '未设置'} - - - 本人认种: {formatNumber(referralTree.currentUser.personalAdoptions)} - - - 引荐: {formatNumber(referralTree.currentUser.directReferralCount)} - -
- {/* 展开/收起直推下级按钮 */} - {referralTree.directReferrals.length > 0 && ( - - )} + {/* 当前用户及其递归下级 */} +
+
- {/* 引荐用户 - 默认收起,点击展开 */} - {referralTree.directReferrals.length > 0 && showDirectReferrals && ( -
-
-
- 引荐用户 ({referralTree.directReferrals.length}) -
-
- {referralTree.directReferrals.map((referral) => ( - - ))} -
-
- )} - {referralTree.directReferrals.length === 0 && referralTree.ancestors.length === 0 && (
暂无引荐关系
)} @@ -810,3 +789,87 @@ export default function UserDetailPage() { ); } + +/** + * 递归渲染引荐节点 + */ +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) => ( + + ))} +
+
+ )} +
+ ); +} diff --git a/frontend/admin-web/src/app/(dashboard)/users/[id]/user-detail.module.scss b/frontend/admin-web/src/app/(dashboard)/users/[id]/user-detail.module.scss index c4722d68..f11217e4 100644 --- a/frontend/admin-web/src/app/(dashboard)/users/[id]/user-detail.module.scss +++ b/frontend/admin-web/src/app/(dashboard)/users/[id]/user-detail.module.scss @@ -425,6 +425,40 @@ font-weight: $font-weight-medium; } +// 递归节点组件样式 +.referralTree__currentWrapper { + @include flex-column; + align-items: center; + width: 100%; +} + +.referralTree__nodeItem { + @include flex-column; + align-items: center; +} + +.referralTree__nodeContent { + @include flex-column; + align-items: center; +} + +.referralTree__children { + @include flex-column; + align-items: center; + margin-top: $spacing-sm; + padding-left: $spacing-xl; + border-left: 2px dashed $border-color; + width: 100%; +} + +.referralTree__childrenGrid { + display: flex; + flex-wrap: wrap; + gap: $spacing-md; + justify-content: center; + margin-top: $spacing-sm; +} + .referralTree__directReferrals { @include flex-column; align-items: center;