查看“︁微件:Card”︁的源代码
←
微件:Card
跳转到导航
跳转到搜索
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您没有权限编辑
微件
命名空间内的页面。
您可以查看和复制此页面的源代码。
<noinclude> 此Widget为卡牌显示添加交互效果: * 鼠标悬停时的动画效果 * 点击后显示放大的模态框 * 自动检测spark_enable属性显示闪光按钮 * 支持闪光卡牌列表显示 * 预加载、批量加载和本地缓存优化 * 事件委托、DOM优化、批量API请求 使用方法:在页面中添加 {{#widget:Card}} </noinclude><includeonly> <style> /* 卡牌容器 */ .card-wrapper { display: inline-block; } /* 卡牌悬停动画 */ .card-deck-trans { cursor: pointer; transition: transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease; } .card-deck-trans:hover { transform: translateY(-8px); filter: brightness(1.1); } .card-deck-trans:active { transform: translateY(-4px) scale(0.98); } /* 模态框通用样式 */ .card-modal-overlay, .spark-modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.85); z-index: 9999; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.3s ease; } .spark-modal-overlay { z-index: 10010; background-color: rgba(0, 0, 0, 0.9); } .card-modal-overlay.active, .spark-modal-overlay.active { display: flex; opacity: 1; } .card-modal-overlay.fade-in, .spark-modal-overlay.fade-in { opacity: 1; } .card-modal-overlay.fade-out, .spark-modal-overlay.fade-out { opacity: 0; } /* 关闭按钮 */ .card-modal-close, .spark-modal-close { position: fixed; top: 20px; right: 30px; font-size: 48px; font-weight: bold; color: #ffffff; cursor: pointer; z-index: 10001; line-height: 1; transition: color 0.2s ease, transform 0.2s ease; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); } .spark-modal-close { z-index: 10012; } .card-modal-close:hover, .spark-modal-close:hover { color: #ff6b6b; transform: scale(1.1); } /* 模态框中的卡牌容器 */ .card-modal-content { position: relative; z-index: 10000; display: flex; flex-direction: column; align-items: center; animation: cardZoomIn 0.3s ease; } .spark-modal-content { position: relative; z-index: 10011; max-width: 90vw; max-height: 85vh; overflow-y: auto; padding: 20px; animation: cardZoomIn 0.3s ease; } @keyframes cardZoomIn { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } } /* 放大后的卡牌样式 */ .card-modal-content > .card-deck-trans, .card-modal-content > .card-wrapper > .card-deck-trans { width: 270px !important; height: 384px !important; transform: none !important; filter: none !important; cursor: default; } .card-modal-content > .card-deck-trans:hover, .card-modal-content > .card-wrapper > .card-deck-trans:hover { transform: none !important; filter: none !important; } .card-modal-content > .card-deck-trans > .card-deck, .card-modal-content > .card-wrapper > .card-deck-trans > .card-deck { transform: scale(0.6) !important; } /* 闪光按钮 */ .spark-button { margin-top: 20px; padding: 12px 36px; font-size: 18px; font-weight: bold; color: #fff; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: 2px solid #a78bfa; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); } .spark-button:hover { background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); transform: translateY(-2px); } .spark-button:active { transform: translateY(0); box-shadow: 0 2px 10px rgba(102, 126, 234, 0.4); } /* 闪光按钮状态 */ .spark-button.loading { opacity: 0.7; cursor: wait; } .spark-button.cached::after { content: " ✓"; color: #90EE90; } /* 闪光图标动画 */ .spark-button:not(.loading)::before { content: "✦ "; animation: sparkle 1.5s ease-in-out infinite; } @keyframes sparkle { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } } /* 闪光卡牌列表样式 */ .spark-modal-title { text-align: center; color: #fff; font-size: 24px; font-weight: bold; margin-bottom: 20px; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); } .spark-modal-title::before { content: "✦ "; color: #a78bfa; } .spark-modal-title::after { content: " ✦"; color: #a78bfa; } .spark-card-list { display: flex; flex-wrap: wrap; justify-content: center; gap: 15px; } .spark-card-list .card-wrapper { display: inline-block; } .spark-card-list .card-deck-trans { cursor: pointer; } .spark-card-list .card-deck-trans:hover { transform: translateY(-8px); filter: brightness(1.1); } /* 加载提示 */ .spark-loading { color: #fff; font-size: 18px; text-align: center; padding: 40px; } .spark-loading::after { content: ""; animation: loadingDots 1.5s infinite; } @keyframes loadingDots { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } } /* 加载进度 */ .spark-loading-progress { color: #aaa; font-size: 14px; margin-top: 10px; } /* 空列表提示 */ .spark-empty { color: #999; font-size: 16px; text-align: center; padding: 40px; } /* 缓存状态提示 */ .spark-cache-hint { color: #666; font-size: 12px; text-align: center; margin-top: 15px; } /* 放大的闪光卡牌 */ .spark-enlarged-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; background: rgba(0, 0, 0, 0.95); z-index: 10015; animation: cardZoomIn 0.3s ease; } .spark-enlarged-card { position: relative; display: flex; flex-direction: column; align-items: center; } .spark-enlarged-card .card-deck-trans { width: 270px !important; height: 384px !important; transform: none !important; filter: none !important; cursor: default; } .spark-enlarged-card .card-deck-trans:hover { transform: none !important; filter: none !important; } .spark-enlarged-card .card-deck { transform: scale(0.6) !important; } .spark-enlarged-close { position: fixed; top: 20px; right: 30px; font-size: 48px; font-weight: bold; color: #ffffff; cursor: pointer; z-index: 10016; line-height: 1; transition: color 0.2s ease, transform 0.2s ease; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); } .spark-enlarged-close:hover { color: #ff6b6b; transform: scale(1.1); } /* 防止模态框打开时页面滚动 */ body.card-modal-open { overflow: hidden; } /* 滚动条样式 */ .spark-modal-content::-webkit-scrollbar { width: 8px; } .spark-modal-content::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.1); border-radius: 4px; } .spark-modal-content::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 4px; } .spark-modal-content::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.5); } </style> <script> // 使用 mw.loader.using 确保 mediawiki.api 模块已加载 mw.loader.using(['mediawiki.api']).then(function() { 'use strict'; // ==================== API 客户端 ==================== var apiClient = null; function getApi() { if (!apiClient) { apiClient = new mw.Api(); } return apiClient; } // ==================== 缓存管理器(优化版) ==================== var SparkCache = (function() { var CACHE_KEY = 'spark_card_cache_v2'; var CACHE_EXPIRE = 24 * 60 * 60 * 1000; // 24小时 // 内存缓存层 var memoryCache = null; var isDirty = false; 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 加载到内存 function loadFromStorage() { if (memoryCache !== null) return memoryCache; if (!storageAvailable) { memoryCache = {}; return memoryCache; } try { var data = localStorage.getItem(CACHE_KEY); memoryCache = data ? JSON.parse(data) : {}; } catch (e) { memoryCache = {}; } return memoryCache; } // 延迟批量写入 localStorage function scheduleSave() { if (!storageAvailable || saveTimer) 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(); 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 (hasChanges) { isDirty = true; scheduleSave(); } } return { get: function(cardId) { var cache = loadFromStorage(); 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); }, getMultiple: function(cardIds) { var cache = loadFromStorage(); var now = Date.now(); var result = {}; var hasExpired = false; cardIds.forEach(function(cardId) { var item = cache[cardId]; if (item) { if (now - item.timestamp <= CACHE_EXPIRE) { result[cardId] = item.html; } else { delete cache[cardId]; hasExpired = true; } } }); if (hasExpired) { isDirty = true; scheduleSave(); } return result; }, setMultiple: function(dataMap) { var cache = loadFromStorage(); var now = Date.now(); for (var cardId in dataMap) { if (dataMap.hasOwnProperty(cardId)) { cache[cardId] = { html: dataMap[cardId], timestamp: now }; } } isDirty = true; scheduleSave(); }, 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() { var pendingRequests = {}; var requestQueue = []; var isProcessing = false; var batchSize = 5; var batchDelay = 50; function processBatch() { if (isProcessing || requestQueue.length === 0) return; isProcessing = true; var batch = requestQueue.splice(0, batchSize); var promises = batch.map(function(cardId) { return fetchSparkList(cardId); }); Promise.all(promises).then(function() { isProcessing = false; if (requestQueue.length > 0) { setTimeout(processBatch, batchDelay); } }); } function fetchSparkList(cardId) { return new Promise(function(resolve) { getApi().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(); }); }); } return { request: function(cardId) { 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) { 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) { modalOverlay = document.createElement('div'); modalOverlay.className = 'card-modal-overlay'; modalOverlay.innerHTML = '<span class="card-modal-close">×</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">×</span><div class="spark-modal-content"></div>'; document.body.appendChild(sparkModalOverlay); sparkModalContent = sparkModalOverlay.querySelector('.spark-modal-content'); } } function closeModal() { if (!modalOverlay) return; 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() { 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); modalContent.innerHTML = ''; modalContent.appendChild(cardClone); if (sparkEnable && sparkEnable.trim() !== '') { 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'); 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'); 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>' : ''; 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'); }); } } 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 = '×'; 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) { 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); 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); } } 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 { 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; } function init() { document.addEventListener('click', function(e) { var target = e.target; if (target.classList.contains('card-modal-close')) { e.stopPropagation(); DOMManager.closeModal(); 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); } } }, 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') || card.closest('.spark-modal-overlay') || card.closest('.spark-enlarged-container')) return; var info = getCardInfo(card); if (info.sparkEnable && info.sparkEnable.trim() !== '' && info.cardId) { if (hoverTimer) { clearTimeout(hoverTimer); } hoverTimer = setTimeout(function() { BatchRequestManager.prioritize(info.cardId); if (!SparkCache.has(info.cardId)) { BatchRequestManager.request(info.cardId); } }, 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(); } }); } return { init: init }; })(); // ==================== 初始化 ==================== function init() { SparkCache.cleanup(); DOMManager.createModals(); EventManager.init(); DOMManager.initExistingCards(); var observer = new MutationObserver(function(mutations) { var newNodes = []; mutations.forEach(function(mutation) { if (mutation.addedNodes.length > 0) { for (var i = 0; i < mutation.addedNodes.length; i++) { newNodes.push(mutation.addedNodes[i]); } } }); if (newNodes.length > 0) { if (window.requestIdleCallback) { requestIdleCallback(function() { DOMManager.processNewCards(newNodes); }, { timeout: 500 }); } else { setTimeout(function() { DOMManager.processNewCards(newNodes); }, 100); } } }); observer.observe(document.body, { childList: true, subtree: true }); } // 启动 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } }); </script> </includeonly>
返回
微件:Card
。
导航菜单
个人工具
登录
命名空间
微件
讨论
大陆简体
查看
阅读
查看源代码
查看历史
更多
刷新
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息