feat: 开户备注、账单展示优化与后台代理管理增强

- 新增初始上分备注(日常上分/开户赠金/自定义)及前后台校验与展示

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

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

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

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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 17:23:58 +08:00
parent 10485ecfaf
commit 03e72ca9b2
46 changed files with 3721 additions and 1059 deletions

View File

@@ -124,6 +124,16 @@ export const API_ERROR_MESSAGES = {
'en-US': 'Match cannot be edited in current status',
'ms-MY': 'Perlawanan tidak boleh diedit dalam status semasa',
},
MATCH_NOT_REOPENABLE: {
'zh-CN': '当前状态不可解除封盘',
'en-US': 'Match cannot be reopened in current status',
'ms-MY': 'Perlawanan tidak boleh dibuka semula dalam status semasa',
},
MATCH_REOPEN_KICKOFF_REQUIRED: {
'zh-CN': '开赛时间已过,请设置新的未来开赛时间',
'en-US': 'Kickoff has passed; set a new future start time',
'ms-MY': 'Masa mula telah berlalu; tetapkan masa mula baharu pada masa hadapan',
},
OUTRIGHT_DELETE_FORBIDDEN: {
'zh-CN': '冠军盘不可删除',
'en-US': 'Outright events cannot be deleted',
@@ -469,6 +479,16 @@ export const API_ERROR_MESSAGES = {
'en-US': 'Use credit limit when promoting to agent, not initial balance',
'ms-MY': 'Guna had kredit apabila naik taraf ke ejen, bukan baki awal',
},
INITIAL_DEPOSIT_REMARK_REQUIRED: {
'zh-CN': '有初始余额时必须选择上分流水说明',
'en-US': 'Ledger note is required when initial balance > 0',
'ms-MY': 'Nota ledger diperlukan apabila baki awal > 0',
},
INITIAL_DEPOSIT_REMARK_CUSTOM_INVALID: {
'zh-CN': '自定义流水说明至少 2 个字符',
'en-US': 'Custom ledger note must be at least 2 characters',
'ms-MY': 'Nota ledger tersuai mesti sekurang-kurangnya 2 aksara',
},
TIER2_REQUIRES_PARENT_AGENT: {
'zh-CN': '二级代理必须指定上级代理',
'en-US': 'Tier-2 agent must specify parent agent',

View File

@@ -126,6 +126,7 @@ export * from './locale';
export * from './builtinPlayers';
export * from './playerLocale';
export * from './playerUsername';
export * from './initial-depositRemark';
export interface ApiResponse<T = unknown> {
success: boolean;

View File

@@ -0,0 +1,45 @@
export type InitialDepositRemarkKind = 'daily' | 'opening_bonus' | 'custom';
export type InitialDepositOperator = 'admin' | 'agent';
export const OPENING_BONUS_REMARK = '开户初始余额';
export const ADMIN_DAILY_DEPOSIT_REMARK = '管理员上分';
export const AGENT_DAILY_DEPOSIT_REMARK = '代理上分';
export type InitialDepositRemarkValidationError =
| 'INITIAL_DEPOSIT_REMARK_REQUIRED'
| 'INITIAL_DEPOSIT_REMARK_CUSTOM_INVALID';
export function dailyDepositRemark(operator: InitialDepositOperator): string {
return operator === 'admin' ? ADMIN_DAILY_DEPOSIT_REMARK : AGENT_DAILY_DEPOSIT_REMARK;
}
export function resolveInitialDepositRemark(
kind: InitialDepositRemarkKind,
custom: string | undefined,
operator: InitialDepositOperator,
): string {
if (kind === 'daily') return dailyDepositRemark(operator);
if (kind === 'opening_bonus') return OPENING_BONUS_REMARK;
const trimmed = custom?.trim() ?? '';
if (trimmed.length < 2) {
throw new Error('INITIAL_DEPOSIT_REMARK_CUSTOM_INVALID');
}
return trimmed;
}
export function validateInitialDepositRemark(
initialDeposit: number,
remark: string | undefined,
operator: InitialDepositOperator,
): { ok: true; remark: string } | { ok: false; code: InitialDepositRemarkValidationError } {
if (initialDeposit <= 0) {
return { ok: true, remark: remark?.trim() ?? '' };
}
const r = remark?.trim();
if (!r) return { ok: false, code: 'INITIAL_DEPOSIT_REMARK_REQUIRED' };
const daily = dailyDepositRemark(operator);
if (r === daily || r === OPENING_BONUS_REMARK) return { ok: true, remark: r };
if (r.length < 2) return { ok: false, code: 'INITIAL_DEPOSIT_REMARK_CUSTOM_INVALID' };
return { ok: true, remark: r };
}