feat(admin,api,player): 代理层级管理、额度上下分与玩家钱包详情

新增代理管理器与二级代理体系,完善信用额度/上下分上下文与冻结策略;代理端玩家与子代理管理增强;玩家端新增钱包详情页与交易筛选优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-08 15:34:12 +08:00
parent b2216abd0c
commit 414998ce36
54 changed files with 6641 additions and 481 deletions

View File

@@ -0,0 +1,132 @@
import { ref, computed } from 'vue';
import { ElMessage } from 'element-plus';
import api from '../api';
import { useAdminLocale } from './useAdminLocale';
import {
depositAmountCap,
parsePlayerAvailable,
type WalletTransferContext,
} from '../utils/wallet-transfer-context';
export type PlayerTransferTarget = { id: string; username?: string };
export function useAdminPlayerTransfer(onSuccess?: () => void | Promise<void>) {
const { t } = useAdminLocale();
const transferVisible = ref(false);
const transferLoading = ref(false);
const transferType = ref<'deposit' | 'withdraw'>('deposit');
const transferTarget = ref<PlayerTransferTarget | null>(null);
const transferAmount = ref(100);
const transferRemark = ref('');
const transferContext = ref<WalletTransferContext | null>(null);
const transferContextLoading = ref(false);
const transferAmountRange = computed(() => {
if (transferType.value === 'withdraw') {
const cap = parsePlayerAvailable(transferContext.value);
if (cap <= 0) return { min: 0, max: 0 };
return { min: Math.min(0.01, cap), max: cap };
}
const cap = depositAmountCap(transferContext.value);
if (cap === undefined) return { min: 0.01, max: undefined as number | undefined };
if (cap <= 0) return { min: 0, max: 0 };
return { min: Math.min(0.01, cap), max: cap };
});
const transferAmountDisabled = computed(() => transferAmountRange.value.max === 0);
function transferTitle() {
const name = transferTarget.value?.username ?? transferTarget.value?.id ?? '';
return transferType.value === 'deposit'
? t('agent_portal.transfer_title_deposit', { name })
: t('agent_portal.transfer_title_withdraw', { name });
}
async function openTransfer(type: 'deposit' | 'withdraw', row: PlayerTransferTarget) {
transferType.value = type;
transferTarget.value = row;
transferContext.value = null;
transferAmount.value = 100;
transferRemark.value = type === 'deposit' ? t('user.deposit_remark_default') : '';
transferVisible.value = true;
transferContextLoading.value = true;
try {
const { data } = await api.get(`/admin/wallet/transfer-context/${row.id}`);
transferContext.value = data.data as WalletTransferContext;
if (type === 'deposit') {
const cap = depositAmountCap(transferContext.value);
transferAmount.value =
cap !== undefined && cap > 0 ? Math.min(100, cap) : cap === undefined ? 100 : 0;
} else {
const cap = parsePlayerAvailable(transferContext.value);
transferAmount.value = cap > 0 ? Math.min(100, cap) : 0;
}
} catch (e: unknown) {
const err = e as { response?: { data?: { error?: string } } };
ElMessage.error(err.response?.data?.error ?? t('msg.load_failed'));
transferVisible.value = false;
} finally {
transferContextLoading.value = false;
}
}
async function submitTransfer() {
if (!transferTarget.value) return;
if (transferAmount.value <= 0) {
ElMessage.warning(t('msg.amount_gt_zero'));
return;
}
const max = transferAmountRange.value.max;
if (max !== undefined && transferAmount.value > max) {
ElMessage.warning(
transferType.value === 'deposit'
? t('err.insufficient_credit')
: t('transfer.context.withdraw_exceed'),
);
return;
}
const userId = transferTarget.value.id;
const amount = transferAmount.value;
transferLoading.value = true;
try {
const requestId = `${transferType.value === 'deposit' ? 'dep' : 'wd'}-${userId}-${Date.now()}`;
const endpoint =
transferType.value === 'deposit' ? '/admin/wallet/deposit' : '/admin/wallet/withdraw';
await api.post(endpoint, {
userId,
amount,
remark: transferRemark.value || undefined,
requestId,
});
ElMessage.success(
transferType.value === 'deposit' ? t('msg.topup_ok') : t('msg.withdraw_ok'),
);
transferVisible.value = false;
await onSuccess?.();
} catch (e: unknown) {
const err = e as { response?: { data?: { error?: string } } };
const fallback =
transferType.value === 'deposit' ? t('msg.topup_failed') : t('msg.transfer_failed');
ElMessage.error(err.response?.data?.error ?? fallback);
} finally {
transferLoading.value = false;
}
}
return {
transferVisible,
transferLoading,
transferType,
transferTarget,
transferAmount,
transferRemark,
transferContext,
transferContextLoading,
transferAmountRange,
transferAmountDisabled,
transferTitle,
openTransfer,
submitTransfer,
};
}