feat: multi-tier agent hierarchy, wallet ledger, and player UX polish

Add configurable agent max level and default sub-agent credit ratio, per-agent block direct player login on suspend, admin/agent wallet transaction views, and match detail my-bets section with refreshed player card styling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-10 16:15:34 +08:00
parent 641c92a5f5
commit ef6b15f119
39 changed files with 2398 additions and 410 deletions

View File

@@ -5,6 +5,15 @@ export const PLAYER_ALLOW_PASSWORD_CHANGE = 'player.allow_password_change';
export const PLAYER_ALLOW_USERNAME_CHANGE = 'player.allow_username_change';
export const AGENT_SUSPEND_FREEZE_DIRECT_PLAYERS = 'agent.suspend_freeze_direct_players';
export const AGENT_SUSPEND_BLOCK_PLAYER_LOGIN = 'agent.suspend_block_player_login';
export const AGENT_MAX_LEVEL = 'agent.max_level';
export const AGENT_DEFAULT_SUB_CREDIT_RATIO = 'agent.default_sub_credit_ratio';
export type AgentHierarchySettings = {
/** 最大代理层级0 = 不限制 */
maxAgentLevel: number;
/** 创建下级代理时默认授信占上级可用授信的比例1100 */
defaultSubAgentCreditRatio: number;
};
export type PlayerAccountSettings = {
allowPasswordChange: boolean;
@@ -40,6 +49,25 @@ export class SystemConfigService {
});
}
async getInt(key: string, defaultValue: number): Promise<number> {
const row = await this.prisma.systemConfig.findUnique({ where: { configKey: key } });
if (!row) return defaultValue;
const parsed = parseInt(row.configValue, 10);
return Number.isFinite(parsed) ? parsed : defaultValue;
}
async setInt(key: string, value: number, description?: string) {
await this.prisma.systemConfig.upsert({
where: { configKey: key },
create: {
configKey: key,
configValue: String(value),
description,
},
update: { configValue: String(value) },
});
}
async getPlayerAccountSettings(): Promise<PlayerAccountSettings> {
const [allowPasswordChange, allowUsernameChange] = await Promise.all([
this.getBoolean(PLAYER_ALLOW_PASSWORD_CHANGE, true),
@@ -91,4 +119,40 @@ export class SystemConfigService {
}
return this.getAgentSuspendSettings();
}
async getAgentHierarchySettings(): Promise<AgentHierarchySettings> {
const [maxAgentLevel, defaultSubAgentCreditRatio] = await Promise.all([
this.getInt(AGENT_MAX_LEVEL, 0),
this.getInt(AGENT_DEFAULT_SUB_CREDIT_RATIO, 50),
]);
return { maxAgentLevel, defaultSubAgentCreditRatio };
}
async updateAgentHierarchySettings(data: Partial<AgentHierarchySettings>) {
if (data.maxAgentLevel !== undefined) {
if (!Number.isInteger(data.maxAgentLevel) || data.maxAgentLevel < 0) {
throw new Error('maxAgentLevel must be a non-negative integer');
}
await this.setInt(
AGENT_MAX_LEVEL,
data.maxAgentLevel,
'最大代理层级0 表示不限制',
);
}
if (data.defaultSubAgentCreditRatio !== undefined) {
if (
!Number.isInteger(data.defaultSubAgentCreditRatio) ||
data.defaultSubAgentCreditRatio < 1 ||
data.defaultSubAgentCreditRatio > 100
) {
throw new Error('defaultSubAgentCreditRatio must be an integer between 1 and 100');
}
await this.setInt(
AGENT_DEFAULT_SUB_CREDIT_RATIO,
data.defaultSubAgentCreditRatio,
'创建下级代理时默认授信占上级可用授信的比例(%',
);
}
return this.getAgentHierarchySettings();
}
}