feat(mining-admin): display adoption order count in user management

Backend:
- Add personalOrders and teamOrders to adoption stats
- Return order count alongside tree count in user list API

Frontend:
- Add personalAdoptionOrders and teamAdoptionOrders to UserOverview type
- Display format: "树数量(订单数)" e.g. "6(3单)"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-13 01:03:59 -08:00
parent 56ff8290c1
commit 2f3a0f3652
5 changed files with 28 additions and 6 deletions

View File

@ -767,7 +767,8 @@
"Bash(git -C \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\" commit -m \"$\\(cat <<''EOF''\nfix\\(mining-app\\): update splash page theme and fix token refresh\n\n- Update splash_page.dart to orange theme \\(#FF6B00\\) matching other pages\n- Change app name from \"榴莲挖矿\" to \"榴莲生态\"\n- Fix refreshTokenIfNeeded to properly throw on failure instead of\n silently calling logout \\(which caused Riverpod ref errors\\)\n- Clear local storage directly on refresh failure without remote API call\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(python3 -c \" import sys content = sys.stdin.read\\(\\) old = '''''' done # 清空 processed_cdc_events 表(因为 migration 时可能已经消费了一些消息) # 这是事务性幂等消费的关键:重置 Kafka offset 后必须同时清空幂等记录 log_info \"\"Truncating processed_cdc_events tables to allow re-consumption...\"\" for db in \"\"rwa_contribution\"\" \"\"rwa_auth\"\"; do if run_psql \"\"$db\"\" \"\"TRUNCATE TABLE processed_cdc_events;\"\" 2>/dev/null; then log_success \"\"Truncated processed_cdc_events in $db\"\" else log_warn \"\"Could not truncate processed_cdc_events in $db \\(table may not exist yet\\)\"\" fi done log_step \"\"Step 9/18: Starting 2.0 services...\"\"'''''' new = '''''' done # 清空 processed_cdc_events 表(因为 migration 时可能已经消费了一些消息) # 这是事务性幂等消费的关键:重置 Kafka offset 后必须同时清空幂等记录 log_info \"\"Truncating processed_cdc_events tables to allow re-consumption...\"\" for db in \"\"rwa_contribution\"\" \"\"rwa_auth\"\"; do if run_psql \"\"$db\"\" \"\"TRUNCATE TABLE processed_cdc_events;\"\" 2>/dev/null; then log_success \"\"Truncated processed_cdc_events in $db\"\" else log_warn \"\"Could not truncate processed_cdc_events in $db \\(table may not exist yet\\)\"\" fi done log_step \"\"Step 9/18: Starting 2.0 services...\"\"'''''' print\\(content.replace\\(old, new\\)\\) \")",
"Bash(git rm:*)",
"Bash(echo \"请在服务器运行以下命令检查 outbox 事件:\n\ndocker exec -it rwa-postgres psql -U rwa_user -d rwa_contribution -c \"\"\nSELECT id, event_type, aggregate_id, \n payload->>''sourceType'' as source_type,\n payload->>''accountSequence'' as account_seq,\n payload->>''sourceAccountSequence'' as source_account_seq,\n payload->>''bonusTier'' as bonus_tier\nFROM outbox_events \nWHERE payload->>''accountSequence'' = ''D25122900007''\nORDER BY id;\n\"\"\")"
"Bash(echo \"请在服务器运行以下命令检查 outbox 事件:\n\ndocker exec -it rwa-postgres psql -U rwa_user -d rwa_contribution -c \"\"\nSELECT id, event_type, aggregate_id, \n payload->>''sourceType'' as source_type,\n payload->>''accountSequence'' as account_seq,\n payload->>''sourceAccountSequence'' as source_account_seq,\n payload->>''bonusTier'' as bonus_tier\nFROM outbox_events \nWHERE payload->>''accountSequence'' = ''D25122900007''\nORDER BY id;\n\"\"\")",
"Bash(ssh -o ConnectTimeout=10 ceshi@14.215.128.96 'find /home/ceshi/rwadurian/frontend/mining-admin-web -name \"\"*.tsx\"\" -o -name \"\"*.ts\"\" | xargs grep -l \"\"用户管理\\\\|users\"\" 2>/dev/null | head -10')"
],
"deny": [],
"ask": []

View File

@ -103,15 +103,15 @@ export class UsersService {
*/
private async getAdoptionStatsForUsers(
accountSequences: string[],
): Promise<Map<string, { personalCount: number; teamCount: number }>> {
): Promise<Map<string, { personalCount: number; personalOrders: number; teamCount: number; teamOrders: number }>> {
const result = new Map<
string,
{ personalCount: number; teamCount: number }
{ personalCount: number; personalOrders: number; teamCount: number; teamOrders: number }
>();
if (accountSequences.length === 0) return result;
// 获取每个用户的个人认种数量(只统计 MINING_ENABLED 状态)
// 获取每个用户的个人认种数量和订单数(只统计 MINING_ENABLED 状态)
const personalAdoptions = await this.prisma.syncedAdoption.groupBy({
by: ['accountSequence'],
where: {
@ -119,19 +119,22 @@ export class UsersService {
status: 'MINING_ENABLED',
},
_sum: { treeCount: true },
_count: { id: true },
});
for (const stat of personalAdoptions) {
result.set(stat.accountSequence, {
personalCount: stat._sum.treeCount || 0,
personalOrders: stat._count.id || 0,
teamCount: 0,
teamOrders: 0,
});
}
// 确保所有用户都有记录
for (const seq of accountSequences) {
if (!result.has(seq)) {
result.set(seq, { personalCount: 0, teamCount: 0 });
result.set(seq, { personalCount: 0, personalOrders: 0, teamCount: 0, teamOrders: 0 });
}
}
@ -159,10 +162,12 @@ export class UsersService {
status: 'MINING_ENABLED',
},
_sum: { treeCount: true },
_count: { id: true },
});
const stats = result.get(ref.accountSequence);
if (stats) {
stats.teamCount = teamAdoptionStats._sum.treeCount || 0;
stats.teamOrders = teamAdoptionStats._count.id || 0;
}
}
}
@ -883,7 +888,7 @@ export class UsersService {
private formatUserListItem(
user: any,
extra?: {
adoptionStats?: { personalCount: number; teamCount: number };
adoptionStats?: { personalCount: number; personalOrders: number; teamCount: number; teamOrders: number };
referrerInfo?: { nickname: string | null; phone: string } | null;
},
) {
@ -899,7 +904,9 @@ export class UsersService {
// 认种统计
adoption: {
personalAdoptionCount: extra?.adoptionStats?.personalCount || 0,
personalAdoptionOrders: extra?.adoptionStats?.personalOrders || 0,
teamAdoptions: extra?.adoptionStats?.teamCount || 0,
teamAdoptionOrders: extra?.adoptionStats?.teamOrders || 0,
},
// 推荐人信息
referral: user.referral

View File

@ -158,6 +158,11 @@ export default function UsersPage() {
<TreePine className="h-3 w-3 text-green-600" />
<span className="font-mono text-sm">
{formatNumber(user.personalAdoptions ?? 0)}
{(user.personalAdoptionOrders ?? 0) > 0 && (
<span className="text-muted-foreground ml-1">
({user.personalAdoptionOrders})
</span>
)}
</span>
</div>
</TableCell>
@ -167,6 +172,11 @@ export default function UsersPage() {
<Users className="h-3 w-3 text-blue-600" />
<span className="font-mono text-sm">
{formatNumber(user.teamAdoptions ?? 0)}
{(user.teamAdoptionOrders ?? 0) > 0 && (
<span className="text-muted-foreground ml-1">
({user.teamAdoptionOrders})
</span>
)}
</span>
</div>
</TableCell>

View File

@ -27,7 +27,9 @@ function transformUserOverview(backendUser: any): UserOverview {
frozenBalance: '0',
// 认种数据 - 从后端 adoption 字段获取
personalAdoptions: backendUser.adoption?.personalAdoptionCount || 0,
personalAdoptionOrders: backendUser.adoption?.personalAdoptionOrders || 0,
teamAdoptions: backendUser.adoption?.teamAdoptions || 0,
teamAdoptionOrders: backendUser.adoption?.teamAdoptionOrders || 0,
// 推荐人
referrerId: backendUser.referral?.referrerAccountSequence || null,
status: backendUser.status?.toLowerCase() as 'active' | 'frozen' | 'deactivated',

View File

@ -13,7 +13,9 @@ export interface UserOverview {
frozenBalance: string;
// 从 admin-web 复用的字段
personalAdoptions?: number;
personalAdoptionOrders?: number; // 个人认种订单数
teamAdoptions?: number;
teamAdoptionOrders?: number; // 团队认种订单数
teamAddresses?: number;
provincialAdoptions?: {
count: number;