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,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed, watch, reactive } from 'vue';
|
||||
import { ref, onMounted, computed, watch, reactive, h } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useAdminLocale } from '../composables/useAdminLocale';
|
||||
@@ -219,6 +219,10 @@ const resetAllowed = ref(false);
|
||||
const resetLoading = ref(false);
|
||||
const resetConfirmPhrase = ref('');
|
||||
const settingsCollapseOpen = ref<string[]>([]);
|
||||
const settingsLoaded = ref(false);
|
||||
const resetDbStatusLoaded = ref(false);
|
||||
const agentOptionsLoading = ref(false);
|
||||
const MAX_EXPANDED_AGENT_ROWS = 2;
|
||||
|
||||
const createDialogTitle = computed(() => {
|
||||
if (createAccountMode.value === 1) return t('agent.dialog.create');
|
||||
@@ -383,19 +387,55 @@ function resolveCreateParentLabel(agentId: string) {
|
||||
|
||||
/* ─── Init ─── */
|
||||
onMounted(() => {
|
||||
loadPlayerSettings();
|
||||
loadBettingLimits();
|
||||
loadHierarchySettings();
|
||||
loadPlatformDirectSettings();
|
||||
loadResetDatabaseStatus();
|
||||
loadAgentOptions();
|
||||
void loadUsersPageInit();
|
||||
loadAllPlayers();
|
||||
loadTier1Agents();
|
||||
loadAgentLevelCounts().then(() => {
|
||||
for (const lvl of visibleSubAgentTabLevels.value) {
|
||||
loadSubAgentsAtLevel(lvl);
|
||||
});
|
||||
|
||||
async function loadUsersPageInit() {
|
||||
try {
|
||||
const { data } = await api.get('/admin/users/page-init');
|
||||
const payload = data.data as {
|
||||
playerSettings?: typeof playerSettings.value;
|
||||
bettingLimits?: typeof bettingLimits.value;
|
||||
hierarchySettings?: { maxAgentLevel: number };
|
||||
platformDirect?: { platformDirectRate?: number | string; adminInviteRate?: number | string };
|
||||
agentLevelCounts?: Record<number, number>;
|
||||
};
|
||||
if (payload.playerSettings) playerSettings.value = payload.playerSettings;
|
||||
if (payload.bettingLimits) bettingLimits.value = payload.bettingLimits;
|
||||
if (payload.hierarchySettings) {
|
||||
hierarchySettings.value = {
|
||||
maxAgentLevel: payload.hierarchySettings.maxAgentLevel ?? 0,
|
||||
};
|
||||
}
|
||||
});
|
||||
if (payload.platformDirect) {
|
||||
platformDirectRate.value = decimalRateToPercent(payload.platformDirect.platformDirectRate ?? 0);
|
||||
adminInviteRate.value = decimalRateToPercent(
|
||||
payload.platformDirect.adminInviteRate ?? payload.platformDirect.platformDirectRate ?? 0,
|
||||
);
|
||||
}
|
||||
if (payload.agentLevelCounts) {
|
||||
agentLevelCounts.value = payload.agentLevelCounts;
|
||||
for (const lvl of visibleSubAgentTabLevels.value) {
|
||||
loadSubAgentsAtLevel(lvl);
|
||||
}
|
||||
}
|
||||
settingsLoaded.value = true;
|
||||
} catch {
|
||||
/* keep defaults */
|
||||
}
|
||||
}
|
||||
|
||||
watch(settingsCollapseOpen, (open) => {
|
||||
if (!open.includes('settings')) return;
|
||||
if (!resetDbStatusLoaded.value) {
|
||||
resetDbStatusLoaded.value = true;
|
||||
void loadResetDatabaseStatus();
|
||||
}
|
||||
if (!settingsLoaded.value) {
|
||||
void loadUsersPageInit();
|
||||
}
|
||||
});
|
||||
|
||||
/* ─── Load tier-1 agents ─── */
|
||||
@@ -513,15 +553,27 @@ async function load() {
|
||||
reloadAgentLists();
|
||||
}
|
||||
|
||||
async function loadAgentOptions() {
|
||||
async function loadAgentOptions(keyword = '') {
|
||||
agentOptionsLoading.value = true;
|
||||
try {
|
||||
const { data } = await api.get('/admin/agents/options');
|
||||
const { data } = await api.get('/admin/agents/options', {
|
||||
params: {
|
||||
keyword: keyword.trim() || undefined,
|
||||
limit: 50,
|
||||
},
|
||||
});
|
||||
agentOptions.value = data.data;
|
||||
} catch {
|
||||
agentOptions.value = [];
|
||||
} finally {
|
||||
agentOptionsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onAgentOptionsSearch(keyword: string) {
|
||||
void loadAgentOptions(keyword);
|
||||
}
|
||||
|
||||
async function loadAllPlayers() {
|
||||
playerLoading.value = true;
|
||||
try {
|
||||
@@ -605,6 +657,10 @@ function onAgentRowClick(row: AgentRow, event: MouseEvent) {
|
||||
if (next.has(userId)) {
|
||||
next.delete(userId);
|
||||
} else {
|
||||
if (next.size >= MAX_EXPANDED_AGENT_ROWS) {
|
||||
const [first] = next;
|
||||
if (first) next.delete(first);
|
||||
}
|
||||
next.add(userId);
|
||||
if (!agentPlayersMap.value[userId]) void loadExpansionData(userId);
|
||||
}
|
||||
@@ -1096,6 +1152,64 @@ async function toggleFreezePlayer(row: PlayerRow) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Delete Player ─── */
|
||||
async function deletePlayer(row: PlayerRow) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
t('msg.delete_player_body', { name: row.username }),
|
||||
t('msg.delete_player_title'),
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonText: t('common.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
// Second confirmation — type username to confirm
|
||||
const input = ref('');
|
||||
try {
|
||||
await ElMessageBox({
|
||||
title: t('msg.delete_player_confirm_title'),
|
||||
message: () =>
|
||||
h('div', {}, [
|
||||
h('p', { style: 'margin: 0 0 8px; font-size: 13px; color: var(--el-text-color-regular)' },
|
||||
t('msg.delete_player_confirm_hint', { name: row.username })),
|
||||
h('input', {
|
||||
value: input.value,
|
||||
placeholder: row.username,
|
||||
onInput: (e: Event) => { input.value = (e.target as HTMLInputElement).value; },
|
||||
style: 'width: 100%; padding: 6px 8px; border: 1px solid var(--el-border-color); border-radius: 4px; font-size: 13px; box-sizing: border-box',
|
||||
}),
|
||||
]),
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t('common.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
beforeClose: (action: string, _instance: unknown, done: () => void) => {
|
||||
if (action === 'confirm') {
|
||||
if (input.value.trim() !== row.username) {
|
||||
ElMessage.warning(t('msg.delete_player_mismatch'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
done();
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.delete(`/admin/users/${row.id}`);
|
||||
ElMessage.success(t('msg.delete_player_done'));
|
||||
load();
|
||||
refreshExpandedParents();
|
||||
} catch (e: unknown) {
|
||||
const err = e as { response?: { data?: { error?: string } } };
|
||||
ElMessage.error(err.response?.data?.error ?? t('msg.delete_player_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Freeze / Unfreeze Agent ─── */
|
||||
const freezeAgentIsSuspend = computed(() => {
|
||||
if (!freezeAgentTarget.value) return true;
|
||||
@@ -1347,7 +1461,13 @@ function creditTypeLabel(type: string) {
|
||||
v-model="playerFilterAgent"
|
||||
:placeholder="t('user.filter.agent_ph')"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="onAgentOptionsSearch"
|
||||
:loading="agentOptionsLoading"
|
||||
style="width: 200px"
|
||||
@focus="() => { if (!agentOptions.length) void loadAgentOptions(); }"
|
||||
>
|
||||
<el-option
|
||||
v-for="a in agentOptions"
|
||||
@@ -1417,6 +1537,7 @@ function creditTypeLabel(type: string) {
|
||||
@deposit="openTransfer('deposit', row)"
|
||||
@withdraw="openTransfer('withdraw', row)"
|
||||
@freeze="toggleFreezePlayer(row)"
|
||||
@delete="deletePlayer(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -1524,6 +1645,7 @@ function creditTypeLabel(type: string) {
|
||||
@deposit="openTransfer('deposit', player)"
|
||||
@withdraw="openTransfer('withdraw', player)"
|
||||
@freeze="toggleFreezePlayer(player)"
|
||||
@delete="deletePlayer(player)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -1679,6 +1801,7 @@ function creditTypeLabel(type: string) {
|
||||
@deposit="openTransfer('deposit', player)"
|
||||
@withdraw="openTransfer('withdraw', player)"
|
||||
@freeze="toggleFreezePlayer(player)"
|
||||
@delete="deletePlayer(player)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
Reference in New Issue
Block a user