feat(admin+api): 列表页子路由重构、结算历史与充值截图清理

赛事/代理管理从表格展开改为子路由详情页;结算页增加预览弹窗与历史记录;媒体库支持截图存储统计与自动/手动清理;Player 端充值截图压缩为 WebP 300KB。
This commit is contained in:
2026-06-22 12:28:25 +08:00
parent 499522f3fb
commit 648c314e23
38 changed files with 3298 additions and 1147 deletions

View File

@@ -388,6 +388,83 @@ export class SettlementService {
};
}
async getActivePreview(
matchId: bigint,
opts?: { page?: number; pageSize?: number },
) {
const batch = await this.prisma.settlementBatch.findFirst({
where: { matchId, status: 'PREVIEW' },
orderBy: { createdAt: 'desc' },
});
if (!batch) return null;
const existingScore = await this.prisma.matchScore.findUnique({
where: { matchId },
});
const computation = await this.computePreviewComputation(matchId, {
htHome: batch.htHomeScore ?? 0,
htAway: batch.htAwayScore ?? 0,
ftHome: batch.ftHomeScore ?? 0,
ftAway: batch.ftAwayScore ?? 0,
homeCorners: batch.homeCorners ?? null,
awayCorners: batch.awayCorners ?? null,
homeYellowCards: batch.homeYellowCards ?? null,
awayYellowCards: batch.awayYellowCards ?? null,
homeRedCards: batch.homeRedCards ?? null,
awayRedCards: batch.awayRedCards ?? null,
homeCards: batch.homeCards ?? null,
awayCards: batch.awayCards ?? null,
winnerTeamId: existingScore?.winnerTeamId ?? null,
});
return this.buildPreviewResponse(computation, batch, opts);
}
async getMatchSettlementHistory(matchId: bigint) {
const batches = await this.prisma.settlementBatch.findMany({
where: { matchId, status: 'CONFIRMED' },
orderBy: { confirmedAt: 'desc' },
});
const operatorIds = batches
.map((b) => b.operatorId)
.filter((id): id is bigint => id !== null);
const operators =
operatorIds.length > 0
? await this.prisma.user.findMany({
where: { id: { in: operatorIds } },
select: { id: true, username: true },
})
: [];
const operatorMap = new Map(operators.map((o) => [o.id.toString(), o.username]));
return batches.map((b) => ({
id: b.id.toString(),
batchNo: b.batchNo,
htHomeScore: b.htHomeScore,
htAwayScore: b.htAwayScore,
ftHomeScore: b.ftHomeScore,
ftAwayScore: b.ftAwayScore,
homeCorners: b.homeCorners,
awayCorners: b.awayCorners,
homeYellowCards: b.homeYellowCards,
awayYellowCards: b.awayYellowCards,
homeRedCards: b.homeRedCards,
awayRedCards: b.awayRedCards,
homeCards: b.homeCards,
awayCards: b.awayCards,
totalBets: b.totalBets,
totalPayout: b.totalPayout.toString(),
totalRefund: b.totalRefund.toString(),
confirmedAt: b.confirmedAt?.toISOString() ?? null,
isResettle: b.isResettle,
reason: b.reason,
operatorUsername: b.operatorId ? operatorMap.get(b.operatorId.toString()) ?? '—' : '—',
}));
}
private buildPreviewResponse(
computation: {
scoreInput: ScoreInput;