diff --git a/backend/services/contribution-service/src/application/queries/get-contribution-stats.query.ts b/backend/services/contribution-service/src/application/queries/get-contribution-stats.query.ts index 9d6b0ad9..f74b8700 100644 --- a/backend/services/contribution-service/src/application/queries/get-contribution-stats.query.ts +++ b/backend/services/contribution-service/src/application/queries/get-contribution-stats.query.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import Decimal from 'decimal.js'; import { ContributionAccountRepository } from '../../infrastructure/persistence/repositories/contribution-account.repository'; import { ContributionRecordRepository } from '../../infrastructure/persistence/repositories/contribution-record.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 { 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 { // 用户统计 totalUsers: number; @@ -16,17 +26,57 @@ export interface ContributionStatsDto { totalAdoptions: number; processedAdoptions: number; unprocessedAdoptions: number; + totalTrees: number; // 算力统计 totalContribution: string; - // 算力分布 + // 算力分布(基础) contributionByType: { personal: string; teamLevel: 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: { accountType: string; @@ -61,6 +111,10 @@ export class GetContributionStatsQuery { systemAccounts, totalUnallocated, unallocatedByType, + detailedStats, + unallocatedByLevelTier, + unallocatedByBonusTier, + totalTrees, ] = await Promise.all([ this.syncedDataRepository.countUsers(), this.accountRepository.countAccounts(), @@ -72,8 +126,33 @@ export class GetContributionStatsQuery { this.systemAccountRepository.findAll(), this.unallocatedRepository.getTotalUnallocated(), 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 { totalUsers, totalAccounts, @@ -81,12 +160,63 @@ export class GetContributionStatsQuery { totalAdoptions, processedAdoptions: totalAdoptions - undistributedAdoptions, unprocessedAdoptions: undistributedAdoptions, + totalTrees, totalContribution: totalContribution.value.toString(), contributionByType: { personal: (contributionByType.get(ContributionSourceType.PERSONAL)?.value || 0).toString(), teamLevel: (contributionByType.get(ContributionSourceType.TEAM_LEVEL)?.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) => ({ accountType: a.accountType, name: a.name, @@ -98,4 +228,5 @@ export class GetContributionStatsQuery { ), }; } + } diff --git a/backend/services/contribution-service/src/infrastructure/persistence/repositories/contribution-account.repository.ts b/backend/services/contribution-service/src/infrastructure/persistence/repositories/contribution-account.repository.ts index 2fb79b86..da93d8da 100644 --- a/backend/services/contribution-service/src/infrastructure/persistence/repositories/contribution-account.repository.ts +++ b/backend/services/contribution-service/src/infrastructure/persistence/repositories/contribution-account.repository.ts @@ -233,6 +233,107 @@ export class ContributionAccountRepository implements IContributionAccountReposi 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 { return ContributionAccountAggregate.fromPersistence({ id: record.id, diff --git a/backend/services/contribution-service/src/infrastructure/persistence/repositories/synced-data.repository.ts b/backend/services/contribution-service/src/infrastructure/persistence/repositories/synced-data.repository.ts index c7c4c15e..3fa94be3 100644 --- a/backend/services/contribution-service/src/infrastructure/persistence/repositories/synced-data.repository.ts +++ b/backend/services/contribution-service/src/infrastructure/persistence/repositories/synced-data.repository.ts @@ -461,6 +461,16 @@ export class SyncedDataRepository implements ISyncedDataRepository { }); } + async getTotalTrees(): Promise { + const result = await this.client.syncedAdoption.aggregate({ + where: { + status: 'MINING_ENABLED', // 只统计最终成功的认种订单 + }, + _sum: { treeCount: true }, + }); + return result._sum.treeCount ?? 0; + } + // ========== 私有方法 ========== private toSyncedUser(record: any): SyncedUser { diff --git a/backend/services/contribution-service/src/infrastructure/persistence/repositories/unallocated-contribution.repository.ts b/backend/services/contribution-service/src/infrastructure/persistence/repositories/unallocated-contribution.repository.ts index 026effa3..8c0cfa01 100644 --- a/backend/services/contribution-service/src/infrastructure/persistence/repositories/unallocated-contribution.repository.ts +++ b/backend/services/contribution-service/src/infrastructure/persistence/repositories/unallocated-contribution.repository.ts @@ -192,6 +192,81 @@ export class UnallocatedContributionRepository { 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 { return { id: record.id, diff --git a/backend/services/mining-admin-service/src/api/controllers/dashboard.controller.ts b/backend/services/mining-admin-service/src/api/controllers/dashboard.controller.ts index 33cc365f..1baafe77 100644 --- a/backend/services/mining-admin-service/src/api/controllers/dashboard.controller.ts +++ b/backend/services/mining-admin-service/src/api/controllers/dashboard.controller.ts @@ -26,15 +26,19 @@ export class DashboardController { priceChange24h = (close - open) / open; } + // 详细算力分解数据 + const dc = raw.detailedContribution || {}; + // 转换为前端期望的格式 return { + // 基础统计 totalUsers: raw.users?.total || 0, adoptedUsers: raw.users?.adopted || 0, totalTrees: raw.contribution?.totalTrees || 0, networkEffectiveContribution: raw.contribution?.effectiveContribution || '0', networkTotalContribution: raw.contribution?.totalContribution || '0', - networkLevelPending: raw.contribution?.teamLevelContribution || '0', - networkBonusPending: raw.contribution?.teamBonusContribution || '0', + networkLevelPending: dc.levelContribution?.pending || '0', + networkBonusPending: dc.bonusContribution?.pending || '0', totalDistributed: raw.mining?.totalMined || '0', totalBurned: raw.mining?.latestDailyStat?.totalBurned || '0', circulationPool: raw.trading?.circulationPool?.totalShares || '0', @@ -42,6 +46,47 @@ export class DashboardController { priceChange24h, totalOrders: 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' }, + }, + }, }; } diff --git a/backend/services/mining-admin-service/src/application/services/dashboard.service.ts b/backend/services/mining-admin-service/src/application/services/dashboard.service.ts index 745b9d4a..82edeb1c 100644 --- a/backend/services/mining-admin-service/src/application/services/dashboard.service.ts +++ b/backend/services/mining-admin-service/src/application/services/dashboard.service.ts @@ -1,7 +1,17 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { Decimal } from 'decimal.js'; 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() export class DashboardService { private readonly logger = new Logger(DashboardService.name); @@ -23,6 +33,7 @@ export class DashboardService { tradingStats, latestReport, latestKLine, + detailedContributionStats, ] = await Promise.all([ this.getUserStats(), this.getContributionStats(), @@ -30,6 +41,7 @@ export class DashboardService { this.getTradingStats(), this.prisma.dailyReport.findFirst({ orderBy: { reportDate: 'desc' } }), this.prisma.syncedDayKLine.findFirst({ orderBy: { klineDate: 'desc' } }), + this.getDetailedContributionStats(), ]); return { @@ -37,6 +49,7 @@ export class DashboardService { contribution: contributionStats, mining: miningStats, trading: tradingStats, + detailedContribution: detailedContributionStats, latestReport: latestReport ? this.formatDailyReport(latestReport) : null, @@ -128,6 +141,7 @@ export class DashboardService { _count: true, }), this.prisma.syncedAdoption.aggregate({ + where: { status: 'MINING_ENABLED' }, _sum: { treeCount: 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' }, + }, + }, + }; + } + /** * 获取挖矿统计 */ diff --git a/frontend/mining-admin-web/src/app/(dashboard)/dashboard/page.tsx b/frontend/mining-admin-web/src/app/(dashboard)/dashboard/page.tsx index 540e6462..379cf841 100644 --- a/frontend/mining-admin-web/src/app/(dashboard)/dashboard/page.tsx +++ b/frontend/mining-admin-web/src/app/(dashboard)/dashboard/page.tsx @@ -4,6 +4,7 @@ import { PageHeader } from '@/components/layout/page-header'; import { StatsCards } from '@/features/dashboard/components/stats-cards'; import { RealtimePanel } from '@/features/dashboard/components/realtime-panel'; import { PriceOverview } from '@/features/dashboard/components/price-overview'; +import { ContributionBreakdown } from '@/features/dashboard/components/contribution-breakdown'; export default function DashboardPage() { return ( @@ -12,6 +13,9 @@ export default function DashboardPage() { + {/* 详细算力分解 */} + +
diff --git a/frontend/mining-admin-web/src/features/dashboard/components/contribution-breakdown.tsx b/frontend/mining-admin-web/src/features/dashboard/components/contribution-breakdown.tsx new file mode 100644 index 00000000..24a598f3 --- /dev/null +++ b/frontend/mining-admin-web/src/features/dashboard/components/contribution-breakdown.tsx @@ -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 ( +
+ + + + + + {[...Array(6)].map((_, i) => ( +
+ + +
+ ))} +
+
+
+ ); +} + +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 ( +
+
+ {Icon && } + {label} +
+
+ {theory && actual && ( +
+ {formatCompactNumber(actual)} + / {formatCompactNumber(theory)} +
+ )} + {unlocked !== undefined && pending !== undefined && ( +
+ {formatCompactNumber(unlocked)} + / + {formatCompactNumber(pending)} +
+ )} +
+
+ ); +} + +export function ContributionBreakdown() { + const { data: stats, isLoading } = useDashboardStats(); + + if (isLoading) { + return ; + } + + const dc = stats?.detailedContribution; + if (!dc) { + return null; + } + + return ( +
+ {/* 基础算力分配 */} + + + + + 算力分配概览 + +

+ 总认种树: {dc.totalTrees} 棵 | 全网理论算力: {formatCompactNumber(dc.networkTotalTheory)} +

+
+ + + + + +
+
+ 层级算力 (7.5%) + {formatCompactNumber(dc.level.theory)} +
+
+ 团队奖励 (7.5%) + {formatCompactNumber(dc.bonus.theory)} +
+
+
+
+ + {/* 层级算力详情 */} + + + + + 层级算力详情 (7.5%) + +

+ 理论: {formatCompactNumber(dc.level.theory)} | + 已解锁: {formatCompactNumber(dc.level.unlocked)} | + 待解锁: {formatCompactNumber(dc.level.pending)} +

+
+ +
+ (已解锁 / 待解锁) +
+ + + +
+
+ + {/* 团队奖励详情 */} + + + + + 团队奖励详情 (7.5%) + +

+ 理论: {formatCompactNumber(dc.bonus.theory)} | + 已解锁: {formatCompactNumber(dc.bonus.unlocked)} | + 待解锁: {formatCompactNumber(dc.bonus.pending)} +

+
+ +
+
+
1档 (2.5%)
+
条件: 自己认种
+
+ 已解锁 + {formatCompactNumber(dc.bonus.tier1.unlocked)} +
+
+ 待解锁 + {formatCompactNumber(dc.bonus.tier1.pending)} +
+
+
+
2档 (2.5%)
+
条件: 2直推认种
+
+ 已解锁 + {formatCompactNumber(dc.bonus.tier2.unlocked)} +
+
+ 待解锁 + {formatCompactNumber(dc.bonus.tier2.pending)} +
+
+
+
3档 (2.5%)
+
条件: 4直推认种
+
+ 已解锁 + {formatCompactNumber(dc.bonus.tier3.unlocked)} +
+
+ 待解锁 + {formatCompactNumber(dc.bonus.tier3.pending)} +
+
+
+
+
+
+ ); +} diff --git a/frontend/mining-admin-web/src/types/dashboard.ts b/frontend/mining-admin-web/src/types/dashboard.ts index 9f674daa..13b33d7c 100644 --- a/frontend/mining-admin-web/src/types/dashboard.ts +++ b/frontend/mining-admin-web/src/types/dashboard.ts @@ -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 { totalUsers: number; adoptedUsers: number; @@ -13,6 +49,8 @@ export interface DashboardStats { priceChange24h: number; totalOrders: number; totalTrades: number; + // 详细算力分解 + detailedContribution?: DetailedContribution; } export interface RealtimeData {