feat(admin,api,player): 代理层级管理、额度上下分与玩家钱包详情
新增代理管理器与二级代理体系,完善信用额度/上下分上下文与冻结策略;代理端玩家与子代理管理增强;玩家端新增钱包详情页与交易筛选优化。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
132
apps/admin/src/composables/useAdminPlayerTransfer.ts
Normal file
132
apps/admin/src/composables/useAdminPlayerTransfer.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user