微件:Card:修订间差异

来自卡厄思梦境WIKI
跳转到导航 跳转到搜索
律Rhyme留言 | 贡献
功能测试
律Rhyme留言 | 贡献
功能测试
第1行: 第1行:
<noinclude>
<includeonly>
此Widget为卡牌显示添加交互效果:
* 鼠标悬停时的动画效果
* 点击后显示放大的模态框
* 自动检测spark_enable属性显示闪光按钮
* 支持闪光卡牌列表显示
* 预加载、批量加载和本地缓存优化
* 事件委托、DOM优化、批量API请求
 
使用方法:在页面中添加 {{#widget:Card}}
</noinclude><includeonly>
<style>
<style>
/* 卡牌容器 */
.card-wrapper {
.card-wrapper {
     display: inline-block;
     display: inline-block;
第254行: 第243行:
     50% { content: ".."; }
     50% { content: ".."; }
     75% { content: "..."; }
     75% { content: "..."; }
}
/* 加载进度 */
.spark-loading-progress {
    color: #aaa;
    font-size: 14px;
    margin-top: 10px;
}
}


第352行: 第348行:
.spark-modal-content::-webkit-scrollbar-thumb:hover {
.spark-modal-content::-webkit-scrollbar-thumb:hover {
     background: rgba(255, 255, 255, 0.5);
     background: rgba(255, 255, 255, 0.5);
}
/* 预加载指示器 */
.preload-indicator {
    position: fixed;
    bottom: 10px;
    right: 10px;
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    padding: 5px 10px;
    border-radius: 4px;
    font-size: 12px;
    z-index: 9998;
    opacity: 0;
    transition: opacity 0.3s ease;
    pointer-events: none;
}
.preload-indicator.active {
    opacity: 1;
}
}
</style>
</style>
第359行: 第375行:
     'use strict';
     'use strict';
      
      
     // ==================== 等待 MediaWiki 加载 ====================
     // ==================== 缓存管理器 ====================
     function waitForMw(callback) {
     var SparkCache = {
         // 检查 mw 和 mw.loader 是否存在
        CACHE_KEY: 'spark_card_cache',
        if (typeof mw !== 'undefined' && mw.loader && typeof mw.loader.using === 'function') {
        CACHE_VERSION: 'v1',
             mw.loader.using(['mediawiki.api']).then(callback);
        CACHE_EXPIRE: 24 * 60 * 60 * 1000, // 24小时过期
         } else if (typeof mw !== 'undefined' && mw.Api) {
       
             // mw.Api 已经可用
        // 获取完整的缓存键
             callback();
        getFullKey: function() {
         } else {
            return this.CACHE_KEY + '_' + this.CACHE_VERSION;
             // 等待 mw 加载
        },
             var checkCount = 0;
       
             var maxChecks = 50; // 最多等待5秒
         // 获取所有缓存
        getAll: function() {
            try {
                var data = localStorage.getItem(this.getFullKey());
                if (!data) return {};
                return JSON.parse(data);
            } catch (e) {
                console.warn('SparkCache: 读取缓存失败', e);
                return {};
            }
        },
       
        // 保存所有缓存
        saveAll: function(data) {
             try {
                localStorage.setItem(this.getFullKey(), JSON.stringify(data));
            } catch (e) {
                console.warn('SparkCache: 保存缓存失败', e);
                // 存储空间不足时,清理过期缓存
                this.cleanup();
            }
         },
       
        // 获取单个卡牌的闪光列表缓存
        get: function(cardId) {
            var cache = this.getAll();
            var item = cache[cardId];
           
            if (!item) return null;
           
            // 检查是否过期
            if (Date.now() - item.timestamp > this.CACHE_EXPIRE) {
                delete cache[cardId];
                this.saveAll(cache);
                return null;
             }
           
            return item.html;
        },
       
        // 设置单个卡牌的闪光列表缓存
        set: function(cardId, html) {
            var cache = this.getAll();
             cache[cardId] = {
                html: html,
                timestamp: Date.now()
            };
            this.saveAll(cache);
         },
       
        // 检查是否有缓存
        has: function(cardId) {
             return this.get(cardId) !== null;
        },
       
        // 清理过期缓存
        cleanup: function() {
             var cache = this.getAll();
             var now = Date.now();
            var cleaned = false;
              
              
             var checkInterval = setInterval(function() {
             for (var cardId in cache) {
                checkCount++;
                 if (cache.hasOwnProperty(cardId)) {
               
                     if (now - cache[cardId].timestamp > this.CACHE_EXPIRE) {
                 if (typeof mw !== 'undefined' && mw.loader && typeof mw.loader.using === 'function') {
                        delete cache[cardId];
                     clearInterval(checkInterval);
                        cleaned = true;
                    mw.loader.using(['mediawiki.api']).then(callback);
                     }
                } else if (typeof mw !== 'undefined' && mw.Api) {
                    clearInterval(checkInterval);
                    callback();
                } else if (checkCount >= maxChecks) {
                    clearInterval(checkInterval);
                     console.warn('CardWidget: MediaWiki API 加载超时,闪光功能可能不可用');
                    // 仍然初始化基本功能
                    callback();
                 }
                 }
             }, 100);
             }
           
            if (cleaned) {
                this.saveAll(cache);
            }
        },
       
        // 清空所有缓存
        clear: function() {
            try {
                localStorage.removeItem(this.getFullKey());
            } catch (e) {
                console.warn('SparkCache: 清空缓存失败', e);
            }
         }
         }
     }
     };
      
      
     // ==================== 主程序 ====================
     // ==================== 预加载队列管理器 ====================
     function initCardWidget() {
     var PreloadQueue = {
        queue: [],
        isProcessing: false,
        batchSize: 3, // 并发请求数
        activeRequests: 0,
        maxRetries: 2,
        retryDelay: 1000,
       
        // 预加载指示器
        indicator: null,
          
          
         // ==================== API 客户端 ====================
         // 初始化指示器
        var apiClient = null;
        initIndicator: function() {
         var apiAvailable = false;
            if (!this.indicator) {
                this.indicator = document.createElement('div');
                this.indicator.className = 'preload-indicator';
                this.indicator.textContent = '预加载中...';
                document.body.appendChild(this.indicator);
            }
         },
          
          
         function getApi() {
         // 显示/隐藏指示器
             if (!apiAvailable) {
        showIndicator: function(show) {
                 // 检查 API 是否可用
            this.initIndicator();
                if (typeof mw !== 'undefined' && typeof mw.Api === 'function') {
             if (show) {
                    apiAvailable = true;
                 this.indicator.classList.add('active');
                } else {
            } else {
                    return null;
                this.indicator.classList.remove('active');
                }
             }
             }
        },
       
        // 更新指示器文本
        updateIndicator: function(current, total) {
            this.initIndicator();
            this.indicator.textContent = '预加载: ' + current + '/' + total;
        },
       
        // 添加到预加载队列
        add: function(cardId, priority) {
            // 如果已缓存,跳过
            if (SparkCache.has(cardId)) return;
              
              
             if (!apiClient && apiAvailable) {
             // 如果已在队列中,跳过
                try {
            for (var i = 0; i < this.queue.length; i++) {
                    apiClient = new mw.Api();
                 if (this.queue[i].cardId === cardId) return;
                 } catch (e) {
                    console.warn('CardWidget: 无法创建 API 客户端', e);
                    apiAvailable = false;
                    return null;
                }
             }
             }
            return apiClient;
        }
       
        // ==================== 缓存管理器 ====================
        var SparkCache = (function() {
            var CACHE_KEY = 'spark_card_cache_v2';
            var CACHE_EXPIRE = 24 * 60 * 60 * 1000;
              
              
             var memoryCache = null;
             this.queue.push({
            var isDirty = false;
                cardId: cardId,
             var saveTimer = null;
                priority: priority || 0,
                retries: 0
             });
              
              
             var storageAvailable = (function() {
             // 按优先级排序(高优先级在前)
                 try {
            this.queue.sort(function(a, b) {
                    var test = '__storage_test__';
                 return b.priority - a.priority;
                    localStorage.setItem(test, test);
             });
                    localStorage.removeItem(test);
                    return true;
                } catch (e) {
                    return false;
                }
             })();
              
              
             function loadFromStorage() {
             this.process();
                if (memoryCache !== null) return memoryCache;
        },
               
       
                 if (!storageAvailable) {
        // 批量添加到预加载队列
                    memoryCache = {};
        addBatch: function(cardIds, priority) {
                    return memoryCache;
            var self = this;
                }
            cardIds.forEach(function(cardId) {
               
                 self.add(cardId, priority);
                try {
            });
                    var data = localStorage.getItem(CACHE_KEY);
        },
                    memoryCache = data ? JSON.parse(data) : {};
       
                 } catch (e) {
        // 提升优先级(用于用户即将查看的卡牌)
                     memoryCache = {};
        prioritize: function(cardId) {
            for (var i = 0; i < this.queue.length; i++) {
                 if (this.queue[i].cardId === cardId) {
                     this.queue[i].priority = 100;
                    break;
                 }
                 }
                return memoryCache;
             }
             }
            this.queue.sort(function(a, b) {
                return b.priority - a.priority;
            });
        },
       
        // 处理队列
        process: function() {
            var self = this;
              
              
             function scheduleSave() {
             if (this.queue.length === 0) {
                if (!storageAvailable || saveTimer) return;
                this.showIndicator(false);
               
                 return;
                saveTimer = setTimeout(function() {
                    saveTimer = null;
                    if (isDirty && memoryCache) {
                        try {
                            localStorage.setItem(CACHE_KEY, JSON.stringify(memoryCache));
                            isDirty = false;
                        } catch (e) {
                            cleanup(true);
                        }
                    }
                 }, 1000);
             }
             }
              
              
             function cleanup(force) {
             // 控制并发数
                var cache = loadFromStorage();
            while (this.activeRequests < this.batchSize && this.queue.length > 0) {
                var now = Date.now();
                var item = this.queue.shift();
                var hasChanges = false;
                 this.loadSparkList(item);
                var keys = Object.keys(cache);
               
                if (force && keys.length > 10) {
                    var items = keys.map(function(k) {
                        return { key: k, time: cache[k].timestamp };
                    }).sort(function(a, b) {
                        return a.time - b.time;
                    });
                   
                    var deleteCount = Math.floor(items.length / 2);
                    for (var i = 0; i < deleteCount; i++) {
                        delete cache[items[i].key];
                        hasChanges = true;
                    }
                 } else {
                    for (var cardId in cache) {
                        if (cache.hasOwnProperty(cardId) && now - cache[cardId].timestamp > CACHE_EXPIRE) {
                            delete cache[cardId];
                            hasChanges = true;
                        }
                    }
                }
               
                if (hasChanges) {
                    isDirty = true;
                    scheduleSave();
                }
             }
             }
              
              
             return {
             if (this.activeRequests > 0) {
                get: function(cardId) {
                 this.showIndicator(true);
                    var cache = loadFromStorage();
                 this.updateIndicator(this.activeRequests, this.queue.length + this.activeRequests);
                    var item = cache[cardId];
             }
                   
         },
                    if (!item) return null;
                    if (Date.now() - item.timestamp > CACHE_EXPIRE) {
                        delete cache[cardId];
                        isDirty = true;
                        scheduleSave();
                        return null;
                    }
                   
                    return item.html;
                },
               
                 set: function(cardId, html) {
                    var cache = loadFromStorage();
                    cache[cardId] = {
                        html: html,
                        timestamp: Date.now()
                    };
                    isDirty = true;
                    scheduleSave();
                 },
               
                has: function(cardId) {
                    var cache = loadFromStorage();
                    var item = cache[cardId];
                    return item && (Date.now() - item.timestamp <= CACHE_EXPIRE);
                },
               
                cleanup: cleanup,
               
                flush: function() {
                    if (!storageAvailable) return;
                   
                    if (saveTimer) {
                        clearTimeout(saveTimer);
                        saveTimer = null;
                    }
                    if (isDirty && memoryCache) {
                        try {
                            localStorage.setItem(CACHE_KEY, JSON.stringify(memoryCache));
                            isDirty = false;
                        } catch (e) {}
                    }
                }
             };
         })();
          
          
         // ==================== 批量API请求管理器 ====================
         // 加载闪光卡牌列表
         var BatchRequestManager = (function() {
         loadSparkList: function(item) {
             var pendingRequests = {};
             var self = this;
            var requestQueue = [];
             this.activeRequests++;
            var isProcessing = false;
             var batchSize = 5;
            var batchDelay = 50;
              
              
             function processBatch() {
             var api = new mw.Api();
                 if (isProcessing || requestQueue.length === 0) return;
            api.get({
                 action: 'parse',
                text: '{{Card/spark/list|' + item.cardId + '}}',
                prop: 'text',
                disablelimitreport: true,
                format: 'json'
            }).done(function(data) {
                self.activeRequests--;
                  
                  
                var api = getApi();
                 if (data.parse && data.parse.text) {
                 if (!api) {
                     var html = data.parse.text['*'];
                     // API 不可用,清空队列并拒绝所有请求
                    SparkCache.set(item.cardId, html);
                    requestQueue.forEach(function(cardId) {
                      
                        var callbacks = pendingRequests[cardId] || [];
                     // 更新按钮状态
                        delete pendingRequests[cardId];
                     self.updateButtonState(item.cardId, true);
                        callbacks.forEach(function(cb) {
                            cb.reject(new Error('API not available'));
                        });
                     });
                     requestQueue = [];
                     return;
                 }
                 }
                  
                  
                 isProcessing = true;
                 self.process();
            }).fail(function() {
                self.activeRequests--;
                  
                  
                 var batch = requestQueue.splice(0, batchSize);
                 // 重试逻辑
                var promises = batch.map(function(cardId) {
                if (item.retries < self.maxRetries) {
                     return fetchSparkList(cardId, api);
                    item.retries++;
                 });
                    setTimeout(function() {
                        self.queue.unshift(item);
                        self.process();
                     }, self.retryDelay * item.retries);
                 }
                  
                  
                 Promise.all(promises).then(function() {
                 self.process();
                     isProcessing = false;
            });
                    if (requestQueue.length > 0) {
        },
                        setTimeout(processBatch, batchDelay);
       
                    }
        // 更新按钮缓存状态
                 });
        updateButtonState: function(cardId, cached) {
            var buttons = document.querySelectorAll('.spark-button[data-card-id="' + cardId + '"]');
            buttons.forEach(function(btn) {
                if (cached) {
                     btn.classList.add('cached');
                    btn.classList.remove('loading');
                }
            });
        }
    };
   
    // ==================== 主逻辑 ====================
   
    // 确保DOM加载完成
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initCardWidget);
    } else {
        initCardWidget();
    }
   
    function initCardWidget() {
        // 清理过期缓存
        SparkCache.cleanup();
       
        // 创建主模态框元素(如果不存在)
        var modalOverlay = document.querySelector('.card-modal-overlay');
       
        if (!modalOverlay) {
            modalOverlay = document.createElement('div');
            modalOverlay.className = 'card-modal-overlay';
            modalOverlay.innerHTML = '<span class="card-modal-close">&times;</span><div class="card-modal-content"></div>';
            document.body.appendChild(modalOverlay);
        }
       
        // 创建闪光卡牌模态框(如果不存在)
        var sparkModalOverlay = document.querySelector('.spark-modal-overlay');
       
        if (!sparkModalOverlay) {
            sparkModalOverlay = document.createElement('div');
            sparkModalOverlay.className = 'spark-modal-overlay';
            sparkModalOverlay.innerHTML = '<span class="spark-modal-close">&times;</span><div class="spark-modal-content"></div>';
            document.body.appendChild(sparkModalOverlay);
        }
       
        var modalContent = modalOverlay.querySelector('.card-modal-content');
        var closeButton = modalOverlay.querySelector('.card-modal-close');
        var sparkModalContent = sparkModalOverlay.querySelector('.spark-modal-content');
        var sparkCloseButton = sparkModalOverlay.querySelector('.spark-modal-close');
       
        // 当前放大的闪光卡牌容器
        var currentEnlargedContainer = null;
       
        // 关闭主模态框函数
        function closeModal() {
            modalOverlay.classList.add('fade-out');
            setTimeout(function() {
                modalOverlay.classList.remove('active', 'fade-in', 'fade-out');
                modalContent.innerHTML = '';
                document.body.classList.remove('card-modal-open');
            }, 300);
        }
       
        // 关闭闪光模态框函数
        function closeSparkModal() {
            // 先关闭放大的卡牌
            if (currentEnlargedContainer) {
                 currentEnlargedContainer.remove();
                currentEnlargedContainer = null;
             }
             }
              
              
             function fetchSparkList(cardId, api) {
             sparkModalOverlay.classList.add('fade-out');
                return new Promise(function(resolve) {
            setTimeout(function() {
                    api.get({
                sparkModalOverlay.classList.remove('active', 'fade-in', 'fade-out');
                        action: 'parse',
                sparkModalContent.innerHTML = '';
                        text: '{{Card/spark/list|' + cardId + '}}',
            }, 300);
                        prop: 'text',
        }
                        disablelimitreport: true,
       
                        format: 'json'
        // 关闭放大的闪光卡牌
                    }).done(function(data) {
        function closeEnlargedCard() {
                        var html = '';
            if (currentEnlargedContainer) {
                        if (data.parse && data.parse.text) {
                currentEnlargedContainer.remove();
                            html = data.parse.text['*'];
                 currentEnlargedContainer = null;
                            SparkCache.set(cardId, html);
                        }
                       
                        var callbacks = pendingRequests[cardId] || [];
                        delete pendingRequests[cardId];
                        callbacks.forEach(function(cb) {
                            cb.resolve(html);
                        });
                       
                        resolve();
                    }).fail(function(error) {
                        var callbacks = pendingRequests[cardId] || [];
                        delete pendingRequests[cardId];
                        callbacks.forEach(function(cb) {
                            cb.reject(error);
                        });
                       
                        resolve();
                    });
                 });
             }
             }
        }
       
        // 打开主模态框函数
        function openModal(cardElement, cardId, sparkEnable) {
            var cardClone = cardElement.cloneNode(true);
            // 移除克隆卡牌的点击事件标记
            delete cardClone.dataset.cardBound;
              
              
             return {
             modalContent.innerHTML = '';
                request: function(cardId) {
             modalContent.appendChild(cardClone);
                    return new Promise(function(resolve, reject) {
                        var cached = SparkCache.get(cardId);
                        if (cached !== null) {
                            resolve(cached);
                            return;
                        }
                       
                        if (pendingRequests[cardId]) {
                            pendingRequests[cardId].push({ resolve: resolve, reject: reject });
                            return;
                        }
                       
                        pendingRequests[cardId] = [{ resolve: resolve, reject: reject }];
                        requestQueue.push(cardId);
                       
                        if (!isProcessing) {
                            setTimeout(processBatch, 10);
                        }
                    });
                },
               
                preload: function(cardIds) {
                    if (!getApi()) return; // API 不可用时不预加载
                   
                    var toLoad = cardIds.filter(function(cardId) {
                        return !SparkCache.has(cardId) &&
                              !pendingRequests[cardId] &&
                              requestQueue.indexOf(cardId) === -1;
                    });
                   
                    if (toLoad.length === 0) return;
                   
                    toLoad.forEach(function(cardId) {
                        pendingRequests[cardId] = [];
                        requestQueue.push(cardId);
                    });
                   
                    if (!isProcessing) {
                        setTimeout(processBatch, 100);
                    }
                },
               
                prioritize: function(cardId) {
                    var idx = requestQueue.indexOf(cardId);
                    if (idx > 0) {
                        requestQueue.splice(idx, 1);
                        requestQueue.unshift(cardId);
                    }
                }
            };
        })();
       
        // ==================== DOM 管理器 ====================
        var DOMManager = (function() {
            var modalOverlay = null;
            var sparkModalOverlay = null;
             var modalContent = null;
            var sparkModalContent = null;
            var currentEnlargedContainer = null;
            var processedCards = new WeakSet();
              
              
             function createModals() {
             // 如果启用了闪光功能,添加闪光按钮
                if (!modalOverlay) {
            if (sparkEnable && sparkEnable.trim() !== '') {
                    modalOverlay = document.createElement('div');
                var sparkButton = document.createElement('button');
                    modalOverlay.className = 'card-modal-overlay';
                sparkButton.className = 'spark-button';
                    modalOverlay.innerHTML = '<span class="card-modal-close">&times;</span><div class="card-modal-content"></div>';
                sparkButton.textContent = '闪光';
                    document.body.appendChild(modalOverlay);
                sparkButton.dataset.cardId = cardId;
                    modalContent = modalOverlay.querySelector('.card-modal-content');
                }
                  
                  
                 if (!sparkModalOverlay) {
                // 检查是否已缓存
                     sparkModalOverlay = document.createElement('div');
                 if (SparkCache.has(cardId)) {
                    sparkModalOverlay.className = 'spark-modal-overlay';
                     sparkButton.classList.add('cached');
                     sparkModalOverlay.innerHTML = '<span class="spark-modal-close">&times;</span><div class="spark-modal-content"></div>';
                } else {
                    document.body.appendChild(sparkModalOverlay);
                     // 提升优先级预加载
                     sparkModalContent = sparkModalOverlay.querySelector('.spark-modal-content');
                     PreloadQueue.prioritize(cardId);
                 }
                 }
            }
           
            function closeModal() {
                if (!modalOverlay) return;
                  
                  
                 modalOverlay.classList.add('fade-out');
                 sparkButton.addEventListener('click', function() {
                setTimeout(function() {
                     openSparkModal(cardId);
                     modalOverlay.classList.remove('active', 'fade-in', 'fade-out');
                });
                    modalContent.innerHTML = '';
                modalContent.appendChild(sparkButton);
                    document.body.classList.remove('card-modal-open');
                }, 300);
             }
             }
              
              
             function closeSparkModal() {
             modalOverlay.classList.add('active');
                closeEnlargedCard();
            document.body.classList.add('card-modal-open');
               
                if (!sparkModalOverlay) return;
               
                sparkModalOverlay.classList.add('fade-out');
                setTimeout(function() {
                    sparkModalOverlay.classList.remove('active', 'fade-in', 'fade-out');
                    sparkModalContent.innerHTML = '';
                }, 300);
            }
              
              
             function closeEnlargedCard() {
             // 强制重绘后添加fade-in
                 if (currentEnlargedContainer) {
            requestAnimationFrame(function() {
                    currentEnlargedContainer.remove();
                 modalOverlay.classList.add('fade-in');
                    currentEnlargedContainer = null;
            });
                }
        }
             }
       
        // 打开闪光卡牌模态框
        function openSparkModal(cardId) {
            // 首先检查缓存
             var cachedHtml = SparkCache.get(cardId);
              
              
             function openModal(cardElement, cardId, sparkEnable) {
             if (cachedHtml) {
                 createModals();
                 // 使用缓存,立即显示
               
                 showSparkContent(cachedHtml, true);
                 var cardClone = cardElement.cloneNode(true);
            } else {
                modalContent.innerHTML = '';
                 // 无缓存,显示加载中并请求
                modalContent.appendChild(cardClone);
                 sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-loading">加载中</div>';
               
                 // 只有在 API 可用且启用闪光时才添加按钮
                 if (sparkEnable && sparkEnable.trim() !== '' && getApi()) {
                    var sparkButton = document.createElement('button');
                    sparkButton.className = 'spark-button';
                    sparkButton.textContent = '闪光';
                    sparkButton.dataset.cardId = cardId;
                   
                    if (SparkCache.has(cardId)) {
                        sparkButton.classList.add('cached');
                    } else {
                        BatchRequestManager.prioritize(cardId);
                    }
                   
                    modalContent.appendChild(sparkButton);
                }
               
                modalOverlay.classList.add('active');
                document.body.classList.add('card-modal-open');
                  
                  
                sparkModalOverlay.classList.add('active');
                 requestAnimationFrame(function() {
                 requestAnimationFrame(function() {
                     modalOverlay.classList.add('fade-in');
                     sparkModalOverlay.classList.add('fade-in');
                 });
                 });
            }
           
            function openSparkModal(cardId) {
                createModals();
                  
                  
                 var cachedHtml = SparkCache.get(cardId);
                 // 发起请求
               
                loadSparkListDirect(cardId);
                if (cachedHtml) {
                    showSparkContent(cachedHtml, true);
                } else {
                    sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-loading">加载中</div>';
                   
                    sparkModalOverlay.classList.add('active');
                    requestAnimationFrame(function() {
                        sparkModalOverlay.classList.add('fade-in');
                    });
                   
                    BatchRequestManager.request(cardId).then(function(html) {
                        showSparkContent(html, false);
                    }).catch(function() {
                        sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-empty">加载失败,请重试</div>';
                    });
                }
             }
             }
              
              
             function showSparkContent(html, fromCache) {
             function showSparkContent(html, fromCache) {
                // 检查是否有卡牌
                 var tempDiv = document.createElement('div');
                 var tempDiv = document.createElement('div');
                 tempDiv.innerHTML = html;
                 tempDiv.innerHTML = html;
第805行: 第771行:
                 if (cards.length > 0) {
                 if (cards.length > 0) {
                     sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div>' + html + cacheHint;
                     sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div>' + html + cacheHint;
                    // 为闪光列表中的卡牌绑定事件
                    bindSparkListCardEvents();
                 } else {
                 } else {
                     sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-empty">暂无闪光卡牌</div>' + cacheHint;
                     sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-empty">暂无闪光卡牌</div>' + cacheHint;
第817行: 第785行:
             }
             }
              
              
             function showEnlargedCard(cardElement) {
             function loadSparkListDirect(cardId) {
                closeEnlargedCard();
                 var api = new mw.Api();
               
                 api.get({
                 var enlargedContainer = document.createElement('div');
                    action: 'parse',
                 enlargedContainer.className = 'spark-enlarged-container';
                    text: '{{Card/spark/list|' + cardId + '}}',
               
                    prop: 'text',
                var closeBtn = document.createElement('span');
                    disablelimitreport: true,
                closeBtn.className = 'spark-enlarged-close';
                    format: 'json'
                closeBtn.innerHTML = '&times;';
                 }).done(function(data) {
                enlargedContainer.appendChild(closeBtn);
                     if (data.parse && data.parse.text) {
               
                         var html = data.parse.text['*'];
                var cardContent = document.createElement('div');
                          
                cardContent.className = 'spark-enlarged-card';
                         // 存入缓存
                cardContent.appendChild(cardElement.cloneNode(true));
                         SparkCache.set(cardId, html);
                 enlargedContainer.appendChild(cardContent);
               
                document.body.appendChild(enlargedContainer);
                currentEnlargedContainer = enlargedContainer;
            }
           
            function processNewCards(nodes) {
                var sparkCardIds = [];
               
                nodes.forEach(function(node) {
                     if (node.nodeType !== 1) return;
                   
                    var cards = [];
                    if (node.classList && node.classList.contains('card-deck-trans')) {
                        cards.push(node);
                    }
                    if (node.querySelectorAll) {
                         var found = node.querySelectorAll('.card-deck-trans');
                        for (var i = 0; i < found.length; i++) {
                            cards.push(found[i]);
                         }
                    }
                   
                    cards.forEach(function(card) {
                         if (processedCards.has(card)) return;
                         if (card.closest('.card-modal-overlay') ||
                            card.closest('.spark-modal-overlay') ||
                            card.closest('.spark-enlarged-container')) return;
                          
                          
                         processedCards.add(card);
                         // 更新按钮状态
                        PreloadQueue.updateButtonState(cardId, true);
                          
                          
                         var sparkEnable = card.dataset.sparkEnable;
                         // 显示内容
                         if (sparkEnable && sparkEnable.trim() !== '') {
                         showSparkContent(html, false);
                            var wrapper = card.closest('.card-wrapper');
                    }
                            var cardId = wrapper ? wrapper.dataset.cardId : '';
                }).fail(function() {
                            if (cardId && sparkCardIds.indexOf(cardId) === -1) {
                    sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-empty">加载失败,请重试</div>';
                                sparkCardIds.push(cardId);
                            }
                        }
                    });
                 });
                 });
               
                if (sparkCardIds.length > 0) {
                    setTimeout(function() {
                        BatchRequestManager.preload(sparkCardIds);
                    }, 2000);
                }
             }
             }
           
         }
            function initExistingCards() {
                var cards = document.querySelectorAll('.card-deck-trans');
                var nodeArray = [];
                cards.forEach(function(card) {
                    nodeArray.push(card);
                });
                processNewCards(nodeArray);
            }
           
            return {
                createModals: createModals,
                closeModal: closeModal,
                closeSparkModal: closeSparkModal,
                closeEnlargedCard: closeEnlargedCard,
                openModal: openModal,
                openSparkModal: openSparkModal,
                showEnlargedCard: showEnlargedCard,
                processNewCards: processNewCards,
                initExistingCards: initExistingCards,
                getModalOverlay: function() { return modalOverlay; },
                getSparkModalOverlay: function() { return sparkModalOverlay; }
            };
         })();
          
          
         // ==================== 事件委托管理器 ====================
         // 为闪光列表中的卡牌绑定点击事件
         var EventManager = (function() {
         function bindSparkListCardEvents() {
             var hoverTimer = null;
             var sparkCards = sparkModalContent.querySelectorAll('.card-deck-trans');
           
            sparkCards.forEach(function(card) {
            function getCardInfo(cardElement) {
                 if (card.dataset.sparkBound) return;
                var wrapper = cardElement.closest('.card-wrapper');
                 card.dataset.sparkBound = 'true';
                return {
                    cardId: wrapper ? wrapper.dataset.cardId : '',
                    sparkEnable: cardElement.dataset.sparkEnable || ''
                };
            }
           
            function isValidCardClick(target) {
                var card = target.closest('.card-deck-trans');
                 if (!card) return null;
               
                var inMainModal = card.closest('.card-modal-content');
                 var inSparkList = card.closest('.spark-card-list');
                var inEnlarged = card.closest('.spark-enlarged-container');
               
                if (inMainModal && !inSparkList) return null;
                if (inEnlarged) return null;
                  
                  
                 return card;
                 card.addEventListener('click', function(e) {
            }
                     e.preventDefault();
           
                    e.stopPropagation();
            function init() {
                document.addEventListener('click', function(e) {
                     var target = e.target;
                      
                      
                     if (target.classList.contains('card-modal-close')) {
                     // 关闭之前放大的卡牌
                        e.stopPropagation();
                    closeEnlargedCard();
                        DOMManager.closeModal();
                        return;
                    }
                      
                      
                     if (target.classList.contains('spark-modal-close')) {
                     // 创建放大容器
                        e.stopPropagation();
                    var enlargedContainer = document.createElement('div');
                        DOMManager.closeSparkModal();
                    enlargedContainer.className = 'spark-enlarged-container';
                        return;
                    }
                      
                      
                     if (target.classList.contains('spark-enlarged-close')) {
                     // 添加关闭按钮
                    var closeBtn = document.createElement('span');
                    closeBtn.className = 'spark-enlarged-close';
                    closeBtn.innerHTML = '&times;';
                    closeBtn.addEventListener('click', function(e) {
                         e.stopPropagation();
                         e.stopPropagation();
                         DOMManager.closeEnlargedCard();
                         closeEnlargedCard();
                        return;
                    });
                     }
                     enlargedContainer.appendChild(closeBtn);
                      
                      
                     if (target.classList.contains('spark-button')) {
                     // 创建卡牌内容区
                        e.preventDefault();
                    var cardContent = document.createElement('div');
                        var cardId = target.dataset.cardId;
                    cardContent.className = 'spark-enlarged-card';
                        if (cardId) {
                            DOMManager.openSparkModal(cardId);
                        }
                        return;
                    }
                      
                      
                     if (target.classList.contains('card-modal-overlay')) {
                     var cardClone = this.cloneNode(true);
                        DOMManager.closeModal();
                    delete cardClone.dataset.sparkBound;
                        return;
                    cardContent.appendChild(cardClone);
                    }
                      
                      
                     if (target.classList.contains('spark-modal-overlay')) {
                     enlargedContainer.appendChild(cardContent);
                        DOMManager.closeSparkModal();
                        return;
                    }
                   
                    if (target.classList.contains('spark-enlarged-container')) {
                        DOMManager.closeEnlargedCard();
                        return;
                    }
                      
                      
                     var card = isValidCardClick(target);
                     // 点击背景关闭
                     if (card) {
                     enlargedContainer.addEventListener('click', function(e) {
                        e.preventDefault();
                         if (e.target === enlargedContainer) {
                       
                             closeEnlargedCard();
                        var inSparkList = card.closest('.spark-card-list');
                        var info = getCardInfo(card);
                       
                         if (inSparkList) {
                            DOMManager.showEnlargedCard(card);
                        } else {
                             DOMManager.openModal(card, info.cardId, info.sparkEnable);
                         }
                         }
                     }
                     });
                }, true);
               
                document.addEventListener('mouseenter', function(e) {
                    var card = e.target.closest && e.target.closest('.card-deck-trans');
                    if (!card) return;
                      
                      
                     if (card.closest('.card-modal-overlay') ||
                     document.body.appendChild(enlargedContainer);
                        card.closest('.spark-modal-overlay') ||
                    currentEnlargedContainer = enlargedContainer;
                        card.closest('.spark-enlarged-container')) return;
                });
                   
            });
                    var info = getCardInfo(card);
        }
                    if (info.sparkEnable && info.sparkEnable.trim() !== '' && info.cardId) {
       
                        if (hoverTimer) {
        // 绑定关闭按钮事件
                            clearTimeout(hoverTimer);
        closeButton.addEventListener('click', function(e) {
                        }
            e.stopPropagation();
                       
            closeModal();
                        hoverTimer = setTimeout(function() {
        });
                            BatchRequestManager.prioritize(info.cardId);
       
                            if (!SparkCache.has(info.cardId)) {
        sparkCloseButton.addEventListener('click', function(e) {
                                BatchRequestManager.request(info.cardId).catch(function() {
            e.stopPropagation();
                                    // 忽略预加载错误
            closeSparkModal();
                                });
        });
                            }
       
                         }, 150);
        // 点击遮罩关闭
        modalOverlay.addEventListener('click', function(e) {
            if (e.target === modalOverlay) {
                closeModal();
            }
        });
       
        sparkModalOverlay.addEventListener('click', function(e) {
            if (e.target === sparkModalOverlay) {
                closeSparkModal();
            }
        });
       
        // ESC键关闭
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape') {
                // 优先关闭放大的闪光卡牌
                if (currentEnlargedContainer) {
                    closeEnlargedCard();
                } else if (sparkModalOverlay.classList.contains('active')) {
                    closeSparkModal();
                } else if (modalOverlay.classList.contains('active')) {
                    closeModal();
                }
            }
        });
       
        // 收集页面上所有启用闪光的卡牌ID
        function collectSparkCardIds() {
            var cardIds = [];
            var cards = document.querySelectorAll('.card-deck-trans[data-spark-enable]');
           
            cards.forEach(function(card) {
                var sparkEnable = card.dataset.sparkEnable;
                if (sparkEnable && sparkEnable.trim() !== '') {
                    var wrapper = card.closest('.card-wrapper');
                    var cardId = wrapper ? wrapper.dataset.cardId : '';
                    if (cardId && cardIds.indexOf(cardId) === -1) {
                         cardIds.push(cardId);
                     }
                     }
                 }, true);
                 }
            });
           
            return cardIds;
        }
       
        // 为所有卡牌绑定点击事件
        function bindCardEvents() {
            var cards = document.querySelectorAll('.card-deck-trans');
            var sparkCardIds = [];
           
            cards.forEach(function(card) {
                // 避免重复绑定
                if (card.dataset.cardBound) return;
                card.dataset.cardBound = 'true';
                  
                  
                 document.addEventListener('mouseleave', function(e) {
                 // 排除模态框内的卡牌
                    var card = e.target.closest && e.target.closest('.card-deck-trans');
                if (card.closest('.card-modal-overlay') || card.closest('.spark-modal-overlay') || card.closest('.spark-enlarged-container')) return;
                    if (card && hoverTimer) {
                        clearTimeout(hoverTimer);
                        hoverTimer = null;
                    }
                }, true);
                  
                  
                 document.addEventListener('keydown', function(e) {
                 // 收集启用闪光的卡牌ID用于预加载
                    if (e.key === 'Escape') {
                var sparkEnable = card.dataset.sparkEnable;
                        var sparkModal = DOMManager.getSparkModalOverlay();
                if (sparkEnable && sparkEnable.trim() !== '') {
                        var mainModal = DOMManager.getModalOverlay();
                    var wrapper = card.closest('.card-wrapper');
                       
                    var cardId = wrapper ? wrapper.dataset.cardId : '';
                        if (document.querySelector('.spark-enlarged-container')) {
                    if (cardId && sparkCardIds.indexOf(cardId) === -1) {
                            DOMManager.closeEnlargedCard();
                        sparkCardIds.push(cardId);
                        } else if (sparkModal && sparkModal.classList.contains('active')) {
                            DOMManager.closeSparkModal();
                        } else if (mainModal && mainModal.classList.contains('active')) {
                            DOMManager.closeModal();
                        }
                     }
                     }
                 });
                 }
                  
                  
                 window.addEventListener('beforeunload', function() {
                 card.addEventListener('click', function(e) {
                     SparkCache.flush();
                     e.preventDefault();
                   
                    // 从card-deck-trans元素获取spark_enable
                    var sparkEnable = this.dataset.sparkEnable || '';
                   
                    // 获取card-id从父级card-wrapper
                    var wrapper = this.closest('.card-wrapper');
                    var cardId = wrapper ? wrapper.dataset.cardId : '';
                   
                    openModal(this, cardId, sparkEnable);
                 });
                 });
                  
                  
                 document.addEventListener('visibilitychange', function() {
                 // 鼠标悬停时预加载
                     if (document.hidden) {
                card.addEventListener('mouseenter', function() {
                         SparkCache.flush();
                    var sparkEnable = this.dataset.sparkEnable || '';
                     if (sparkEnable && sparkEnable.trim() !== '') {
                         var wrapper = this.closest('.card-wrapper');
                        var cardId = wrapper ? wrapper.dataset.cardId : '';
                        if (cardId) {
                            // 高优先级预加载
                            PreloadQueue.add(cardId, 50);
                        }
                     }
                     }
                 });
                 });
            });
           
            // 延迟低优先级预加载页面上所有闪光卡牌
            if (sparkCardIds.length > 0) {
                setTimeout(function() {
                    PreloadQueue.addBatch(sparkCardIds, 1);
                }, 2000);
             }
             }
           
         }
            return {
                init: init
            };
         })();
          
          
         // ==================== 初始化 ====================
         // 初始绑定
         SparkCache.cleanup();
         bindCardEvents();
        DOMManager.createModals();
        EventManager.init();
        DOMManager.initExistingCards();
          
          
        // 监听DOM变化,为动态加载的卡牌绑定事件
         var observer = new MutationObserver(function(mutations) {
         var observer = new MutationObserver(function(mutations) {
             var newNodes = [];
             var shouldBind = false;
           
             mutations.forEach(function(mutation) {
             mutations.forEach(function(mutation) {
                 if (mutation.addedNodes.length > 0) {
                 if (mutation.addedNodes.length > 0) {
                     for (var i = 0; i < mutation.addedNodes.length; i++) {
                     mutation.addedNodes.forEach(function(node) {
                         newNodes.push(mutation.addedNodes[i]);
                         if (node.nodeType === 1) {
                     }
                            if (node.classList && node.classList.contains('card-deck-trans')) {
                                shouldBind = true;
                            } else if (node.querySelector && node.querySelector('.card-deck-trans')) {
                                shouldBind = true;
                            }
                        }
                     });
                 }
                 }
             });
             });
              
             if (shouldBind) {
            if (newNodes.length > 0) {
                 bindCardEvents();
                if (window.requestIdleCallback) {
                    requestIdleCallback(function() {
                        DOMManager.processNewCards(newNodes);
                    }, { timeout: 500 });
                 } else {
                    setTimeout(function() {
                        DOMManager.processNewCards(newNodes);
                    }, 100);
                }
             }
             }
         });
         });
第1,088行: 第1,005行:
             childList: true,
             childList: true,
             subtree: true
             subtree: true
        });
       
        // 页面可见性变化时暂停/恢复预加载
        document.addEventListener('visibilitychange', function() {
            if (document.hidden) {
                // 页面不可见时,暂停预加载(通过不处理队列)
            } else {
                // 页面可见时,恢复预加载
                PreloadQueue.process();
            }
         });
         });
     }
     }
   
    // ==================== 启动 ====================
    function start() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', function() {
                waitForMw(initCardWidget);
            });
        } else {
            waitForMw(initCardWidget);
        }
    }
   
    start();
})();
})();
</script>
</script>
</includeonly>
</includeonly>

2026年1月17日 (六) 10:45的版本