微件:Card:修订间差异

来自卡厄思梦境WIKI
跳转到导航 跳转到搜索
律Rhyme留言 | 贡献
功能测试
律Rhyme留言 | 贡献
功能测试
第1行: 第1行:
<includeonly>
<noinclude>
此Widget为卡牌显示添加交互效果:
* 鼠标悬停时的动画效果
* 点击后显示放大的模态框
* 自动检测spark_enable属性显示闪光按钮
* 支持闪光卡牌列表显示
* 预加载、批量加载和本地缓存优化
* 事件委托、DOM优化、批量API请求
 
使用方法:在页面中添加 {{#widget:Card}}
</noinclude><includeonly>
<style>
<style>
/* 卡牌容器 */
/* 卡牌容器 */
第353行: 第363行:


<script>
<script>
(function() {
// 使用 mw.loader.using 确保 mediawiki.api 模块已加载
mw.loader.using(['mediawiki.api']).then(function() {
     'use strict';
     'use strict';
   
    // ==================== API 客户端 ====================
    var apiClient = null;
   
    function getApi() {
        if (!apiClient) {
            apiClient = new mw.Api();
        }
        return apiClient;
    }
      
      
     // ==================== 缓存管理器(优化版) ====================
     // ==================== 缓存管理器(优化版) ====================
第365行: 第386行:
         var isDirty = false;
         var isDirty = false;
         var saveTimer = null;
         var saveTimer = null;
       
        // 检查 localStorage 是否可用
        var storageAvailable = (function() {
            try {
                var test = '__storage_test__';
                localStorage.setItem(test, test);
                localStorage.removeItem(test);
                return true;
            } catch (e) {
                return false;
            }
        })();
          
          
         // 从 localStorage 加载到内存
         // 从 localStorage 加载到内存
         function loadFromStorage() {
         function loadFromStorage() {
             if (memoryCache !== null) return memoryCache;
             if (memoryCache !== null) return memoryCache;
           
            if (!storageAvailable) {
                memoryCache = {};
                return memoryCache;
            }
              
              
             try {
             try {
第381行: 第419行:
         // 延迟批量写入 localStorage
         // 延迟批量写入 localStorage
         function scheduleSave() {
         function scheduleSave() {
             if (saveTimer) return;
             if (!storageAvailable || saveTimer) return;
              
              
             saveTimer = setTimeout(function() {
             saveTimer = setTimeout(function() {
第394行: 第432行:
                     }
                     }
                 }
                 }
             }, 1000); // 1秒后批量写入
             }, 1000);
         }
         }
          
          
第404行: 第442行:
             var keys = Object.keys(cache);
             var keys = Object.keys(cache);
              
              
            // 强制清理时,删除最旧的一半
             if (force && keys.length > 10) {
             if (force && keys.length > 10) {
                 var items = keys.map(function(k) {
                 var items = keys.map(function(k) {
第418行: 第455行:
                 }
                 }
             } else {
             } else {
                // 正常清理过期数据
                 for (var cardId in cache) {
                 for (var cardId in cache) {
                     if (cache.hasOwnProperty(cardId) && now - cache[cardId].timestamp > CACHE_EXPIRE) {
                     if (cache.hasOwnProperty(cardId) && now - cache[cardId].timestamp > CACHE_EXPIRE) {
第465行: 第501行:
             },
             },
              
              
            // 批量获取
             getMultiple: function(cardIds) {
             getMultiple: function(cardIds) {
                 var cache = loadFromStorage();
                 var cache = loadFromStorage();
第492行: 第527行:
             },
             },
              
              
            // 批量设置
             setMultiple: function(dataMap) {
             setMultiple: function(dataMap) {
                 var cache = loadFromStorage();
                 var cache = loadFromStorage();
第512行: 第546行:
             cleanup: cleanup,
             cleanup: cleanup,
              
              
            // 页面卸载时立即保存
             flush: function() {
             flush: function() {
                if (!storageAvailable) return;
               
                 if (saveTimer) {
                 if (saveTimer) {
                     clearTimeout(saveTimer);
                     clearTimeout(saveTimer);
第530行: 第565行:
     // ==================== 批量API请求管理器 ====================
     // ==================== 批量API请求管理器 ====================
     var BatchRequestManager = (function() {
     var BatchRequestManager = (function() {
         var pendingRequests = {}; // cardId -> [resolve, reject][]
         var pendingRequests = {};
         var requestQueue = [];
         var requestQueue = [];
         var isProcessing = false;
         var isProcessing = false;
         var batchSize = 5; // 每批并行请求数
         var batchSize = 5;
         var batchDelay = 50; // 批次间延迟
         var batchDelay = 50;
          
          
         function processBatch() {
         function processBatch() {
第541行: 第576行:
             isProcessing = true;
             isProcessing = true;
              
              
            // 取出一批
             var batch = requestQueue.splice(0, batchSize);
             var batch = requestQueue.splice(0, batchSize);
             var promises = batch.map(function(cardId) {
             var promises = batch.map(function(cardId) {
第557行: 第591行:
         function fetchSparkList(cardId) {
         function fetchSparkList(cardId) {
             return new Promise(function(resolve) {
             return new Promise(function(resolve) {
                 var api = new mw.Api();
                 getApi().get({
                api.get({
                     action: 'parse',
                     action: 'parse',
                     text: '{{Card/spark/list|' + cardId + '}}',
                     text: '{{Card/spark/list|' + cardId + '}}',
第571行: 第604行:
                     }
                     }
                      
                      
                    // 通知所有等待者
                     var callbacks = pendingRequests[cardId] || [];
                     var callbacks = pendingRequests[cardId] || [];
                     delete pendingRequests[cardId];
                     delete pendingRequests[cardId];
第592行: 第624行:
          
          
         return {
         return {
            // 请求单个卡牌(会自动合并到批量请求)
             request: function(cardId) {
             request: function(cardId) {
                 return new Promise(function(resolve, reject) {
                 return new Promise(function(resolve, reject) {
                    // 先检查缓存
                     var cached = SparkCache.get(cardId);
                     var cached = SparkCache.get(cardId);
                     if (cached !== null) {
                     if (cached !== null) {
第602行: 第632行:
                     }
                     }
                      
                      
                    // 检查是否已在请求中
                     if (pendingRequests[cardId]) {
                     if (pendingRequests[cardId]) {
                         pendingRequests[cardId].push({ resolve: resolve, reject: reject });
                         pendingRequests[cardId].push({ resolve: resolve, reject: reject });
第608行: 第637行:
                     }
                     }
                      
                      
                    // 新请求
                     pendingRequests[cardId] = [{ resolve: resolve, reject: reject }];
                     pendingRequests[cardId] = [{ resolve: resolve, reject: reject }];
                     requestQueue.push(cardId);
                     requestQueue.push(cardId);
                      
                      
                    // 启动处理
                     if (!isProcessing) {
                     if (!isProcessing) {
                         setTimeout(processBatch, 10);
                         setTimeout(processBatch, 10);
第619行: 第646行:
             },
             },
              
              
            // 预加载多个卡牌(低优先级)
             preload: function(cardIds) {
             preload: function(cardIds) {
                // 过滤掉已缓存和已在队列中的
                 var toLoad = cardIds.filter(function(cardId) {
                 var toLoad = cardIds.filter(function(cardId) {
                     return !SparkCache.has(cardId) &&  
                     return !SparkCache.has(cardId) &&  
第630行: 第655行:
                 if (toLoad.length === 0) return;
                 if (toLoad.length === 0) return;
                  
                  
                // 添加到队列末尾
                 toLoad.forEach(function(cardId) {
                 toLoad.forEach(function(cardId) {
                     pendingRequests[cardId] = [];
                     pendingRequests[cardId] = [];
第636行: 第660行:
                 });
                 });
                  
                  
                // 启动处理
                 if (!isProcessing) {
                 if (!isProcessing) {
                     setTimeout(processBatch, 100);
                     setTimeout(processBatch, 100);
第642行: 第665行:
             },
             },
              
              
            // 提升优先级
             prioritize: function(cardId) {
             prioritize: function(cardId) {
                 var idx = requestQueue.indexOf(cardId);
                 var idx = requestQueue.indexOf(cardId);
第660行: 第682行:
         var sparkModalContent = null;
         var sparkModalContent = null;
         var currentEnlargedContainer = null;
         var currentEnlargedContainer = null;
         var processedCards = new WeakSet(); // 使用 WeakSet 跟踪已处理的卡牌
         var processedCards = new WeakSet();
          
          
        // 创建模态框
         function createModals() {
         function createModals() {
             if (!modalOverlay) {
             if (!modalOverlay) {
第681行: 第702行:
         }
         }
          
          
        // 关闭主模态框
         function closeModal() {
         function closeModal() {
             if (!modalOverlay) return;
             if (!modalOverlay) return;
第693行: 第713行:
         }
         }
          
          
        // 关闭闪光模态框
         function closeSparkModal() {
         function closeSparkModal() {
             closeEnlargedCard();
             closeEnlargedCard();
第706行: 第725行:
         }
         }
          
          
        // 关闭放大的闪光卡牌
         function closeEnlargedCard() {
         function closeEnlargedCard() {
             if (currentEnlargedContainer) {
             if (currentEnlargedContainer) {
第714行: 第732行:
         }
         }
          
          
        // 打开主模态框
         function openModal(cardElement, cardId, sparkEnable) {
         function openModal(cardElement, cardId, sparkEnable) {
             createModals();
             createModals();
第722行: 第739行:
             modalContent.appendChild(cardClone);
             modalContent.appendChild(cardClone);
              
              
            // 如果启用了闪光功能
             if (sparkEnable && sparkEnable.trim() !== '') {
             if (sparkEnable && sparkEnable.trim() !== '') {
                 var sparkButton = document.createElement('button');
                 var sparkButton = document.createElement('button');
第746行: 第762行:
         }
         }
          
          
        // 打开闪光卡牌模态框
         function openSparkModal(cardId) {
         function openSparkModal(cardId) {
             createModals();
             createModals();
第791行: 第806行:
         }
         }
          
          
        // 显示放大的闪光卡牌
         function showEnlargedCard(cardElement) {
         function showEnlargedCard(cardElement) {
             closeEnlargedCard();
             closeEnlargedCard();
第812行: 第826行:
         }
         }
          
          
        // 处理新添加的卡牌节点
         function processNewCards(nodes) {
         function processNewCards(nodes) {
             var sparkCardIds = [];
             var sparkCardIds = [];
第831行: 第844行:
                  
                  
                 cards.forEach(function(card) {
                 cards.forEach(function(card) {
                    // 跳过已处理的和模态框内的
                     if (processedCards.has(card)) return;
                     if (processedCards.has(card)) return;
                     if (card.closest('.card-modal-overlay') ||  
                     if (card.closest('.card-modal-overlay') ||  
第839行: 第851行:
                     processedCards.add(card);
                     processedCards.add(card);
                      
                      
                    // 收集需要预加载的卡牌ID
                     var sparkEnable = card.dataset.sparkEnable;
                     var sparkEnable = card.dataset.sparkEnable;
                     if (sparkEnable && sparkEnable.trim() !== '') {
                     if (sparkEnable && sparkEnable.trim() !== '') {
第851行: 第862行:
             });
             });
              
              
            // 延迟预加载
             if (sparkCardIds.length > 0) {
             if (sparkCardIds.length > 0) {
                 setTimeout(function() {
                 setTimeout(function() {
第859行: 第869行:
         }
         }
          
          
        // 初始化页面上已有的卡牌
         function initExistingCards() {
         function initExistingCards() {
             var cards = document.querySelectorAll('.card-deck-trans');
             var cards = document.querySelectorAll('.card-deck-trans');
第888行: 第897行:
         var hoverTimer = null;
         var hoverTimer = null;
          
          
        // 获取卡牌信息
         function getCardInfo(cardElement) {
         function getCardInfo(cardElement) {
             var wrapper = cardElement.closest('.card-wrapper');
             var wrapper = cardElement.closest('.card-wrapper');
第897行: 第905行:
         }
         }
          
          
        // 检查是否是有效的卡牌点击
         function isValidCardClick(target) {
         function isValidCardClick(target) {
             var card = target.closest('.card-deck-trans');
             var card = target.closest('.card-deck-trans');
             if (!card) return null;
             if (!card) return null;
              
              
            // 排除模态框内的卡牌(除了闪光列表中的)
             var inMainModal = card.closest('.card-modal-content');
             var inMainModal = card.closest('.card-modal-content');
             var inSparkList = card.closest('.spark-card-list');
             var inSparkList = card.closest('.spark-card-list');
第913行: 第919行:
         }
         }
          
          
        // 初始化事件监听
         function init() {
         function init() {
            // 使用事件委托处理所有点击事件
             document.addEventListener('click', function(e) {
             document.addEventListener('click', function(e) {
                 var target = e.target;
                 var target = e.target;
                  
                  
                // 关闭按钮
                 if (target.classList.contains('card-modal-close')) {
                 if (target.classList.contains('card-modal-close')) {
                     e.stopPropagation();
                     e.stopPropagation();
第938行: 第941行:
                 }
                 }
                  
                  
                // 闪光按钮
                 if (target.classList.contains('spark-button')) {
                 if (target.classList.contains('spark-button')) {
                     e.preventDefault();
                     e.preventDefault();
第948行: 第950行:
                 }
                 }
                  
                  
                // 点击遮罩关闭
                 if (target.classList.contains('card-modal-overlay')) {
                 if (target.classList.contains('card-modal-overlay')) {
                     DOMManager.closeModal();
                     DOMManager.closeModal();
第964行: 第965行:
                 }
                 }
                  
                  
                // 卡牌点击
                 var card = isValidCardClick(target);
                 var card = isValidCardClick(target);
                 if (card) {
                 if (card) {
第973行: 第973行:
                      
                      
                     if (inSparkList) {
                     if (inSparkList) {
                        // 闪光列表中的卡牌 - 显示放大
                         DOMManager.showEnlargedCard(card);
                         DOMManager.showEnlargedCard(card);
                     } else {
                     } else {
                        // 普通卡牌 - 打开模态框
                         DOMManager.openModal(card, info.cardId, info.sparkEnable);
                         DOMManager.openModal(card, info.cardId, info.sparkEnable);
                     }
                     }
第982行: 第980行:
             }, true);
             }, true);
              
              
            // 使用事件委托处理鼠标悬停
             document.addEventListener('mouseenter', function(e) {
             document.addEventListener('mouseenter', function(e) {
                 var card = e.target.closest && e.target.closest('.card-deck-trans');
                 var card = e.target.closest && e.target.closest('.card-deck-trans');
                 if (!card) return;
                 if (!card) return;
                  
                  
                // 排除模态框内的卡牌
                 if (card.closest('.card-modal-overlay') ||  
                 if (card.closest('.card-modal-overlay') ||  
                     card.closest('.spark-modal-overlay') ||  
                     card.closest('.spark-modal-overlay') ||  
第994行: 第990行:
                 var info = getCardInfo(card);
                 var info = getCardInfo(card);
                 if (info.sparkEnable && info.sparkEnable.trim() !== '' && info.cardId) {
                 if (info.sparkEnable && info.sparkEnable.trim() !== '' && info.cardId) {
                    // 清除之前的定时器
                     if (hoverTimer) {
                     if (hoverTimer) {
                         clearTimeout(hoverTimer);
                         clearTimeout(hoverTimer);
                     }
                     }
                      
                      
                    // 延迟触发预加载,避免快速滑过时的无效请求
                     hoverTimer = setTimeout(function() {
                     hoverTimer = setTimeout(function() {
                         BatchRequestManager.prioritize(info.cardId);
                         BatchRequestManager.prioritize(info.cardId);
第1,017行: 第1,011行:
             }, true);
             }, true);
              
              
            // ESC键关闭
             document.addEventListener('keydown', function(e) {
             document.addEventListener('keydown', function(e) {
                 if (e.key === 'Escape') {
                 if (e.key === 'Escape') {
第1,023行: 第1,016行:
                     var mainModal = DOMManager.getModalOverlay();
                     var mainModal = DOMManager.getModalOverlay();
                      
                      
                    // 按层级关闭
                     if (document.querySelector('.spark-enlarged-container')) {
                     if (document.querySelector('.spark-enlarged-container')) {
                         DOMManager.closeEnlargedCard();
                         DOMManager.closeEnlargedCard();
第1,034行: 第1,026行:
             });
             });
              
              
            // 页面卸载时保存缓存
             window.addEventListener('beforeunload', function() {
             window.addEventListener('beforeunload', function() {
                 SparkCache.flush();
                 SparkCache.flush();
             });
             });
              
              
            // 页面可见性变化
             document.addEventListener('visibilitychange', function() {
             document.addEventListener('visibilitychange', function() {
                 if (document.hidden) {
                 if (document.hidden) {
第1,054行: 第1,044行:
     // ==================== 初始化 ====================
     // ==================== 初始化 ====================
     function init() {
     function init() {
        // 清理过期缓存
         SparkCache.cleanup();
         SparkCache.cleanup();
       
        // 创建模态框
         DOMManager.createModals();
         DOMManager.createModals();
       
        // 初始化事件监听(事件委托)
         EventManager.init();
         EventManager.init();
       
        // 处理已有卡牌
         DOMManager.initExistingCards();
         DOMManager.initExistingCards();
          
          
        // 监听DOM变化 - 只处理新添加的节点
         var observer = new MutationObserver(function(mutations) {
         var observer = new MutationObserver(function(mutations) {
             var newNodes = [];
             var newNodes = [];
第1,079行: 第1,061行:
              
              
             if (newNodes.length > 0) {
             if (newNodes.length > 0) {
                // 使用 requestIdleCallback 或 setTimeout 延迟处理
                 if (window.requestIdleCallback) {
                 if (window.requestIdleCallback) {
                     requestIdleCallback(function() {
                     requestIdleCallback(function() {
第1,104行: 第1,085行:
         init();
         init();
     }
     }
})();
});
</script>
</script>
</includeonly>
</includeonly>

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

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

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

使用方法:在页面中添加

×
×