跳转到内容

MediaWiki:Common.js:修订间差异

来自电棍ottowiki
第150行: 第150行:
});
});
//---
//---
// IIFE
// 全局加载指示器
(function() {
(function() {
    var loadingImgUrl = 'https://wiki.ottohub.cn/images/0/02/Loading.png';
  // 配置系统
    var loadingDiv = null; // Will be created on demand
  const defaultConfig = {
    var removalTimeoutId = null;
    imageUrl: 'https://wiki.ottohub.cn/images/0/02/Loading.png',
    var isShowingLoading = false; // Track if loading is currently managed by this script
    position: { bottom: '20px', right: '20px' },
 
    size: '256px',
    // --- Function to CREATE and SHOW loading indicator ---
    timeout: 15000,
    function showLoading(reason) {
    zIndex: '99999',
        if (isShowingLoading && document.getElementById('loadingIndicator')) {
    debug: true,
            // console.log('[Loading] Already showing, triggered by:', reason);
    renderIndicator: defaultRenderer,
            return; // Already showing or element exists
    fadeDuration: 300
        }
  };
        isShowingLoading = true;
 
        console.log('[Loading] Showing. Reason:', reason);
  let config = {...defaultConfig};
 
  let loadingDiv = null;
        // Create if it doesn't exist
  let removalTimeoutId = null;
        if (!document.getElementById('loadingIndicator')) {
  let isShowingLoading = false;
            loadingDiv = document.createElement('div');
  let linkTrackingEnabled = false;
            loadingDiv.id = 'loadingIndicator';
  const eventListeners = [];
            loadingDiv.style.position = 'fixed';
            loadingDiv.style.bottom = '20px';
  // 默认渲染器
            loadingDiv.style.right = '20px';
  function defaultRenderer(container) {
            loadingDiv.style.width = '256px';
    const img = document.createElement('img');
            loadingDiv.style.height = '256px';
    img.src = config.imageUrl;
            loadingDiv.style.zIndex = '99999';
    img.alt = '加载中...';
            loadingDiv.style.pointerEvents = 'none';
    img.style.width = '100%';
            loadingDiv.style.opacity = '0'; // Start transparent for fadeIn
    img.style.height = '100%';
 
    img.style.objectFit = 'contain';
            var img = document.createElement('img');
    container.appendChild(img);
            img.src = loadingImgUrl;
  }
            img.alt = 'Loading...';
            img.style.width = '100%';
  // 图片预加载
            img.style.height = '100%';
  const imgPreload = new Image();
            img.style.objectFit = 'contain';
  imgPreload.src = config.imageUrl;
            loadingDiv.appendChild(img);
 
  // 事件管理
            if (document.body) {
  function addEventListener(target, type, handler) {
                document.body.appendChild(loadingDiv);
    target.addEventListener(type, handler);
            } else {
    eventListeners.push({ target, type, handler });
                // Fallback if body isn't ready (e.g. script in head without defer)
  }
                document.addEventListener('DOMContentLoaded', function onReadyAppend() {
                    if (document.body && !document.getElementById('loadingIndicator')) {
  function cleanupEventListeners() {
                        document.body.appendChild(loadingDiv);
    eventListeners.forEach(({ target, type, handler }) => {
                        fadeInLoading();
      target.removeEventListener(type, handler);
                    }
    });
                    document.removeEventListener('DOMContentLoaded', onReadyAppend);
    eventListeners.length = 0;
                });
  }
                return; // fadeIn will be called in DOMContentLoaded
            }
  // --- 核心显示函数 ---
        } else {
  function showLoading(reason) {
            loadingDiv = document.getElementById('loadingIndicator'); // Get existing
    if (isShowingLoading && document.getElementById('loadingIndicator')) {
        }
      config.debug && console.log('[Loading] 已显示,触发原因:', reason);
 
      return;
        fadeInLoading();
    }
 
   
        // Reset timeout for removal (if any previous one was set for page load)
    isShowingLoading = true;
        if (removalTimeoutId) {
    config.debug && console.log('[Loading] 显示。原因:', reason);
            clearTimeout(removalTimeoutId);
        }
    // 性能标记
        // Set a timeout for link clicks as well, in case something goes wrong
    performance.mark(`loadingStart_${reason}`);
        // You might want a different timeout duration for link clicks vs initial load
        removalTimeoutId = setTimeout(function() {
    // 创建元素(如果不存在)
            hideLoading('timeout fallback after click (15s)');
    if (!document.getElementById('loadingIndicator')) {
        }, 15000); // e.g., 15 seconds for navigation
      loadingDiv = document.createElement('div');
    }
      loadingDiv.id = 'loadingIndicator';
 
      loadingDiv.style.position = 'fixed';
    function fadeInLoading() {
      loadingDiv.style.bottom = config.position.bottom;
        if (loadingDiv) {
      loadingDiv.style.right = config.position.right;
            loadingDiv.style.display = 'block'; // Make sure it's visible if previously hidden
      loadingDiv.style.width = config.size;
            requestAnimationFrame(function() { // Ensure display:block is applied before transition
      loadingDiv.style.height = config.size;
                loadingDiv.style.transition = 'opacity 0.2s ease-in';
      loadingDiv.style.zIndex = config.zIndex;
                loadingDiv.style.opacity = '1';
      loadingDiv.style.pointerEvents = 'none';
            });
      loadingDiv.style.opacity = '0';
        }
      loadingDiv.style.transition = `opacity ${config.fadeDuration}ms ease`;
    }
     
 
      // 无障碍支持
    // --- Function to HIDE and REMOVE loading indicator ---
      loadingDiv.setAttribute('role', 'status');
    function hideLoading(reason) {
      loadingDiv.setAttribute('aria-live', 'polite');
        if (!isShowingLoading && !document.getElementById('loadingIndicator')) {
      loadingDiv.setAttribute('aria-label', '页面加载中');
            // console.log('[Loading] Already hidden or not found, trigger for hide:', reason);
      loadingDiv.setAttribute('aria-busy', 'true');
            return;
        }
      config.renderIndicator(loadingDiv);
        console.log('[Loading] Hiding. Reason:', reason);
 
      // 添加到DOM
        var el = document.getElementById('loadingIndicator');
      if (document.body) {
        if (el) {
        document.body.appendChild(loadingDiv);
            el.style.transition = 'opacity 0.3s ease-out';
      } else {
            el.style.opacity = '0';
        document.addEventListener('DOMContentLoaded', function onReady() {
            setTimeout(function() {
          document.body.appendChild(loadingDiv);
                if (el.parentNode) {
          fadeInLoading();
                    el.parentNode.removeChild(el);
          document.removeEventListener('DOMContentLoaded', onReady);
                }
        });
                // console.log('[Loading] Removed from DOM. Reason:', reason);
        return;
                loadingDiv = null; // Clear reference
      }
            }, 300);
    } else {
        }
      loadingDiv = document.getElementById('loadingIndicator');
 
    }
        isShowingLoading = false;
        if (removalTimeoutId) {
    fadeInLoading();
            clearTimeout(removalTimeoutId);
            removalTimeoutId = null;
    // 重置超时
        }
    if (removalTimeoutId) clearTimeout(removalTimeoutId);
        // Clean up general page load listeners if they were for the initial load
   
        // For link clicks, these might not be relevant or might need to be re-added if it's SPA
    // 网络感知超时
        window.removeEventListener('load', onWindowLoad);
    const isSlowConnection = navigator.connection &&
        document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
      (navigator.connection.saveData || /2g|3g/.test(navigator.connection.effectiveType));
    }
   
 
    removalTimeoutId = setTimeout(
 
      () => hideLoading(`超时回退 (${isSlowConnection ? '慢速网络' : '常规'})`),
    // --- Initial Page Load Logic ---
      isSlowConnection ? 30000 : config.timeout
    // (This part is similar to before, but uses show/hide functions)
    );
 
  }
    // If script runs very early, body might not be available.
    // `showLoading` handles appending to body when it's ready.
  function fadeInLoading() {
    // Check initial state
    if (!loadingDiv) return;
    if (document.readyState === 'loading') { // Document is still loading
   
        showLoading('initial page load - document loading');
    loadingDiv.style.display = 'block';
    } else if (document.readyState === 'interactive' || document.readyState === 'complete') {
    requestAnimationFrame(() => {
        // Document already interactive or complete (e.g. from cache, or script deferred)
      loadingDiv.style.opacity = '1';
        // We might still want to show it briefly if it's 'interactive' but not 'complete'
    });
        // or if it's 'complete' but the script just ran.
  }
        // For 'complete' from cache, this might flash, so careful.
        if (document.readyState === 'complete') {
  // --- 核心隐藏函数 ---
            // If truly complete, maybe don't show, or show and hide very quickly
  function hideLoading(reason) {
            // This addresses the "cache loaded too fast" problem.
    if (!isShowingLoading && !document.getElementById('loadingIndicator')) {
            // For now, let's assume we don't show if already fully complete on script exec.
      config.debug && console.log('[Loading] 已隐藏,隐藏原因:', reason);
            // console.log('[Loading] Initial page load - document already complete. Skipping initial loading indicator.');
      return;
            isShowingLoading = false; // Ensure it's not marked as showing for initial load
    }
        } else { // interactive
   
            showLoading('initial page load - document interactive');
    config.debug && console.log('[Loading] 隐藏。原因:', reason);
        }
    }
    // 性能标记
 
    performance.mark(`loadingEnd_${reason}`);
 
    performance.measure(
    function onWindowLoad() {
      `loadingDuration_${reason}`,
        hideLoading('window.load');
      `loadingStart_${reason}`,
    }
      `loadingEnd_${reason}`
    function onDOMContentLoaded() {
    );
        if (document.readyState === 'interactive' || document.readyState === 'complete') {
            hideLoading('DOMContentLoaded (interactive/complete)');
    const el = document.getElementById('loadingIndicator');
        }
    if (el) {
    }
      el.style.opacity = '0';
 
      el.setAttribute('aria-busy', 'false');
    // Only add these listeners if we decided to show loading initially
     
    if (isShowingLoading || document.readyState === 'loading') { // Add if showing or if we expect it to show
      setTimeout(() => {
        window.addEventListener('load', onWindowLoad);
        if (el.parentNode) {
        document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
          el.parentNode.removeChild(el);
 
        }
        // Fallback timeout for initial load
        loadingDiv = null;
        if (removalTimeoutId) clearTimeout(removalTimeoutId); // Clear any from showLoading
      }, config.fadeDuration);
        removalTimeoutId = setTimeout(function() {
    }
            hideLoading('timeout fallback (initial 5s)');
        }, 5000);
    isShowingLoading = false;
    }
    cleanupEventListeners();
 
   
 
    if (removalTimeoutId) {
    // --- Logic for Link Clicks ---
      clearTimeout(removalTimeoutId);
    document.addEventListener('click', function(event) {
      removalTimeoutId = null;
        // Find the closest <a> tag ancestor
    }
        var targetElement = event.target.closest('a');
  }
 
        if (targetElement && targetElement.href) {
  // --- 初始页面加载逻辑 ---
            // Exclude specific links if needed:
  if (document.readyState === 'loading') {
            // if (targetElement.classList.contains('no-loading-indicator')) return;
    showLoading('初始页面加载 - 文档加载中');
            // if (targetElement.getAttribute('target') === '_blank') return; // Don't show for new tabs
  } else if (document.readyState === 'interactive') {
            // if (targetElement.href.startsWith('javascript:')) return;
    showLoading('初始页面加载 - 文档交互状态');
            // if (targetElement.href.startsWith('#')) return; // Typically same-page anchors
  }
 
            var href = targetElement.getAttribute('href');
  // 事件监听器
            var targetAttr = targetElement.getAttribute('target');
  function onWindowLoad() {
 
    hideLoading('window.load 事件');
            // Basic checks to avoid showing loading for non-navigation links
  }
            if (href.startsWith('#') || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:')) {
 
                return;
  function onDOMContentLoaded() {
            }
    hideLoading('DOMContentLoaded 事件');
            if (targetAttr === '_blank') { // Opening in new tab, current page isn't "loading"
  }
                return;
            }
  if (isShowingLoading || document.readyState === 'loading') {
 
    addEventListener(window, 'load', onWindowLoad);
            // Check if it's an internal link that might be handled by SPA router
    addEventListener(document, 'DOMContentLoaded', onDOMContentLoaded);
            // This is a very basic check. SPA routers often have their own events.
  }
            var isLikelyInternalSPALink = false; // Set to true if you detect SPA behavior
            if (typeof window.MyAppRouter !== 'undefined' && window.MyAppRouter.isHandling(href)) {
  // --- 链接点击跟踪 ---
                isLikelyInternalSPALink = true;
  function enableLinkTracking() {
            }
    if (linkTrackingEnabled) return;
 
    linkTrackingEnabled = true;
 
   
            // For regular page navigations:
    addEventListener(document, 'click', function(event) {
            if (!isLikelyInternalSPALink) {
      const targetElement = event.target.closest('a');
                showLoading('link click - page navigation');
                // The `beforeunload` event is also an option here, but click is more direct
      if (!targetElement || !targetElement.href) return;
                // `beforeunload` fires when the page is *about* to unload.
            }
      const href = targetElement.getAttribute('href');
            // For SPA/AJAX, you'd typically call showLoading() *before* your fetch/XHR call
      const targetAttr = targetElement.getAttribute('target');
            // and hideLoading() in the .then()/.catch()/.finally() or equivalent callback.
        }
      // 排除特殊链接
    }, true); // Use capture phase to catch clicks early
      if (href.startsWith('#') ||  
 
          href.startsWith('javascript:') ||  
    // --- Handling page unload for regular navigations ---
          href.startsWith('mailto:') ||  
    // This ensures the loading indicator is shown if a navigation is initiated
          href.startsWith('tel:')) {
    // by something other than a direct click (e.g. form submission, window.location change)
        return;
    // However, `beforeunload` can be tricky and sometimes doesn't allow much time for UI updates.
      }
    // Also, if the user cancels the navigation, the loading indicator might stay.
    /*
    window.addEventListener('beforeunload', function() {
        // Only show if not already triggered by a click and it's a real navigation
        // This is harder to get right because 'beforeunload' doesn't tell you *if* navigation will proceed.
        // For simplicity, relying on link click + timeout might be better.
        // If you do use this, make sure `isShowingLoading` is checked.
        if (!isShowingLoading) {
            // showLoading('beforeunload - page navigating');
        }
    });
    */
 
 
    // --- For SPAs or AJAX-driven content ---
    // You need to manually call showLoading() and hideLoading() around your async operations.
    // Example:
    /*
    function fetchDataAndUpdatePage(url) {
        showLoading('AJAX request started');
        fetch(url)
            .then(response => response.json())
            .then(data => {
                // update page content
                hideLoading('AJAX request complete');
            })
            .catch(error => {
                console.error('AJAX error:', error);
                hideLoading('AJAX request failed');
            });
    }
    // When a link for SPA navigation is clicked:
    // document.getElementById('mySpaLink').addEventListener('click', function(e) {
    //    e.preventDefault();
    //    fetchDataAndUpdatePage(this.href);
    // });
    */
 
    // Expose functions if needed for manual control (e.g., from SPA router)
    window.globalLoadingIndicator = {
        show: showLoading,
        hide: hideLoading
    };
 
})();


      if (targetAttr === '_blank') return;
      // 排除SPA处理的链接
      let isLikelyInternalSPALink = false;
      if (typeof window.MyAppRouter !== 'undefined' &&
          window.MyAppRouter.isHandling(href)) {
        isLikelyInternalSPALink = true;
      }
      if (!isLikelyInternalSPALink) {
        showLoading('链接点击 - 页面导航');
      }
    }, true);
  }
  // 公开API
  window.globalLoadingIndicator = {
    show: showLoading,
    hide: hideLoading,
    configure(newConfig) {
      config = {...config, ...newConfig};
    },
    initRouter(router) {
      if (typeof router?.beforeEach === 'function') {
        router.beforeEach(() => this.show('路由器导航'));
      }
      if (typeof router?.afterEach === 'function') {
        router.afterEach(() => this.hide('路由器导航'));
      }
    }
  };
  // 初始启用链接跟踪
  enableLinkTracking();
})();
mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui.styles'], function () {
mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui.styles'], function () {
});
});

2025年5月31日 (六) 06:29的版本

const rootStyles = getComputedStyle(document.documentElement);
const hexColor = rootStyles.getPropertyValue('--background-color-base').trim();

function hexToRgb(hex) {
  hex = hex.replace('#', '');
  
  if (hex.length === 3) {
    hex = hex.split('').map(c => c + c).join('');
  }

  const r = parseInt(hex.substring(0, 2), 16);
  const g = parseInt(hex.substring(2, 4), 16);
  const b = parseInt(hex.substring(4, 6), 16);

  return `${r}, ${g}, ${b}`; 
}

const rgbColor = hexToRgb(hexColor);
document.documentElement.style.setProperty(
  '--background-color-rgb', 
  rgbColor
);

console.log('转换结果:', rgbColor); 

//上传重定向
mw.loader.using(['mediawiki.util', 'oojs-ui', 'mediawiki.storage'], function () {
    if (mw.config.get('wgCanonicalSpecialPageName') === 'Upload') {

        const STORAGE_KEY = 'uploadPreference';

        const savedChoice = mw.storage.get(STORAGE_KEY);

        // 用户之前选择了上传向导,自动跳转
        if (savedChoice === 'wizard') {
            window.location.href = mw.util.getUrl('Special:UploadWizard');
            return;
        }

        // 用户选择了继续使用传统上传,不再提示
        if (savedChoice === 'classic') {
            return;
        }

        // 否则弹出选择窗口
        var windowManager = new OO.ui.WindowManager();
        $(document.body).append(windowManager.$element);

        function UploadDialog(config) {
            UploadDialog.super.call(this, config);
        }

        OO.inheritClass(UploadDialog, OO.ui.ProcessDialog);

        UploadDialog.static.name = 'UploadDialog';
        UploadDialog.static.title = '请选择上传方式';
        UploadDialog.static.actions = [
            {
                action: 'classic',
                label: '❌ 使用传统上传方式(更快)',
                flags: ['safe']
            }
        ];

        UploadDialog.prototype.initialize = function () {
            UploadDialog.super.prototype.initialize.call(this);

            var panel = new OO.ui.PanelLayout({ padded: true, expanded: false });

            // 创建“记住我的选择”复选框
            var rememberCheckbox = new OO.ui.CheckboxInputWidget({ selected: false });
            this._rememberCheckbox = rememberCheckbox;

            var rememberField = new OO.ui.FieldLayout(rememberCheckbox, {
                label: '记住我的选择,下次不再提示',
                align: 'inline'
            });

            // 创建“上传向导”按钮(跳转链接形式)
            var wizardButton = new OO.ui.ButtonWidget({
                label: '✅ 使用上传向导(推荐)',
                flags: ['primary', 'progressive'],
                href: mw.util.getUrl('Special:UploadWizard'),
                target: '_self',
                framed: true
            });

            // 给按钮添加“记住选择”逻辑
            wizardButton.on('click', () => {
                if (rememberCheckbox.isSelected()) {
                    mw.storage.set(STORAGE_KEY, 'wizard');
                }
            });

            // 拼接内容
            panel.$element.append(
                $('<p>').text('请选择你要使用的上传方式:'),

                $('<div>').css({
                    'margin': '12px 0',
                    'background': '#f8f9fa',
                    'padding': '10px',
                    'border': '1px solid #ccc',
                    'border-radius': '5px'
                }).append(
                    $('<strong>').text('✅ 上传向导(推荐)'),
                    $('<ul>').append(
                        $('<li>').text('支持多文件批量上传'),
                        $('<li>').text('显示上传进度条'),
                        $('<li>').text('信息填写更完整')
                    ),
                    $('<div>').css('margin-top', '8px').append(wizardButton.$element)
                ),

                $('<div>').css({
                    'margin-top': '15px',
                    'background': '#ffffff',
                    'padding': '10px',
                    'border': '1px dashed #bbb',
                    'border-radius': '5px'
                }).append(
                    $('<strong>').text('⚡️ 传统上传方式'),
                    $('<ul>').append(
                        $('<li>').text('界面更简单'),
                        $('<li>').text('加载速度快'),
                        $('<li>').text('适合上传单个文件')
                    )
                ),

                $('<div>').css('margin-top', '15px').append(rememberField.$element)
            );

            this.$body.append(panel.$element);
        };

        UploadDialog.prototype.getActionProcess = function (action) {
            if (action === 'classic') {
                if (this._rememberCheckbox.isSelected()) {
                    mw.storage.set(STORAGE_KEY, 'classic');
                }
                return new OO.ui.Process(() => this.close());
            }
            return UploadDialog.super.prototype.getActionProcess.call(this, action);
        };

        var dialog = new UploadDialog();
        windowManager.addWindows([dialog]);
        windowManager.openWindow(dialog);
    }
});
//---
 // 全局加载指示器
 (function() {
   // 配置系统
   const defaultConfig = {
     imageUrl: 'https://wiki.ottohub.cn/images/0/02/Loading.png',
     position: { bottom: '20px', right: '20px' },
     size: '256px',
     timeout: 15000,
     zIndex: '99999',
     debug: true,
     renderIndicator: defaultRenderer,
     fadeDuration: 300
   };
   
   let config = {...defaultConfig};
   let loadingDiv = null;
   let removalTimeoutId = null;
   let isShowingLoading = false;
   let linkTrackingEnabled = false;
   const eventListeners = [];
 
   // 默认渲染器
   function defaultRenderer(container) {
     const img = document.createElement('img');
     img.src = config.imageUrl;
     img.alt = '加载中...';
     img.style.width = '100%';
     img.style.height = '100%';
     img.style.objectFit = 'contain';
     container.appendChild(img);
   }
 
   // 图片预加载
   const imgPreload = new Image();
   imgPreload.src = config.imageUrl;
 
   // 事件管理
   function addEventListener(target, type, handler) {
     target.addEventListener(type, handler);
     eventListeners.push({ target, type, handler });
   }
 
   function cleanupEventListeners() {
     eventListeners.forEach(({ target, type, handler }) => {
       target.removeEventListener(type, handler);
     });
     eventListeners.length = 0;
   }
 
   // --- 核心显示函数 ---
   function showLoading(reason) {
     if (isShowingLoading && document.getElementById('loadingIndicator')) {
       config.debug && console.log('[Loading] 已显示,触发原因:', reason);
       return;
     }
     
     isShowingLoading = true;
     config.debug && console.log('[Loading] 显示。原因:', reason);
 
     // 性能标记
     performance.mark(`loadingStart_${reason}`);
 
     // 创建元素(如果不存在)
     if (!document.getElementById('loadingIndicator')) {
       loadingDiv = document.createElement('div');
       loadingDiv.id = 'loadingIndicator';
       loadingDiv.style.position = 'fixed';
       loadingDiv.style.bottom = config.position.bottom;
       loadingDiv.style.right = config.position.right;
       loadingDiv.style.width = config.size;
       loadingDiv.style.height = config.size;
       loadingDiv.style.zIndex = config.zIndex;
       loadingDiv.style.pointerEvents = 'none';
       loadingDiv.style.opacity = '0';
       loadingDiv.style.transition = `opacity ${config.fadeDuration}ms ease`;
       
       // 无障碍支持
       loadingDiv.setAttribute('role', 'status');
       loadingDiv.setAttribute('aria-live', 'polite');
       loadingDiv.setAttribute('aria-label', '页面加载中');
       loadingDiv.setAttribute('aria-busy', 'true');
 
       config.renderIndicator(loadingDiv);
 
       // 添加到DOM
       if (document.body) {
         document.body.appendChild(loadingDiv);
       } else {
         document.addEventListener('DOMContentLoaded', function onReady() {
           document.body.appendChild(loadingDiv);
           fadeInLoading();
           document.removeEventListener('DOMContentLoaded', onReady);
         });
         return;
       }
     } else {
       loadingDiv = document.getElementById('loadingIndicator');
     }
 
     fadeInLoading();
 
     // 重置超时
     if (removalTimeoutId) clearTimeout(removalTimeoutId);
     
     // 网络感知超时
     const isSlowConnection = navigator.connection && 
       (navigator.connection.saveData || /2g|3g/.test(navigator.connection.effectiveType));
     
     removalTimeoutId = setTimeout(
       () => hideLoading(`超时回退 (${isSlowConnection ? '慢速网络' : '常规'})`),
       isSlowConnection ? 30000 : config.timeout
     );
   }
 
   function fadeInLoading() {
     if (!loadingDiv) return;
     
     loadingDiv.style.display = 'block';
     requestAnimationFrame(() => {
       loadingDiv.style.opacity = '1';
     });
   }
 
   // --- 核心隐藏函数 ---
   function hideLoading(reason) {
     if (!isShowingLoading && !document.getElementById('loadingIndicator')) {
       config.debug && console.log('[Loading] 已隐藏,隐藏原因:', reason);
       return;
     }
     
     config.debug && console.log('[Loading] 隐藏。原因:', reason);
 
     // 性能标记
     performance.mark(`loadingEnd_${reason}`);
     performance.measure(
       `loadingDuration_${reason}`, 
       `loadingStart_${reason}`, 
       `loadingEnd_${reason}`
     );
 
     const el = document.getElementById('loadingIndicator');
     if (el) {
       el.style.opacity = '0';
       el.setAttribute('aria-busy', 'false');
       
       setTimeout(() => {
         if (el.parentNode) {
           el.parentNode.removeChild(el);
         }
         loadingDiv = null;
       }, config.fadeDuration);
     }
 
     isShowingLoading = false;
     cleanupEventListeners();
     
     if (removalTimeoutId) {
       clearTimeout(removalTimeoutId);
       removalTimeoutId = null;
     }
   }
 
   // --- 初始页面加载逻辑 ---
   if (document.readyState === 'loading') {
     showLoading('初始页面加载 - 文档加载中');
   } else if (document.readyState === 'interactive') {
     showLoading('初始页面加载 - 文档交互状态');
   }
 
   // 事件监听器
   function onWindowLoad() {
     hideLoading('window.load 事件');
   }
   
   function onDOMContentLoaded() {
     hideLoading('DOMContentLoaded 事件');
   }
 
   if (isShowingLoading || document.readyState === 'loading') {
     addEventListener(window, 'load', onWindowLoad);
     addEventListener(document, 'DOMContentLoaded', onDOMContentLoaded);
   }
 
   // --- 链接点击跟踪 ---
   function enableLinkTracking() {
     if (linkTrackingEnabled) return;
     linkTrackingEnabled = true;
     
     addEventListener(document, 'click', function(event) {
       const targetElement = event.target.closest('a');
 
       if (!targetElement || !targetElement.href) return;
 
       const href = targetElement.getAttribute('href');
       const targetAttr = targetElement.getAttribute('target');
 
       // 排除特殊链接
       if (href.startsWith('#') || 
           href.startsWith('javascript:') || 
           href.startsWith('mailto:') || 
           href.startsWith('tel:')) {
         return;
       }

      if (targetAttr === '_blank') return;
 
       // 排除SPA处理的链接
       let isLikelyInternalSPALink = false;
       if (typeof window.MyAppRouter !== 'undefined' && 
           window.MyAppRouter.isHandling(href)) {
         isLikelyInternalSPALink = true;
       }
       if (!isLikelyInternalSPALink) {
         showLoading('链接点击 - 页面导航');
       }
     }, true);
   }
 
   // 公开API
   window.globalLoadingIndicator = {
     show: showLoading,
     hide: hideLoading,
     configure(newConfig) {
       config = {...config, ...newConfig};
     },
     initRouter(router) {
       if (typeof router?.beforeEach === 'function') {
         router.beforeEach(() => this.show('路由器导航'));
       }
       if (typeof router?.afterEach === 'function') {
         router.afterEach(() => this.hide('路由器导航'));
       }
     }
   };
 
   // 初始启用链接跟踪
   enableLinkTracking();
 })();
mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui.styles'], function () {
});