diff --git a/frontend/src/views/setting/license-required/index.vue b/frontend/src/views/setting/license-required/index.vue index e18a75798578..af93dd3f3684 100644 --- a/frontend/src/views/setting/license-required/index.vue +++ b/frontend/src/views/setting/license-required/index.vue @@ -1,10 +1,5 @@ @@ -130,7 +139,6 @@ const { isEnterpriseLicensed, isFxplay, isIntl, - isLoading: restoring, isLogin, isMasterProductPro, isOffline, @@ -140,6 +148,7 @@ const { themeConfig, } = useGlobalStore(); const loading = ref(false); +const restoring = ref(isOnRestart.value); const communityRestoreRef = ref>(); const uploadRef = ref(); const uploaderFiles = ref([]); @@ -147,6 +156,8 @@ const licenseInfo = reactive({ deviceID: '', }); let restoreTimer: number | undefined; +let pageInitialized = false; +let pageInitialization: Promise | undefined; const defaultLoginImage = new URL('@/assets/images/1panel-login-enterprise.png', import.meta.url).href; const defaultLoginBgImage = new URL('@/assets/images/1panel-login-bg.jpg', import.meta.url).href; const loadedLoginImage = ref(null); @@ -205,10 +216,6 @@ const applyLoginThemeColor = () => { '--login-btn-link-hover-color', adjustColorToRGBA(loginBtnLinkColor, -10, 80), ); - document.documentElement.style.setProperty( - '--login-loading-mask-color', - adjustColorToRGBA(loginBtnLinkColor, 0, 60), - ); }; const loadLoginTheme = async () => { @@ -283,6 +290,40 @@ const loadLicenseInfo = async () => { isEnterpriseLicensed.value = false; }; +const initializePage = () => { + if (pageInitialized) { + return Promise.resolve(); + } + if (pageInitialization) { + return pageInitialization; + } + + loading.value = true; + pageInitialization = (async () => { + const shouldLoadLicense = await loadLoginTheme(); + if (!shouldLoadLicense) { + pageInitialized = true; + return; + } + await loadXpackLoginTheme(); + await loadBackground(); + await loadLicenseInfo(); + pageInitialized = true; + })().finally(() => { + loading.value = false; + pageInitialization = undefined; + }); + return pageInitialization; +}; + +const resumePageInitialization = async () => { + try { + await initializePage(); + } catch { + // Request errors are already presented by the shared API interceptor. + } +}; + const loadRestoreInfo = async () => { const res = await getCommunityRestoreStatus(); restoring.value = res.data.state === 'Running'; @@ -331,13 +372,16 @@ const pollRestoreStatus = async () => { restoring.value = false; isOnRestart.value = false; MsgError(res.data.message); + await resumePageInitialization(); return; } restoring.value = res.data.state === 'Running'; isOnRestart.value = restoring.value; if (restoring.value) { scheduleRestorePoll(); + return; } + await resumePageInitialization(); } catch { await waitForCommunityService(); } @@ -400,22 +444,22 @@ onMounted(async () => { window.addEventListener('resize', updateSize); loading.value = true; try { - const shouldLoadLicense = await loadLoginTheme(); - if (!shouldLoadLicense) { - return; - } - await loadXpackLoginTheme(); - await loadBackground(); - await loadLicenseInfo(); - if (!isEnterpriseLicensed.value) { + try { await loadRestoreInfo(); + } catch { if (restoring.value) { - scheduleRestorePoll(); + await waitForCommunityService(); + return; } } + if (restoring.value) { + scheduleRestorePoll(); + return; + } } finally { loading.value = false; } + await initializePage(); }); onUnmounted(() => { @@ -429,6 +473,10 @@ onUnmounted(() => { border-radius: 0; } +.license-required-page { + isolation: isolate; +} + .community-restore-link { position: absolute; top: 16px; @@ -554,15 +602,125 @@ onUnmounted(() => { line-height: 20px; } -:deep(.el-loading-mask) { - background-color: var(--login-loading-mask-color) !important; +.restore-mask { + position: fixed; + z-index: 2000; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: rgb(15 23 42 / 48%); + backdrop-filter: blur(8px) saturate(90%); +} + +.restore-status-card { + display: flex; + width: min(400px, 100%); + flex-direction: column; + align-items: center; + padding: 32px 36px; + border: 1px solid rgb(255 255 255 / 70%); + border-radius: 16px; + background: rgb(255 255 255 / 96%); + box-shadow: + 0 24px 64px rgb(15 23 42 / 24%), + 0 4px 16px rgb(15 23 42 / 10%); + text-align: center; +} - .el-loading-spinner .path { - stroke: #ffffff; +.restore-spinner { + position: relative; + width: 54px; + height: 54px; + margin-bottom: 22px; + + &::before, + &::after { + position: absolute; + inset: 0; + border: 3px solid rgb(148 163 184 / 22%); + border-radius: 50%; + content: ''; } - .el-loading-text { - color: #ffffff; + &::after { + border-color: var(--login-btn-link-color, #005eeb) transparent transparent; + animation: restore-spin 0.9s linear infinite; + } + + span { + position: absolute; + inset: 19px; + border-radius: 50%; + background: var(--login-btn-link-color, #005eeb); + box-shadow: 0 0 0 6px color-mix(in srgb, var(--login-btn-link-color, #005eeb) 10%, transparent); + animation: restore-pulse 1.5s ease-in-out infinite; + } +} + +.restore-status-title { + color: var(--el-text-color-primary, #1f2937); + font-size: 18px; + font-weight: 600; + line-height: 26px; +} + +.restore-status-description { + max-width: 320px; + margin-top: 8px; + color: var(--el-text-color-secondary, #64748b); + font-size: 14px; + line-height: 22px; +} + +.restore-mask-enter-active, +.restore-mask-leave-active { + transition: opacity 0.22s ease; + + .restore-status-card { + transition: + opacity 0.22s ease, + transform 0.22s ease; + } +} + +.restore-mask-enter-from, +.restore-mask-leave-to { + opacity: 0; + + .restore-status-card { + opacity: 0; + transform: translateY(8px) scale(0.98); + } +} + +@keyframes restore-spin { + to { + transform: rotate(360deg); + } +} + +@keyframes restore-pulse { + 0%, + 100% { + opacity: 0.75; + transform: scale(0.9); + } + + 50% { + opacity: 1; + transform: scale(1); + } +} + +@media (prefers-reduced-motion: reduce) { + .restore-spinner::after { + animation-duration: 1.8s; + } + + .restore-spinner span { + animation: none; } }