From 37dd4449a6f7d28dc15d3225562cfd6cb883a552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9D=91=BE=F0=9D=92=96=F0=9D=92=99=F0=9D=92=89?= Date: Fri, 30 May 2025 15:34:14 +0800 Subject: [PATCH] docs(site): Network connection is not smooth notification (#53930) --- .dumi/scripts/mirror-modal.js | 176 -------------------- .dumi/scripts/mirror-notify.js | 230 +++++++++++++++++++++++++++ .dumi/theme/layouts/GlobalLayout.tsx | 6 + .dumirc.ts | 2 +- 4 files changed, 237 insertions(+), 177 deletions(-) delete mode 100644 .dumi/scripts/mirror-modal.js create mode 100644 .dumi/scripts/mirror-notify.js diff --git a/.dumi/scripts/mirror-modal.js b/.dumi/scripts/mirror-modal.js deleted file mode 100644 index 9b024a2e0c..0000000000 --- a/.dumi/scripts/mirror-modal.js +++ /dev/null @@ -1,176 +0,0 @@ -(function createMirrorModal() { - if ( - (navigator.languages.includes('zh') || navigator.languages.includes('zh-CN')) && - /-cn\/?$/.test(window.location.pathname) && - !['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(window.location.hostname) && - !window.location.host.includes('surge') && - window.location.hostname !== 'localhost' - ) { - const ANTD_DOT_NOT_SHOW_MIRROR_MODAL = 'ANT_DESIGN_DO_NOT_OPEN_MIRROR_MODAL'; - - const lastShowTime = window.localStorage.getItem(ANTD_DOT_NOT_SHOW_MIRROR_MODAL); - if ( - lastShowTime && - lastShowTime !== 'true' && - Date.now() - new Date(lastShowTime).getTime() < 7 * 24 * 60 * 60 * 1000 - ) { - return; - } - - const style = document.createElement('style'); - style.innerHTML = ` - @keyframes mirror-fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - - @keyframes mirror-zoom-in { - from { - transform: scale(0.8); - } - to { - transform: scale(1); - } - } - - .mirror-modal-mask { - position: fixed; - inset: 0; - height: 100vh; - width: 100vw; - background: rgba(0, 0, 0, 0.3); - z-index: 9999; - animation: mirror-fade-in 0.3s forwards; - } - - .mirror-modal-dialog { - position: fixed; - top: 120px; - inset-inline-start: 0; - inset-inline-end: 0; - margin: 0 auto; - width: 420px; - display: flex; - align-items: center; - flex-direction: column; - border-radius: 8px; - border: 1px solid #eee; - background: #fff; - padding: 20px 24px; - box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); - animation: mirror-zoom-in 0.3s forwards; - box-sizing: border-box; - max-width: 100vw; - z-index: 9999; - } - - .mirror-modal-title { - font-size: 16px; - font-weight: 500; - align-self: flex-start; - margin-bottom: 8px; - } - - .mirror-modal-content { - font-size: 14px; - align-self: flex-start; - margin-bottom: 24px; - } - - .mirror-modal-btns { - align-self: flex-end; - margin-top: auto; - display: flex; - align-items: center; - } - - .mirror-modal-btn { - border-radius: 6px; - cursor: pointer; - height: 32px; - box-sizing: border-box; - font-size: 14px; - padding: 4px 16px; - display: inline-flex; - align-items: center; - text-decoration: none; - transition: all 0.2s; - } - - .mirror-modal-confirm-btn { - background: #1677ff; - color: #fff; - } - - .mirror-modal-confirm-btn:hover { - background: #4096ff; - } - - .mirror-modal-confirm-btn:active { - background: #0958d9; - } - - .mirror-modal-cancel-btn { - border: 1px solid #eee; - color: #000; - margin-inline-end: 8px; - } - - .mirror-modal-cancel-btn:hover { - border-color: #4096ff; - color: #4096ff - } - - .mirror-modal-cancel-btn:active { - border-color: #0958d9; - color: #0958d9; - } - `; - document.head.append(style); - - const modal = document.createElement('div'); - modal.className = 'mirror-modal-mask'; - - const dialog = document.createElement('div'); - dialog.className = 'mirror-modal-dialog'; - modal.append(dialog); - - const title = document.createElement('div'); - title.className = 'mirror-modal-title'; - title.textContent = '提示'; - dialog.append(title); - - const content = document.createElement('div'); - content.className = 'mirror-modal-content'; - content.textContent = '🚀 国内用户推荐访问国内镜像以获得极速体验~'; - dialog.append(content); - - const btnWrapper = document.createElement('div'); - btnWrapper.className = 'mirror-modal-btns'; - dialog.append(btnWrapper); - - const cancelBtn = document.createElement('a'); - cancelBtn.className = 'mirror-modal-cancel-btn mirror-modal-btn'; - cancelBtn.textContent = '7 天内不再显示'; - btnWrapper.append(cancelBtn); - cancelBtn.addEventListener('click', () => { - window.localStorage.setItem(ANTD_DOT_NOT_SHOW_MIRROR_MODAL, new Date().toISOString()); - document.body.removeChild(modal); - document.head.removeChild(style); - document.body.style.overflow = ''; - }); - - const confirmBtn = document.createElement('a'); - confirmBtn.className = 'mirror-modal-confirm-btn mirror-modal-btn'; - confirmBtn.href = window.location.href.replace(window.location.host, 'ant-design.antgroup.com'); - confirmBtn.textContent = '🚀 立刻前往'; - btnWrapper.append(confirmBtn); - - document.body.append(modal); - document.body.style.overflow = 'hidden'; - } -})(); diff --git a/.dumi/scripts/mirror-notify.js b/.dumi/scripts/mirror-notify.js new file mode 100644 index 0000000000..469a0c4d03 --- /dev/null +++ b/.dumi/scripts/mirror-notify.js @@ -0,0 +1,230 @@ +(function createMirrorModal() { + const SIGN = Symbol.for('antd.mirror-notify'); + const always = window.localStorage.getItem('DEBUG') === 'antd'; + const officialChinaMirror = 'https://ant-design.antgroup.com'; + + const enabledCondition = [ + // Check if the browser language is Chinese + navigator.languages.includes('zh') || navigator.languages.includes('zh-CN'), + // Check if the URL path ends with -cn + /-cn\/?$/.test(window.location.pathname), + // chinese mirror URL + !['ant-design.gitee.io', new URL(officialChinaMirror).hostname].includes( + window.location.hostname, + ), + // PR review URL + !window.location.host.includes('surge'), + // development mode + !['127.0.0.1', 'localhost'].includes(window.location.hostname), + ]; + + const isEnabled = always || enabledCondition.every(Boolean); + + if (!isEnabled) return; + + const prefixCls = 'antd-mirror-notify'; + const primaryColor = '#1677ff'; + + function insertCss() { + const style = document.createElement('style'); + style.innerHTML = ` + @keyframes slideInRight { + from { + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + transform: translate3d(0, 0, 0); + } + } + + .${prefixCls} { + position: fixed; + inset-inline-end: 12px; + inset-block-start: 12px; + z-index: 9999; + width: 360px; + background-color: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + border-radius: 4px; + overflow: hidden; + animation: slideInRight 0.3s ease-in-out; + } + .${prefixCls}-content { + padding: 16px; + } + .${prefixCls}-content a { + color: ${primaryColor}; + text-decoration: none; + &:hover { + text-decoration: underline; + } + } + .${prefixCls}-title { + font-size: 16px; + font-weight: bold; + margin-block-end: 8px; + } + .${prefixCls}-message { + font-size: 14px; + color: #555; + line-height: 1.57; + } + .${prefixCls}-footer { + display: none; + margin-block-start: 16px; + justify-content: flex-end; + } + + .${prefixCls}-progress { + position: relative; + inset-inline-end: 0; + width: 100%; + height: 4px; + background-color: #f0f0f0; + border-radius: 2px; + overflow: hidden; + } + + .${prefixCls}-progress::after { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: var(--progress, 0%); + background-color: ${primaryColor}; + transition: width 0.05s linear; /* Adjusted for smoother animation matching refreshRate */ + } + .${prefixCls}-close { + all: unset; + position: absolute; + inset-inline-end: 2px; + inset-block-start: 2px; + width: 32px; + height: 32px; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; + color: #999; + cursor: pointer; + } + + .${prefixCls}-close:hover { + color: #333; + } + + .${prefixCls}-action { + all: unset; + display: inline-block; + padding: 4px 8px; + background-color: ${primaryColor}; + color: #fff; + border-radius: 4px; + text-align: center; + cursor: pointer; + font-size: 14px; + } + `; + + document.head.append(style); + } + + function createNotification() { + insertCss(); + + const notify = document.createElement('div'); + notify.className = `${prefixCls} slideInRight`; + notify.innerHTML = ` +
+
🇨🇳 访问不畅?试试国内镜像
+
+ 国内镜像站点可以帮助您更快地访问文档和资源。
+ 请尝试访问 国内镜像站点,以获得更好的体验。 +
+ +
+ +
+ `; + document.body.appendChild(notify); + + notify.querySelector(`.${prefixCls}-close`).addEventListener('click', () => { + removeNotify(); + }); + + notify.querySelector(`.${prefixCls}-action`).addEventListener('click', () => { + window.location.href = officialChinaMirror; + removeNotify(); + }); + + const refreshRate = 50; // ms + const duration = 10; // s + const step = 100 / ((duration * 1000) / refreshRate); + let progressInterval = -1; + + function removeNotify() { + clearInterval(progressInterval); + notify.remove(); + } + + const progressEl = notify.querySelector(`.${prefixCls}-progress`); + let currentProgressValue = 100; + + const progress = { + get value() { + return currentProgressValue; + }, + set value(val) { + currentProgressValue = Math.max(0, Math.min(100, val)); + progressEl.style.setProperty('--progress', `${currentProgressValue}%`); + }, + }; + + function startProgressTimer() { + if (progressInterval !== -1) { + clearInterval(progressInterval); + } + progressInterval = setInterval(() => { + if (progress.value <= 0) { + removeNotify(); + } else { + progress.value -= step; + } + }, refreshRate); + } + + startProgressTimer(); + + notify.addEventListener('mouseenter', () => { + clearInterval(progressInterval); + }); + + notify.addEventListener('mouseleave', () => { + startProgressTimer(); + }); + } + + // 断定网络不畅阈值(秒) + const delayDuration = 3; + + const reactTimeoutId = setTimeout(() => { + if (typeof window[SIGN]?.YES === 'undefined') { + console.error( + `antd.mirror-notify: 页面加载超过 ${delayDuration} 秒,可能是网络不畅。\n请尝试访问国内镜像站点。%c${officialChinaMirror}`, + `color: ${primaryColor}; font-weight: bold;`, + ); + createNotification(); + } + }, delayDuration * 1000); + + // 交给 React effect 清理 + window[SIGN] = function stopMirrorNotify() { + window[SIGN].YES = Date.now(); + clearTimeout(reactTimeoutId); + }; +})(); diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx index d1da1e9640..0d158eed65 100644 --- a/.dumi/theme/layouts/GlobalLayout.tsx +++ b/.dumi/theme/layouts/GlobalLayout.tsx @@ -140,6 +140,12 @@ const GlobalLayout: React.FC = () => { // Handle isMobile updateMobileMode(); + // 配合 dumi 的 mirror-notify 脚本使用 + const retrieveMirrorNotification = (window as any)[Symbol.for('antd.mirror-notify')]; + if (typeof retrieveMirrorNotification === 'function') { + retrieveMirrorNotification(); + } + window.addEventListener('resize', updateMobileMode); return () => { window.removeEventListener('resize', updateMobileMode); diff --git a/.dumirc.ts b/.dumirc.ts index 3bea8e16b8..5b9957f2f0 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -190,7 +190,7 @@ export default defineConfig({ { async: true, content: fs - .readFileSync(path.join(__dirname, '.dumi', 'scripts', 'mirror-modal.js')) + .readFileSync(path.join(__dirname, '.dumi', 'scripts', 'mirror-notify.js')) .toString(), }, {