feat: 充值订单审计与重新申请,优化赛事展示和余额刷新
- 新增 deposit_order_audit_logs 表,记录提交/审批/拒绝/撤销/重提全链路 - 管理端充值单页增加审计历史;玩家端充值历史支持时间线与重新申请 - 已拒绝订单可原单号重提;撤销入账使用 PLAYER_DEPOSIT_REVERSAL 并加强幂等 - 结算后清除热门标记,允许归档已结算赛事,完善今日赛事时区窗口 - 足球页今日/早盘独立折叠;资料与余额在进入钱包/个人页及下注后自动刷新 - 补充投注玩法、结算返水规则文档;新增 smoke/settlement CLI 脚本 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import imageCompression from 'browser-image-compression';
|
||||
import api from '../api';
|
||||
import GoldSpinner from '../components/GoldSpinner.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const reapplyOrderId = computed(() => {
|
||||
const id = route.query.orderId;
|
||||
return typeof id === 'string' && id ? id : '';
|
||||
});
|
||||
const isReapply = computed(() => !!reapplyOrderId.value);
|
||||
|
||||
interface PaymentMethod {
|
||||
id: string;
|
||||
methodType: string;
|
||||
@@ -36,13 +43,39 @@ const bankMethods = computed(() => methods.value.filter((m) => m.methodType ===
|
||||
const usdtMethods = computed(() => methods.value.filter((m) => m.methodType === 'USDT'));
|
||||
const currentMethods = computed(() => methodType.value === 'BANK' ? bankMethods.value : usdtMethods.value);
|
||||
|
||||
function applyReapplyQuery() {
|
||||
const type = route.query.methodType;
|
||||
if (type === 'BANK' || type === 'USDT') {
|
||||
methodType.value = type;
|
||||
}
|
||||
|
||||
const methodId = typeof route.query.methodId === 'string' ? route.query.methodId : '';
|
||||
if (methodId) {
|
||||
const match = methods.value.find((m) => m.id === methodId);
|
||||
if (match) {
|
||||
selectedMethod.value = match;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedMethod.value && currentMethods.value.length) {
|
||||
selectedMethod.value = currentMethods.value[0];
|
||||
}
|
||||
|
||||
const amountQuery = typeof route.query.amount === 'string' ? route.query.amount : '';
|
||||
const parsedAmount = parseFloat(amountQuery);
|
||||
if (amountQuery && parsedAmount > 0) {
|
||||
amount.value = amountQuery;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMethods() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await api.get('/player/payment-methods');
|
||||
methods.value = (data.data ?? []).map((m: any) => ({ ...m, id: String(m.id) }));
|
||||
// Auto-select first available
|
||||
if (currentMethods.value.length) {
|
||||
if (isReapply.value) {
|
||||
applyReapplyQuery();
|
||||
} else if (currentMethods.value.length) {
|
||||
selectedMethod.value = currentMethods.value[0];
|
||||
}
|
||||
} catch { /* */ } finally {
|
||||
@@ -145,10 +178,19 @@ async function handleSubmit() {
|
||||
submitting.value = true;
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append('paymentMethodId', selectedMethod.value.id);
|
||||
fd.append('amount', String(amt));
|
||||
fd.append('screenshot', screenshotFile.value);
|
||||
|
||||
if (isReapply.value) {
|
||||
fd.append('paymentMethodId', selectedMethod.value.id);
|
||||
const { data } = await api.post(`/player/deposit-orders/${reapplyOrderId.value}/reapply`, fd);
|
||||
const result = data.data;
|
||||
orderNo.value = result?.orderNo ?? '';
|
||||
success.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
fd.append('paymentMethodId', selectedMethod.value.id);
|
||||
const { data } = await api.post('/player/deposit-orders', fd);
|
||||
const result = data.data;
|
||||
orderNo.value = result?.orderNo ?? '';
|
||||
@@ -206,10 +248,12 @@ onMounted(fetchMethods);
|
||||
<h3>{{ t('recharge.submitted') }}</h3>
|
||||
<p class="order-no">{{ orderNo }}</p>
|
||||
<p class="success-hint">{{ t('recharge.pending_review') }}</p>
|
||||
<button class="btn-primary" @click="resetForm">{{ t('recharge.new_recharge') }}</button>
|
||||
<button v-if="isReapply" class="btn-primary" @click="goHistory">{{ t('recharge.back_to_history') }}</button>
|
||||
<button v-else class="btn-primary" @click="resetForm">{{ t('recharge.new_recharge') }}</button>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="isReapply" class="reapply-banner">{{ t('recharge.reapply_hint') }}</div>
|
||||
<div class="type-tabs">
|
||||
<button
|
||||
:class="['tab', methodType === 'BANK' && 'active']"
|
||||
@@ -330,6 +374,17 @@ onMounted(fetchMethods);
|
||||
|
||||
.state { display: flex; justify-content: center; padding: 48px; }
|
||||
|
||||
.reapply-banner {
|
||||
margin-bottom: 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: #ffd0d0;
|
||||
background: rgba(245, 108, 108, 0.12);
|
||||
border: 1px solid rgba(245, 108, 108, 0.25);
|
||||
}
|
||||
|
||||
.type-tabs {
|
||||
display: flex; margin-bottom: 12px;
|
||||
border-radius: 6px; overflow: hidden;
|
||||
|
||||
Reference in New Issue
Block a user