Files
thebet365/apps/admin/src/views/user-form.ts
Mars 03e72ca9b2 feat: 开户备注、账单展示优化与后台代理管理增强
- 新增初始上分备注(日常上分/开户赠金/自定义)及前后台校验与展示

- 优化钱包流水类型与备注显示,区分管理员/代理/玩家上下分

- 修复登录后语言被后端覆盖的问题,登录时同步当前语言到服务端

- 后台代理/玩家表格操作栏重构,充值订单增加备注列

- 前台个人中心、充值、账单与验证码组件体验优化

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 17:23:58 +08:00

207 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;
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',
),
};
}