Files
thebet365/apps/admin/src/views/user-form.ts

208 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { InitialDepositRemarkKind } from '@thebet365/shared';
import { FormValidationError } from '../i18n/form-validation';
import { buildInitialDepositRemark } from '../utils/initial-depositRemark';
import { decimalRateToPercent } from '../utils/rate-percent';
import { percentToDecimalRate } from '../utils/rate-percent';
/** 玩家用户名仅英文字母与数字332 位 */
export const PLAYER_USERNAME_PATTERN = /^[a-zA-Z0-9]{3,32}$/;
export function assertPlayerUsername(username: string): void {
const trimmed = username.trim();
if (!trimmed) throw new FormValidationError('err.username_required');
if (!PLAYER_USERNAME_PATTERN.test(trimmed)) {
throw new FormValidationError('err.username_player_invalid');
}
}
export interface PlayerCreateForm {
username: string;
password: string;
confirmPassword: string;
parentId: string;
phone: string;
email: string;
initialDeposit: number;
initialDepositRemarkKind: InitialDepositRemarkKind | '';
initialDepositRemarkCustom: string;
/** 创建为一级代理(非玩家) */
asTier1Agent: boolean;
creditLimit: number;
cashbackRate: number;
maxSingleDeposit: number;
maxDailyDeposit: number;
}
export interface PlayerEditForm {
id: string;
username: string;
status: string;
parentUsername: string | null;
affiliationAgents?: string[];
availableBalance: string;
frozenBalance: string;
betCount: number;
totalStake: string;
totalReturn: string;
createdAt: string;
lastLoginAt: string | null;
loginFailCount: number;
phone: string;
email: string;
managedPassword: string | null;
newPassword: string;
customCashbackRate: number;
defaultCashbackRate: number;
useCustomCashback: boolean;
}
export interface PlayerRow {
id: string;
username: string;
status: string;
locale: string;
parentId: string | null;
parentUsername: string | null;
/** 归属代理链:一级代理、二级代理(如有) */
affiliationAgents?: string[];
inviteCode?: string | null;
phone: string | null;
email: string | null;
managedPassword: string | null;
availableBalance: string;
frozenBalance: string;
lastLoginAt: string | null;
isOnline?: boolean;
betCount: number;
totalStake: string;
totalReturn: string;
createdAt: string;
}
export interface PlayerDetail extends PlayerRow {
loginFailCount: number;
lockedUntil: string | null;
updatedAt: string;
customCashbackRate: string | null;
defaultCashbackRate: string;
}
/** 玩家归属标签,格式:玩家-平台 | 玩家-一级代理 | 玩家-一级代理-二级代理 */
export function formatPlayerAffiliationLabel(
row: Pick<PlayerRow, 'affiliationAgents'>,
playerLabel: string,
platformLabel: string,
): string {
const agents = row.affiliationAgents ?? [];
if (agents.length === 0) {
return [playerLabel, platformLabel].join('-');
}
return [playerLabel, ...agents].join('-');
}
export function emptyPlayerCreateForm(): PlayerCreateForm {
return {
username: '',
password: '',
confirmPassword: '',
parentId: '',
phone: '',
email: '',
initialDeposit: 0,
initialDepositRemarkKind: '',
initialDepositRemarkCustom: '',
asTier1Agent: false,
creditLimit: 50000,
cashbackRate: 0,
maxSingleDeposit: 0,
maxDailyDeposit: 0,
};
}
export function emptyPlayerEditForm(): PlayerEditForm {
return {
id: '',
username: '',
status: 'ACTIVE',
parentUsername: null,
availableBalance: '0',
frozenBalance: '0',
betCount: 0,
totalStake: '0',
totalReturn: '0',
createdAt: '',
lastLoginAt: null,
loginFailCount: 0,
phone: '',
email: '',
managedPassword: null,
newPassword: '',
customCashbackRate: 0,
defaultCashbackRate: 0,
useCustomCashback: false,
};
}
export function editFormFromDetail(d: PlayerDetail): PlayerEditForm {
return {
id: d.id,
username: d.username,
status: d.status,
parentUsername: d.parentUsername,
affiliationAgents: d.affiliationAgents,
availableBalance: d.availableBalance,
frozenBalance: d.frozenBalance,
betCount: d.betCount,
totalStake: d.totalStake,
totalReturn: d.totalReturn,
createdAt: d.createdAt,
lastLoginAt: d.lastLoginAt,
loginFailCount: d.loginFailCount,
phone: d.phone ?? '',
email: d.email ?? '',
managedPassword: d.managedPassword ?? null,
newPassword: '',
customCashbackRate:
d.customCashbackRate != null ? decimalRateToPercent(d.customCashbackRate) : 0,
defaultCashbackRate: decimalRateToPercent(d.defaultCashbackRate),
useCustomCashback: d.customCashbackRate != null,
};
}
export function buildCreatePlayerPayload(form: PlayerCreateForm) {
if (!form.username.trim()) throw new FormValidationError('err.username_required');
if (form.password.length < 8) throw new FormValidationError('err.password_min');
if (form.password !== form.confirmPassword) throw new FormValidationError('err.password_mismatch');
if (form.asTier1Agent) {
if (form.parentId) throw new FormValidationError('err.agent_no_parent');
if (form.initialDeposit > 0) throw new FormValidationError('err.agent_no_initial_deposit');
if (form.creditLimit < 0) throw new FormValidationError('err.credit_negative');
return {
username: form.username.trim(),
password: form.password,
phone: form.phone.trim() || undefined,
email: form.email.trim() || undefined,
asTier1Agent: true,
creditLimit: form.creditLimit,
cashbackRate: percentToDecimalRate(form.cashbackRate),
maxSingleDeposit: form.maxSingleDeposit > 0 ? form.maxSingleDeposit : undefined,
maxDailyDeposit: form.maxDailyDeposit > 0 ? form.maxDailyDeposit : undefined,
};
}
assertPlayerUsername(form.username);
return {
username: form.username.trim(),
password: form.password,
parentId: form.parentId || undefined,
phone: form.phone.trim() || undefined,
email: form.email.trim() || undefined,
initialDeposit: form.initialDeposit > 0 ? form.initialDeposit : undefined,
remark: buildInitialDepositRemark(
form.initialDeposit,
form.initialDepositRemarkKind,
form.initialDepositRemarkCustom,
'admin',
),
};
}