微件:Card:修订间差异

来自卡厄思梦境WIKI
跳转到导航 跳转到搜索
律Rhyme留言 | 贡献
功能测试
律Rhyme留言 | 贡献
功能测试
第254行: 第254行:
     50% { content: ".."; }
     50% { content: ".."; }
     75% { content: "..."; }
     75% { content: "..."; }
}
/* 加载进度 */
.spark-loading-progress {
    color: #aaa;
    font-size: 14px;
    margin-top: 10px;
}
}


第363行: 第356行:


<script>
<script>
// 使用 mw.loader.using 确保 mediawiki.api 模块已加载
(function() {
mw.loader.using(['mediawiki.api']).then(function() {
     'use strict';
     'use strict';
      
      
     // ==================== API 客户端 ====================
     // ==================== 等待 MediaWiki 加载 ====================
     var apiClient = null;
     function waitForMw(callback) {
   
        // 检查 mw 和 mw.loader 是否存在
    function getApi() {
        if (typeof mw !== 'undefined' && mw.loader && typeof mw.loader.using === 'function') {
        if (!apiClient) {
            mw.loader.using(['mediawiki.api']).then(callback);
            apiClient = new mw.Api();
        } else if (typeof mw !== 'undefined' && mw.Api) {
            // mw.Api 已经可用
            callback();
        } else {
            // 等待 mw 加载
            var checkCount = 0;
            var maxChecks = 50; // 最多等待5秒
           
            var checkInterval = setInterval(function() {
                checkCount++;
               
                if (typeof mw !== 'undefined' && mw.loader && typeof mw.loader.using === 'function') {
                    clearInterval(checkInterval);
                    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);
         }
         }
        return apiClient;
     }
     }
      
      
     // ==================== 缓存管理器(优化版) ====================
     // ==================== 主程序 ====================
     var SparkCache = (function() {
     function initCardWidget() {
        var CACHE_KEY = 'spark_card_cache_v2';
        var CACHE_EXPIRE = 24 * 60 * 60 * 1000; // 24小时
          
          
         // 内存缓存层
         // ==================== API 客户端 ====================
         var memoryCache = null;
         var apiClient = null;
         var isDirty = false;
         var apiAvailable = false;
        var saveTimer = null;
          
          
         // 检查 localStorage 是否可用
         function getApi() {
        var storageAvailable = (function() {
             if (!apiAvailable) {
             try {
                 // 检查 API 是否可用
                 var test = '__storage_test__';
                if (typeof mw !== 'undefined' && typeof mw.Api === 'function') {
                localStorage.setItem(test, test);
                    apiAvailable = true;
                localStorage.removeItem(test);
                 } else {
                 return true;
                    return null;
            } catch (e) {
                }
                return false;
             }
             }
        })();
       
        // 从 localStorage 加载到内存
        function loadFromStorage() {
            if (memoryCache !== null) return memoryCache;
              
              
             if (!storageAvailable) {
             if (!apiClient && apiAvailable) {
                 memoryCache = {};
                 try {
                return memoryCache;
                    apiClient = new mw.Api();
            }
                 } catch (e) {
           
                    console.warn('CardWidget: 无法创建 API 客户端', e);
            try {
                    apiAvailable = false;
                var data = localStorage.getItem(CACHE_KEY);
                    return null;
                 memoryCache = data ? JSON.parse(data) : {};
                 }
            } catch (e) {
                 memoryCache = {};
             }
             }
             return memoryCache;
             return apiClient;
         }
         }
          
          
         // 延迟批量写入 localStorage
         // ==================== 缓存管理器 ====================
         function scheduleSave() {
         var SparkCache = (function() {
             if (!storageAvailable || saveTimer) return;
             var CACHE_KEY = 'spark_card_cache_v2';
            var CACHE_EXPIRE = 24 * 60 * 60 * 1000;
           
            var memoryCache = null;
            var isDirty = false;
            var saveTimer = null;
              
              
             saveTimer = setTimeout(function() {
             var storageAvailable = (function() {
                 saveTimer = null;
                 try {
                if (isDirty && memoryCache) {
                    var test = '__storage_test__';
                     try {
                     localStorage.setItem(test, test);
                        localStorage.setItem(CACHE_KEY, JSON.stringify(memoryCache));
                    localStorage.removeItem(test);
                        isDirty = false;
                    return true;
                    } catch (e) {
                } catch (e) {
                        // 存储空间不足,清理旧数据
                    return false;
                        cleanup(true);
                    }
                 }
                 }
             }, 1000);
             })();
        }
       
        // 清理过期缓存
        function cleanup(force) {
            var cache = loadFromStorage();
            var now = Date.now();
            var hasChanges = false;
            var keys = Object.keys(cache);
              
              
             if (force && keys.length > 10) {
             function loadFromStorage() {
                var items = keys.map(function(k) {
                 if (memoryCache !== null) return memoryCache;
                    return { key: k, time: cache[k].timestamp };
                 }).sort(function(a, b) {
                    return a.time - b.time;
                });
                  
                  
                 var deleteCount = Math.floor(items.length / 2);
                 if (!storageAvailable) {
                for (var i = 0; i < deleteCount; i++) {
                    memoryCache = {};
                    delete cache[items[i].key];
                     return memoryCache;
                     hasChanges = true;
                 }
                 }
            } else {
               
                 for (var cardId in cache) {
                 try {
                     if (cache.hasOwnProperty(cardId) && now - cache[cardId].timestamp > CACHE_EXPIRE) {
                     var data = localStorage.getItem(CACHE_KEY);
                        delete cache[cardId];
                    memoryCache = data ? JSON.parse(data) : {};
                        hasChanges = true;
                } catch (e) {
                     }
                     memoryCache = {};
                 }
                 }
                return memoryCache;
             }
             }
              
              
             if (hasChanges) {
             function scheduleSave() {
                isDirty = true;
                if (!storageAvailable || saveTimer) return;
                 scheduleSave();
               
                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) {
        return {
             get: function(cardId) {
                 var cache = loadFromStorage();
                 var cache = loadFromStorage();
                 var item = cache[cardId];
                 var now = Date.now();
                var hasChanges = false;
                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 (!item) return null;
                 if (hasChanges) {
                if (Date.now() - item.timestamp > CACHE_EXPIRE) {
                    delete cache[cardId];
                     isDirty = true;
                     isDirty = true;
                     scheduleSave();
                     scheduleSave();
                    return null;
                 }
                 }
               
             }
                return item.html;
             },
              
              
             set: function(cardId, html) {
             return {
                 var cache = loadFromStorage();
                 get: function(cardId) {
                cache[cardId] = {
                    var cache = loadFromStorage();
                    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);
            },
           
            getMultiple: function(cardIds) {
                var cache = loadFromStorage();
                var now = Date.now();
                var result = {};
                var hasExpired = false;
               
                cardIds.forEach(function(cardId) {
                     var item = cache[cardId];
                     var item = cache[cardId];
                     if (item) {
                   
                        if (now - item.timestamp <= CACHE_EXPIRE) {
                     if (!item) return null;
                            result[cardId] = item.html;
                    if (Date.now() - item.timestamp > CACHE_EXPIRE) {
                         } else {
                         delete cache[cardId];
                            delete cache[cardId];
                        isDirty = true;
                            hasExpired = true;
                         scheduleSave();
                         }
                        return null;
                     }
                     }
                 });
                   
                    return item.html;
                 },
                  
                  
                 if (hasExpired) {
                 set: function(cardId, html) {
                    var cache = loadFromStorage();
                    cache[cardId] = {
                        html: html,
                        timestamp: Date.now()
                    };
                     isDirty = true;
                     isDirty = true;
                     scheduleSave();
                     scheduleSave();
                 }
                 },
               
                has: function(cardId) {
                    var cache = loadFromStorage();
                    var item = cache[cardId];
                    return item && (Date.now() - item.timestamp <= CACHE_EXPIRE);
                },
                  
                  
                 return result;
                 cleanup: cleanup,
            },
           
            setMultiple: function(dataMap) {
                var cache = loadFromStorage();
                var now = Date.now();
                  
                  
                 for (var cardId in dataMap) {
                 flush: function() {
                     if (dataMap.hasOwnProperty(cardId)) {
                     if (!storageAvailable) return;
                         cache[cardId] = {
                   
                             html: dataMap[cardId],
                    if (saveTimer) {
                             timestamp: now
                        clearTimeout(saveTimer);
                         };
                        saveTimer = null;
                    }
                    if (isDirty && memoryCache) {
                         try {
                             localStorage.setItem(CACHE_KEY, JSON.stringify(memoryCache));
                             isDirty = false;
                         } catch (e) {}
                     }
                     }
                 }
                 }
               
            };
                isDirty = true;
        })();
                scheduleSave();
       
             },
        // ==================== 批量API请求管理器 ====================
        var BatchRequestManager = (function() {
            var pendingRequests = {};
             var requestQueue = [];
            var isProcessing = false;
            var batchSize = 5;
            var batchDelay = 50;
              
              
             cleanup: cleanup,
             function processBatch() {
           
                 if (isProcessing || requestQueue.length === 0) return;
            flush: function() {
                 if (!storageAvailable) return;
                  
                  
                 if (saveTimer) {
                var api = getApi();
                     clearTimeout(saveTimer);
                 if (!api) {
                    saveTimer = null;
                     // API 不可用,清空队列并拒绝所有请求
                }
                    requestQueue.forEach(function(cardId) {
                if (isDirty && memoryCache) {
                        var callbacks = pendingRequests[cardId] || [];
                    try {
                        delete pendingRequests[cardId];
                        localStorage.setItem(CACHE_KEY, JSON.stringify(memoryCache));
                        callbacks.forEach(function(cb) {
                         isDirty = false;
                            cb.reject(new Error('API not available'));
                     } catch (e) {}
                         });
                     });
                    requestQueue = [];
                    return;
                 }
                 }
               
                isProcessing = true;
               
                var batch = requestQueue.splice(0, batchSize);
                var promises = batch.map(function(cardId) {
                    return fetchSparkList(cardId, api);
                });
               
                Promise.all(promises).then(function() {
                    isProcessing = false;
                    if (requestQueue.length > 0) {
                        setTimeout(processBatch, batchDelay);
                    }
                });
             }
             }
        };
    })();
   
    // ==================== 批量API请求管理器 ====================
    var BatchRequestManager = (function() {
        var pendingRequests = {};
        var requestQueue = [];
        var isProcessing = false;
        var batchSize = 5;
        var batchDelay = 50;
       
        function processBatch() {
            if (isProcessing || requestQueue.length === 0) return;
              
              
             isProcessing = true;
             function fetchSparkList(cardId, api) {
                return new Promise(function(resolve) {
                    api.get({
                        action: 'parse',
                        text: '{{Card/spark/list|' + cardId + '}}',
                        prop: 'text',
                        disablelimitreport: true,
                        format: 'json'
                    }).done(function(data) {
                        var html = '';
                        if (data.parse && data.parse.text) {
                            html = data.parse.text['*'];
                            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();
                    });
                });
            }
              
              
             var batch = requestQueue.splice(0, batchSize);
             return {
            var promises = batch.map(function(cardId) {
                request: function(cardId) {
                return fetchSparkList(cardId);
                    return new Promise(function(resolve, reject) {
            });
                        var cached = SparkCache.get(cardId);
           
                        if (cached !== null) {
            Promise.all(promises).then(function() {
                            resolve(cached);
                isProcessing = false;
                            return;
                if (requestQueue.length > 0) {
                        }
                    setTimeout(processBatch, batchDelay);
                       
                }
                        if (pendingRequests[cardId]) {
            });
                            pendingRequests[cardId].push({ resolve: resolve, reject: reject });
        }
                            return;
       
                         }
        function fetchSparkList(cardId) {
                       
            return new Promise(function(resolve) {
                        pendingRequests[cardId] = [{ resolve: resolve, reject: reject }];
                getApi().get({
                        requestQueue.push(cardId);
                    action: 'parse',
                       
                    text: '{{Card/spark/list|' + cardId + '}}',
                        if (!isProcessing) {
                    prop: 'text',
                            setTimeout(processBatch, 10);
                    disablelimitreport: true,
                        }
                    format: 'json'
                }).done(function(data) {
                    var html = '';
                    if (data.parse && data.parse.text) {
                        html = data.parse.text['*'];
                         SparkCache.set(cardId, html);
                    }
                   
                    var callbacks = pendingRequests[cardId] || [];
                    delete pendingRequests[cardId];
                    callbacks.forEach(function(cb) {
                        cb.resolve(html);
                     });
                     });
                },
               
                preload: function(cardIds) {
                    if (!getApi()) return; // API 不可用时不预加载
                      
                      
                     resolve();
                     var toLoad = cardIds.filter(function(cardId) {
                }).fail(function(error) {
                        return !SparkCache.has(cardId) &&
                    var callbacks = pendingRequests[cardId] || [];
                              !pendingRequests[cardId] &&
                    delete pendingRequests[cardId];
                              requestQueue.indexOf(cardId) === -1;
                    callbacks.forEach(function(cb) {
                        cb.reject(error);
                     });
                     });
                      
                      
                     resolve();
                     if (toLoad.length === 0) return;
                });
            });
        }
       
        return {
            request: function(cardId) {
                return new Promise(function(resolve, reject) {
                    var cached = SparkCache.get(cardId);
                    if (cached !== null) {
                        resolve(cached);
                        return;
                    }
                      
                      
                     if (pendingRequests[cardId]) {
                     toLoad.forEach(function(cardId) {
                         pendingRequests[cardId].push({ resolve: resolve, reject: reject });
                         pendingRequests[cardId] = [];
                        return;
                        requestQueue.push(cardId);
                    }
                    });
                   
                    pendingRequests[cardId] = [{ resolve: resolve, reject: reject }];
                    requestQueue.push(cardId);
                      
                      
                     if (!isProcessing) {
                     if (!isProcessing) {
                         setTimeout(processBatch, 10);
                         setTimeout(processBatch, 100);
                     }
                     }
                 });
                 },
            },
           
            preload: function(cardIds) {
                var toLoad = cardIds.filter(function(cardId) {
                    return !SparkCache.has(cardId) &&
                          !pendingRequests[cardId] &&
                          requestQueue.indexOf(cardId) === -1;
                });
                  
                  
                 if (toLoad.length === 0) return;
                 prioritize: function(cardId) {
               
                     var idx = requestQueue.indexOf(cardId);
                toLoad.forEach(function(cardId) {
                    if (idx > 0) {
                     pendingRequests[cardId] = [];
                        requestQueue.splice(idx, 1);
                    requestQueue.push(cardId);
                        requestQueue.unshift(cardId);
                });
                    }
               
                if (!isProcessing) {
                    setTimeout(processBatch, 100);
                 }
                 }
             },
             };
        })();
       
        // ==================== DOM 管理器 ====================
        var DOMManager = (function() {
            var modalOverlay = null;
            var sparkModalOverlay = null;
            var modalContent = null;
            var sparkModalContent = null;
            var currentEnlargedContainer = null;
            var processedCards = new WeakSet();
              
              
             prioritize: function(cardId) {
             function createModals() {
                 var idx = requestQueue.indexOf(cardId);
                 if (!modalOverlay) {
                 if (idx > 0) {
                    modalOverlay = document.createElement('div');
                     requestQueue.splice(idx, 1);
                    modalOverlay.className = 'card-modal-overlay';
                     requestQueue.unshift(cardId);
                    modalOverlay.innerHTML = '<span class="card-modal-close">&times;</span><div class="card-modal-content"></div>';
                    document.body.appendChild(modalOverlay);
                    modalContent = modalOverlay.querySelector('.card-modal-content');
                }
               
                 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);
                    sparkModalContent = sparkModalOverlay.querySelector('.spark-modal-content');
                 }
                 }
             }
             }
        };
           
    })();
            function closeModal() {
   
                if (!modalOverlay) return;
    // ==================== DOM 管理器 ====================
               
    var DOMManager = (function() {
                modalOverlay.classList.add('fade-out');
        var modalOverlay = null;
                setTimeout(function() {
        var sparkModalOverlay = null;
                    modalOverlay.classList.remove('active', 'fade-in', 'fade-out');
        var modalContent = null;
                    modalContent.innerHTML = '';
        var sparkModalContent = null;
                    document.body.classList.remove('card-modal-open');
        var currentEnlargedContainer = null;
                }, 300);
        var processedCards = new WeakSet();
       
        function createModals() {
            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);
                modalContent = modalOverlay.querySelector('.card-modal-content');
             }
             }
              
              
             if (!sparkModalOverlay) {
             function closeSparkModal() {
                 sparkModalOverlay = document.createElement('div');
                closeEnlargedCard();
                 sparkModalOverlay.className = 'spark-modal-overlay';
               
                sparkModalOverlay.innerHTML = '<span class="spark-modal-close">&times;</span><div class="spark-modal-content"></div>';
                if (!sparkModalOverlay) return;
                 document.body.appendChild(sparkModalOverlay);
               
                sparkModalContent = sparkModalOverlay.querySelector('.spark-modal-content');
                 sparkModalOverlay.classList.add('fade-out');
                 setTimeout(function() {
                    sparkModalOverlay.classList.remove('active', 'fade-in', 'fade-out');
                    sparkModalContent.innerHTML = '';
                 }, 300);
             }
             }
        }
       
        function closeModal() {
            if (!modalOverlay) return;
              
              
             modalOverlay.classList.add('fade-out');
             function closeEnlargedCard() {
            setTimeout(function() {
                 if (currentEnlargedContainer) {
                modalOverlay.classList.remove('active', 'fade-in', 'fade-out');
                    currentEnlargedContainer.remove();
                modalContent.innerHTML = '';
                    currentEnlargedContainer = null;
                document.body.classList.remove('card-modal-open');
                }
            }, 300);
        }
       
        function closeSparkModal() {
            closeEnlargedCard();
           
            if (!sparkModalOverlay) return;
           
            sparkModalOverlay.classList.add('fade-out');
            setTimeout(function() {
                 sparkModalOverlay.classList.remove('active', 'fade-in', 'fade-out');
                sparkModalContent.innerHTML = '';
            }, 300);
        }
       
        function closeEnlargedCard() {
            if (currentEnlargedContainer) {
                currentEnlargedContainer.remove();
                currentEnlargedContainer = null;
             }
             }
        }
       
        function openModal(cardElement, cardId, sparkEnable) {
            createModals();
              
              
             var cardClone = cardElement.cloneNode(true);
             function openModal(cardElement, cardId, sparkEnable) {
            modalContent.innerHTML = '';
                createModals();
            modalContent.appendChild(cardClone);
               
           
                var cardClone = cardElement.cloneNode(true);
            if (sparkEnable && sparkEnable.trim() !== '') {
                modalContent.innerHTML = '';
                var sparkButton = document.createElement('button');
                modalContent.appendChild(cardClone);
                sparkButton.className = 'spark-button';
                sparkButton.textContent = '闪光';
                sparkButton.dataset.cardId = cardId;
                  
                  
                 if (SparkCache.has(cardId)) {
                 // 只有在 API 可用且启用闪光时才添加按钮
                    sparkButton.classList.add('cached');
                if (sparkEnable && sparkEnable.trim() !== '' && getApi()) {
                } else {
                    var sparkButton = document.createElement('button');
                    BatchRequestManager.prioritize(cardId);
                    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);
                 }
                 }
                  
                  
                 modalContent.appendChild(sparkButton);
                 modalOverlay.classList.add('active');
                document.body.classList.add('card-modal-open');
               
                requestAnimationFrame(function() {
                    modalOverlay.classList.add('fade-in');
                });
             }
             }
              
              
             modalOverlay.classList.add('active');
             function openSparkModal(cardId) {
            document.body.classList.add('card-modal-open');
                createModals();
           
            requestAnimationFrame(function() {
                modalOverlay.classList.add('fade-in');
            });
        }
       
        function openSparkModal(cardId) {
            createModals();
           
            var cachedHtml = SparkCache.get(cardId);
           
            if (cachedHtml) {
                showSparkContent(cachedHtml, true);
            } else {
                sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-loading">加载中</div>';
                  
                  
                 sparkModalOverlay.classList.add('active');
                 var cachedHtml = SparkCache.get(cardId);
                requestAnimationFrame(function() {
                    sparkModalOverlay.classList.add('fade-in');
                });
                  
                  
                 BatchRequestManager.request(cardId).then(function(html) {
                 if (cachedHtml) {
                    showSparkContent(html, false);
                    showSparkContent(cachedHtml, true);
                }).catch(function() {
                } else {
                    sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-empty">加载失败,请重试</div>';
                    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) {
            var tempDiv = document.createElement('div');
            tempDiv.innerHTML = html;
            var cards = tempDiv.querySelectorAll('.card-deck-trans');
              
              
             var cacheHint = fromCache ? '<div class="spark-cache-hint">已从缓存加载</div>' : '';
             function showSparkContent(html, fromCache) {
           
                var tempDiv = document.createElement('div');
            if (cards.length > 0) {
                tempDiv.innerHTML = html;
                sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div>' + html + cacheHint;
                var cards = tempDiv.querySelectorAll('.card-deck-trans');
            } else {
               
                sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-empty">暂无闪光卡牌</div>' + cacheHint;
                var cacheHint = fromCache ? '<div class="spark-cache-hint">已从缓存加载</div>' : '';
               
                if (cards.length > 0) {
                    sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div>' + html + cacheHint;
                } else {
                    sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-empty">暂无闪光卡牌</div>' + cacheHint;
                }
               
                if (!sparkModalOverlay.classList.contains('active')) {
                    sparkModalOverlay.classList.add('active');
                    requestAnimationFrame(function() {
                        sparkModalOverlay.classList.add('fade-in');
                    });
                }
             }
             }
              
              
             if (!sparkModalOverlay.classList.contains('active')) {
             function showEnlargedCard(cardElement) {
                 sparkModalOverlay.classList.add('active');
                closeEnlargedCard();
                 requestAnimationFrame(function() {
               
                    sparkModalOverlay.classList.add('fade-in');
                var enlargedContainer = document.createElement('div');
                 });
                 enlargedContainer.className = 'spark-enlarged-container';
               
                var closeBtn = document.createElement('span');
                 closeBtn.className = 'spark-enlarged-close';
                closeBtn.innerHTML = '&times;';
                enlargedContainer.appendChild(closeBtn);
               
                var cardContent = document.createElement('div');
                cardContent.className = 'spark-enlarged-card';
                cardContent.appendChild(cardElement.cloneNode(true));
                 enlargedContainer.appendChild(cardContent);
               
                document.body.appendChild(enlargedContainer);
                currentEnlargedContainer = enlargedContainer;
             }
             }
        }
       
        function showEnlargedCard(cardElement) {
            closeEnlargedCard();
           
            var enlargedContainer = document.createElement('div');
            enlargedContainer.className = 'spark-enlarged-container';
           
            var closeBtn = document.createElement('span');
            closeBtn.className = 'spark-enlarged-close';
            closeBtn.innerHTML = '&times;';
            enlargedContainer.appendChild(closeBtn);
           
            var cardContent = document.createElement('div');
            cardContent.className = 'spark-enlarged-card';
            cardContent.appendChild(cardElement.cloneNode(true));
            enlargedContainer.appendChild(cardContent);
           
            document.body.appendChild(enlargedContainer);
            currentEnlargedContainer = enlargedContainer;
        }
       
        function processNewCards(nodes) {
            var sparkCardIds = [];
              
              
             nodes.forEach(function(node) {
             function processNewCards(nodes) {
                 if (node.nodeType !== 1) return;
                 var sparkCardIds = [];
                  
                  
                 var cards = [];
                 nodes.forEach(function(node) {
                if (node.classList && node.classList.contains('card-deck-trans')) {
                    if (node.nodeType !== 1) return;
                    cards.push(node);
                   
                }
                    var cards = [];
                if (node.querySelectorAll) {
                    if (node.classList && node.classList.contains('card-deck-trans')) {
                    var found = node.querySelectorAll('.card-deck-trans');
                        cards.push(node);
                    for (var i = 0; i < found.length; i++) {
                    }
                        cards.push(found[i]);
                    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);
                      
                      
                     var sparkEnable = card.dataset.sparkEnable;
                     cards.forEach(function(card) {
                    if (sparkEnable && sparkEnable.trim() !== '') {
                        if (processedCards.has(card)) return;
                        var wrapper = card.closest('.card-wrapper');
                        if (card.closest('.card-modal-overlay') ||
                        var cardId = wrapper ? wrapper.dataset.cardId : '';
                            card.closest('.spark-modal-overlay') ||
                        if (cardId && sparkCardIds.indexOf(cardId) === -1) {
                            card.closest('.spark-enlarged-container')) return;
                            sparkCardIds.push(cardId);
                       
                        processedCards.add(card);
                       
                        var sparkEnable = card.dataset.sparkEnable;
                        if (sparkEnable && sparkEnable.trim() !== '') {
                            var wrapper = card.closest('.card-wrapper');
                            var cardId = wrapper ? wrapper.dataset.cardId : '';
                            if (cardId && sparkCardIds.indexOf(cardId) === -1) {
                                sparkCardIds.push(cardId);
                            }
                         }
                         }
                     }
                     });
                 });
                 });
            });
               
                if (sparkCardIds.length > 0) {
                    setTimeout(function() {
                        BatchRequestManager.preload(sparkCardIds);
                    }, 2000);
                }
            }
              
              
             if (sparkCardIds.length > 0) {
             function initExistingCards() {
                 setTimeout(function() {
                var cards = document.querySelectorAll('.card-deck-trans');
                     BatchRequestManager.preload(sparkCardIds);
                var nodeArray = [];
                 }, 2000);
                 cards.forEach(function(card) {
                     nodeArray.push(card);
                 });
                processNewCards(nodeArray);
             }
             }
        }
              
       
        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() {
        var hoverTimer = null;
       
        function getCardInfo(cardElement) {
            var wrapper = cardElement.closest('.card-wrapper');
             return {
             return {
                 cardId: wrapper ? wrapper.dataset.cardId : '',
                 createModals: createModals,
                 sparkEnable: cardElement.dataset.sparkEnable || ''
                closeModal: closeModal,
                closeSparkModal: closeSparkModal,
                closeEnlargedCard: closeEnlargedCard,
                openModal: openModal,
                openSparkModal: openSparkModal,
                showEnlargedCard: showEnlargedCard,
                processNewCards: processNewCards,
                initExistingCards: initExistingCards,
                getModalOverlay: function() { return modalOverlay; },
                 getSparkModalOverlay: function() { return sparkModalOverlay; }
             };
             };
         }
         })();
          
          
         function isValidCardClick(target) {
         // ==================== 事件委托管理器 ====================
             var card = target.closest('.card-deck-trans');
        var EventManager = (function() {
            if (!card) return null;
             var hoverTimer = null;
              
              
             var inMainModal = card.closest('.card-modal-content');
             function getCardInfo(cardElement) {
            var inSparkList = card.closest('.spark-card-list');
                var wrapper = cardElement.closest('.card-wrapper');
            var inEnlarged = card.closest('.spark-enlarged-container');
                return {
           
                    cardId: wrapper ? wrapper.dataset.cardId : '',
            if (inMainModal && !inSparkList) return null;
                    sparkEnable: cardElement.dataset.sparkEnable || ''
             if (inEnlarged) return null;
                };
             }
              
              
             return card;
             function isValidCardClick(target) {
        }
                var card = target.closest('.card-deck-trans');
       
                 if (!card) return null;
        function init() {
            document.addEventListener('click', function(e) {
                 var target = e.target;
                  
                  
                 if (target.classList.contains('card-modal-close')) {
                 var inMainModal = card.closest('.card-modal-content');
                    e.stopPropagation();
                var inSparkList = card.closest('.spark-card-list');
                    DOMManager.closeModal();
                var inEnlarged = card.closest('.spark-enlarged-container');
                    return;
                }
                  
                  
                 if (target.classList.contains('spark-modal-close')) {
                 if (inMainModal && !inSparkList) return null;
                    e.stopPropagation();
                if (inEnlarged) return null;
                    DOMManager.closeSparkModal();
                    return;
                }
                  
                  
                 if (target.classList.contains('spark-enlarged-close')) {
                 return card;
                    e.stopPropagation();
            }
                    DOMManager.closeEnlargedCard();
           
                    return;
            function init() {
                }
                document.addEventListener('click', function(e) {
               
                    var target = e.target;
                if (target.classList.contains('spark-button')) {
                   
                    e.preventDefault();
                    if (target.classList.contains('card-modal-close')) {
                    var cardId = target.dataset.cardId;
                        e.stopPropagation();
                     if (cardId) {
                        DOMManager.closeModal();
                         DOMManager.openSparkModal(cardId);
                        return;
                    }
                   
                    if (target.classList.contains('spark-modal-close')) {
                        e.stopPropagation();
                        DOMManager.closeSparkModal();
                        return;
                    }
                   
                    if (target.classList.contains('spark-enlarged-close')) {
                        e.stopPropagation();
                        DOMManager.closeEnlargedCard();
                        return;
                    }
                   
                    if (target.classList.contains('spark-button')) {
                        e.preventDefault();
                        var cardId = target.dataset.cardId;
                        if (cardId) {
                            DOMManager.openSparkModal(cardId);
                        }
                        return;
                    }
                   
                    if (target.classList.contains('card-modal-overlay')) {
                        DOMManager.closeModal();
                        return;
                    }
                   
                    if (target.classList.contains('spark-modal-overlay')) {
                        DOMManager.closeSparkModal();
                        return;
                    }
                   
                     if (target.classList.contains('spark-enlarged-container')) {
                        DOMManager.closeEnlargedCard();
                        return;
                    }
                   
                    var card = isValidCardClick(target);
                    if (card) {
                        e.preventDefault();
                       
                        var inSparkList = card.closest('.spark-card-list');
                        var info = getCardInfo(card);
                       
                        if (inSparkList) {
                            DOMManager.showEnlargedCard(card);
                         } else {
                            DOMManager.openModal(card, info.cardId, info.sparkEnable);
                        }
                     }
                     }
                    return;
                 }, true);
                 }
                  
                  
                 if (target.classList.contains('card-modal-overlay')) {
                 document.addEventListener('mouseenter', function(e) {
                     DOMManager.closeModal();
                    var card = e.target.closest && e.target.closest('.card-deck-trans');
                     return;
                     if (!card) return;
                }
                      
               
                    if (card.closest('.card-modal-overlay') ||
                if (target.classList.contains('spark-modal-overlay')) {
                        card.closest('.spark-modal-overlay') ||
                    DOMManager.closeSparkModal();
                        card.closest('.spark-enlarged-container')) return;
                    return;
                }
               
                if (target.classList.contains('spark-enlarged-container')) {
                    DOMManager.closeEnlargedCard();
                    return;
                }
               
                var card = isValidCardClick(target);
                if (card) {
                    e.preventDefault();
                      
                      
                    var inSparkList = card.closest('.spark-card-list');
                     var info = getCardInfo(card);
                     var info = getCardInfo(card);
                      
                     if (info.sparkEnable && info.sparkEnable.trim() !== '' && info.cardId) {
                    if (inSparkList) {
                        if (hoverTimer) {
                         DOMManager.showEnlargedCard(card);
                            clearTimeout(hoverTimer);
                    } else {
                        }
                        DOMManager.openModal(card, info.cardId, info.sparkEnable);
                       
                         hoverTimer = setTimeout(function() {
                            BatchRequestManager.prioritize(info.cardId);
                            if (!SparkCache.has(info.cardId)) {
                                BatchRequestManager.request(info.cardId).catch(function() {
                                    // 忽略预加载错误
                                });
                            }
                        }, 150);
                     }
                     }
                 }
                 }, true);
            }, 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.addEventListener('mouseleave', function(e) {
                     card.closest('.spark-modal-overlay') ||
                     var card = e.target.closest && e.target.closest('.card-deck-trans');
                    card.closest('.spark-enlarged-container')) return;
                    if (card && hoverTimer) {
               
                var info = getCardInfo(card);
                if (info.sparkEnable && info.sparkEnable.trim() !== '' && info.cardId) {
                    if (hoverTimer) {
                         clearTimeout(hoverTimer);
                         clearTimeout(hoverTimer);
                        hoverTimer = null;
                     }
                     }
                   
                }, true);
                    hoverTimer = setTimeout(function() {
               
                         BatchRequestManager.prioritize(info.cardId);
                document.addEventListener('keydown', function(e) {
                         if (!SparkCache.has(info.cardId)) {
                    if (e.key === 'Escape') {
                             BatchRequestManager.request(info.cardId);
                         var sparkModal = DOMManager.getSparkModalOverlay();
                        var mainModal = DOMManager.getModalOverlay();
                       
                         if (document.querySelector('.spark-enlarged-container')) {
                             DOMManager.closeEnlargedCard();
                        } else if (sparkModal && sparkModal.classList.contains('active')) {
                            DOMManager.closeSparkModal();
                        } else if (mainModal && mainModal.classList.contains('active')) {
                            DOMManager.closeModal();
                         }
                         }
                    }, 150);
                }
            }, true);
           
            document.addEventListener('mouseleave', function(e) {
                var card = e.target.closest && e.target.closest('.card-deck-trans');
                if (card && hoverTimer) {
                    clearTimeout(hoverTimer);
                    hoverTimer = null;
                }
            }, true);
           
            document.addEventListener('keydown', function(e) {
                if (e.key === 'Escape') {
                    var sparkModal = DOMManager.getSparkModalOverlay();
                    var mainModal = DOMManager.getModalOverlay();
                   
                    if (document.querySelector('.spark-enlarged-container')) {
                        DOMManager.closeEnlargedCard();
                    } else if (sparkModal && sparkModal.classList.contains('active')) {
                        DOMManager.closeSparkModal();
                    } else if (mainModal && mainModal.classList.contains('active')) {
                        DOMManager.closeModal();
                     }
                     }
                 }
                 });
            });
               
                window.addEventListener('beforeunload', function() {
                    SparkCache.flush();
                });
               
                document.addEventListener('visibilitychange', function() {
                    if (document.hidden) {
                        SparkCache.flush();
                    }
                });
            }
              
              
             window.addEventListener('beforeunload', function() {
             return {
                 SparkCache.flush();
                 init: init
             });
             };
           
        })();
            document.addEventListener('visibilitychange', function() {
                if (document.hidden) {
                    SparkCache.flush();
                }
            });
        }
          
          
         return {
         // ==================== 初始化 ====================
            init: init
        };
    })();
   
    // ==================== 初始化 ====================
    function init() {
         SparkCache.cleanup();
         SparkCache.cleanup();
         DOMManager.createModals();
         DOMManager.createModals();
第1,079行: 第1,091行:
     }
     }
      
      
     // 启动
     // ==================== 启动 ====================
     if (document.readyState === 'loading') {
     function start() {
        document.addEventListener('DOMContentLoaded', init);
        if (document.readyState === 'loading') {
    } else {
            document.addEventListener('DOMContentLoaded', function() {
        init();
                waitForMw(initCardWidget);
            });
        } else {
            waitForMw(initCardWidget);
        }
     }
     }
});
   
    start();
})();
</script>
</script>
</includeonly>
</includeonly>

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

此Widget为卡牌显示添加交互效果:

  • 鼠标悬停时的动画效果
  • 点击后显示放大的模态框
  • 自动检测spark_enable属性显示闪光按钮
  • 支持闪光卡牌列表显示
  • 预加载、批量加载和本地缓存优化
  • 事件委托、DOM优化、批量API请求

使用方法:在页面中添加

×
×