From 283252c8417e821f44227267e951a6aeb06c852a Mon Sep 17 00:00:00 2001 From: Mars <3361409208a@gmail.com> Date: Thu, 11 Jun 2026 17:52:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E7=94=9F=E4=BA=A7=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E9=9A=90=E8=97=8F=E5=B9=B6=E7=A6=81=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 侧栏与路由按服务端 NODE_ENV 控制;可通过 ALLOW_SMOKE_TESTS=true 临时开启。 Co-authored-by: Cursor --- .../src/composables/useSmokeTestsAllowed.ts | 24 ++++++++++++ apps/admin/src/layouts/ManageLayout.vue | 37 ++++++++++++------- apps/admin/src/router/index.ts | 9 ++++- apps/admin/src/views/SmokeTests.vue | 13 ++++++- .../applications/admin/admin.controller.ts | 9 +++++ .../smoke-tests/smoke-test.service.ts | 12 ++++++ packages/shared/src/api-errors.ts | 5 +++ 7 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 apps/admin/src/composables/useSmokeTestsAllowed.ts diff --git a/apps/admin/src/composables/useSmokeTestsAllowed.ts b/apps/admin/src/composables/useSmokeTestsAllowed.ts new file mode 100644 index 0000000..1998dbd --- /dev/null +++ b/apps/admin/src/composables/useSmokeTestsAllowed.ts @@ -0,0 +1,24 @@ +import { ref } from 'vue'; +import api from '../api'; + +const allowed = ref(null); +let loadPromise: Promise | null = null; + +export function useSmokeTestsAllowed() { + async function ensureLoaded() { + if (allowed.value !== null) return; + if (!loadPromise) { + loadPromise = (async () => { + try { + const { data } = await api.get('/admin/system/smoke-tests'); + allowed.value = !!data.data?.allowed; + } catch { + allowed.value = false; + } + })(); + } + await loadPromise; + } + + return { allowed, ensureLoaded }; +} diff --git a/apps/admin/src/layouts/ManageLayout.vue b/apps/admin/src/layouts/ManageLayout.vue index 774610f..69bc24c 100644 --- a/apps/admin/src/layouts/ManageLayout.vue +++ b/apps/admin/src/layouts/ManageLayout.vue @@ -3,6 +3,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'; import { RouterView, RouterLink, useRoute, useRouter } from 'vue-router'; import { useAuthStore } from '../stores/auth'; import { useAdminLocale } from '../composables/useAdminLocale'; +import { useSmokeTestsAllowed } from '../composables/useSmokeTestsAllowed'; import AdminLocaleSwitcher from '../components/AdminLocaleSwitcher.vue'; import AdminNavIcon from '../components/AdminNavIcon.vue'; import { resolveAdminBreadcrumb } from '../utils/admin-breadcrumb'; @@ -11,23 +12,30 @@ const route = useRoute(); const router = useRouter(); const auth = useAuthStore(); const { t } = useAdminLocale(); +const { allowed: smokeTestsAllowed, ensureLoaded: ensureSmokeTestsAllowed } = useSmokeTestsAllowed(); const sidebarOpen = ref(false); const isMobileNav = ref(false); -const adminMenus = computed(() => [ - { path: '/', label: t('nav.dashboard'), icon: 'dashboard', matchPrefix: true }, - { path: '/matches', label: t('nav.matches'), icon: 'matches', matchPrefix: true }, - { path: '/users', label: t('nav.agents_players'), icon: 'users' }, - { path: '/finance-logs', label: t('nav.finance_logs'), icon: 'finance' }, - { path: '/deposit', label: t('nav.deposit_manage'), icon: 'deposit', matchPrefix: true }, - { path: '/cashback', label: t('nav.cashback'), icon: 'cashback' }, - { path: '/bets', label: t('nav.bets'), icon: 'bets' }, - { path: '/contents', label: t('nav.contents'), icon: 'contents' }, - { path: '/media', label: t('nav.media'), icon: 'media' }, - { path: '/audit', label: t('nav.audit'), icon: 'audit' }, - { path: '/smoke-tests', label: t('nav.smoke_tests'), icon: 'smoke-tests' }, -]); +const adminMenus = computed(() => { + const items = [ + { path: '/', label: t('nav.dashboard'), icon: 'dashboard', matchPrefix: true }, + { path: '/matches', label: t('nav.matches'), icon: 'matches', matchPrefix: true }, + { path: '/users', label: t('nav.agents_players'), icon: 'users' }, + { path: '/finance-logs', label: t('nav.finance_logs'), icon: 'finance' }, + { path: '/deposit', label: t('nav.deposit_manage'), icon: 'deposit', matchPrefix: true }, + { path: '/cashback', label: t('nav.cashback'), icon: 'cashback' }, + { path: '/bets', label: t('nav.bets'), icon: 'bets' }, + { path: '/contents', label: t('nav.contents'), icon: 'contents' }, + { path: '/media', label: t('nav.media'), icon: 'media' }, + { path: '/audit', label: t('nav.audit'), icon: 'audit' }, + { path: '/smoke-tests', label: t('nav.smoke_tests'), icon: 'smoke-tests' }, + ]; + if (smokeTestsAllowed.value === false) { + return items.filter((item) => item.path !== '/smoke-tests'); + } + return items; +}); const agentMenus = computed(() => [ { path: '/', label: t('nav.dashboard'), icon: 'dashboard' }, @@ -114,6 +122,9 @@ function logout() { onMounted(() => { syncMobileNav(); window.addEventListener('resize', syncMobileNav); + if (auth.isAdmin.value) { + void ensureSmokeTestsAllowed(); + } }); onUnmounted(() => { diff --git a/apps/admin/src/router/index.ts b/apps/admin/src/router/index.ts index 4a75daa..38195bf 100644 --- a/apps/admin/src/router/index.ts +++ b/apps/admin/src/router/index.ts @@ -1,5 +1,6 @@ import { createRouter, createWebHistory } from 'vue-router'; import { useAuthStore } from '../stores/auth'; +import { useSmokeTestsAllowed } from '../composables/useSmokeTestsAllowed'; import { ensureStaffSession } from '../utils/session-hydrate'; const router = createRouter({ @@ -107,7 +108,7 @@ const router = createRouter({ { path: 'smoke-tests', component: () => import('../views/SmokeTests.vue'), - meta: { adminOnly: true }, + meta: { adminOnly: true, smokeTestsOnly: true }, }, { path: 'media', @@ -185,6 +186,12 @@ router.beforeEach(async (to) => { return '/'; } + if (to.meta.smokeTestsOnly) { + const { ensureLoaded, allowed } = useSmokeTestsAllowed(); + await ensureLoaded(); + if (!allowed.value) return '/'; + } + return true; }); diff --git a/apps/admin/src/views/SmokeTests.vue b/apps/admin/src/views/SmokeTests.vue index 0bc192a..59c5287 100644 --- a/apps/admin/src/views/SmokeTests.vue +++ b/apps/admin/src/views/SmokeTests.vue @@ -1,11 +1,15 @@