From e2b2c17d384d37cacd79a999d1fa1f3f77e59f81 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 8 Jan 2026 05:12:28 -0800 Subject: [PATCH] =?UTF-8?q?feat(admin-web):=20=E7=94=A8=E6=88=B7=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E5=BC=95=E8=8D=90=E5=85=B3=E7=B3=BB=E6=A0=91?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引荐关系节点显示团队认种量:本人认种 / 团队认种 - 无上级引荐人时显示"总部"节点 - 角色标签优化:社区权益→部门权益,区域→部门名称/城市名称 - 后端 ReferralNode 添加 teamAdoptionCount 字段 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../api/controllers/user-detail.controller.ts | 7 +- .../src/api/dto/response/user-detail.dto.ts | 1 + .../user-detail-query.repository.ts | 1 + .../user-detail-query.repository.impl.ts | 20 ++-- .../src/app/(dashboard)/users/[id]/page.tsx | 92 ++++++++++++------- .../users/[id]/user-detail.module.scss | 23 +++++ .../admin-web/src/types/userDetail.types.ts | 1 + 7 files changed, 104 insertions(+), 41 deletions(-) diff --git a/backend/services/admin-service/src/api/controllers/user-detail.controller.ts b/backend/services/admin-service/src/api/controllers/user-detail.controller.ts index cdf9a294..ae3710d2 100644 --- a/backend/services/admin-service/src/api/controllers/user-detail.controller.ts +++ b/backend/services/admin-service/src/api/controllers/user-detail.controller.ts @@ -133,18 +133,21 @@ export class UserDetailController { } // 获取引荐信息和实时统计 - const [referralInfo, personalAdoptionCount, directReferralCount] = await Promise.all([ + const [referralInfo, personalAdoptionCount, directReferralCount, teamStats] = await Promise.all([ this.userDetailRepository.getReferralInfo(accountSequence), this.userDetailRepository.getPersonalAdoptionCount(accountSequence), this.userDetailRepository.getDirectReferralCount(accountSequence), + this.userDetailRepository.getBatchUserStats([accountSequence]), ]); + const currentUserStats = teamStats.get(accountSequence); const currentUser: ReferralNodeDto = { accountSequence: user.accountSequence, userId: user.userId.toString(), nickname: user.nickname, avatar: user.avatarUrl, personalAdoptions: personalAdoptionCount, + teamAdoptions: currentUserStats?.teamAdoptionCount || 0, depth: referralInfo?.depth || 0, directReferralCount: directReferralCount, isCurrentUser: true, @@ -165,6 +168,7 @@ export class UserDetailController { nickname: node.nickname, avatar: node.avatarUrl, personalAdoptions: node.personalAdoptionCount, + teamAdoptions: node.teamAdoptionCount, depth: node.depth, directReferralCount: node.directReferralCount, })); @@ -179,6 +183,7 @@ export class UserDetailController { nickname: node.nickname, avatar: node.avatarUrl, personalAdoptions: node.personalAdoptionCount, + teamAdoptions: node.teamAdoptionCount, depth: node.depth, directReferralCount: node.directReferralCount, })); diff --git a/backend/services/admin-service/src/api/dto/response/user-detail.dto.ts b/backend/services/admin-service/src/api/dto/response/user-detail.dto.ts index 96b30052..30b3602a 100644 --- a/backend/services/admin-service/src/api/dto/response/user-detail.dto.ts +++ b/backend/services/admin-service/src/api/dto/response/user-detail.dto.ts @@ -69,6 +69,7 @@ export class ReferralNodeDto { nickname!: string | null; avatar!: string | null; personalAdoptions!: number; + teamAdoptions!: number; // 团队认种量 depth!: number; directReferralCount!: number; isCurrentUser?: boolean; diff --git a/backend/services/admin-service/src/domain/repositories/user-detail-query.repository.ts b/backend/services/admin-service/src/domain/repositories/user-detail-query.repository.ts index fda72c42..d4d8d011 100644 --- a/backend/services/admin-service/src/domain/repositories/user-detail-query.repository.ts +++ b/backend/services/admin-service/src/domain/repositories/user-detail-query.repository.ts @@ -25,6 +25,7 @@ export interface ReferralNode { nickname: string | null; avatarUrl: string | null; personalAdoptionCount: number; + teamAdoptionCount: number; // 团队认种量(包括本人和所有下级) depth: number; directReferralCount: number; } diff --git a/backend/services/admin-service/src/infrastructure/persistence/repositories/user-detail-query.repository.impl.ts b/backend/services/admin-service/src/infrastructure/persistence/repositories/user-detail-query.repository.impl.ts index bdcc0d94..652cab72 100644 --- a/backend/services/admin-service/src/infrastructure/persistence/repositories/user-detail-query.repository.impl.ts +++ b/backend/services/admin-service/src/infrastructure/persistence/repositories/user-detail-query.repository.impl.ts @@ -82,14 +82,14 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository }), ]); - // 实时统计:获取每个祖先的认种数量和直推数量 - const accountSequences = users.map(u => u.accountSequence); - const [adoptionCounts, directReferralCounts] = await Promise.all([ + // 实时统计:获取每个祖先的认种数量、团队认种量和直推数量 + const userAccountSequences = users.map(u => u.accountSequence); + const [adoptionCounts, directReferralCounts, teamStats] = await Promise.all([ // 统计每个用户的认种订单数量(状态为 MINING_ENABLED) this.prisma.plantingOrderQueryView.groupBy({ by: ['accountSequence'], where: { - accountSequence: { in: accountSequences }, + accountSequence: { in: userAccountSequences }, status: 'MINING_ENABLED', }, _count: { id: true }, @@ -100,6 +100,8 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository where: { referrerId: { in: ancestorIds } }, _count: { userId: true }, }), + // 获取团队认种量 + this.getBatchUserStats(userAccountSequences), ]); const adoptionCountMap = new Map(adoptionCounts.map(a => [a.accountSequence, a._count.id])); @@ -116,12 +118,14 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository return ancestorIds.map((id, index) => { const user = userMap.get(id.toString()); const ref = referralMap.get(id.toString()); + const stats = teamStats.get(user?.accountSequence || ''); return { userId: id, accountSequence: user?.accountSequence || '', nickname: user?.nickname || null, avatarUrl: user?.avatarUrl || null, personalAdoptionCount: adoptionCountMap.get(user?.accountSequence || '') || 0, + teamAdoptionCount: stats?.teamAdoptionCount || 0, depth: ref?.depth || index, directReferralCount: directCountMap.get(id.toString()) || 0, }; @@ -159,9 +163,9 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository }, }); - // 实时统计:获取每个用户的认种数量和直推数量 + // 实时统计:获取每个用户的认种数量、团队认种量和直推数量 const userAccountSequences = directReferrals.map(r => r.accountSequence); - const [adoptionCounts, directReferralCounts] = await Promise.all([ + const [adoptionCounts, directReferralCounts, teamStats] = await Promise.all([ // 统计每个用户的认种订单数量(状态为 MINING_ENABLED) this.prisma.plantingOrderQueryView.groupBy({ by: ['accountSequence'], @@ -177,6 +181,8 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository where: { referrerId: { in: userIds } }, _count: { userId: true }, }), + // 获取团队认种量 + this.getBatchUserStats(userAccountSequences), ]); const adoptionCountMap = new Map(adoptionCounts.map(a => [a.accountSequence, a._count.id])); @@ -189,12 +195,14 @@ export class UserDetailQueryRepositoryImpl implements IUserDetailQueryRepository return directReferrals.map((ref) => { const user = userMap.get(ref.userId.toString()); + const stats = teamStats.get(ref.accountSequence); return { userId: ref.userId, accountSequence: ref.accountSequence, nickname: user?.nickname || null, avatarUrl: user?.avatarUrl || null, personalAdoptionCount: adoptionCountMap.get(ref.accountSequence) || 0, + teamAdoptionCount: stats?.teamAdoptionCount || 0, depth: ref.depth, directReferralCount: directCountMap.get(ref.userId.toString()) || 0, }; 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 b1308f9c..01444ac3 100644 --- a/frontend/admin-web/src/app/(dashboard)/users/[id]/page.tsx +++ b/frontend/admin-web/src/app/(dashboard)/users/[id]/page.tsx @@ -82,14 +82,31 @@ const plantingStatusLabels: Record = { }; const roleTypeLabels: Record = { - COMMUNITY: '社区权益', - COMMUNITY_PARTNER: '社区权益', + 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: '已授权', @@ -394,33 +411,43 @@ export default function UserDetailPage() { ) : referralTree ? (
{/* 向上的引荐人链 */} - {referralTree.ancestors.length > 0 && ( -
-
引荐人链 (向上)
-
- {referralTree.ancestors.map((ancestor, index) => ( -
- - {index < referralTree.ancestors.length - 1 && ( -
- )} -
- ))} -
-
-
- )} +
+
引荐人链 (向上)
+ {referralTree.ancestors.length > 0 ? ( + <> +
+ {referralTree.ancestors.map((ancestor, index) => ( +
+ + {index < referralTree.ancestors.length - 1 && ( +
+ )} +
+ ))} +
+
+ + ) : ( + <> +
+ 总部 + 顶级用户,无上级引荐人 +
+
+ + )} +
{/* 当前用户及其递归下级 */}
@@ -434,9 +461,6 @@ export default function UserDetailPage() { />
- {referralTree.directReferrals.length === 0 && referralTree.ancestors.length === 0 && ( -
暂无引荐关系
- )}
) : (
暂无引荐关系数据
@@ -699,7 +723,7 @@ export default function UserDetailPage() {
-

区域: {role.regionName} ({role.regionCode})

+

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

显示头衔: {role.displayTitle}

权益状态: @@ -945,7 +969,7 @@ function ReferralNodeItem({ {node.nickname || '未设置'} - 本人认种: {formatNumber(node.personalAdoptions)} + 本人认种: {formatNumber(node.personalAdoptions)} / 团队认种: {formatNumber(node.teamAdoptions)} {node.directReferralCount > 0 && ( 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 e29c0663..732386aa 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 @@ -480,6 +480,29 @@ color: $text-secondary; } +// 总部节点样式 +.referralTree__headquarters { + @include flex-column; + align-items: center; + padding: $spacing-base $spacing-xl; + background: linear-gradient(135deg, rgba($primary-color, 0.1) 0%, rgba($success-color, 0.1) 100%); + border: 2px solid $primary-color; + border-radius: $border-radius-base; + min-width: 140px; +} + +.referralTree__headquartersLabel { + font-size: $font-size-lg; + font-weight: $font-weight-bold; + color: $primary-color; +} + +.referralTree__headquartersDesc { + font-size: $font-size-xs; + color: $text-secondary; + margin-top: $spacing-xs; +} + // ============================================================================ // 认种信息 Tab // ============================================================================ diff --git a/frontend/admin-web/src/types/userDetail.types.ts b/frontend/admin-web/src/types/userDetail.types.ts index 485b10ef..33181a7f 100644 --- a/frontend/admin-web/src/types/userDetail.types.ts +++ b/frontend/admin-web/src/types/userDetail.types.ts @@ -59,6 +59,7 @@ export interface ReferralNode { nickname: string | null; avatar: string | null; personalAdoptions: number; + teamAdoptions: number; // 团队认种量 depth: number; directReferralCount: number; isCurrentUser?: boolean;