feat(mining-admin): add totalTrees, separate level/bonus pending display

- Add totalTrees field from syncedAdoption aggregate
- Rename fields: networkLevelPending, networkBonusPending
- Stats card: show level pending and bonus pending separately
- Add new stats card for total trees count
- Price overview: 2-row layout showing all contribution metrics

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-14 23:59:32 -08:00
parent b310fde426
commit 12f8fa67fc
5 changed files with 50 additions and 38 deletions

View File

@ -30,10 +30,11 @@ export class DashboardController {
return { return {
totalUsers: raw.users?.total || 0, totalUsers: raw.users?.total || 0,
adoptedUsers: raw.users?.adopted || 0, adoptedUsers: raw.users?.adopted || 0,
totalTrees: raw.contribution?.totalTrees || 0,
networkEffectiveContribution: raw.contribution?.effectiveContribution || '0', networkEffectiveContribution: raw.contribution?.effectiveContribution || '0',
networkTotalContribution: raw.contribution?.totalContribution || '0', networkTotalContribution: raw.contribution?.totalContribution || '0',
networkPendingContribution: raw.contribution?.teamLevelContribution || '0', networkLevelPending: raw.contribution?.teamLevelContribution || '0',
networkBonusPendingContribution: raw.contribution?.teamBonusContribution || '0', networkBonusPending: raw.contribution?.teamBonusContribution || '0',
totalDistributed: raw.mining?.totalMined || '0', totalDistributed: raw.mining?.totalMined || '0',
totalBurned: raw.mining?.latestDailyStat?.totalBurned || '0', totalBurned: raw.mining?.latestDailyStat?.totalBurned || '0',
circulationPool: raw.trading?.circulationPool?.totalShares || '0', circulationPool: raw.trading?.circulationPool?.totalShares || '0',

View File

@ -112,22 +112,26 @@ export class DashboardService {
* *
*/ */
private async getContributionStats() { private async getContributionStats() {
const accounts = await this.prisma.syncedContributionAccount.aggregate({ const [accounts, systemContributions, adoptionStats] = await Promise.all([
_sum: { this.prisma.syncedContributionAccount.aggregate({
totalContribution: true, _sum: {
effectiveContribution: true, totalContribution: true,
personalContribution: true, effectiveContribution: true,
teamLevelContribution: true, personalContribution: true,
teamBonusContribution: true, teamLevelContribution: true,
}, teamBonusContribution: true,
_count: true, },
}); _count: true,
}),
const systemContributions = this.prisma.syncedSystemContribution.aggregate({
await this.prisma.syncedSystemContribution.aggregate({
_sum: { contributionBalance: true }, _sum: { contributionBalance: true },
_count: true, _count: true,
}); }),
this.prisma.syncedAdoption.aggregate({
_sum: { treeCount: true },
_count: true,
}),
]);
return { return {
totalAccounts: accounts._count, totalAccounts: accounts._count,
@ -143,6 +147,8 @@ export class DashboardService {
systemAccounts: systemContributions._count, systemAccounts: systemContributions._count,
systemContribution: systemContribution:
systemContributions._sum.contributionBalance?.toString() || '0', systemContributions._sum.contributionBalance?.toString() || '0',
totalAdoptions: adoptionStats._count,
totalTrees: adoptionStats._sum.treeCount || 0,
}; };
} }

View File

@ -44,30 +44,33 @@ export function PriceOverview() {
</div> </div>
<div className="mt-6 pt-6 border-t"> <div className="mt-6 pt-6 border-t">
<div className="grid grid-cols-4 gap-4 text-center"> <div className="grid grid-cols-3 gap-4 text-center">
<div>
<p className="text-xs text-muted-foreground"></p>
<p className="text-sm font-medium">{formatCompactNumber(stats?.circulationPool)}</p>
</div>
<div> <div>
<p className="text-xs text-muted-foreground"></p> <p className="text-xs text-muted-foreground"></p>
<p className="text-sm font-medium">{formatCompactNumber(stats?.networkEffectiveContribution)}</p> <p className="text-sm font-medium">{formatCompactNumber(stats?.networkEffectiveContribution)}</p>
</div> </div>
<div> <div>
<p className="text-xs text-muted-foreground"></p> <p className="text-xs text-muted-foreground"></p>
<p className="text-sm font-medium"> <p className="text-sm font-medium">{formatCompactNumber(stats?.networkLevelPending)}</p>
{formatCompactNumber( </div>
String( <div>
Number(stats?.networkPendingContribution || 0) + <p className="text-xs text-muted-foreground"></p>
Number(stats?.networkBonusPendingContribution || 0) <p className="text-sm font-medium">{formatCompactNumber(stats?.networkBonusPending)}</p>
) </div>
)} </div>
</p> <div className="grid grid-cols-3 gap-4 text-center mt-4">
<div>
<p className="text-xs text-muted-foreground"></p>
<p className="text-sm font-medium">{stats?.totalTrees?.toLocaleString() ?? '-'}</p>
</div> </div>
<div> <div>
<p className="text-xs text-muted-foreground"></p> <p className="text-xs text-muted-foreground"></p>
<p className="text-sm font-medium">{stats?.adoptedUsers?.toLocaleString() ?? '-'}</p> <p className="text-sm font-medium">{stats?.adoptedUsers?.toLocaleString() ?? '-'}</p>
</div> </div>
<div>
<p className="text-xs text-muted-foreground"></p>
<p className="text-sm font-medium">{formatCompactNumber(stats?.circulationPool)}</p>
</div>
</div> </div>
</div> </div>
</CardContent> </CardContent>

View File

@ -48,15 +48,16 @@ export function StatsCards() {
{ {
title: '全网算力', title: '全网算力',
value: formatCompactNumber(stats?.networkEffectiveContribution), value: formatCompactNumber(stats?.networkEffectiveContribution),
subValue: `待解锁: ${formatCompactNumber( subValue: `层级待解锁: ${formatCompactNumber(stats?.networkLevelPending)} | 团队待解锁: ${formatCompactNumber(stats?.networkBonusPending)}`,
String(
Number(stats?.networkPendingContribution || 0) +
Number(stats?.networkBonusPendingContribution || 0)
)
)}`,
icon: Activity, icon: Activity,
iconColor: 'text-blue-500', iconColor: 'text-blue-500',
}, },
{
title: '认种树总数',
value: formatNumber(stats?.totalTrees),
icon: Activity,
iconColor: 'text-emerald-500',
},
{ {
title: '已分配积分股', title: '已分配积分股',
value: formatCompactNumber(stats?.totalDistributed), value: formatCompactNumber(stats?.totalDistributed),

View File

@ -1,10 +1,11 @@
export interface DashboardStats { export interface DashboardStats {
totalUsers: number; totalUsers: number;
adoptedUsers: number; adoptedUsers: number;
totalTrees: number;
networkEffectiveContribution: string; networkEffectiveContribution: string;
networkTotalContribution: string; networkTotalContribution: string;
networkPendingContribution: string; networkLevelPending: string;
networkBonusPendingContribution: string; networkBonusPending: string;
totalDistributed: string; totalDistributed: string;
totalBurned: string; totalBurned: string;
circulationPool: string; circulationPool: string;