feat(admin,api,player): 返水流程优化、账单详情与数据库重置

优化返水预览/确认/作废,新增玩家账变详情与后台一键重置为 seed 数据,并修复 dev 启动时 3000 端口占用。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-08 11:14:22 +08:00
parent 24fa1b275c
commit b2216abd0c
24 changed files with 2253 additions and 849 deletions

View File

@@ -1,11 +1,14 @@
<script setup lang="ts">
import { ref, onMounted } 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 api from '../api';
import { clearStaffSession } from '../stores/auth';
const { t, localeTag } = useAdminLocale();
const router = useRouter();
import {
emptyPlayerCreateForm,
emptyPlayerEditForm,
@@ -58,15 +61,63 @@ const bettingLimits = ref({
});
const settingsSaving = ref(false);
const limitsSaving = ref(false);
const resetAllowed = ref(false);
const resetLoading = ref(false);
const resetConfirmPhrase = ref('');
const settingsCollapseOpen = ref<string[]>([]);
onMounted(() => {
loadAgentOptions();
loadPlayerSettings();
loadBettingLimits();
loadResetDatabaseStatus();
load();
});
async function loadResetDatabaseStatus() {
try {
const { data } = await api.get('/admin/system/reset-database');
resetAllowed.value = !!data.data?.allowed;
} catch {
resetAllowed.value = false;
}
}
async function resetDatabase() {
if (resetConfirmPhrase.value !== 'RESET') {
ElMessage.warning(t('user.reset_database_confirm_label'));
return;
}
try {
await ElMessageBox.confirm(t('user.reset_database_hint'), t('user.reset_database'), {
type: 'warning',
confirmButtonText: t('user.reset_database_btn'),
cancelButtonText: t('common.cancel'),
});
} catch {
return;
}
resetLoading.value = true;
try {
const { data } = await api.post('/admin/system/reset-database', {
confirmPhrase: 'RESET',
});
const accounts: string[] = data.data?.demoAccounts ?? [];
ElMessage.success({
message: `${t('user.reset_database_success')}\n${t('user.reset_database_accounts')}: ${accounts.join(' · ')}`,
duration: 8000,
});
clearStaffSession();
await router.push('/login');
} catch (e: unknown) {
const err = e as { response?: { data?: { error?: string } } };
ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
} finally {
resetLoading.value = false;
}
}
async function loadBettingLimits() {
try {
const { data } = await api.get('/admin/settings/betting-limits');
@@ -370,6 +421,40 @@ function statusLabel(s: string) {
</el-form-item>
</el-form>
</div>
<div class="list-settings-block list-settings-block--danger">
<p class="list-settings-title">{{ t('user.reset_database') }}</p>
<p class="list-settings-hint">{{ t('user.reset_database_hint') }}</p>
<el-alert
v-if="!resetAllowed"
type="warning"
:closable="false"
show-icon
class="reset-db-alert"
:title="t('user.reset_database_disabled_prod')"
/>
<el-form inline size="small" class="settings-form reset-db-form">
<el-form-item :label="t('user.reset_database_confirm_label')">
<el-input
v-model="resetConfirmPhrase"
:placeholder="t('user.reset_database_confirm_ph')"
style="width: 160px"
:disabled="!resetAllowed"
autocomplete="off"
/>
</el-form-item>
<el-form-item>
<el-button
type="danger"
plain
:loading="resetLoading"
:disabled="!resetAllowed || resetConfirmPhrase !== 'RESET'"
@click="resetDatabase"
>
{{ t('user.reset_database_btn') }}
</el-button>
</el-form-item>
</el-form>
</div>
</el-collapse-item>
</el-collapse>
@@ -793,6 +878,20 @@ function statusLabel(s: string) {
.edit-stats {
margin-top: 4px;
}
.list-settings-block--danger {
margin-top: 12px;
padding-top: 12px;
border-top: 1px dashed rgba(245, 108, 108, 0.35);
}
.list-settings-hint {
font-size: 12px;
color: #888;
margin: 0 0 10px;
line-height: 1.5;
}
.reset-db-alert {
margin-bottom: 10px;
}
</style>
<style>