feat(dashboard): add detailed contribution breakdown by category
Backend (contribution-service): - Add getDetailedContributionStats() to repository - Add getUnallocatedByLevelTier/BonusTier() to repository - Extend stats API with level/bonus breakdown by tier - Add getTotalTrees() to synced-data repository Backend (mining-admin-service): - Add detailed contribution stats calculation - Calculate theoretical vs actual values per category - Return level/bonus breakdown with unlocked/pending amounts Frontend (mining-admin-web): - Add ContributionBreakdown component showing: - Personal (70%), Operation (12%), Province (1%), City (2%) - Level contribution (7.5%) by tier: 1-5, 6-10, 11-15 - Bonus contribution (7.5%) by tier: T1, T2, T3 - Update DashboardStats type definition - Integrate breakdown component into dashboard page Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
1f15daa6c5
commit
cfbf1b21f3
|
|
@ -1,4 +1,5 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import Decimal from 'decimal.js';
|
||||||
import { ContributionAccountRepository } from '../../infrastructure/persistence/repositories/contribution-account.repository';
|
import { ContributionAccountRepository } from '../../infrastructure/persistence/repositories/contribution-account.repository';
|
||||||
import { ContributionRecordRepository } from '../../infrastructure/persistence/repositories/contribution-record.repository';
|
import { ContributionRecordRepository } from '../../infrastructure/persistence/repositories/contribution-record.repository';
|
||||||
import { UnallocatedContributionRepository } from '../../infrastructure/persistence/repositories/unallocated-contribution.repository';
|
import { UnallocatedContributionRepository } from '../../infrastructure/persistence/repositories/unallocated-contribution.repository';
|
||||||
|
|
@ -6,6 +7,15 @@ import { SystemAccountRepository } from '../../infrastructure/persistence/reposi
|
||||||
import { SyncedDataRepository } from '../../infrastructure/persistence/repositories/synced-data.repository';
|
import { SyncedDataRepository } from '../../infrastructure/persistence/repositories/synced-data.repository';
|
||||||
import { ContributionSourceType } from '../../domain/aggregates/contribution-account.aggregate';
|
import { ContributionSourceType } from '../../domain/aggregates/contribution-account.aggregate';
|
||||||
|
|
||||||
|
// 基准算力常量
|
||||||
|
const BASE_CONTRIBUTION_PER_TREE = new Decimal('22617');
|
||||||
|
const RATE_PERSONAL = new Decimal('0.70');
|
||||||
|
const RATE_OPERATION = new Decimal('0.12');
|
||||||
|
const RATE_PROVINCE = new Decimal('0.01');
|
||||||
|
const RATE_CITY = new Decimal('0.02');
|
||||||
|
const RATE_LEVEL_TOTAL = new Decimal('0.075');
|
||||||
|
const RATE_BONUS_TOTAL = new Decimal('0.075');
|
||||||
|
|
||||||
export interface ContributionStatsDto {
|
export interface ContributionStatsDto {
|
||||||
// 用户统计
|
// 用户统计
|
||||||
totalUsers: number;
|
totalUsers: number;
|
||||||
|
|
@ -16,17 +26,57 @@ export interface ContributionStatsDto {
|
||||||
totalAdoptions: number;
|
totalAdoptions: number;
|
||||||
processedAdoptions: number;
|
processedAdoptions: number;
|
||||||
unprocessedAdoptions: number;
|
unprocessedAdoptions: number;
|
||||||
|
totalTrees: number;
|
||||||
|
|
||||||
// 算力统计
|
// 算力统计
|
||||||
totalContribution: string;
|
totalContribution: string;
|
||||||
|
|
||||||
// 算力分布
|
// 算力分布(基础)
|
||||||
contributionByType: {
|
contributionByType: {
|
||||||
personal: string;
|
personal: string;
|
||||||
teamLevel: string;
|
teamLevel: string;
|
||||||
teamBonus: string;
|
teamBonus: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ========== 详细算力分解(按用户需求) ==========
|
||||||
|
// 全网算力 = 总认种树 * 22617
|
||||||
|
networkTotalContribution: string;
|
||||||
|
// 个人用户总算力 = 总认种树 * (22617 * 70%)
|
||||||
|
personalTotalContribution: string;
|
||||||
|
// 运营账户总算力 = 总认种树 * (22617 * 12%)
|
||||||
|
operationTotalContribution: string;
|
||||||
|
// 省公司总算力 = 总认种树 * (22617 * 1%)
|
||||||
|
provinceTotalContribution: string;
|
||||||
|
// 市公司总算力 = 总认种树 * (22617 * 2%)
|
||||||
|
cityTotalContribution: string;
|
||||||
|
|
||||||
|
// 层级算力详情 (7.5%)
|
||||||
|
levelContribution: {
|
||||||
|
total: string;
|
||||||
|
unlocked: string;
|
||||||
|
pending: string;
|
||||||
|
byTier: {
|
||||||
|
// 1档: 1-5级
|
||||||
|
tier1: { unlocked: string; pending: string };
|
||||||
|
// 2档: 6-10级
|
||||||
|
tier2: { unlocked: string; pending: string };
|
||||||
|
// 3档: 11-15级
|
||||||
|
tier3: { unlocked: string; pending: string };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 团队奖励算力详情 (7.5%)
|
||||||
|
bonusContribution: {
|
||||||
|
total: string;
|
||||||
|
unlocked: string;
|
||||||
|
pending: string;
|
||||||
|
byTier: {
|
||||||
|
tier1: { unlocked: string; pending: string };
|
||||||
|
tier2: { unlocked: string; pending: string };
|
||||||
|
tier3: { unlocked: string; pending: string };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// 系统账户
|
// 系统账户
|
||||||
systemAccounts: {
|
systemAccounts: {
|
||||||
accountType: string;
|
accountType: string;
|
||||||
|
|
@ -61,6 +111,10 @@ export class GetContributionStatsQuery {
|
||||||
systemAccounts,
|
systemAccounts,
|
||||||
totalUnallocated,
|
totalUnallocated,
|
||||||
unallocatedByType,
|
unallocatedByType,
|
||||||
|
detailedStats,
|
||||||
|
unallocatedByLevelTier,
|
||||||
|
unallocatedByBonusTier,
|
||||||
|
totalTrees,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
this.syncedDataRepository.countUsers(),
|
this.syncedDataRepository.countUsers(),
|
||||||
this.accountRepository.countAccounts(),
|
this.accountRepository.countAccounts(),
|
||||||
|
|
@ -72,8 +126,33 @@ export class GetContributionStatsQuery {
|
||||||
this.systemAccountRepository.findAll(),
|
this.systemAccountRepository.findAll(),
|
||||||
this.unallocatedRepository.getTotalUnallocated(),
|
this.unallocatedRepository.getTotalUnallocated(),
|
||||||
this.unallocatedRepository.getTotalUnallocatedByType(),
|
this.unallocatedRepository.getTotalUnallocatedByType(),
|
||||||
|
this.accountRepository.getDetailedContributionStats(),
|
||||||
|
this.unallocatedRepository.getUnallocatedByLevelTier(),
|
||||||
|
this.unallocatedRepository.getUnallocatedByBonusTier(),
|
||||||
|
this.syncedDataRepository.getTotalTrees(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 计算理论算力(基于总认种树 * 基准算力)
|
||||||
|
const networkTotal = BASE_CONTRIBUTION_PER_TREE.mul(totalTrees);
|
||||||
|
const personalTotal = networkTotal.mul(RATE_PERSONAL);
|
||||||
|
const operationTotal = networkTotal.mul(RATE_OPERATION);
|
||||||
|
const provinceTotal = networkTotal.mul(RATE_PROVINCE);
|
||||||
|
const cityTotal = networkTotal.mul(RATE_CITY);
|
||||||
|
const levelTotal = networkTotal.mul(RATE_LEVEL_TOTAL);
|
||||||
|
const bonusTotal = networkTotal.mul(RATE_BONUS_TOTAL);
|
||||||
|
|
||||||
|
// 层级算力: 已解锁 + 未解锁
|
||||||
|
const levelUnlocked = new Decimal(detailedStats.levelUnlocked);
|
||||||
|
const levelPending = new Decimal(unallocatedByLevelTier.tier1)
|
||||||
|
.plus(unallocatedByLevelTier.tier2)
|
||||||
|
.plus(unallocatedByLevelTier.tier3);
|
||||||
|
|
||||||
|
// 团队奖励算力: 已解锁 + 未解锁
|
||||||
|
const bonusUnlocked = new Decimal(detailedStats.bonusUnlocked);
|
||||||
|
const bonusPending = new Decimal(unallocatedByBonusTier.tier1)
|
||||||
|
.plus(unallocatedByBonusTier.tier2)
|
||||||
|
.plus(unallocatedByBonusTier.tier3);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalUsers,
|
totalUsers,
|
||||||
totalAccounts,
|
totalAccounts,
|
||||||
|
|
@ -81,12 +160,63 @@ export class GetContributionStatsQuery {
|
||||||
totalAdoptions,
|
totalAdoptions,
|
||||||
processedAdoptions: totalAdoptions - undistributedAdoptions,
|
processedAdoptions: totalAdoptions - undistributedAdoptions,
|
||||||
unprocessedAdoptions: undistributedAdoptions,
|
unprocessedAdoptions: undistributedAdoptions,
|
||||||
|
totalTrees,
|
||||||
totalContribution: totalContribution.value.toString(),
|
totalContribution: totalContribution.value.toString(),
|
||||||
contributionByType: {
|
contributionByType: {
|
||||||
personal: (contributionByType.get(ContributionSourceType.PERSONAL)?.value || 0).toString(),
|
personal: (contributionByType.get(ContributionSourceType.PERSONAL)?.value || 0).toString(),
|
||||||
teamLevel: (contributionByType.get(ContributionSourceType.TEAM_LEVEL)?.value || 0).toString(),
|
teamLevel: (contributionByType.get(ContributionSourceType.TEAM_LEVEL)?.value || 0).toString(),
|
||||||
teamBonus: (contributionByType.get(ContributionSourceType.TEAM_BONUS)?.value || 0).toString(),
|
teamBonus: (contributionByType.get(ContributionSourceType.TEAM_BONUS)?.value || 0).toString(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 详细算力分解
|
||||||
|
networkTotalContribution: networkTotal.toString(),
|
||||||
|
personalTotalContribution: personalTotal.toString(),
|
||||||
|
operationTotalContribution: operationTotal.toString(),
|
||||||
|
provinceTotalContribution: provinceTotal.toString(),
|
||||||
|
cityTotalContribution: cityTotal.toString(),
|
||||||
|
|
||||||
|
// 层级算力详情
|
||||||
|
levelContribution: {
|
||||||
|
total: levelTotal.toString(),
|
||||||
|
unlocked: levelUnlocked.toString(),
|
||||||
|
pending: levelPending.toString(),
|
||||||
|
byTier: {
|
||||||
|
tier1: {
|
||||||
|
unlocked: detailedStats.levelByTier.tier1.unlocked,
|
||||||
|
pending: unallocatedByLevelTier.tier1,
|
||||||
|
},
|
||||||
|
tier2: {
|
||||||
|
unlocked: detailedStats.levelByTier.tier2.unlocked,
|
||||||
|
pending: unallocatedByLevelTier.tier2,
|
||||||
|
},
|
||||||
|
tier3: {
|
||||||
|
unlocked: detailedStats.levelByTier.tier3.unlocked,
|
||||||
|
pending: unallocatedByLevelTier.tier3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// 团队奖励算力详情
|
||||||
|
bonusContribution: {
|
||||||
|
total: bonusTotal.toString(),
|
||||||
|
unlocked: bonusUnlocked.toString(),
|
||||||
|
pending: bonusPending.toString(),
|
||||||
|
byTier: {
|
||||||
|
tier1: {
|
||||||
|
unlocked: detailedStats.bonusByTier.tier1.unlocked,
|
||||||
|
pending: unallocatedByBonusTier.tier1,
|
||||||
|
},
|
||||||
|
tier2: {
|
||||||
|
unlocked: detailedStats.bonusByTier.tier2.unlocked,
|
||||||
|
pending: unallocatedByBonusTier.tier2,
|
||||||
|
},
|
||||||
|
tier3: {
|
||||||
|
unlocked: detailedStats.bonusByTier.tier3.unlocked,
|
||||||
|
pending: unallocatedByBonusTier.tier3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
systemAccounts: systemAccounts.map((a) => ({
|
systemAccounts: systemAccounts.map((a) => ({
|
||||||
accountType: a.accountType,
|
accountType: a.accountType,
|
||||||
name: a.name,
|
name: a.name,
|
||||||
|
|
@ -98,4 +228,5 @@ export class GetContributionStatsQuery {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,107 @@ export class ContributionAccountRepository implements IContributionAccountReposi
|
||||||
return records.map((r) => this.toDomain(r));
|
return records.map((r) => this.toDomain(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取详细算力汇总(按类型分解)
|
||||||
|
*/
|
||||||
|
async getDetailedContributionStats(): Promise<{
|
||||||
|
// 个人算力总计
|
||||||
|
personalTotal: string;
|
||||||
|
// 层级算力 - 已解锁(已分配给上线)
|
||||||
|
levelUnlocked: string;
|
||||||
|
// 层级算力 - 未解锁(待解锁的pending)
|
||||||
|
levelPending: string;
|
||||||
|
// 层级按档位分解
|
||||||
|
levelByTier: {
|
||||||
|
tier1: { unlocked: string; pending: string }; // 1-5级
|
||||||
|
tier2: { unlocked: string; pending: string }; // 6-10级
|
||||||
|
tier3: { unlocked: string; pending: string }; // 11-15级
|
||||||
|
};
|
||||||
|
// 团队奖励算力 - 已解锁
|
||||||
|
bonusUnlocked: string;
|
||||||
|
// 团队奖励算力 - 未解锁
|
||||||
|
bonusPending: string;
|
||||||
|
// 团队奖励按档位分解
|
||||||
|
bonusByTier: {
|
||||||
|
tier1: { unlocked: string; pending: string };
|
||||||
|
tier2: { unlocked: string; pending: string };
|
||||||
|
tier3: { unlocked: string; pending: string };
|
||||||
|
};
|
||||||
|
}> {
|
||||||
|
const result = await this.client.contributionAccount.aggregate({
|
||||||
|
_sum: {
|
||||||
|
personalContribution: true,
|
||||||
|
// 层级 1-5
|
||||||
|
level1Pending: true,
|
||||||
|
level2Pending: true,
|
||||||
|
level3Pending: true,
|
||||||
|
level4Pending: true,
|
||||||
|
level5Pending: true,
|
||||||
|
// 层级 6-10
|
||||||
|
level6Pending: true,
|
||||||
|
level7Pending: true,
|
||||||
|
level8Pending: true,
|
||||||
|
level9Pending: true,
|
||||||
|
level10Pending: true,
|
||||||
|
// 层级 11-15
|
||||||
|
level11Pending: true,
|
||||||
|
level12Pending: true,
|
||||||
|
level13Pending: true,
|
||||||
|
level14Pending: true,
|
||||||
|
level15Pending: true,
|
||||||
|
// 团队奖励
|
||||||
|
bonusTier1Pending: true,
|
||||||
|
bonusTier2Pending: true,
|
||||||
|
bonusTier3Pending: true,
|
||||||
|
// 汇总
|
||||||
|
totalLevelPending: true,
|
||||||
|
totalBonusPending: true,
|
||||||
|
totalUnlocked: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sum = result._sum;
|
||||||
|
|
||||||
|
// 层级 1-5 已解锁(在pending字段中存储的是已分配给该用户的层级算力)
|
||||||
|
const level1to5 = new Decimal(sum.level1Pending || 0)
|
||||||
|
.plus(sum.level2Pending || 0)
|
||||||
|
.plus(sum.level3Pending || 0)
|
||||||
|
.plus(sum.level4Pending || 0)
|
||||||
|
.plus(sum.level5Pending || 0);
|
||||||
|
|
||||||
|
// 层级 6-10
|
||||||
|
const level6to10 = new Decimal(sum.level6Pending || 0)
|
||||||
|
.plus(sum.level7Pending || 0)
|
||||||
|
.plus(sum.level8Pending || 0)
|
||||||
|
.plus(sum.level9Pending || 0)
|
||||||
|
.plus(sum.level10Pending || 0);
|
||||||
|
|
||||||
|
// 层级 11-15
|
||||||
|
const level11to15 = new Decimal(sum.level11Pending || 0)
|
||||||
|
.plus(sum.level12Pending || 0)
|
||||||
|
.plus(sum.level13Pending || 0)
|
||||||
|
.plus(sum.level14Pending || 0)
|
||||||
|
.plus(sum.level15Pending || 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
personalTotal: (sum.personalContribution || new Decimal(0)).toString(),
|
||||||
|
levelUnlocked: (sum.totalLevelPending || new Decimal(0)).toString(),
|
||||||
|
levelPending: '0', // 未解锁的存储在 unallocated 表中
|
||||||
|
levelByTier: {
|
||||||
|
tier1: { unlocked: level1to5.toString(), pending: '0' },
|
||||||
|
tier2: { unlocked: level6to10.toString(), pending: '0' },
|
||||||
|
tier3: { unlocked: level11to15.toString(), pending: '0' },
|
||||||
|
},
|
||||||
|
bonusUnlocked: (sum.totalBonusPending || new Decimal(0)).toString(),
|
||||||
|
bonusPending: '0', // 未解锁的存储在 unallocated 表中
|
||||||
|
bonusByTier: {
|
||||||
|
tier1: { unlocked: (sum.bonusTier1Pending || new Decimal(0)).toString(), pending: '0' },
|
||||||
|
tier2: { unlocked: (sum.bonusTier2Pending || new Decimal(0)).toString(), pending: '0' },
|
||||||
|
tier3: { unlocked: (sum.bonusTier3Pending || new Decimal(0)).toString(), pending: '0' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private toDomain(record: any): ContributionAccountAggregate {
|
private toDomain(record: any): ContributionAccountAggregate {
|
||||||
return ContributionAccountAggregate.fromPersistence({
|
return ContributionAccountAggregate.fromPersistence({
|
||||||
id: record.id,
|
id: record.id,
|
||||||
|
|
|
||||||
|
|
@ -461,6 +461,16 @@ export class SyncedDataRepository implements ISyncedDataRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTotalTrees(): Promise<number> {
|
||||||
|
const result = await this.client.syncedAdoption.aggregate({
|
||||||
|
where: {
|
||||||
|
status: 'MINING_ENABLED', // 只统计最终成功的认种订单
|
||||||
|
},
|
||||||
|
_sum: { treeCount: true },
|
||||||
|
});
|
||||||
|
return result._sum.treeCount ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 私有方法 ==========
|
// ========== 私有方法 ==========
|
||||||
|
|
||||||
private toSyncedUser(record: any): SyncedUser {
|
private toSyncedUser(record: any): SyncedUser {
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,81 @@ export class UnallocatedContributionRepository {
|
||||||
return records.map((r) => this.toDomain(r));
|
return records.map((r) => this.toDomain(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分层级的未分配算力统计
|
||||||
|
*/
|
||||||
|
async getUnallocatedByLevelTier(): Promise<{
|
||||||
|
tier1: string; // 1-5级未分配
|
||||||
|
tier2: string; // 6-10级未分配
|
||||||
|
tier3: string; // 11-15级未分配
|
||||||
|
}> {
|
||||||
|
const results = await this.client.unallocatedContribution.groupBy({
|
||||||
|
by: ['levelDepth'],
|
||||||
|
where: {
|
||||||
|
levelDepth: { not: null },
|
||||||
|
status: 'PENDING',
|
||||||
|
},
|
||||||
|
_sum: { amount: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
let tier1 = new ContributionAmount(0);
|
||||||
|
let tier2 = new ContributionAmount(0);
|
||||||
|
let tier3 = new ContributionAmount(0);
|
||||||
|
|
||||||
|
for (const item of results) {
|
||||||
|
const depth = item.levelDepth!;
|
||||||
|
const amount = new ContributionAmount(item._sum.amount || 0);
|
||||||
|
if (depth >= 1 && depth <= 5) {
|
||||||
|
tier1 = tier1.add(amount);
|
||||||
|
} else if (depth >= 6 && depth <= 10) {
|
||||||
|
tier2 = tier2.add(amount);
|
||||||
|
} else if (depth >= 11 && depth <= 15) {
|
||||||
|
tier3 = tier3.add(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tier1: tier1.value.toString(),
|
||||||
|
tier2: tier2.value.toString(),
|
||||||
|
tier3: tier3.value.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分档位的未分配奖励统计
|
||||||
|
*/
|
||||||
|
async getUnallocatedByBonusTier(): Promise<{
|
||||||
|
tier1: string;
|
||||||
|
tier2: string;
|
||||||
|
tier3: string;
|
||||||
|
}> {
|
||||||
|
const results = await this.client.unallocatedContribution.groupBy({
|
||||||
|
by: ['unallocType'],
|
||||||
|
where: {
|
||||||
|
unallocType: { startsWith: 'BONUS_TIER_' },
|
||||||
|
status: 'PENDING',
|
||||||
|
},
|
||||||
|
_sum: { amount: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
let tier1 = '0';
|
||||||
|
let tier2 = '0';
|
||||||
|
let tier3 = '0';
|
||||||
|
|
||||||
|
for (const item of results) {
|
||||||
|
const amount = (item._sum.amount || 0).toString();
|
||||||
|
if (item.unallocType === 'BONUS_TIER_1') {
|
||||||
|
tier1 = amount;
|
||||||
|
} else if (item.unallocType === 'BONUS_TIER_2') {
|
||||||
|
tier2 = amount;
|
||||||
|
} else if (item.unallocType === 'BONUS_TIER_3') {
|
||||||
|
tier3 = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { tier1, tier2, tier3 };
|
||||||
|
}
|
||||||
|
|
||||||
private toDomain(record: any): UnallocatedContribution {
|
private toDomain(record: any): UnallocatedContribution {
|
||||||
return {
|
return {
|
||||||
id: record.id,
|
id: record.id,
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,19 @@ export class DashboardController {
|
||||||
priceChange24h = (close - open) / open;
|
priceChange24h = (close - open) / open;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 详细算力分解数据
|
||||||
|
const dc = raw.detailedContribution || {};
|
||||||
|
|
||||||
// 转换为前端期望的格式
|
// 转换为前端期望的格式
|
||||||
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,
|
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',
|
||||||
networkLevelPending: raw.contribution?.teamLevelContribution || '0',
|
networkLevelPending: dc.levelContribution?.pending || '0',
|
||||||
networkBonusPending: raw.contribution?.teamBonusContribution || '0',
|
networkBonusPending: dc.bonusContribution?.pending || '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',
|
||||||
|
|
@ -42,6 +46,47 @@ export class DashboardController {
|
||||||
priceChange24h,
|
priceChange24h,
|
||||||
totalOrders: raw.trading?.totalAccounts || 0,
|
totalOrders: raw.trading?.totalAccounts || 0,
|
||||||
totalTrades: raw.trading?.totalAccounts || 0,
|
totalTrades: raw.trading?.totalAccounts || 0,
|
||||||
|
|
||||||
|
// ========== 详细算力分解 ==========
|
||||||
|
detailedContribution: {
|
||||||
|
totalTrees: dc.totalTrees || 0,
|
||||||
|
// 全网算力(理论值)= 总树数 * 22617
|
||||||
|
networkTotalTheory: dc.networkTotalTheory || '0',
|
||||||
|
// 个人算力(70%)
|
||||||
|
personalTheory: dc.personalTheory || '0',
|
||||||
|
personalActual: raw.contribution?.personalContribution || '0',
|
||||||
|
// 运营账户(12%)
|
||||||
|
operationTheory: dc.operationTheory || '0',
|
||||||
|
operationActual: dc.operationActual || '0',
|
||||||
|
// 省公司(1%)
|
||||||
|
provinceTheory: dc.provinceTheory || '0',
|
||||||
|
provinceActual: dc.provinceActual || '0',
|
||||||
|
// 市公司(2%)
|
||||||
|
cityTheory: dc.cityTheory || '0',
|
||||||
|
cityActual: dc.cityActual || '0',
|
||||||
|
|
||||||
|
// 层级算力(7.5%)
|
||||||
|
level: {
|
||||||
|
theory: dc.levelTheory || '0',
|
||||||
|
unlocked: dc.levelContribution?.unlocked || '0',
|
||||||
|
pending: dc.levelContribution?.pending || '0',
|
||||||
|
// 分档详情
|
||||||
|
tier1: dc.levelContribution?.byTier?.tier1 || { unlocked: '0', pending: '0' },
|
||||||
|
tier2: dc.levelContribution?.byTier?.tier2 || { unlocked: '0', pending: '0' },
|
||||||
|
tier3: dc.levelContribution?.byTier?.tier3 || { unlocked: '0', pending: '0' },
|
||||||
|
},
|
||||||
|
|
||||||
|
// 团队奖励算力(7.5%)
|
||||||
|
bonus: {
|
||||||
|
theory: dc.bonusTheory || '0',
|
||||||
|
unlocked: dc.bonusContribution?.unlocked || '0',
|
||||||
|
pending: dc.bonusContribution?.pending || '0',
|
||||||
|
// 分档详情
|
||||||
|
tier1: dc.bonusContribution?.byTier?.tier1 || { unlocked: '0', pending: '0' },
|
||||||
|
tier2: dc.bonusContribution?.byTier?.tier2 || { unlocked: '0', pending: '0' },
|
||||||
|
tier3: dc.bonusContribution?.byTier?.tier3 || { unlocked: '0', pending: '0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,17 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { Decimal } from 'decimal.js';
|
||||||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
||||||
|
|
||||||
|
// 基准算力常量
|
||||||
|
const BASE_CONTRIBUTION_PER_TREE = new Decimal('22617');
|
||||||
|
const RATE_PERSONAL = new Decimal('0.70');
|
||||||
|
const RATE_OPERATION = new Decimal('0.12');
|
||||||
|
const RATE_PROVINCE = new Decimal('0.01');
|
||||||
|
const RATE_CITY = new Decimal('0.02');
|
||||||
|
const RATE_LEVEL_TOTAL = new Decimal('0.075');
|
||||||
|
const RATE_BONUS_TOTAL = new Decimal('0.075');
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DashboardService {
|
export class DashboardService {
|
||||||
private readonly logger = new Logger(DashboardService.name);
|
private readonly logger = new Logger(DashboardService.name);
|
||||||
|
|
@ -23,6 +33,7 @@ export class DashboardService {
|
||||||
tradingStats,
|
tradingStats,
|
||||||
latestReport,
|
latestReport,
|
||||||
latestKLine,
|
latestKLine,
|
||||||
|
detailedContributionStats,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
this.getUserStats(),
|
this.getUserStats(),
|
||||||
this.getContributionStats(),
|
this.getContributionStats(),
|
||||||
|
|
@ -30,6 +41,7 @@ export class DashboardService {
|
||||||
this.getTradingStats(),
|
this.getTradingStats(),
|
||||||
this.prisma.dailyReport.findFirst({ orderBy: { reportDate: 'desc' } }),
|
this.prisma.dailyReport.findFirst({ orderBy: { reportDate: 'desc' } }),
|
||||||
this.prisma.syncedDayKLine.findFirst({ orderBy: { klineDate: 'desc' } }),
|
this.prisma.syncedDayKLine.findFirst({ orderBy: { klineDate: 'desc' } }),
|
||||||
|
this.getDetailedContributionStats(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -37,6 +49,7 @@ export class DashboardService {
|
||||||
contribution: contributionStats,
|
contribution: contributionStats,
|
||||||
mining: miningStats,
|
mining: miningStats,
|
||||||
trading: tradingStats,
|
trading: tradingStats,
|
||||||
|
detailedContribution: detailedContributionStats,
|
||||||
latestReport: latestReport
|
latestReport: latestReport
|
||||||
? this.formatDailyReport(latestReport)
|
? this.formatDailyReport(latestReport)
|
||||||
: null,
|
: null,
|
||||||
|
|
@ -128,6 +141,7 @@ export class DashboardService {
|
||||||
_count: true,
|
_count: true,
|
||||||
}),
|
}),
|
||||||
this.prisma.syncedAdoption.aggregate({
|
this.prisma.syncedAdoption.aggregate({
|
||||||
|
where: { status: 'MINING_ENABLED' },
|
||||||
_sum: { treeCount: true },
|
_sum: { treeCount: true },
|
||||||
_count: true,
|
_count: true,
|
||||||
}),
|
}),
|
||||||
|
|
@ -152,6 +166,137 @@ export class DashboardService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取详细算力分解统计(按用户需求)
|
||||||
|
*/
|
||||||
|
private async getDetailedContributionStats() {
|
||||||
|
// 获取总树数
|
||||||
|
const adoptionStats = await this.prisma.syncedAdoption.aggregate({
|
||||||
|
where: { status: 'MINING_ENABLED' },
|
||||||
|
_sum: { treeCount: true },
|
||||||
|
});
|
||||||
|
const totalTrees = adoptionStats._sum.treeCount || 0;
|
||||||
|
|
||||||
|
// 按层级统计已分配的层级算力
|
||||||
|
const levelRecords = await this.prisma.syncedContributionRecord.groupBy({
|
||||||
|
by: ['levelDepth'],
|
||||||
|
where: {
|
||||||
|
sourceType: 'TEAM_LEVEL',
|
||||||
|
levelDepth: { not: null },
|
||||||
|
},
|
||||||
|
_sum: { amount: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按档位统计已分配的团队奖励算力
|
||||||
|
const bonusRecords = await this.prisma.syncedContributionRecord.groupBy({
|
||||||
|
by: ['bonusTier'],
|
||||||
|
where: {
|
||||||
|
sourceType: 'TEAM_BONUS',
|
||||||
|
bonusTier: { not: null },
|
||||||
|
},
|
||||||
|
_sum: { amount: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取系统账户按类型的算力
|
||||||
|
const systemAccounts = await this.prisma.syncedSystemContribution.findMany();
|
||||||
|
|
||||||
|
// 汇总层级1-5, 6-10, 11-15
|
||||||
|
let levelTier1 = new Decimal(0);
|
||||||
|
let levelTier2 = new Decimal(0);
|
||||||
|
let levelTier3 = new Decimal(0);
|
||||||
|
for (const record of levelRecords) {
|
||||||
|
const depth = record.levelDepth!;
|
||||||
|
const amount = new Decimal(record._sum.amount || 0);
|
||||||
|
if (depth >= 1 && depth <= 5) levelTier1 = levelTier1.plus(amount);
|
||||||
|
else if (depth >= 6 && depth <= 10) levelTier2 = levelTier2.plus(amount);
|
||||||
|
else if (depth >= 11 && depth <= 15) levelTier3 = levelTier3.plus(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 汇总团队奖励档位
|
||||||
|
let bonusTier1 = new Decimal(0);
|
||||||
|
let bonusTier2 = new Decimal(0);
|
||||||
|
let bonusTier3 = new Decimal(0);
|
||||||
|
for (const record of bonusRecords) {
|
||||||
|
const tier = record.bonusTier!;
|
||||||
|
const amount = new Decimal(record._sum.amount || 0);
|
||||||
|
if (tier === 1) bonusTier1 = amount;
|
||||||
|
else if (tier === 2) bonusTier2 = amount;
|
||||||
|
else if (tier === 3) bonusTier3 = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelUnlocked = levelTier1.plus(levelTier2).plus(levelTier3);
|
||||||
|
const bonusUnlocked = bonusTier1.plus(bonusTier2).plus(bonusTier3);
|
||||||
|
|
||||||
|
// 计算理论值
|
||||||
|
const networkTotal = BASE_CONTRIBUTION_PER_TREE.mul(totalTrees);
|
||||||
|
const personalTheory = networkTotal.mul(RATE_PERSONAL);
|
||||||
|
const operationTheory = networkTotal.mul(RATE_OPERATION);
|
||||||
|
const provinceTheory = networkTotal.mul(RATE_PROVINCE);
|
||||||
|
const cityTheory = networkTotal.mul(RATE_CITY);
|
||||||
|
const levelTheory = networkTotal.mul(RATE_LEVEL_TOTAL);
|
||||||
|
const bonusTheory = networkTotal.mul(RATE_BONUS_TOTAL);
|
||||||
|
|
||||||
|
// 计算未解锁(理论 - 已解锁)
|
||||||
|
const levelPending = levelTheory.minus(levelUnlocked).greaterThan(0)
|
||||||
|
? levelTheory.minus(levelUnlocked)
|
||||||
|
: new Decimal(0);
|
||||||
|
const bonusPending = bonusTheory.minus(bonusUnlocked).greaterThan(0)
|
||||||
|
? bonusTheory.minus(bonusUnlocked)
|
||||||
|
: new Decimal(0);
|
||||||
|
|
||||||
|
// 系统账户按类型汇总
|
||||||
|
let operationActual = new Decimal(0);
|
||||||
|
let provinceActual = new Decimal(0);
|
||||||
|
let cityActual = new Decimal(0);
|
||||||
|
for (const account of systemAccounts) {
|
||||||
|
const balance = new Decimal(account.contributionBalance || 0);
|
||||||
|
if (account.accountType === 'OPERATION') operationActual = operationActual.plus(balance);
|
||||||
|
else if (account.accountType === 'PROVINCE') provinceActual = provinceActual.plus(balance);
|
||||||
|
else if (account.accountType === 'CITY') cityActual = cityActual.plus(balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalTrees,
|
||||||
|
// 理论值(基于总树数计算)
|
||||||
|
networkTotalTheory: networkTotal.toString(),
|
||||||
|
personalTheory: personalTheory.toString(),
|
||||||
|
operationTheory: operationTheory.toString(),
|
||||||
|
provinceTheory: provinceTheory.toString(),
|
||||||
|
cityTheory: cityTheory.toString(),
|
||||||
|
levelTheory: levelTheory.toString(),
|
||||||
|
bonusTheory: bonusTheory.toString(),
|
||||||
|
|
||||||
|
// 实际值(从数据库统计)
|
||||||
|
operationActual: operationActual.toString(),
|
||||||
|
provinceActual: provinceActual.toString(),
|
||||||
|
cityActual: cityActual.toString(),
|
||||||
|
|
||||||
|
// 层级算力详情
|
||||||
|
levelContribution: {
|
||||||
|
total: levelTheory.toString(),
|
||||||
|
unlocked: levelUnlocked.toString(),
|
||||||
|
pending: levelPending.toString(),
|
||||||
|
byTier: {
|
||||||
|
tier1: { unlocked: levelTier1.toString(), pending: '0' },
|
||||||
|
tier2: { unlocked: levelTier2.toString(), pending: '0' },
|
||||||
|
tier3: { unlocked: levelTier3.toString(), pending: '0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// 团队奖励算力详情
|
||||||
|
bonusContribution: {
|
||||||
|
total: bonusTheory.toString(),
|
||||||
|
unlocked: bonusUnlocked.toString(),
|
||||||
|
pending: bonusPending.toString(),
|
||||||
|
byTier: {
|
||||||
|
tier1: { unlocked: bonusTier1.toString(), pending: '0' },
|
||||||
|
tier2: { unlocked: bonusTier2.toString(), pending: '0' },
|
||||||
|
tier3: { unlocked: bonusTier3.toString(), pending: '0' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取挖矿统计
|
* 获取挖矿统计
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { PageHeader } from '@/components/layout/page-header';
|
||||||
import { StatsCards } from '@/features/dashboard/components/stats-cards';
|
import { StatsCards } from '@/features/dashboard/components/stats-cards';
|
||||||
import { RealtimePanel } from '@/features/dashboard/components/realtime-panel';
|
import { RealtimePanel } from '@/features/dashboard/components/realtime-panel';
|
||||||
import { PriceOverview } from '@/features/dashboard/components/price-overview';
|
import { PriceOverview } from '@/features/dashboard/components/price-overview';
|
||||||
|
import { ContributionBreakdown } from '@/features/dashboard/components/contribution-breakdown';
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -12,6 +13,9 @@ export default function DashboardPage() {
|
||||||
|
|
||||||
<StatsCards />
|
<StatsCards />
|
||||||
|
|
||||||
|
{/* 详细算力分解 */}
|
||||||
|
<ContributionBreakdown />
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<div className="lg:col-span-2">
|
<div className="lg:col-span-2">
|
||||||
<PriceOverview />
|
<PriceOverview />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { useDashboardStats } from '../hooks/use-dashboard-stats';
|
||||||
|
import { formatCompactNumber } from '@/lib/utils/format';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { Activity, Users, Building2, Landmark, Layers, Gift, TreePine } from 'lucide-react';
|
||||||
|
|
||||||
|
function ContributionBreakdownSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<Skeleton className="h-5 w-32" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{[...Array(6)].map((_, i) => (
|
||||||
|
<div key={i} className="flex justify-between">
|
||||||
|
<Skeleton className="h-4 w-24" />
|
||||||
|
<Skeleton className="h-4 w-20" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BreakdownRowProps {
|
||||||
|
label: string;
|
||||||
|
theory?: string;
|
||||||
|
actual?: string;
|
||||||
|
unlocked?: string;
|
||||||
|
pending?: string;
|
||||||
|
icon?: React.ElementType;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BreakdownRow({ label, theory, actual, unlocked, pending, icon: Icon, color = 'text-gray-600' }: BreakdownRowProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between py-2 border-b last:border-0">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{Icon && <Icon className={`h-4 w-4 ${color}`} />}
|
||||||
|
<span className="text-sm text-muted-foreground">{label}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
{theory && actual && (
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="font-medium">{formatCompactNumber(actual)}</span>
|
||||||
|
<span className="text-muted-foreground text-xs ml-1">/ {formatCompactNumber(theory)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{unlocked !== undefined && pending !== undefined && (
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="font-medium text-green-600">{formatCompactNumber(unlocked)}</span>
|
||||||
|
<span className="text-muted-foreground mx-1">/</span>
|
||||||
|
<span className="font-medium text-orange-500">{formatCompactNumber(pending)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContributionBreakdown() {
|
||||||
|
const { data: stats, isLoading } = useDashboardStats();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <ContributionBreakdownSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dc = stats?.detailedContribution;
|
||||||
|
if (!dc) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
{/* 基础算力分配 */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="text-base flex items-center gap-2">
|
||||||
|
<TreePine className="h-5 w-5 text-green-600" />
|
||||||
|
算力分配概览
|
||||||
|
</CardTitle>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
总认种树: {dc.totalTrees} 棵 | 全网理论算力: {formatCompactNumber(dc.networkTotalTheory)}
|
||||||
|
</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-1">
|
||||||
|
<BreakdownRow
|
||||||
|
label="个人算力 (70%)"
|
||||||
|
theory={dc.personalTheory}
|
||||||
|
actual={dc.personalActual}
|
||||||
|
icon={Users}
|
||||||
|
color="text-blue-500"
|
||||||
|
/>
|
||||||
|
<BreakdownRow
|
||||||
|
label="运营账户 (12%)"
|
||||||
|
theory={dc.operationTheory}
|
||||||
|
actual={dc.operationActual}
|
||||||
|
icon={Building2}
|
||||||
|
color="text-purple-500"
|
||||||
|
/>
|
||||||
|
<BreakdownRow
|
||||||
|
label="省公司 (1%)"
|
||||||
|
theory={dc.provinceTheory}
|
||||||
|
actual={dc.provinceActual}
|
||||||
|
icon={Landmark}
|
||||||
|
color="text-indigo-500"
|
||||||
|
/>
|
||||||
|
<BreakdownRow
|
||||||
|
label="市公司 (2%)"
|
||||||
|
theory={dc.cityTheory}
|
||||||
|
actual={dc.cityActual}
|
||||||
|
icon={Building2}
|
||||||
|
color="text-cyan-500"
|
||||||
|
/>
|
||||||
|
<div className="pt-2 mt-2 border-t">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">层级算力 (7.5%)</span>
|
||||||
|
<span className="font-medium">{formatCompactNumber(dc.level.theory)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">团队奖励 (7.5%)</span>
|
||||||
|
<span className="font-medium">{formatCompactNumber(dc.bonus.theory)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 层级算力详情 */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="text-base flex items-center gap-2">
|
||||||
|
<Layers className="h-5 w-5 text-blue-600" />
|
||||||
|
层级算力详情 (7.5%)
|
||||||
|
</CardTitle>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
理论: {formatCompactNumber(dc.level.theory)} |
|
||||||
|
<span className="text-green-600"> 已解锁: {formatCompactNumber(dc.level.unlocked)}</span> |
|
||||||
|
<span className="text-orange-500"> 待解锁: {formatCompactNumber(dc.level.pending)}</span>
|
||||||
|
</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-1">
|
||||||
|
<div className="text-xs text-muted-foreground mb-2">
|
||||||
|
(已解锁 / 待解锁)
|
||||||
|
</div>
|
||||||
|
<BreakdownRow
|
||||||
|
label="1档 (1-5级, 需1直推)"
|
||||||
|
unlocked={dc.level.tier1.unlocked}
|
||||||
|
pending={dc.level.tier1.pending}
|
||||||
|
icon={Activity}
|
||||||
|
color="text-green-500"
|
||||||
|
/>
|
||||||
|
<BreakdownRow
|
||||||
|
label="2档 (6-10级, 需3直推)"
|
||||||
|
unlocked={dc.level.tier2.unlocked}
|
||||||
|
pending={dc.level.tier2.pending}
|
||||||
|
icon={Activity}
|
||||||
|
color="text-yellow-500"
|
||||||
|
/>
|
||||||
|
<BreakdownRow
|
||||||
|
label="3档 (11-15级, 需5直推)"
|
||||||
|
unlocked={dc.level.tier3.unlocked}
|
||||||
|
pending={dc.level.tier3.pending}
|
||||||
|
icon={Activity}
|
||||||
|
color="text-orange-500"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 团队奖励详情 */}
|
||||||
|
<Card className="lg:col-span-2">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="text-base flex items-center gap-2">
|
||||||
|
<Gift className="h-5 w-5 text-purple-600" />
|
||||||
|
团队奖励详情 (7.5%)
|
||||||
|
</CardTitle>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
理论: {formatCompactNumber(dc.bonus.theory)} |
|
||||||
|
<span className="text-green-600"> 已解锁: {formatCompactNumber(dc.bonus.unlocked)}</span> |
|
||||||
|
<span className="text-orange-500"> 待解锁: {formatCompactNumber(dc.bonus.pending)}</span>
|
||||||
|
</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div className="p-3 bg-green-50 rounded-lg">
|
||||||
|
<div className="text-sm font-medium text-green-800">1档 (2.5%)</div>
|
||||||
|
<div className="text-xs text-green-600 mb-2">条件: 自己认种</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-xs text-muted-foreground">已解锁</span>
|
||||||
|
<span className="text-sm font-medium text-green-700">{formatCompactNumber(dc.bonus.tier1.unlocked)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-xs text-muted-foreground">待解锁</span>
|
||||||
|
<span className="text-sm font-medium text-orange-600">{formatCompactNumber(dc.bonus.tier1.pending)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-yellow-50 rounded-lg">
|
||||||
|
<div className="text-sm font-medium text-yellow-800">2档 (2.5%)</div>
|
||||||
|
<div className="text-xs text-yellow-600 mb-2">条件: 2直推认种</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-xs text-muted-foreground">已解锁</span>
|
||||||
|
<span className="text-sm font-medium text-green-700">{formatCompactNumber(dc.bonus.tier2.unlocked)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-xs text-muted-foreground">待解锁</span>
|
||||||
|
<span className="text-sm font-medium text-orange-600">{formatCompactNumber(dc.bonus.tier2.pending)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-orange-50 rounded-lg">
|
||||||
|
<div className="text-sm font-medium text-orange-800">3档 (2.5%)</div>
|
||||||
|
<div className="text-xs text-orange-600 mb-2">条件: 4直推认种</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-xs text-muted-foreground">已解锁</span>
|
||||||
|
<span className="text-sm font-medium text-green-700">{formatCompactNumber(dc.bonus.tier3.unlocked)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-xs text-muted-foreground">待解锁</span>
|
||||||
|
<span className="text-sm font-medium text-orange-600">{formatCompactNumber(dc.bonus.tier3.pending)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,39 @@
|
||||||
|
export interface TierContribution {
|
||||||
|
unlocked: string;
|
||||||
|
pending: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContributionBreakdown {
|
||||||
|
theory: string;
|
||||||
|
unlocked: string;
|
||||||
|
pending: string;
|
||||||
|
tier1: TierContribution;
|
||||||
|
tier2: TierContribution;
|
||||||
|
tier3: TierContribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DetailedContribution {
|
||||||
|
totalTrees: number;
|
||||||
|
// 全网算力(理论值)
|
||||||
|
networkTotalTheory: string;
|
||||||
|
// 个人算力(70%)
|
||||||
|
personalTheory: string;
|
||||||
|
personalActual: string;
|
||||||
|
// 运营账户(12%)
|
||||||
|
operationTheory: string;
|
||||||
|
operationActual: string;
|
||||||
|
// 省公司(1%)
|
||||||
|
provinceTheory: string;
|
||||||
|
provinceActual: string;
|
||||||
|
// 市公司(2%)
|
||||||
|
cityTheory: string;
|
||||||
|
cityActual: string;
|
||||||
|
// 层级算力(7.5%)
|
||||||
|
level: ContributionBreakdown;
|
||||||
|
// 团队奖励算力(7.5%)
|
||||||
|
bonus: ContributionBreakdown;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DashboardStats {
|
export interface DashboardStats {
|
||||||
totalUsers: number;
|
totalUsers: number;
|
||||||
adoptedUsers: number;
|
adoptedUsers: number;
|
||||||
|
|
@ -13,6 +49,8 @@ export interface DashboardStats {
|
||||||
priceChange24h: number;
|
priceChange24h: number;
|
||||||
totalOrders: number;
|
totalOrders: number;
|
||||||
totalTrades: number;
|
totalTrades: number;
|
||||||
|
// 详细算力分解
|
||||||
|
detailedContribution?: DetailedContribution;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RealtimeData {
|
export interface RealtimeData {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue