MediaWiki:Gadget-site-js.js:修订间差异
MediaWiki界面页面
更多操作
第1行: | 第1行: | ||
/********************** | /********************** | ||
* 模块 | * 模块 0:优先加载 vConsole(立即执行) | ||
**********************/ | **********************/ | ||
const | (function() { | ||
// 调试控制开关(生产环境可设置为 false) | |||
const DEBUG_MODE = true; | |||
if (DEBUG_MODE && !window.localStorage.getItem('disableVConsole')) { | |||
console.log('[Init] 正在加载调试控制台...'); | |||
const vConsoleScript = document.createElement('script'); | |||
vConsoleScript.src = 'https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.1/vconsole.min.js'; | |||
vConsoleScript.integrity = 'sha384-+EmbrakM5WQd6BSpR2GQKJqH15zFVECXZQ5qPj4hEtZj0jFz4iF2PnmFUTTvWH+1'; | |||
vConsoleScript.crossOrigin = 'anonymous'; | |||
vConsoleScript.onload = function() { | |||
try { | |||
// 初始化 vConsole 并配置 | |||
const vConsole = new window.VConsole({ | |||
theme: 'dark', | |||
onReady: function() { | |||
console.log('[vConsole] 调试面板已就绪'); | |||
// 捕获未处理的Promise错误 | |||
window.addEventListener('unhandledrejection', event => { | |||
console.error('[未处理的Promise错误]', event.reason); | |||
}); | |||
} | |||
}); | |||
// 全局错误监听(捕获语法错误之外的运行时错误) | |||
window.addEventListener('error', function(event) { | |||
console.error( | |||
'[全局错误]', | |||
`类型: ${event.error?.name || 'Error'}\n`, | |||
`信息: ${event.message}\n`, | |||
`文件: ${event.filename}\n`, | |||
`行号: ${event.lineno}:${event.colno}` | |||
); | |||
}); | |||
} catch (e) { | |||
console.error('[vConsole] 初始化失败', e); | |||
} | |||
}; | |||
vConsoleScript.onerror = function() { | |||
console.warn('[vConsole] 加载失败,尝试备用CDN...'); | |||
loadFallbackVConsole(); | |||
}; | |||
document.head.insertBefore(vConsoleScript, document.head.firstChild); | |||
// 备用CDN加载 | |||
function loadFallbackVConsole() { | |||
const fallbackScript = document.createElement('script'); | |||
fallbackScript.src = 'https://cdn.jsdelivr.net/npm/vconsole@3.15.1/dist/vconsole.min.js'; | |||
fallbackScript.onload = vConsoleScript.onload; | |||
fallbackScript.onerror = function() { | |||
console.error('[vConsole] 所有CDN加载失败,将无法显示控制台'); | |||
}; | |||
document.head.appendChild(fallbackScript); | |||
} | } | ||
} | } | ||
})(); | |||
/********************** | |||
* 模块 1:CSS 变量 RGB 转换(安全封装) | |||
**********************/ | |||
;(function() { | |||
try { | |||
const ColorUtil = (function () { | |||
const OBS_VAR = '--background-color-base'; | |||
let cachedRgb = ''; | |||
function parseHex(hex) { | |||
try { | |||
hex = hex.replace(/^#/, ''); | |||
if (hex.length === 3) { | |||
hex = hex.split('').map(c => c + c).join(''); | |||
} | |||
if (hex.length !== 6) return '0, 0, 0'; | |||
const [r, g, b] = [0, 2, 4].map(i => parseInt(hex.substring(i, i + 2), 16) || 0); | |||
return `${r}, ${g}, ${b}`; | |||
} catch (e) { | |||
console.error('[ColorUtil] 颜色解析失败', e); | |||
return '0, 0, 0'; | |||
} | |||
} | |||
function safeUpdate() { | |||
try { | |||
const root = document.documentElement; | |||
const hex = getComputedStyle(root).getPropertyValue(OBS_VAR).trim(); | |||
const rgb = parseHex(hex || '#000'); | |||
if (rgb !== cachedRgb) { | |||
root.style.setProperty('--background-color-rgb', rgb); | |||
cachedRgb = rgb; | |||
console.log('[ColorUtil] RGB值已更新:', rgb); | |||
} | |||
} catch (e) { | |||
console.warn('[ColorUtil] 更新失败', e); | |||
} | |||
} | |||
function init() { | |||
try { | |||
safeUpdate(); | |||
const mo = new MutationObserver(safeUpdate); | |||
mo.observe(document.documentElement, { | |||
attributes: true, | |||
attributeFilter: ['style'] | |||
}); | |||
return { | |||
destroy: () => mo.disconnect(), | |||
forceUpdate: safeUpdate | |||
}; | |||
} catch (e) { | |||
console.error('[ColorUtil] 初始化失败', e); | |||
return { destroy: () => {}, forceUpdate: () => {} }; | |||
} | |||
} | |||
return init(); | |||
})(); | |||
} | // 暴露到全局(可选) | ||
window.ColorUtil = ColorUtil; | |||
} catch (e) { | |||
console.error('[模块1] 初始化异常', e); | |||
} | |||
})(); | })(); | ||
/********************** | /********************** | ||
* 模块 | * 模块 2:上传页引导弹窗(安全封装) | ||
**********************/ | **********************/ | ||
(function ( | ;(function() { | ||
if (!mw || mw.config.get('wgCanonicalSpecialPageName') !== 'Upload') return; | try { | ||
if (!window.mw || !mw.config || mw.config.get('wgCanonicalSpecialPageName') !== 'Upload') { | |||
return; | |||
} | |||
const KEY = 'uploadPreference_v2'; | |||
const pref = mw.storage.get(KEY); | |||
if (pref === 'wizard') { | |||
window.location.href = mw.util.getUrl('Special:UploadWizard'); | |||
return; | |||
} | |||
if (pref === 'classic') return; | |||
function initDialog(event) { | |||
// 确保只触发一次 | |||
if (event && event.target.closest('#mw-content-text')) { | |||
document.removeEventListener('click', initDialog); | |||
showDialog(); | |||
} | |||
} | |||
function showDialog() { | |||
mw.loader.using(['mediawiki.util', 'mediawiki.storage', 'oojs-ui-core', 'oojs-ui-widgets']) | |||
.then(() => { | |||
try { | |||
class UploadDialog extends OO.ui.ProcessDialog { | |||
static static = { | |||
name: 'UploadDialog', | |||
title: '请选择上传方式', | |||
actions: [ | |||
{ | |||
action: 'classic', | |||
label: '❌ 使用传统上传方式', | |||
flags: ['safe'] | |||
} | |||
] | |||
}; | |||
initialize() { | |||
super.initialize(); | |||
const panel = new OO.ui.PanelLayout({ padded: true }); | |||
this._remember = new OO.ui.CheckboxInputWidget(); | |||
const rememberField = new OO.ui.FieldLayout(this._remember, { | |||
label: '记住我的选择', | |||
align: 'inline' | |||
}); | |||
const wizardBtn = new OO.ui.ButtonWidget({ | |||
label: '✅ 使用上传向导(推荐)', | |||
flags: ['primary', 'progressive'], | |||
href: mw.util.getUrl('Special:UploadWizard'), | |||
target: '_self' | |||
}); | |||
wizardBtn.on('click', () => { | |||
if (this._remember.isSelected()) { | |||
mw.storage.set(KEY, 'wizard'); | |||
} | |||
}); | |||
panel.$element.append( | |||
$('<p>').text('请选择上传方式:'), | |||
$('<div>').css('margin', '10px 0').append(wizardBtn.$element), | |||
$('<div>').append(rememberField.$element) | |||
); | |||
this.$body.append(panel.$element); | |||
} | |||
getActionProcess(action) { | |||
if (action === 'classic') { | |||
return new OO.ui.Process(() => { | |||
if (this._remember.isSelected()) { | |||
mw.storage.set(KEY, 'classic'); | |||
} | |||
this.close(); | |||
}); | |||
} | |||
return super.getActionProcess(action); | |||
} | |||
} | } | ||
const windowManager = new OO.ui.WindowManager(); | |||
document.body.appendChild(windowManager.$element); | |||
windowManager.addWindows([new UploadDialog()]); | |||
windowManager.openWindow('UploadDialog'); | |||
} catch (e) { | |||
console.error('[UploadDialog] 创建失败', e); | |||
// 降级处理:直接跳转 | |||
window.location.href = mw.util.getUrl('Special:UploadWizard'); | |||
} | } | ||
} | }) | ||
.catch(e => { | |||
console.error('[OOUI] 加载失败', e); | |||
}); | |||
} | |||
// 延迟绑定事件,避免阻塞 | |||
setTimeout(() => { | |||
document.addEventListener('click', initDialog, { once: true }); | |||
}, 500); | |||
} catch (e) { | |||
console.error('[模块2] 初始化异常', e); | |||
} | } | ||
})(); | |||
})( | |||
/********************** | /********************** | ||
* 模块 | * 模块 3:全局加载指示器(安全封装) | ||
**********************/ | **********************/ | ||
;(function() { | |||
try { | |||
const LoadingIndicator = (function () { | |||
// 配置项(可自定义) | |||
const config = { | |||
imageUrl: 'https://example.com/loading-spinner.svg', | |||
fallbackImage: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMDA3M2JhIiBzdHJva2Utd2lkdGg9IjgiIGZpbGw9Im5vbmUiIHN0cm9rZS1kYXNoYXJyYXk9IjYyLjgzIDYyLjgzIiB0cmFuc2Zvcm09InJvdGF0ZSgzNjAgNTAgNTApIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGR1cj0iMXMiIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiLz48L2NpcmNsZT48L3N2Zz4=', | |||
size: '64px', | |||
position: { bottom: '20px', right: '20px' }, | |||
timeout: 15000, | |||
animation: { fadeIn: 300, fadeOut: 500 }, | |||
zIndex: 99999 | |||
}; | |||
let instance = null; | |||
let timeoutId = null; | |||
let startTime = 0; | |||
function createLoader() { | |||
const loader = document.createElement('div'); | |||
loader.className = 'global-loader'; | |||
loader.setAttribute('aria-live', 'polite'); | |||
Object.assign(loader.style, { | |||
position: 'fixed', | |||
width: config.size, | |||
height: config.size, | |||
bottom: config.position.bottom, | |||
right: config.position.right, | |||
opacity: '0', | |||
transition: `opacity ${config.animation.fadeIn}ms ease-out`, | |||
zIndex: config.zIndex, | |||
pointerEvents: 'none', | |||
backgroundImage: `url(${config.imageUrl})`, | |||
backgroundSize: 'contain', | |||
backgroundRepeat: 'no-repeat' | |||
}); | |||
// 备用图片处理 | |||
loader.onerror = function() { | |||
this.style.backgroundImage = `url(${config.fallbackImage})`; | |||
}; | |||
return loader; | |||
} | |||
function show() { | |||
if (instance) return resetTimer(); | |||
startTime = Date.now(); | |||
instance = createLoader(); | |||
document.body.appendChild(instance); | |||
requestAnimationFrame(() => { | |||
instance.style.opacity = '1'; | |||
}); | |||
resetTimer(); | |||
addEventListeners(); | |||
} | |||
function hide() { | |||
if (!instance) return; | |||
const elapsed = Date.now() - startTime; | |||
const minDisplayTime = 500; // 最少显示500ms | |||
const delay = Math.max(minDisplayTime - elapsed, 0); | |||
clearTimeout(timeoutId); | |||
setTimeout(() => { | |||
instance.style.opacity = '0'; | |||
instance.setAttribute('aria-busy', 'false'); | |||
setTimeout(() => { | |||
if (instance && instance.parentNode) { | |||
instance.parentNode.removeChild(instance); | |||
} | |||
instance = null; | |||
removeEventListeners(); | |||
}, config.animation.fadeOut); | |||
}, delay); | |||
} | |||
function resetTimer() { | |||
clearTimeout(timeoutId); | |||
timeoutId = setTimeout(hide, config.timeout); | |||
} | |||
function handleOnline() { | |||
console.log('[Loader] 网络恢复'); | |||
} | |||
function handleOffline() { | |||
console.warn('[Loader] 网络断开'); | |||
hide(); | |||
} | |||
function addEventListeners() { | |||
window.addEventListener('online', handleOnline); | |||
window.addEventListener('offline', handleOffline); | |||
} | |||
function removeEventListeners() { | |||
window.removeEventListener('online', handleOnline); | |||
window.removeEventListener('offline', handleOffline); | |||
} | |||
// 自动绑定生命周期事件 | |||
document.addEventListener('DOMContentLoaded', show); | |||
window.addEventListener('load', hide); | |||
// 页面离开时清理 | |||
window.addEventListener('beforeunload', () => { | |||
if (instance) { | |||
hide(); | |||
} | |||
}); | |||
return { | |||
show, | |||
hide, | |||
updateConfig: (newConfig) => { | |||
Object.assign(config, newConfig); | |||
} | |||
}; | |||
})(); | |||
// 暴露到全局(可选) | |||
window.LoadingIndicator = LoadingIndicator; | |||
} catch (e) { | |||
console.error('[模块3] 初始化异常', e); | |||
} | |||
})(); | })(); | ||
/********************** | /********************** | ||
* | * 错误兜底处理(最后执行) | ||
**********************/ | **********************/ | ||
if (!window. | ;(function() { | ||
// 如果vConsole未加载,显示简化错误提示 | |||
if (!window.vConsole) { | |||
window.addEventListener('error', function(event) { | |||
console.error('页面错误:', event.error); | |||
const errorBox = document.createElement('div'); | |||
errorBox.style.cssText = ` | |||
} | position: fixed; | ||
} | bottom: 20px; | ||
left: 20px; | |||
right: 20px; | |||
padding: 15px; | |||
background: #ffebee; | |||
border: 2px solid #f44336; | |||
border-radius: 5px; | |||
z-index: 99999; | |||
font-family: sans-serif; | |||
`; | |||
errorBox.innerHTML = ` | |||
<h3 style="margin-top:0;color:#d32f2f">页面错误</h3> | |||
<p>${event.message}</p> | |||
<button onclick="this.parentNode.remove()" | |||
style="background:#f44336;color:white;border:none;padding:5px 10px;border-radius:3px"> | |||
关闭 | |||
</button> | |||
`; | |||
document.body.appendChild(errorBox); | |||
}); | |||
} | |||
})(); |
2025年6月21日 (六) 08:02的版本
/**********************
* 模块 0:优先加载 vConsole(立即执行)
**********************/
(function() {
// 调试控制开关(生产环境可设置为 false)
const DEBUG_MODE = true;
if (DEBUG_MODE && !window.localStorage.getItem('disableVConsole')) {
console.log('[Init] 正在加载调试控制台...');
const vConsoleScript = document.createElement('script');
vConsoleScript.src = 'https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.1/vconsole.min.js';
vConsoleScript.integrity = 'sha384-+EmbrakM5WQd6BSpR2GQKJqH15zFVECXZQ5qPj4hEtZj0jFz4iF2PnmFUTTvWH+1';
vConsoleScript.crossOrigin = 'anonymous';
vConsoleScript.onload = function() {
try {
// 初始化 vConsole 并配置
const vConsole = new window.VConsole({
theme: 'dark',
onReady: function() {
console.log('[vConsole] 调试面板已就绪');
// 捕获未处理的Promise错误
window.addEventListener('unhandledrejection', event => {
console.error('[未处理的Promise错误]', event.reason);
});
}
});
// 全局错误监听(捕获语法错误之外的运行时错误)
window.addEventListener('error', function(event) {
console.error(
'[全局错误]',
`类型: ${event.error?.name || 'Error'}\n`,
`信息: ${event.message}\n`,
`文件: ${event.filename}\n`,
`行号: ${event.lineno}:${event.colno}`
);
});
} catch (e) {
console.error('[vConsole] 初始化失败', e);
}
};
vConsoleScript.onerror = function() {
console.warn('[vConsole] 加载失败,尝试备用CDN...');
loadFallbackVConsole();
};
document.head.insertBefore(vConsoleScript, document.head.firstChild);
// 备用CDN加载
function loadFallbackVConsole() {
const fallbackScript = document.createElement('script');
fallbackScript.src = 'https://cdn.jsdelivr.net/npm/vconsole@3.15.1/dist/vconsole.min.js';
fallbackScript.onload = vConsoleScript.onload;
fallbackScript.onerror = function() {
console.error('[vConsole] 所有CDN加载失败,将无法显示控制台');
};
document.head.appendChild(fallbackScript);
}
}
})();
/**********************
* 模块 1:CSS 变量 RGB 转换(安全封装)
**********************/
;(function() {
try {
const ColorUtil = (function () {
const OBS_VAR = '--background-color-base';
let cachedRgb = '';
function parseHex(hex) {
try {
hex = hex.replace(/^#/, '');
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('');
}
if (hex.length !== 6) return '0, 0, 0';
const [r, g, b] = [0, 2, 4].map(i => parseInt(hex.substring(i, i + 2), 16) || 0);
return `${r}, ${g}, ${b}`;
} catch (e) {
console.error('[ColorUtil] 颜色解析失败', e);
return '0, 0, 0';
}
}
function safeUpdate() {
try {
const root = document.documentElement;
const hex = getComputedStyle(root).getPropertyValue(OBS_VAR).trim();
const rgb = parseHex(hex || '#000');
if (rgb !== cachedRgb) {
root.style.setProperty('--background-color-rgb', rgb);
cachedRgb = rgb;
console.log('[ColorUtil] RGB值已更新:', rgb);
}
} catch (e) {
console.warn('[ColorUtil] 更新失败', e);
}
}
function init() {
try {
safeUpdate();
const mo = new MutationObserver(safeUpdate);
mo.observe(document.documentElement, {
attributes: true,
attributeFilter: ['style']
});
return {
destroy: () => mo.disconnect(),
forceUpdate: safeUpdate
};
} catch (e) {
console.error('[ColorUtil] 初始化失败', e);
return { destroy: () => {}, forceUpdate: () => {} };
}
}
return init();
})();
// 暴露到全局(可选)
window.ColorUtil = ColorUtil;
} catch (e) {
console.error('[模块1] 初始化异常', e);
}
})();
/**********************
* 模块 2:上传页引导弹窗(安全封装)
**********************/
;(function() {
try {
if (!window.mw || !mw.config || mw.config.get('wgCanonicalSpecialPageName') !== 'Upload') {
return;
}
const KEY = 'uploadPreference_v2';
const pref = mw.storage.get(KEY);
if (pref === 'wizard') {
window.location.href = mw.util.getUrl('Special:UploadWizard');
return;
}
if (pref === 'classic') return;
function initDialog(event) {
// 确保只触发一次
if (event && event.target.closest('#mw-content-text')) {
document.removeEventListener('click', initDialog);
showDialog();
}
}
function showDialog() {
mw.loader.using(['mediawiki.util', 'mediawiki.storage', 'oojs-ui-core', 'oojs-ui-widgets'])
.then(() => {
try {
class UploadDialog extends OO.ui.ProcessDialog {
static static = {
name: 'UploadDialog',
title: '请选择上传方式',
actions: [
{
action: 'classic',
label: '❌ 使用传统上传方式',
flags: ['safe']
}
]
};
initialize() {
super.initialize();
const panel = new OO.ui.PanelLayout({ padded: true });
this._remember = new OO.ui.CheckboxInputWidget();
const rememberField = new OO.ui.FieldLayout(this._remember, {
label: '记住我的选择',
align: 'inline'
});
const wizardBtn = new OO.ui.ButtonWidget({
label: '✅ 使用上传向导(推荐)',
flags: ['primary', 'progressive'],
href: mw.util.getUrl('Special:UploadWizard'),
target: '_self'
});
wizardBtn.on('click', () => {
if (this._remember.isSelected()) {
mw.storage.set(KEY, 'wizard');
}
});
panel.$element.append(
$('<p>').text('请选择上传方式:'),
$('<div>').css('margin', '10px 0').append(wizardBtn.$element),
$('<div>').append(rememberField.$element)
);
this.$body.append(panel.$element);
}
getActionProcess(action) {
if (action === 'classic') {
return new OO.ui.Process(() => {
if (this._remember.isSelected()) {
mw.storage.set(KEY, 'classic');
}
this.close();
});
}
return super.getActionProcess(action);
}
}
const windowManager = new OO.ui.WindowManager();
document.body.appendChild(windowManager.$element);
windowManager.addWindows([new UploadDialog()]);
windowManager.openWindow('UploadDialog');
} catch (e) {
console.error('[UploadDialog] 创建失败', e);
// 降级处理:直接跳转
window.location.href = mw.util.getUrl('Special:UploadWizard');
}
})
.catch(e => {
console.error('[OOUI] 加载失败', e);
});
}
// 延迟绑定事件,避免阻塞
setTimeout(() => {
document.addEventListener('click', initDialog, { once: true });
}, 500);
} catch (e) {
console.error('[模块2] 初始化异常', e);
}
})();
/**********************
* 模块 3:全局加载指示器(安全封装)
**********************/
;(function() {
try {
const LoadingIndicator = (function () {
// 配置项(可自定义)
const config = {
imageUrl: 'https://example.com/loading-spinner.svg',
fallbackImage: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMDA3M2JhIiBzdHJva2Utd2lkdGg9IjgiIGZpbGw9Im5vbmUiIHN0cm9rZS1kYXNoYXJyYXk9IjYyLjgzIDYyLjgzIiB0cmFuc2Zvcm09InJvdGF0ZSgzNjAgNTAgNTApIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGR1cj0iMXMiIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiLz48L2NpcmNsZT48L3N2Zz4=',
size: '64px',
position: { bottom: '20px', right: '20px' },
timeout: 15000,
animation: { fadeIn: 300, fadeOut: 500 },
zIndex: 99999
};
let instance = null;
let timeoutId = null;
let startTime = 0;
function createLoader() {
const loader = document.createElement('div');
loader.className = 'global-loader';
loader.setAttribute('aria-live', 'polite');
Object.assign(loader.style, {
position: 'fixed',
width: config.size,
height: config.size,
bottom: config.position.bottom,
right: config.position.right,
opacity: '0',
transition: `opacity ${config.animation.fadeIn}ms ease-out`,
zIndex: config.zIndex,
pointerEvents: 'none',
backgroundImage: `url(${config.imageUrl})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat'
});
// 备用图片处理
loader.onerror = function() {
this.style.backgroundImage = `url(${config.fallbackImage})`;
};
return loader;
}
function show() {
if (instance) return resetTimer();
startTime = Date.now();
instance = createLoader();
document.body.appendChild(instance);
requestAnimationFrame(() => {
instance.style.opacity = '1';
});
resetTimer();
addEventListeners();
}
function hide() {
if (!instance) return;
const elapsed = Date.now() - startTime;
const minDisplayTime = 500; // 最少显示500ms
const delay = Math.max(minDisplayTime - elapsed, 0);
clearTimeout(timeoutId);
setTimeout(() => {
instance.style.opacity = '0';
instance.setAttribute('aria-busy', 'false');
setTimeout(() => {
if (instance && instance.parentNode) {
instance.parentNode.removeChild(instance);
}
instance = null;
removeEventListeners();
}, config.animation.fadeOut);
}, delay);
}
function resetTimer() {
clearTimeout(timeoutId);
timeoutId = setTimeout(hide, config.timeout);
}
function handleOnline() {
console.log('[Loader] 网络恢复');
}
function handleOffline() {
console.warn('[Loader] 网络断开');
hide();
}
function addEventListeners() {
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
}
function removeEventListeners() {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
}
// 自动绑定生命周期事件
document.addEventListener('DOMContentLoaded', show);
window.addEventListener('load', hide);
// 页面离开时清理
window.addEventListener('beforeunload', () => {
if (instance) {
hide();
}
});
return {
show,
hide,
updateConfig: (newConfig) => {
Object.assign(config, newConfig);
}
};
})();
// 暴露到全局(可选)
window.LoadingIndicator = LoadingIndicator;
} catch (e) {
console.error('[模块3] 初始化异常', e);
}
})();
/**********************
* 错误兜底处理(最后执行)
**********************/
;(function() {
// 如果vConsole未加载,显示简化错误提示
if (!window.vConsole) {
window.addEventListener('error', function(event) {
console.error('页面错误:', event.error);
const errorBox = document.createElement('div');
errorBox.style.cssText = `
position: fixed;
bottom: 20px;
left: 20px;
right: 20px;
padding: 15px;
background: #ffebee;
border: 2px solid #f44336;
border-radius: 5px;
z-index: 99999;
font-family: sans-serif;
`;
errorBox.innerHTML = `
<h3 style="margin-top:0;color:#d32f2f">页面错误</h3>
<p>${event.message}</p>
<button onclick="this.parentNode.remove()"
style="background:#f44336;color:white;border:none;padding:5px 10px;border-radius:3px">
关闭
</button>
`;
document.body.appendChild(errorBox);
});
}
})();