fix(admin,api): 上分超额提示而非静默截断,并返回中文业务错误

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-08 15:47:45 +08:00
parent 414998ce36
commit 22535d4c27
8 changed files with 97 additions and 29 deletions

View File

@@ -3,7 +3,7 @@ import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useAdminLocale } from '../composables/useAdminLocale';
import { resolveFormError } from '../i18n/form-validation';
import { resolveFormError, resolveApiError } from '../i18n/form-validation';
import api from '../api';
import { clearStaffSession } from '../stores/auth';
@@ -43,6 +43,7 @@ import AgentCreditContext from '../components/AgentCreditContext.vue';
import { useAdminPlayerTransfer } from '../composables/useAdminPlayerTransfer';
import {
fetchAdminAgentCreditContext,
maxCreditIncreaseAmount,
type AgentCreditAdjustContext,
} from '../utils/agent-credit-context';
@@ -571,8 +572,7 @@ async function openCredit(userId: string) {
try {
creditContext.value = await fetchAdminAgentCreditContext(userId);
} catch (e: unknown) {
const err = e as { response?: { data?: { error?: string } } };
ElMessage.error(err.response?.data?.error ?? t('msg.load_failed'));
ElMessage.error(resolveApiError(e, t, 'msg.load_failed'));
creditVisible.value = false;
} finally {
creditContextLoading.value = false;
@@ -584,6 +584,11 @@ async function submitCredit() {
ElMessage.warning(t('msg.credit_zero'));
return;
}
const maxInc = maxCreditIncreaseAmount(creditContext.value);
if (creditForm.value.amount > 0 && maxInc !== undefined && creditForm.value.amount > maxInc) {
ElMessage.warning(t('err.insufficient_credit'));
return;
}
creditLoading.value = true;
try {
await api.post(`/admin/agents/${editingId.value}/credit`, {
@@ -596,8 +601,7 @@ async function submitCredit() {
load();
refreshExpandedParents();
} catch (e: unknown) {
const err = e as { response?: { data?: { error?: string } } };
ElMessage.error(err.response?.data?.error ?? t('msg.credit_adjust_failed'));
ElMessage.error(resolveApiError(e, t, 'msg.credit_adjust_failed'));
} finally {
creditLoading.value = false;
}
@@ -737,6 +741,8 @@ const {
transferContextLoading,
transferAmountRange,
transferAmountDisabled,
transferAmountExceedsCap,
transferAmountCapError,
transferTitle,
openTransfer,
submitTransfer,
@@ -1340,11 +1346,10 @@ function creditTypeLabel(type: string) {
<el-form-item :label="t('common.col_id')">
<span>{{ transferTarget?.id }}</span>
</el-form-item>
<el-form-item :label="t('user.field.amount')">
<el-form-item :label="t('user.field.amount')" :error="transferAmountCapError">
<el-input-number
v-model="transferAmount"
:min="transferAmountRange.min"
:max="transferAmountRange.max"
:disabled="transferAmountDisabled"
:step="10"
:precision="2"
@@ -1360,7 +1365,7 @@ function creditTypeLabel(type: string) {
<el-button
type="primary"
:loading="transferLoading"
:disabled="transferAmountDisabled"
:disabled="transferAmountDisabled || transferAmountExceedsCap"
@click="submitTransfer"
>
{{ t('common.confirm') }}

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useAdminLocale } from '../composables/useAdminLocale';
import { resolveFormError } from '../i18n/form-validation';
import { resolveFormError, resolveApiError } from '../i18n/form-validation';
import api from '../api';
const { t } = useAdminLocale();
@@ -27,6 +27,7 @@ import AdminTableEmpty from '../components/AdminTableEmpty.vue';
import AgentCreditContext from '../components/AgentCreditContext.vue';
import {
fetchAdminAgentCreditContext,
maxCreditIncreaseAmount,
type AgentCreditAdjustContext,
} from '../utils/agent-credit-context';
@@ -201,6 +202,11 @@ async function submitCredit() {
ElMessage.warning(t('msg.credit_zero'));
return;
}
const maxInc = maxCreditIncreaseAmount(creditContext.value);
if (creditForm.value.amount > 0 && maxInc !== undefined && creditForm.value.amount > maxInc) {
ElMessage.warning(t('err.insufficient_credit'));
return;
}
creditLoading.value = true;
try {
await api.post(`/admin/agents/${editingId.value}/credit`, {
@@ -212,8 +218,7 @@ async function submitCredit() {
creditVisible.value = false;
load();
} catch (e: unknown) {
const err = e as { response?: { data?: { error?: string } } };
ElMessage.error(err.response?.data?.error ?? t('msg.credit_adjust_failed'));
ElMessage.error(resolveApiError(e, t, 'msg.credit_adjust_failed'));
} finally {
creditLoading.value = false;
}

View File

@@ -60,6 +60,8 @@ const {
transferContextLoading,
transferAmountRange,
transferAmountDisabled,
transferAmountExceedsCap,
transferAmountCapError,
transferTitle,
openTransfer,
submitTransfer,
@@ -757,11 +759,10 @@ function statusLabel(s: string) {
<el-form-item :label="t('common.col_id')">
<span>{{ transferTarget?.id }}</span>
</el-form-item>
<el-form-item :label="t('user.field.amount')">
<el-form-item :label="t('user.field.amount')" :error="transferAmountCapError">
<el-input-number
v-model="transferAmount"
:min="transferAmountRange.min"
:max="transferAmountRange.max"
:disabled="transferAmountDisabled"
:step="10"
:precision="2"
@@ -777,7 +778,7 @@ function statusLabel(s: string) {
<el-button
type="primary"
:loading="transferLoading"
:disabled="transferAmountDisabled"
:disabled="transferAmountDisabled || transferAmountExceedsCap"
@click="submitTransfer"
>
{{ t('common.confirm') }}

View File

@@ -143,6 +143,20 @@ const transferAmountDisabled = computed(() => {
return max === 0;
});
/** 勿用 el-input-number :max否则会静默截断到上限 */
const transferAmountExceedsCap = computed(() => {
const max = transferAmountRange.value.max;
if (max === undefined) return false;
return transferAmount.value > max;
});
const transferAmountCapError = computed(() => {
if (!transferAmountExceedsCap.value) return '';
return transferType.value === 'deposit'
? t('err.insufficient_credit')
: t('transfer.context.withdraw_exceed');
});
/* ─── Init ─── */
onMounted(async () => {
await loadProfile();
@@ -818,17 +832,20 @@ function statusTagType(s: string) {
<el-form-item :label="t('common.col_id')">
<span>{{ transferTarget?.id }}</span>
</el-form-item>
<el-form-item :label="t('user.field.amount')">
<el-form-item :label="t('user.field.amount')" :error="transferAmountCapError">
<el-input-number
v-model="transferAmount"
:min="transferAmountRange.min" :max="transferAmountRange.max"
:disabled="transferAmountDisabled" :step="10" :precision="2" style="width: 100%"
:min="transferAmountRange.min"
:disabled="transferAmountDisabled"
:step="10"
:precision="2"
style="width: 100%"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="transferVisible = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" :loading="transferLoading" :disabled="transferAmountDisabled" @click="submitTransfer">{{ t('common.confirm') }}</el-button>
<el-button type="primary" :loading="transferLoading" :disabled="transferAmountDisabled || transferAmountExceedsCap" @click="submitTransfer">{{ t('common.confirm') }}</el-button>
</template>
</el-dialog>