docs(site): Network connection is not smooth notification (#53930)

This commit is contained in:
𝑾𝒖𝒙𝒉 2025-05-30 15:34:14 +08:00 committed by GitHub
parent 8944de51e6
commit 37dd4449a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 237 additions and 177 deletions

View File

@ -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';
}
})();

View File

@ -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 = `
<div class="${prefixCls}-content">
<div class="${prefixCls}-title">🇨🇳 访问不畅试试国内镜像</div>
<div class="${prefixCls}-message">
国内镜像站点可以帮助您更快地访问文档和资源<br>
请尝试访问 <a href="${officialChinaMirror}">国内镜像站点</a>
</div>
<div class="${prefixCls}-footer">
<button class="${prefixCls}-action">🚀 立即前往</button>
</div>
</div>
<button class="${prefixCls}-close">X</button>
<div class="${prefixCls}-progress" style="--progress: 100%;"></div>
`;
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);
};
})();

View File

@ -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);

View File

@ -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(),
},
{