feat: WC2026 赛事 seed、生产上线初始化脚本与目录归档
重构 seed 为 WC2026 72 场小组赛与 48 强优胜盘;新增 production 模式仅保留 admin 与赛事示例;提供 prod-init-db 全量重置脚本;管理端 i18n 分包与赛事归档能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useAdminLocale } from '../composables/useAdminLocale';
|
||||
import api from '../api';
|
||||
import AdminTableEmpty from '../components/AdminTableEmpty.vue';
|
||||
@@ -86,8 +87,33 @@ function openScreenshot(url: string) {
|
||||
previewVisible.value = true;
|
||||
}
|
||||
|
||||
async function confirmDepositAction(message: string, title: string): Promise<boolean> {
|
||||
try {
|
||||
await ElMessageBox.confirm(message, title, {
|
||||
type: 'warning',
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showApiError(e: unknown) {
|
||||
const msg = (e as { response?: { data?: { message?: string } } })?.response?.data?.message;
|
||||
ElMessage.error(msg || 'Error');
|
||||
}
|
||||
|
||||
async function handleApprove() {
|
||||
if (!approveTarget.value) return;
|
||||
const amountText = formatAmount(String(approveAmount.value));
|
||||
if (!await confirmDepositAction(
|
||||
t('deposit.confirm_approve_message', { amount: amountText }),
|
||||
t('deposit.confirm_approve'),
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.post(`/admin/deposit-orders/${approveTarget.value.id}/approve`, {
|
||||
approvedAmount: approveAmount.value,
|
||||
@@ -95,15 +121,15 @@ async function handleApprove() {
|
||||
});
|
||||
approveDialogVisible.value = false;
|
||||
await fetchList();
|
||||
} catch (e: any) {
|
||||
alert(e.response?.data?.message || 'Error');
|
||||
} catch (e: unknown) {
|
||||
showApiError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReject() {
|
||||
if (!rejectTarget.value) return;
|
||||
if (!rejectReason.value.trim()) {
|
||||
alert(t('deposit.reason_required'));
|
||||
ElMessage.warning(t('deposit.reason_required'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -112,8 +138,45 @@ async function handleReject() {
|
||||
});
|
||||
rejectDialogVisible.value = false;
|
||||
await fetchList();
|
||||
} catch (e: any) {
|
||||
alert(e.response?.data?.message || 'Error');
|
||||
} catch (e: unknown) {
|
||||
showApiError(e);
|
||||
}
|
||||
}
|
||||
|
||||
const DEPOSIT_REVOKE_WINDOW_MS = 5 * 60 * 1000;
|
||||
|
||||
function canRevokeApproved(row: DepositOrderRow): boolean {
|
||||
if (row.status !== 'APPROVED' || !row.reviewedAt) return false;
|
||||
return Date.now() - new Date(row.reviewedAt).getTime() <= DEPOSIT_REVOKE_WINDOW_MS;
|
||||
}
|
||||
|
||||
async function handleReopen(row: DepositOrderRow) {
|
||||
if (!await confirmDepositAction(t('deposit.confirm_reopen'), t('deposit.reopen_review'))) return;
|
||||
try {
|
||||
await api.post(`/admin/deposit-orders/${row.id}/reopen`);
|
||||
await fetchList();
|
||||
} catch (e: unknown) {
|
||||
showApiError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRevoke(row: DepositOrderRow) {
|
||||
if (!await confirmDepositAction(t('deposit.confirm_revoke'), t('deposit.revoke'))) return;
|
||||
try {
|
||||
await api.post(`/admin/deposit-orders/${row.id}/reopen`);
|
||||
await fetchList();
|
||||
} catch (e: unknown) {
|
||||
showApiError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row: DepositOrderRow) {
|
||||
if (!await confirmDepositAction(t('deposit.confirm_delete'), t('deposit.delete'))) return;
|
||||
try {
|
||||
await api.delete(`/admin/deposit-orders/${row.id}`);
|
||||
await fetchList();
|
||||
} catch (e: unknown) {
|
||||
showApiError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +265,21 @@ onMounted(fetchList);
|
||||
<template v-if="row.status === 'PENDING'">
|
||||
<button class="btn-sm btn-approve" @click="openApprove(row)">{{ t('deposit.approve') }}</button>
|
||||
<button class="btn-sm btn-reject" @click="openReject(row)">{{ t('deposit.reject') }}</button>
|
||||
<button class="btn-sm btn-delete" @click="handleDelete(row)">{{ t('deposit.delete') }}</button>
|
||||
</template>
|
||||
<template v-else-if="row.status === 'APPROVED'">
|
||||
<button
|
||||
v-if="canRevokeApproved(row)"
|
||||
class="btn-sm btn-reopen"
|
||||
@click="handleRevoke(row)"
|
||||
>
|
||||
{{ t('deposit.revoke') }}
|
||||
</button>
|
||||
<button class="btn-sm btn-delete" @click="handleDelete(row)">{{ t('deposit.delete') }}</button>
|
||||
</template>
|
||||
<template v-else-if="row.status === 'REJECTED'">
|
||||
<button class="btn-sm btn-reopen" @click="handleReopen(row)">{{ t('deposit.reopen_review') }}</button>
|
||||
<button class="btn-sm btn-delete" @click="handleDelete(row)">{{ t('deposit.delete') }}</button>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -218,6 +296,7 @@ onMounted(fetchList);
|
||||
<div v-if="approveDialogVisible" class="dialog-overlay" @click.self="approveDialogVisible = false">
|
||||
<div class="dialog-box">
|
||||
<h3>{{ t('deposit.approve_title') }}</h3>
|
||||
<p class="approve-check-hint">{{ t('deposit.approve_check_hint') }}</p>
|
||||
<div v-if="approveTarget" class="approve-content">
|
||||
<div class="info-row">
|
||||
<span>{{ t('deposit.player') }}:</span> <strong>{{ approveTarget.playerUsername }}</strong>
|
||||
@@ -305,6 +384,11 @@ onMounted(fetchList);
|
||||
.btn-approve:hover { background: rgba(61, 115, 88, 0.24); }
|
||||
.btn-reject { background: #3a1a1a; color: #f56c6c; }
|
||||
.btn-reject:hover { background: #4a2525; }
|
||||
.btn-reopen { background: #2a2410; color: #e6a23c; }
|
||||
.btn-reopen:hover { background: #3a3218; }
|
||||
.btn-delete { background: #2a2a2a; color: #aaa; }
|
||||
.btn-delete:hover { background: #3a3a3a; color: #f56c6c; }
|
||||
.actions-cell { white-space: nowrap; }
|
||||
.pagination { display: flex; justify-content: center; align-items: center; gap: 12px; margin-top: 16px; }
|
||||
.pagination button { background: #333; color: #ddd; border: none; border-radius: 4px; padding: 6px 14px; cursor: pointer; }
|
||||
.pagination button:disabled { opacity: 0.4; cursor: default; }
|
||||
@@ -312,6 +396,16 @@ onMounted(fetchList);
|
||||
.dialog-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 999; }
|
||||
.dialog-box { background: #1e1e1e; border-radius: 8px; padding: 24px; min-width: 440px; max-width: 560px; }
|
||||
.dialog-box h3 { margin: 0 0 16px; font-size: 16px; }
|
||||
.approve-check-hint {
|
||||
margin: -8px 0 14px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
background: rgba(230, 162, 60, 0.12);
|
||||
border: 1px solid rgba(230, 162, 60, 0.35);
|
||||
color: #e6a23c;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.info-row { margin-bottom: 8px; font-size: 13px; }
|
||||
.info-row span { color: #888; margin-right: 4px; }
|
||||
.approve-screenshot { width: 200px; max-height: 200px; object-fit: contain; border-radius: 4px; cursor: pointer; margin-top: 4px; }
|
||||
|
||||
Reference in New Issue
Block a user