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 { DashboardController } from './controllers/dashboard.controller';
|
||||
import { ConfigController } from './controllers/config.controller';
|
||||
import { InitializationController } from './controllers/initialization.controller';
|
||||
import { AuditController } from './controllers/audit.controller';
|
||||
import { HealthController } from './controllers/health.controller';
|
||||
import { UsersController } from './controllers/users.controller';
|
||||
|
|
@ -16,7 +15,6 @@ import { ReportsController } from './controllers/reports.controller';
|
|||
AuthController,
|
||||
DashboardController,
|
||||
ConfigController,
|
||||
InitializationController,
|
||||
AuditController,
|
||||
HealthController,
|
||||
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 { AuthService } from './services/auth.service';
|
||||
import { ConfigManagementService } from './services/config.service';
|
||||
import { InitializationService } from './services/initialization.service';
|
||||
import { DashboardService } from './services/dashboard.service';
|
||||
import { UsersService } from './services/users.service';
|
||||
import { SystemAccountsService } from './services/system-accounts.service';
|
||||
|
|
@ -13,7 +12,6 @@ import { DailyReportService } from './services/daily-report.service';
|
|||
providers: [
|
||||
AuthService,
|
||||
ConfigManagementService,
|
||||
InitializationService,
|
||||
DashboardService,
|
||||
UsersService,
|
||||
SystemAccountsService,
|
||||
|
|
@ -22,7 +20,6 @@ import { DailyReportService } from './services/daily-report.service';
|
|||
exports: [
|
||||
AuthService,
|
||||
ConfigManagementService,
|
||||
InitializationService,
|
||||
DashboardService,
|
||||
UsersService,
|
||||
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,
|
||||
FileBarChart,
|
||||
ClipboardList,
|
||||
Play,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
|
|
@ -24,7 +23,6 @@ const menuItems = [
|
|||
{ name: '系统账户', href: '/system-accounts', icon: Building2 },
|
||||
{ name: '报表统计', href: '/reports', icon: FileBarChart },
|
||||
{ name: '审计日志', href: '/audit-logs', icon: ClipboardList },
|
||||
{ name: '初始化', href: '/initialization', icon: Play },
|
||||
];
|
||||
|
||||
export function Sidebar() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue