feat(batch-mining): 按阶段创建补发记录并添加用户查询功能
- 修改BatchMiningRecord表结构,添加phase和daysInPhase字段 - 修改execute函数,按阶段为每个用户创建记录 - 添加用户批量补发记录查询API - mining-admin-web用户详情页添加"批量补发"Tab Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f44af3a2ed
commit
8a47659c47
|
|
@ -141,4 +141,17 @@ export class UsersController {
|
|||
) {
|
||||
return this.usersService.getWalletLedger(accountSequence, page ?? 1, pageSize ?? 20);
|
||||
}
|
||||
|
||||
@Get(':accountSequence/batch-mining-records')
|
||||
@ApiOperation({ summary: '获取用户批量补发记录' })
|
||||
@ApiParam({ name: 'accountSequence', type: String })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
async getBatchMiningRecords(
|
||||
@Param('accountSequence') accountSequence: string,
|
||||
@Query('page') page?: number,
|
||||
@Query('pageSize') pageSize?: number,
|
||||
) {
|
||||
return this.usersService.getBatchMiningRecords(accountSequence, page ?? 1, pageSize ?? 20);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -904,6 +904,73 @@ export class UsersService {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户批量补发记录(从 mining-service 获取)
|
||||
*/
|
||||
async getBatchMiningRecords(
|
||||
accountSequence: string,
|
||||
page: number,
|
||||
pageSize: number,
|
||||
) {
|
||||
const user = await this.prisma.syncedUser.findUnique({
|
||||
where: { accountSequence },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException(`用户 ${accountSequence} 不存在`);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${this.miningServiceUrl}/api/v2/admin/batch-mining/records/${accountSequence}?page=${page}&pageSize=${pageSize}`;
|
||||
this.logger.log(`Fetching batch mining records from ${url}`);
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
return {
|
||||
records: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: 0,
|
||||
totalAmount: '0',
|
||||
};
|
||||
}
|
||||
this.logger.warn(`Failed to fetch batch mining records: ${response.status}`);
|
||||
return {
|
||||
records: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: 0,
|
||||
totalAmount: '0',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const data = result.data || result;
|
||||
|
||||
return {
|
||||
records: data.records || [],
|
||||
total: data.total || 0,
|
||||
page: data.page || page,
|
||||
pageSize: data.pageSize || pageSize,
|
||||
totalPages: Math.ceil((data.total || 0) / pageSize),
|
||||
totalAmount: data.totalAmount || '0',
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to fetch batch mining records from mining-service', error);
|
||||
return {
|
||||
records: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: 0,
|
||||
totalAmount: '0',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户钱包流水
|
||||
* 从 SyncedUserWallet 获取钱包汇总,从 SyncedMiningAccount 获取挖矿余额
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
-- AlterTable: 添加 phase 和 days_in_phase 字段到 batch_mining_records
|
||||
ALTER TABLE "batch_mining_records" ADD COLUMN "phase" INTEGER NOT NULL DEFAULT 1;
|
||||
ALTER TABLE "batch_mining_records" ADD COLUMN "days_in_phase" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- DropIndex: 删除旧的唯一约束
|
||||
DROP INDEX IF EXISTS "batch_mining_records_execution_id_account_sequence_key";
|
||||
|
||||
-- CreateIndex: 创建新的唯一约束(包含 phase)
|
||||
CREATE UNIQUE INDEX "batch_mining_records_execution_id_account_sequence_phase_key" ON "batch_mining_records"("execution_id", "account_sequence", "phase");
|
||||
|
||||
-- CreateIndex: 为 phase 创建索引
|
||||
CREATE INDEX "batch_mining_records_phase_idx" ON "batch_mining_records"("phase");
|
||||
|
||||
-- 移除默认值(可选,因为新数据会明确指定值)
|
||||
ALTER TABLE "batch_mining_records" ALTER COLUMN "phase" DROP DEFAULT;
|
||||
ALTER TABLE "batch_mining_records" ALTER COLUMN "days_in_phase" DROP DEFAULT;
|
||||
|
|
@ -615,29 +615,32 @@ model BatchMiningExecution {
|
|||
@@map("batch_mining_executions")
|
||||
}
|
||||
|
||||
// 批量补发明细记录
|
||||
// 批量补发明细记录(每个用户每个阶段一条记录)
|
||||
model BatchMiningRecord {
|
||||
id String @id @default(uuid())
|
||||
executionId String @map("execution_id")
|
||||
accountSequence String @map("account_sequence")
|
||||
batch Int // 批次号
|
||||
batch Int // 用户所属批次号
|
||||
phase Int // 挖矿阶段号(1,2,3...)
|
||||
treeCount Int @map("tree_count") // 认种棵数
|
||||
preMineDays Int @map("pre_mine_days") // 提前挖的天数
|
||||
daysInPhase Int @map("days_in_phase") // 该阶段挖矿天数
|
||||
preMineDays Int @map("pre_mine_days") // 提前挖的天数(原始数据)
|
||||
|
||||
// 计算参数快照
|
||||
userContribution Decimal @map("user_contribution") @db.Decimal(30, 10) // 用户算力 (70%)
|
||||
networkContribution Decimal @map("network_contribution") @db.Decimal(30, 10) // 当时全网算力
|
||||
userContribution Decimal @map("user_contribution") @db.Decimal(30, 10) // 用户在该阶段的算力
|
||||
networkContribution Decimal @map("network_contribution") @db.Decimal(30, 10) // 该阶段全网参与算力
|
||||
contributionRatio Decimal @map("contribution_ratio") @db.Decimal(30, 18) // 算力占比
|
||||
totalSeconds BigInt @map("total_seconds") // 补发总秒数
|
||||
amount Decimal @db.Decimal(30, 8) // 补发金额
|
||||
totalSeconds BigInt @map("total_seconds") // 该阶段补发总秒数
|
||||
amount Decimal @db.Decimal(30, 8) // 该阶段补发金额
|
||||
|
||||
remark String? @db.Text
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
execution BatchMiningExecution @relation(fields: [executionId], references: [id])
|
||||
|
||||
@@unique([executionId, accountSequence])
|
||||
@@unique([executionId, accountSequence, phase])
|
||||
@@index([batch])
|
||||
@@index([phase])
|
||||
@@index([accountSequence])
|
||||
@@map("batch_mining_records")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -775,4 +775,61 @@ export class AdminController {
|
|||
}
|
||||
return execution;
|
||||
}
|
||||
|
||||
@Get('batch-mining/records/:accountSequence')
|
||||
@Public()
|
||||
@ApiOperation({ summary: '获取用户的批量补发记录' })
|
||||
@ApiParam({ name: 'accountSequence', type: String, description: '用户账户序列号' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number })
|
||||
async getUserBatchMiningRecords(
|
||||
@Param('accountSequence') accountSequence: string,
|
||||
@Query('page') page?: number,
|
||||
@Query('pageSize') pageSize?: number,
|
||||
) {
|
||||
const pageNum = page ?? 1;
|
||||
const pageSizeNum = pageSize ?? 20;
|
||||
const skip = (pageNum - 1) * pageSizeNum;
|
||||
|
||||
const [records, total] = await Promise.all([
|
||||
this.prisma.batchMiningRecord.findMany({
|
||||
where: { accountSequence },
|
||||
orderBy: [{ phase: 'asc' }],
|
||||
skip,
|
||||
take: pageSizeNum,
|
||||
}),
|
||||
this.prisma.batchMiningRecord.count({
|
||||
where: { accountSequence },
|
||||
}),
|
||||
]);
|
||||
|
||||
// 计算该用户的总补发金额
|
||||
const totalAmount = await this.prisma.batchMiningRecord.aggregate({
|
||||
where: { accountSequence },
|
||||
_sum: { amount: true },
|
||||
});
|
||||
|
||||
return {
|
||||
records: records.map((r) => ({
|
||||
id: r.id,
|
||||
accountSequence: r.accountSequence,
|
||||
batch: r.batch,
|
||||
phase: r.phase,
|
||||
treeCount: r.treeCount,
|
||||
daysInPhase: r.daysInPhase,
|
||||
preMineDays: r.preMineDays,
|
||||
userContribution: r.userContribution.toString(),
|
||||
networkContribution: r.networkContribution.toString(),
|
||||
contributionRatio: r.contributionRatio.toString(),
|
||||
totalSeconds: r.totalSeconds.toString(),
|
||||
amount: r.amount.toString(),
|
||||
remark: r.remark,
|
||||
createdAt: r.createdAt,
|
||||
})),
|
||||
total,
|
||||
page: pageNum,
|
||||
pageSize: pageSizeNum,
|
||||
totalAmount: totalAmount._sum.amount?.toString() || '0',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -569,6 +569,47 @@ export class BatchMiningService {
|
|||
let failedCount = 0;
|
||||
let totalAmount = new Decimal(0);
|
||||
|
||||
// 计算每个用户在每个阶段的收益(用于创建阶段记录)
|
||||
// key: `${accountSequence}-${phaseNumber}`, value: { amount, userPhaseContribution, phaseContribution, daysInPhase }
|
||||
const userPhaseAmounts = new Map<string, {
|
||||
amount: Decimal;
|
||||
userPhaseContribution: Decimal;
|
||||
phaseContribution: Decimal;
|
||||
daysInPhase: number;
|
||||
}>();
|
||||
|
||||
for (const phase of phases) {
|
||||
const phaseAllocation = dailyAllocation.times(phase.daysInPhase);
|
||||
const processedInPhase = new Set<string>();
|
||||
|
||||
for (const item of items) {
|
||||
if (phase.participatingBatches.includes(item.batch) && !processedInPhase.has(item.accountSequence)) {
|
||||
processedInPhase.add(item.accountSequence);
|
||||
|
||||
// 计算用户在该阶段参与的批次中的总算力
|
||||
let userPhaseContribution = new Decimal(0);
|
||||
for (const batch of phase.participatingBatches) {
|
||||
const key = `${item.accountSequence}-${batch}`;
|
||||
const batchContrib = userBatchContributions.get(key);
|
||||
if (batchContrib) {
|
||||
userPhaseContribution = userPhaseContribution.plus(batchContrib);
|
||||
}
|
||||
}
|
||||
|
||||
const ratio = userPhaseContribution.dividedBy(phase.participatingContribution);
|
||||
const phaseAmount = phaseAllocation.times(ratio);
|
||||
|
||||
const phaseKey = `${item.accountSequence}-${phase.phaseNumber}`;
|
||||
userPhaseAmounts.set(phaseKey, {
|
||||
amount: phaseAmount,
|
||||
userPhaseContribution,
|
||||
phaseContribution: phase.participatingContribution,
|
||||
daysInPhase: phase.daysInPhase,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用事务执行所有操作
|
||||
const batchId = await this.prisma.$transaction(async (tx) => {
|
||||
// 1. 创建批量执行记录(用于防重复)
|
||||
|
|
@ -583,13 +624,18 @@ export class BatchMiningService {
|
|||
},
|
||||
});
|
||||
|
||||
// 2. 处理每个用户
|
||||
// 2. 处理每个用户(创建账户、更新余额、创建交易记录)
|
||||
const processedUsers = new Set<string>();
|
||||
for (const item of items) {
|
||||
// 每个用户只处理一次(账户和交易记录)
|
||||
if (processedUsers.has(item.accountSequence)) {
|
||||
continue;
|
||||
}
|
||||
processedUsers.add(item.accountSequence);
|
||||
|
||||
try {
|
||||
const userContribution = userContributions.get(item.accountSequence)!;
|
||||
const amount = userAmounts.get(item.accountSequence)!;
|
||||
const ratio = userContribution.dividedBy(totalUserContribution);
|
||||
const totalSeconds = BigInt(item.preMineDays * SECONDS_PER_DAY);
|
||||
const manualAmount = new ShareAmount(amount);
|
||||
|
||||
// 查找或创建挖矿账户
|
||||
|
|
@ -598,14 +644,13 @@ export class BatchMiningService {
|
|||
});
|
||||
|
||||
if (!account) {
|
||||
// 创建新账户
|
||||
account = await tx.miningAccount.create({
|
||||
data: {
|
||||
accountSequence: item.accountSequence,
|
||||
totalMined: new Decimal(0),
|
||||
availableBalance: new Decimal(0),
|
||||
frozenBalance: new Decimal(0),
|
||||
totalContribution: userContribution, // 设置初始算力
|
||||
totalContribution: userContribution,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -620,13 +665,13 @@ export class BatchMiningService {
|
|||
data: {
|
||||
totalMined: totalMinedAfter,
|
||||
availableBalance: balanceAfter,
|
||||
totalContribution: userContribution, // 同时更新算力
|
||||
totalContribution: userContribution,
|
||||
updatedAt: now,
|
||||
},
|
||||
});
|
||||
|
||||
// 创建明细记录
|
||||
const description = `批量补发挖矿收益 - 批次:${item.batch} - 认种棵数:${item.treeCount} - 提前挖${item.preMineDays}天 - 操作人:${operatorName} - ${reason}`;
|
||||
// 创建交易明细记录(总金额)
|
||||
const description = `批量补发挖矿收益 - 批次:${item.batch} - 认种棵数:${item.treeCount} - 共${phases.length}个阶段 - 操作人:${operatorName} - ${reason}`;
|
||||
|
||||
await tx.miningTransaction.create({
|
||||
data: {
|
||||
|
|
@ -641,24 +686,40 @@ export class BatchMiningService {
|
|||
},
|
||||
});
|
||||
|
||||
// 创建批量补发明细记录
|
||||
await tx.batchMiningRecord.create({
|
||||
data: {
|
||||
executionId: execution.id,
|
||||
accountSequence: item.accountSequence,
|
||||
batch: item.batch,
|
||||
treeCount: item.treeCount,
|
||||
preMineDays: item.preMineDays,
|
||||
userContribution,
|
||||
networkContribution: totalUserContribution,
|
||||
contributionRatio: ratio,
|
||||
totalSeconds,
|
||||
amount: manualAmount.value,
|
||||
remark: item.remark,
|
||||
},
|
||||
});
|
||||
// 3. 为该用户创建每个阶段的明细记录
|
||||
for (const phase of phases) {
|
||||
// 检查该用户是否参与此阶段
|
||||
if (!phase.participatingBatches.includes(item.batch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 发布事件到 Kafka
|
||||
const phaseKey = `${item.accountSequence}-${phase.phaseNumber}`;
|
||||
const phaseData = userPhaseAmounts.get(phaseKey);
|
||||
if (!phaseData) continue;
|
||||
|
||||
const ratio = phaseData.userPhaseContribution.dividedBy(phaseData.phaseContribution);
|
||||
const totalSeconds = BigInt(phaseData.daysInPhase * SECONDS_PER_DAY);
|
||||
|
||||
await tx.batchMiningRecord.create({
|
||||
data: {
|
||||
executionId: execution.id,
|
||||
accountSequence: item.accountSequence,
|
||||
batch: item.batch,
|
||||
phase: phase.phaseNumber,
|
||||
treeCount: item.treeCount,
|
||||
daysInPhase: phaseData.daysInPhase,
|
||||
preMineDays: item.preMineDays,
|
||||
userContribution: phaseData.userPhaseContribution,
|
||||
networkContribution: phaseData.phaseContribution,
|
||||
contributionRatio: ratio,
|
||||
totalSeconds,
|
||||
amount: phaseData.amount,
|
||||
remark: `阶段${phase.phaseNumber}: ${phaseData.daysInPhase}天, 参与批次[${phase.participatingBatches.join(',')}]`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 发布事件到 Kafka(每个用户一个事件,包含总金额)
|
||||
await tx.outboxEvent.create({
|
||||
data: {
|
||||
aggregateType: 'BatchMining',
|
||||
|
|
@ -673,11 +734,8 @@ export class BatchMiningService {
|
|||
batch: item.batch,
|
||||
amount: manualAmount.value.toString(),
|
||||
treeCount: item.treeCount,
|
||||
preMineDays: item.preMineDays,
|
||||
totalPhases: phases.length,
|
||||
userContribution: userContribution.toString(),
|
||||
networkContribution: totalUserContribution.toString(),
|
||||
contributionRatio: ratio.toString(),
|
||||
totalSeconds: totalSeconds.toString(),
|
||||
operatorId,
|
||||
operatorName,
|
||||
reason,
|
||||
|
|
@ -686,6 +744,7 @@ export class BatchMiningService {
|
|||
},
|
||||
});
|
||||
|
||||
const ratio = userContribution.dividedBy(totalUserContribution);
|
||||
results.push({
|
||||
accountSequence: item.accountSequence,
|
||||
batch: item.batch,
|
||||
|
|
@ -694,7 +753,7 @@ export class BatchMiningService {
|
|||
networkContribution: totalUserContribution.toFixed(10),
|
||||
contributionRatio: ratio.toFixed(18),
|
||||
preMineDays: item.preMineDays,
|
||||
totalSeconds: totalSeconds.toString(),
|
||||
totalSeconds: (phases.length * SECONDS_PER_DAY).toString(),
|
||||
amount: manualAmount.value.toFixed(8),
|
||||
success: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ import { TradeOrdersList } from '@/features/users/components/trade-orders-list';
|
|||
import { ReferralTree } from '@/features/users/components/referral-tree';
|
||||
import { PlantingLedger } from '@/features/users/components/planting-ledger';
|
||||
import { WalletLedger } from '@/features/users/components/wallet-ledger';
|
||||
import { Users, TreePine, Wallet, Zap, ShoppingCart, Network, Coins } from 'lucide-react';
|
||||
import { BatchMiningRecordsList } from '@/features/users/components/batch-mining-records-list';
|
||||
import { Users, TreePine, Wallet, Zap, ShoppingCart, Network, Coins, Gift } from 'lucide-react';
|
||||
|
||||
function UserDetailSkeleton() {
|
||||
return (
|
||||
|
|
@ -353,7 +354,7 @@ export default function UserDetailPage() {
|
|||
|
||||
{/* Tab 区域 */}
|
||||
<Tabs defaultValue="contributions">
|
||||
<TabsList className="grid w-full grid-cols-6">
|
||||
<TabsList className="grid w-full grid-cols-7">
|
||||
<TabsTrigger value="contributions" className="flex items-center gap-1">
|
||||
<Zap className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">算力记录</span>
|
||||
|
|
@ -374,6 +375,10 @@ export default function UserDetailPage() {
|
|||
<Coins className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">挖矿记录</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="batch-mining" className="flex items-center gap-1">
|
||||
<Gift className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">批量补发</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="trading" className="flex items-center gap-1">
|
||||
<ShoppingCart className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">交易订单</span>
|
||||
|
|
@ -400,6 +405,10 @@ export default function UserDetailPage() {
|
|||
<MiningRecordsList accountSequence={accountSequence} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="batch-mining" className="mt-4">
|
||||
<BatchMiningRecordsList accountSequence={accountSequence} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="trading" className="mt-4">
|
||||
<TradeOrdersList accountSequence={accountSequence} />
|
||||
</TabsContent>
|
||||
|
|
|
|||
|
|
@ -172,4 +172,37 @@ export const usersApi = {
|
|||
const response = await apiClient.get(`/users/${accountSequence}/wallet-ledger`, { params });
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
getBatchMiningRecords: async (
|
||||
accountSequence: string,
|
||||
params: PaginationParams
|
||||
): Promise<{
|
||||
records: BatchMiningRecord[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
totalAmount: string;
|
||||
}> => {
|
||||
const response = await apiClient.get(`/users/${accountSequence}/batch-mining-records`, { params });
|
||||
return response.data.data;
|
||||
},
|
||||
};
|
||||
|
||||
// 批量补发记录类型
|
||||
export interface BatchMiningRecord {
|
||||
id: string;
|
||||
accountSequence: string;
|
||||
batch: number;
|
||||
phase: number;
|
||||
treeCount: number;
|
||||
daysInPhase: number;
|
||||
preMineDays: number;
|
||||
userContribution: string;
|
||||
networkContribution: string;
|
||||
contributionRatio: string;
|
||||
totalSeconds: string;
|
||||
amount: string;
|
||||
remark: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useBatchMiningRecords } from '../hooks/use-users';
|
||||
import { formatDecimal, formatPercent } from '@/lib/utils/format';
|
||||
import { formatDateTime } from '@/lib/utils/date';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { ChevronLeft, ChevronRight, Gift } from 'lucide-react';
|
||||
|
||||
interface BatchMiningRecordsListProps {
|
||||
accountSequence: string;
|
||||
}
|
||||
|
||||
export function BatchMiningRecordsList({ accountSequence }: BatchMiningRecordsListProps) {
|
||||
const [page, setPage] = useState(1);
|
||||
const pageSize = 20;
|
||||
|
||||
const { data, isLoading } = useBatchMiningRecords(accountSequence, { page, pageSize });
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{/* 汇总信息 */}
|
||||
{data && data.total > 0 && (
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Gift className="h-4 w-4 text-primary" />
|
||||
批量补发汇总
|
||||
</CardTitle>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-2">
|
||||
<div className="p-3 bg-muted rounded-lg">
|
||||
<p className="text-sm text-muted-foreground">阶段记录数</p>
|
||||
<p className="text-lg font-bold text-primary">{data.total}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-green-50 dark:bg-green-950 rounded-lg">
|
||||
<p className="text-sm text-muted-foreground">补发总金额</p>
|
||||
<p className="text-lg font-bold text-green-600">{formatDecimal(data.totalAmount, 8)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
)}
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>阶段</TableHead>
|
||||
<TableHead>所属批次</TableHead>
|
||||
<TableHead className="text-right">阶段天数</TableHead>
|
||||
<TableHead className="text-right">用户算力</TableHead>
|
||||
<TableHead className="text-right">全网算力</TableHead>
|
||||
<TableHead className="text-right">算力占比</TableHead>
|
||||
<TableHead className="text-right">补发金额</TableHead>
|
||||
<TableHead>备注</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
[...Array(5)].map((_, i) => (
|
||||
<TableRow key={i}>
|
||||
{[...Array(8)].map((_, j) => (
|
||||
<TableCell key={j}>
|
||||
<Skeleton className="h-4 w-full" />
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : !data || data.records.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="text-center py-8 text-muted-foreground">
|
||||
暂无批量补发记录
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
data.records.map((record) => (
|
||||
<TableRow key={record.id}>
|
||||
<TableCell className="font-mono font-medium">阶段 {record.phase}</TableCell>
|
||||
<TableCell>批次 {record.batch}</TableCell>
|
||||
<TableCell className="text-right">{record.daysInPhase} 天</TableCell>
|
||||
<TableCell className="text-right font-mono text-sm">
|
||||
{formatDecimal(record.userContribution, 2)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-sm">
|
||||
{formatDecimal(record.networkContribution, 2)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatPercent(record.contributionRatio)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-primary font-medium">
|
||||
+{formatDecimal(record.amount, 8)}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground max-w-[200px] truncate">
|
||||
{record.remark || '-'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
{data && data.totalPages > 1 && (
|
||||
<div className="flex items-center justify-between p-4 border-t">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
共 {data.total} 条,第 {page} / {data.totalPages} 页
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setPage(page - 1)} disabled={page <= 1}>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={() => setPage(page + 1)} disabled={page >= data.totalPages}>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
@ -69,3 +69,11 @@ export function useWalletLedger(accountSequence: string, params: PaginationParam
|
|||
enabled: !!accountSequence,
|
||||
});
|
||||
}
|
||||
|
||||
export function useBatchMiningRecords(accountSequence: string, params: PaginationParams) {
|
||||
return useQuery({
|
||||
queryKey: ['users', accountSequence, 'batch-mining-records', params],
|
||||
queryFn: () => usersApi.getBatchMiningRecords(accountSequence, params),
|
||||
enabled: !!accountSequence,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue