refactor(mining-admin): remove initialization feature
System initialization is now handled by seed scripts and CDC sync, so the manual initialization UI is no longer needed. Removed: - Frontend: initialization page and sidebar menu item - Backend: InitializationController and InitializationService Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
36c3ada6a6
commit
d6064294d7
|
|
@ -3,7 +3,6 @@ import { ApplicationModule } from '../application/application.module';
|
||||||
import { AuthController } from './controllers/auth.controller';
|
import { AuthController } from './controllers/auth.controller';
|
||||||
import { DashboardController } from './controllers/dashboard.controller';
|
import { DashboardController } from './controllers/dashboard.controller';
|
||||||
import { ConfigController } from './controllers/config.controller';
|
import { ConfigController } from './controllers/config.controller';
|
||||||
import { InitializationController } from './controllers/initialization.controller';
|
|
||||||
import { AuditController } from './controllers/audit.controller';
|
import { AuditController } from './controllers/audit.controller';
|
||||||
import { HealthController } from './controllers/health.controller';
|
import { HealthController } from './controllers/health.controller';
|
||||||
import { UsersController } from './controllers/users.controller';
|
import { UsersController } from './controllers/users.controller';
|
||||||
|
|
@ -16,7 +15,6 @@ import { ReportsController } from './controllers/reports.controller';
|
||||||
AuthController,
|
AuthController,
|
||||||
DashboardController,
|
DashboardController,
|
||||||
ConfigController,
|
ConfigController,
|
||||||
InitializationController,
|
|
||||||
AuditController,
|
AuditController,
|
||||||
HealthController,
|
HealthController,
|
||||||
UsersController,
|
UsersController,
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
import { Controller, Post, Body, Req } from '@nestjs/common';
|
|
||||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
|
||||||
import { InitializationService } from '../../application/services/initialization.service';
|
|
||||||
|
|
||||||
class InitMiningConfigDto {
|
|
||||||
totalShares: string;
|
|
||||||
distributionPool: string;
|
|
||||||
halvingPeriodYears: number;
|
|
||||||
burnTarget: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiTags('Initialization')
|
|
||||||
@ApiBearerAuth()
|
|
||||||
@Controller('initialization')
|
|
||||||
export class InitializationController {
|
|
||||||
constructor(private readonly initService: InitializationService) {}
|
|
||||||
|
|
||||||
@Post('mining-config')
|
|
||||||
@ApiOperation({ summary: '初始化挖矿配置' })
|
|
||||||
async initMiningConfig(@Body() dto: InitMiningConfigDto, @Req() req: any) {
|
|
||||||
return this.initService.initializeMiningConfig(req.admin.id, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('system-accounts')
|
|
||||||
@ApiOperation({ summary: '初始化系统账户' })
|
|
||||||
async initSystemAccounts(@Req() req: any) {
|
|
||||||
return this.initService.initializeSystemAccounts(req.admin.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('activate-mining')
|
|
||||||
@ApiOperation({ summary: '激活挖矿' })
|
|
||||||
async activateMining(@Req() req: any) {
|
|
||||||
return this.initService.activateMining(req.admin.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('sync-users')
|
|
||||||
@ApiOperation({ summary: '同步所有用户数据(从auth-service初始同步)' })
|
|
||||||
async syncUsers(@Req() req: any) {
|
|
||||||
return this.initService.syncAllUsers(req.admin.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('sync-contribution-accounts')
|
|
||||||
@ApiOperation({ summary: '同步所有算力账户(从contribution-service初始同步)' })
|
|
||||||
async syncContributionAccounts(@Req() req: any) {
|
|
||||||
return this.initService.syncAllContributionAccounts(req.admin.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('sync-mining-accounts')
|
|
||||||
@ApiOperation({ summary: '同步所有挖矿账户(从mining-service初始同步)' })
|
|
||||||
async syncMiningAccounts(@Req() req: any) {
|
|
||||||
return this.initService.syncAllMiningAccounts(req.admin.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('sync-trading-accounts')
|
|
||||||
@ApiOperation({ summary: '同步所有交易账户(从trading-service初始同步)' })
|
|
||||||
async syncTradingAccounts(@Req() req: any) {
|
|
||||||
return this.initService.syncAllTradingAccounts(req.admin.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('sync-all')
|
|
||||||
@ApiOperation({ summary: '执行完整的数据同步(用户+算力+挖矿+交易)' })
|
|
||||||
async syncAll(@Req() req: any) {
|
|
||||||
const adminId = req.admin.id;
|
|
||||||
const results = {
|
|
||||||
users: await this.initService.syncAllUsers(adminId),
|
|
||||||
contribution: await this.initService.syncAllContributionAccounts(adminId),
|
|
||||||
mining: await this.initService.syncAllMiningAccounts(adminId),
|
|
||||||
trading: await this.initService.syncAllTradingAccounts(adminId),
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: '全部同步完成',
|
|
||||||
details: results,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { Module, OnModuleInit } from '@nestjs/common';
|
||||||
import { InfrastructureModule } from '../infrastructure/infrastructure.module';
|
import { InfrastructureModule } from '../infrastructure/infrastructure.module';
|
||||||
import { AuthService } from './services/auth.service';
|
import { AuthService } from './services/auth.service';
|
||||||
import { ConfigManagementService } from './services/config.service';
|
import { ConfigManagementService } from './services/config.service';
|
||||||
import { InitializationService } from './services/initialization.service';
|
|
||||||
import { DashboardService } from './services/dashboard.service';
|
import { DashboardService } from './services/dashboard.service';
|
||||||
import { UsersService } from './services/users.service';
|
import { UsersService } from './services/users.service';
|
||||||
import { SystemAccountsService } from './services/system-accounts.service';
|
import { SystemAccountsService } from './services/system-accounts.service';
|
||||||
|
|
@ -13,7 +12,6 @@ import { DailyReportService } from './services/daily-report.service';
|
||||||
providers: [
|
providers: [
|
||||||
AuthService,
|
AuthService,
|
||||||
ConfigManagementService,
|
ConfigManagementService,
|
||||||
InitializationService,
|
|
||||||
DashboardService,
|
DashboardService,
|
||||||
UsersService,
|
UsersService,
|
||||||
SystemAccountsService,
|
SystemAccountsService,
|
||||||
|
|
@ -22,7 +20,6 @@ import { DailyReportService } from './services/daily-report.service';
|
||||||
exports: [
|
exports: [
|
||||||
AuthService,
|
AuthService,
|
||||||
ConfigManagementService,
|
ConfigManagementService,
|
||||||
InitializationService,
|
|
||||||
DashboardService,
|
DashboardService,
|
||||||
UsersService,
|
UsersService,
|
||||||
SystemAccountsService,
|
SystemAccountsService,
|
||||||
|
|
|
||||||
|
|
@ -1,304 +0,0 @@
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class InitializationService {
|
|
||||||
private readonly logger = new Logger(InitializationService.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly prisma: PrismaService,
|
|
||||||
private readonly configService: ConfigService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async initializeMiningConfig(
|
|
||||||
adminId: string,
|
|
||||||
config: {
|
|
||||||
totalShares: string;
|
|
||||||
distributionPool: string;
|
|
||||||
halvingPeriodYears: number;
|
|
||||||
burnTarget: string;
|
|
||||||
},
|
|
||||||
): Promise<{ success: boolean; message: string }> {
|
|
||||||
const record = await this.prisma.initializationRecord.create({
|
|
||||||
data: { type: 'MINING_CONFIG', status: 'PENDING', config, executedBy: adminId },
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const miningServiceUrl = this.configService.get<string>('MINING_SERVICE_URL', 'http://localhost:3021');
|
|
||||||
const response = await fetch(`${miningServiceUrl}/api/v1/admin/initialize`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(config),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to initialize mining config');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prisma.initializationRecord.update({
|
|
||||||
where: { id: record.id },
|
|
||||||
data: { status: 'COMPLETED', executedAt: new Date() },
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.prisma.auditLog.create({
|
|
||||||
data: { adminId, action: 'INIT', resource: 'MINING', resourceId: record.id, newValue: config },
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, message: 'Mining config initialized successfully' };
|
|
||||||
} catch (error: any) {
|
|
||||||
await this.prisma.initializationRecord.update({
|
|
||||||
where: { id: record.id },
|
|
||||||
data: { status: 'FAILED', errorMessage: error.message },
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: false, message: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async initializeSystemAccounts(adminId: string): Promise<{ success: boolean; message: string }> {
|
|
||||||
const accounts = [
|
|
||||||
{ accountType: 'OPERATION', name: '运营账户', description: '12% 运营收入' },
|
|
||||||
{ accountType: 'PROVINCE', name: '省公司账户', description: '1% 省公司收入' },
|
|
||||||
{ accountType: 'CITY', name: '市公司账户', description: '2% 市公司收入' },
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const account of accounts) {
|
|
||||||
await this.prisma.systemAccount.upsert({
|
|
||||||
where: { accountType: account.accountType },
|
|
||||||
create: account,
|
|
||||||
update: { name: account.name, description: account.description },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prisma.auditLog.create({
|
|
||||||
data: { adminId, action: 'INIT', resource: 'SYSTEM_ACCOUNT', newValue: accounts },
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, message: 'System accounts initialized successfully' };
|
|
||||||
}
|
|
||||||
|
|
||||||
async activateMining(adminId: string): Promise<{ success: boolean; message: string }> {
|
|
||||||
try {
|
|
||||||
const miningServiceUrl = this.configService.get<string>('MINING_SERVICE_URL', 'http://localhost:3021');
|
|
||||||
const response = await fetch(`${miningServiceUrl}/api/v1/admin/activate`, { method: 'POST' });
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to activate mining');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prisma.auditLog.create({
|
|
||||||
data: { adminId, action: 'INIT', resource: 'MINING', newValue: { action: 'ACTIVATE' } },
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, message: 'Mining activated successfully' };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, message: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncAllUsers(adminId: string): Promise<{ success: boolean; message: string; syncedCount?: number }> {
|
|
||||||
try {
|
|
||||||
const authServiceUrl = this.configService.get<string>('AUTH_SERVICE_URL', 'http://localhost:3024');
|
|
||||||
const response = await fetch(`${authServiceUrl}/api/v2/admin/users/sync`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch users: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseData = await response.json();
|
|
||||||
const users = responseData.data?.users || responseData.users || [];
|
|
||||||
let syncedCount = 0;
|
|
||||||
|
|
||||||
for (const user of users) {
|
|
||||||
try {
|
|
||||||
await this.prisma.syncedUser.upsert({
|
|
||||||
where: { accountSequence: user.accountSequence },
|
|
||||||
create: {
|
|
||||||
originalUserId: user.id || user.accountSequence,
|
|
||||||
accountSequence: user.accountSequence,
|
|
||||||
phone: user.phone,
|
|
||||||
status: user.status || 'ACTIVE',
|
|
||||||
kycStatus: user.kycStatus || 'PENDING',
|
|
||||||
realName: user.realName || null,
|
|
||||||
isLegacyUser: user.isLegacyUser || false,
|
|
||||||
createdAt: new Date(user.createdAt),
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
phone: user.phone,
|
|
||||||
status: user.status || 'ACTIVE',
|
|
||||||
kycStatus: user.kycStatus || 'PENDING',
|
|
||||||
realName: user.realName || null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
syncedCount++;
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.warn(`Failed to sync user ${user.accountSequence}: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prisma.auditLog.create({
|
|
||||||
data: { adminId, action: 'SYNC', resource: 'USER', newValue: { syncedCount } },
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, message: `Synced ${syncedCount} users`, syncedCount };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, message: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncAllContributionAccounts(adminId: string): Promise<{ success: boolean; message: string; syncedCount?: number }> {
|
|
||||||
try {
|
|
||||||
const contributionServiceUrl = this.configService.get<string>('CONTRIBUTION_SERVICE_URL', 'http://localhost:3020');
|
|
||||||
const response = await fetch(`${contributionServiceUrl}/api/v2/admin/accounts/sync`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch accounts: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseData = await response.json();
|
|
||||||
const accounts = responseData.data?.accounts || responseData.accounts || [];
|
|
||||||
let syncedCount = 0;
|
|
||||||
|
|
||||||
for (const account of accounts) {
|
|
||||||
try {
|
|
||||||
await this.prisma.syncedContributionAccount.upsert({
|
|
||||||
where: { accountSequence: account.accountSequence },
|
|
||||||
create: {
|
|
||||||
accountSequence: account.accountSequence,
|
|
||||||
personalContribution: account.personalContribution || 0,
|
|
||||||
teamLevelContribution: account.teamLevelContribution || 0,
|
|
||||||
teamBonusContribution: account.teamBonusContribution || 0,
|
|
||||||
totalContribution: account.totalContribution || 0,
|
|
||||||
effectiveContribution: account.effectiveContribution || 0,
|
|
||||||
hasAdopted: account.hasAdopted || false,
|
|
||||||
directReferralCount: account.directReferralAdoptedCount || 0,
|
|
||||||
unlockedLevelDepth: account.unlockedLevelDepth || 0,
|
|
||||||
unlockedBonusTiers: account.unlockedBonusTiers || 0,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
personalContribution: account.personalContribution,
|
|
||||||
teamLevelContribution: account.teamLevelContribution,
|
|
||||||
teamBonusContribution: account.teamBonusContribution,
|
|
||||||
totalContribution: account.totalContribution,
|
|
||||||
effectiveContribution: account.effectiveContribution,
|
|
||||||
hasAdopted: account.hasAdopted,
|
|
||||||
directReferralCount: account.directReferralAdoptedCount,
|
|
||||||
unlockedLevelDepth: account.unlockedLevelDepth,
|
|
||||||
unlockedBonusTiers: account.unlockedBonusTiers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
syncedCount++;
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.warn(`Failed to sync account ${account.accountSequence}: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prisma.auditLog.create({
|
|
||||||
data: { adminId, action: 'SYNC', resource: 'CONTRIBUTION_ACCOUNT', newValue: { syncedCount } },
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, message: `Synced ${syncedCount} accounts`, syncedCount };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, message: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncAllMiningAccounts(adminId: string): Promise<{ success: boolean; message: string; syncedCount?: number }> {
|
|
||||||
try {
|
|
||||||
const miningServiceUrl = this.configService.get<string>('MINING_SERVICE_URL', 'http://localhost:3021');
|
|
||||||
const response = await fetch(`${miningServiceUrl}/api/v1/admin/accounts/sync`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch accounts: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseData = await response.json();
|
|
||||||
const accounts = responseData.data?.accounts || responseData.accounts || [];
|
|
||||||
let syncedCount = 0;
|
|
||||||
|
|
||||||
for (const account of accounts) {
|
|
||||||
try {
|
|
||||||
await this.prisma.syncedMiningAccount.upsert({
|
|
||||||
where: { accountSequence: account.accountSequence },
|
|
||||||
create: {
|
|
||||||
accountSequence: account.accountSequence,
|
|
||||||
totalMined: account.totalMined || 0,
|
|
||||||
availableBalance: account.availableBalance || 0,
|
|
||||||
frozenBalance: account.frozenBalance || 0,
|
|
||||||
totalContribution: account.totalContribution || 0,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
totalMined: account.totalMined,
|
|
||||||
availableBalance: account.availableBalance,
|
|
||||||
frozenBalance: account.frozenBalance,
|
|
||||||
totalContribution: account.totalContribution,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
syncedCount++;
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.warn(`Failed to sync mining account ${account.accountSequence}: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prisma.auditLog.create({
|
|
||||||
data: { adminId, action: 'SYNC', resource: 'MINING_ACCOUNT', newValue: { syncedCount } },
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, message: `Synced ${syncedCount} mining accounts`, syncedCount };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, message: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncAllTradingAccounts(adminId: string): Promise<{ success: boolean; message: string; syncedCount?: number }> {
|
|
||||||
try {
|
|
||||||
const tradingServiceUrl = this.configService.get<string>('TRADING_SERVICE_URL', 'http://localhost:3022');
|
|
||||||
const response = await fetch(`${tradingServiceUrl}/api/v1/admin/accounts/sync`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch accounts: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseData = await response.json();
|
|
||||||
const accounts = responseData.data?.accounts || responseData.accounts || [];
|
|
||||||
let syncedCount = 0;
|
|
||||||
|
|
||||||
for (const account of accounts) {
|
|
||||||
try {
|
|
||||||
await this.prisma.syncedTradingAccount.upsert({
|
|
||||||
where: { accountSequence: account.accountSequence },
|
|
||||||
create: {
|
|
||||||
accountSequence: account.accountSequence,
|
|
||||||
shareBalance: account.shareBalance || 0,
|
|
||||||
cashBalance: account.cashBalance || 0,
|
|
||||||
frozenShares: account.frozenShares || 0,
|
|
||||||
frozenCash: account.frozenCash || 0,
|
|
||||||
totalBought: account.totalBought || 0,
|
|
||||||
totalSold: account.totalSold || 0,
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
shareBalance: account.shareBalance,
|
|
||||||
cashBalance: account.cashBalance,
|
|
||||||
frozenShares: account.frozenShares,
|
|
||||||
frozenCash: account.frozenCash,
|
|
||||||
totalBought: account.totalBought,
|
|
||||||
totalSold: account.totalSold,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
syncedCount++;
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.warn(`Failed to sync trading account ${account.accountSequence}: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.prisma.auditLog.create({
|
|
||||||
data: { adminId, action: 'SYNC', resource: 'TRADING_ACCOUNT', newValue: { syncedCount } },
|
|
||||||
});
|
|
||||||
|
|
||||||
return { success: true, message: `Synced ${syncedCount} trading accounts`, syncedCount };
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, message: error.message };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,168 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { PageHeader } from '@/components/layout/page-header';
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
||||||
import { apiClient } from '@/lib/api/client';
|
|
||||||
import { formatDateTime } from '@/lib/utils/date';
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
|
||||||
import { useToast } from '@/lib/hooks/use-toast';
|
|
||||||
import { Play, CheckCircle, AlertCircle, Loader2 } from 'lucide-react';
|
|
||||||
|
|
||||||
interface InitializationStatus {
|
|
||||||
initialized: boolean;
|
|
||||||
initializedAt: string | null;
|
|
||||||
initializedBy: string | null;
|
|
||||||
distributionPoolBalance: string;
|
|
||||||
blackHoleBalance: string;
|
|
||||||
circulationPoolBalance: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function InitializationPage() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { toast } = useToast();
|
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
|
||||||
|
|
||||||
const { data: status, isLoading } = useQuery({
|
|
||||||
queryKey: ['initialization', 'status'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const response = await apiClient.get('/initialization/status');
|
|
||||||
return response.data.data as InitializationStatus;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const initializeMutation = useMutation({
|
|
||||||
mutationFn: async () => {
|
|
||||||
const response = await apiClient.post('/initialization/initialize');
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['initialization', 'status'] });
|
|
||||||
toast({ title: '初始化成功', variant: 'success' as any });
|
|
||||||
setShowConfirm(false);
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
toast({ title: '初始化失败', variant: 'destructive' });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleInitialize = () => {
|
|
||||||
initializeMutation.mutate();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<PageHeader title="系统初始化" description="初始化挖矿系统的基础数据" />
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">初始化状态</CardTitle>
|
|
||||||
<CardDescription>查看系统是否已完成初始化</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{isLoading ? (
|
|
||||||
<Skeleton className="h-24 w-full" />
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
{status?.initialized ? (
|
|
||||||
<>
|
|
||||||
<CheckCircle className="h-8 w-8 text-green-500" />
|
|
||||||
<div>
|
|
||||||
<p className="font-medium text-green-600">系统已初始化</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
初始化时间: {formatDateTime(status.initializedAt)}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-muted-foreground">操作人: {status.initializedBy}</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<AlertCircle className="h-8 w-8 text-yellow-500" />
|
|
||||||
<div>
|
|
||||||
<p className="font-medium text-yellow-600">系统未初始化</p>
|
|
||||||
<p className="text-sm text-muted-foreground">请点击下方按钮进行系统初始化</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!status?.initialized && (
|
|
||||||
<Button onClick={() => setShowConfirm(true)} className="mt-4">
|
|
||||||
<Play className="h-4 w-4 mr-2" />
|
|
||||||
开始初始化
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{status?.initialized && (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">系统账户余额</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-3 gap-6">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">分配池</p>
|
|
||||||
<p className="text-xl font-bold font-mono">{status.distributionPoolBalance}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">黑洞</p>
|
|
||||||
<p className="text-xl font-bold font-mono">{status.blackHoleBalance}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">流通池</p>
|
|
||||||
<p className="text-xl font-bold font-mono">{status.circulationPoolBalance}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">初始化说明</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="prose prose-sm max-w-none">
|
|
||||||
<p>系统初始化将执行以下操作:</p>
|
|
||||||
<ol className="list-decimal list-inside space-y-2 text-muted-foreground">
|
|
||||||
<li>创建分配池账户,初始余额为 2 亿积分股</li>
|
|
||||||
<li>创建黑洞账户,初始余额为 0</li>
|
|
||||||
<li>创建流通池账户,初始余额为 0</li>
|
|
||||||
<li>创建系统运营账户 (12% 分配比例)</li>
|
|
||||||
<li>创建系统省级账户 (1% 分配比例)</li>
|
|
||||||
<li>创建系统市级账户 (2% 分配比例)</li>
|
|
||||||
<li>初始化系统配置参数</li>
|
|
||||||
</ol>
|
|
||||||
<p className="text-yellow-600 mt-4">注意: 初始化操作只能执行一次,请谨慎操作。</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Dialog open={showConfirm} onOpenChange={setShowConfirm}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>确认初始化</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
系统初始化操作只能执行一次,确定要开始初始化吗?
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={() => setShowConfirm(false)}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleInitialize} disabled={initializeMutation.isPending}>
|
|
||||||
{initializeMutation.isPending && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
|
||||||
确认初始化
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
Building2,
|
Building2,
|
||||||
FileBarChart,
|
FileBarChart,
|
||||||
ClipboardList,
|
ClipboardList,
|
||||||
Play,
|
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
@ -24,7 +23,6 @@ const menuItems = [
|
||||||
{ name: '系统账户', href: '/system-accounts', icon: Building2 },
|
{ name: '系统账户', href: '/system-accounts', icon: Building2 },
|
||||||
{ name: '报表统计', href: '/reports', icon: FileBarChart },
|
{ name: '报表统计', href: '/reports', icon: FileBarChart },
|
||||||
{ name: '审计日志', href: '/audit-logs', icon: ClipboardList },
|
{ name: '审计日志', href: '/audit-logs', icon: ClipboardList },
|
||||||
{ name: '初始化', href: '/initialization', icon: Play },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue