|
|
| 第1行: |
第1行: |
| <includeonly> | | <noinclude> |
| | 此Widget为卡牌显示提供交互功能,包括悬停动画、点击放大和闪光卡牌列表显示。 |
| | [[分类:Widget]] |
| | </noinclude><includeonly> |
| <style> | | <style> |
| .card-wrapper {
| | /* 卡牌悬停和点击动画 */ |
| display: inline-block;
| |
| }
| |
| | |
| /* 卡牌悬停动画 */ | |
| .card-deck-trans { | | .card-deck-trans { |
| cursor: pointer; | | cursor: pointer; |
| transition: transform 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease; | | transition: transform 0.2s ease, box-shadow 0.2s ease; |
| } | | } |
|
| |
|
| .card-deck-trans:hover { | | .card-deck-trans:hover { |
| transform: translateY(-8px); | | transform: translateY(-5px); |
| filter: brightness(1.1); | | filter: brightness(1.1); |
| } | | } |
|
| |
|
| .card-deck-trans:active { | | .card-deck-trans:active { |
| transform: translateY(-4px) scale(0.98); | | transform: translateY(-2px) scale(0.98); |
| } | | } |
|
| |
|
| /* 模态框通用样式 */ | | /* 模态框基础样式 */ |
| .card-modal-overlay, | | .card-modal-overlay { |
| .spark-modal-overlay {
| |
| display: none; | | display: none; |
| position: fixed; | | position: fixed; |
| 第33行: |
第31行: |
| justify-content: center; | | justify-content: center; |
| align-items: center; | | align-items: center; |
| opacity: 0; | | flex-direction: column; |
| transition: opacity 0.3s ease;
| |
| } | | } |
|
| |
|
| .spark-modal-overlay {
| | .card-modal-overlay.active { |
| z-index: 10010;
| |
| background-color: rgba(0, 0, 0, 0.9);
| |
| }
| |
| | |
| .card-modal-overlay.active, | |
| .spark-modal-overlay.active {
| |
| display: flex; | | 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, | | .card-modal-close { |
| .spark-modal-close {
| | position: absolute; |
| position: fixed; | |
| top: 20px; | | top: 20px; |
| right: 30px; | | right: 30px; |
| font-size: 48px; | | font-size: 40px; |
| font-weight: bold;
| | color: #fff; |
| color: #ffffff; | |
| cursor: pointer; | | cursor: pointer; |
| z-index: 10001; | | z-index: 10001; |
| | transition: color 0.2s ease, transform 0.2s ease; |
| | font-family: Arial, sans-serif; |
| line-height: 1; | | 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 {
| | .card-modal-close:hover { |
| z-index: 10012;
| |
| }
| |
| | |
| .card-modal-close:hover, | |
| .spark-modal-close:hover {
| |
| color: #ff6b6b; | | color: #ff6b6b; |
| transform: scale(1.1); | | transform: scale(1.2); |
| } | | } |
|
| |
|
| /* 模态框中的卡牌容器 */ | | /* 模态框内容容器 */ |
| .card-modal-content { | | .card-modal-content { |
| position: relative;
| |
| z-index: 10000;
| |
| display: flex; | | display: flex; |
| flex-direction: column; | | flex-direction: column; |
| align-items: center; | | align-items: center; |
| animation: cardZoomIn 0.3s ease; | | justify-content: center; |
| | gap: 20px; |
| } | | } |
|
| |
|
| .spark-modal-content { | | /* 放大后的卡牌容器 */ |
| | .card-modal-card { |
| position: relative; | | position: relative; |
| z-index: 10011;
| | width: 270px; |
| max-width: 90vw;
| | height: 384px; |
| max-height: 85vh;
| | display: inline-block; |
| 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-card .card-deck { |
| .card-modal-content > .card-wrapper > .card-deck-trans > .card-deck {
| | position: absolute; |
| transform: scale(0.6) !important; | | width: 450px; |
| | height: 640px; |
| | transform: scale(0.6); |
| | transform-origin: top left; |
| } | | } |
|
| |
|
| /* 闪光按钮 */ | | /* 闪光按钮 */ |
| .spark-button { | | .card-spark-button { |
| margin-top: 20px;
| | padding: 12px 30px; |
| padding: 12px 36px; | |
| font-size: 18px; | | font-size: 18px; |
| font-weight: bold; | | font-weight: bold; |
| color: #fff; | | color: #fff; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | | background: linear-gradient(135deg, #f5af19 0%, #f12711 100%); |
| border: 2px solid #a78bfa; | | border: none; |
| border-radius: 8px; | | border-radius: 25px; |
| cursor: pointer; | | cursor: pointer; |
| transition: all 0.3s ease; | | transition: all 0.3s ease; |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); | | box-shadow: 0 4px 15px rgba(245, 175, 25, 0.4); |
| text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); | | margin-top: 10px; |
| } | | } |
|
| |
|
| .spark-button:hover { | | .card-spark-button:hover { |
| background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); | | transform: scale(1.05); |
| box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); | | box-shadow: 0 6px 20px rgba(245, 175, 25, 0.6); |
| transform: translateY(-2px);
| |
| } | | } |
|
| |
|
| .spark-button:active { | | .card-spark-button:active { |
| transform: translateY(0); | | transform: scale(0.98); |
| box-shadow: 0 2px 10px rgba(102, 126, 234, 0.4);
| |
| } | | } |
|
| |
|
| /* 闪光按钮状态 */ | | /* 闪光卡牌模态框 */ |
| .spark-button.loading { | | .spark-modal-overlay { |
| opacity: 0.7; | | display: none; |
| cursor: wait; | | position: fixed; |
| | top: 0; |
| | left: 0; |
| | width: 100%; |
| | height: 100%; |
| | background-color: rgba(0, 0, 0, 0.9); |
| | z-index: 10002; |
| | justify-content: center; |
| | align-items: flex-start; |
| | overflow-y: auto; |
| | padding: 60px 20px 20px; |
| } | | } |
|
| |
|
| .spark-button.cached::after { | | .spark-modal-overlay.active { |
| content: " ✓"; | | display: flex; |
| color: #90EE90; | | } |
| | |
| | .spark-modal-close { |
| | position: fixed; |
| | top: 20px; |
| | right: 30px; |
| | font-size: 40px; |
| | color: #fff; |
| | cursor: pointer; |
| | z-index: 10003; |
| | transition: color 0.2s ease, transform 0.2s ease; |
| | font-family: Arial, sans-serif; |
| | line-height: 1; |
| } | | } |
|
| |
|
| /* 闪光图标动画 */
| | .spark-modal-close:hover { |
| .spark-button:not(.loading)::before { | | color: #ff6b6b; |
| content: "✦ "; | | transform: scale(1.2); |
| animation: sparkle 1.5s ease-in-out infinite; | |
| } | | } |
|
| |
|
| @keyframes sparkle {
| | /* 闪光卡牌列表容器 */ |
| 0%, 100% { opacity: 0.5; } | | .spark-modal-content { |
| 50% { opacity: 1; } | | display: flex; |
| | flex-wrap: wrap; |
| | justify-content: center; |
| | gap: 20px; |
| | max-width: 1200px; |
| | padding: 20px; |
| } | | } |
|
| |
|
| /* 闪光卡牌列表样式 */
| |
| .spark-modal-title { | | .spark-modal-title { |
| | width: 100%; |
| text-align: center; | | text-align: center; |
| color: #fff; | | color: #f5af19; |
| font-size: 24px; | | font-size: 28px; |
| font-weight: bold; | | font-weight: bold; |
| margin-bottom: 20px; | | margin-bottom: 20px; |
| text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); | | text-shadow: 0 0 10px rgba(245, 175, 25, 0.5); |
| }
| |
| | |
| .spark-modal-title::before {
| |
| content: "✦ ";
| |
| color: #a78bfa;
| |
| }
| |
| | |
| .spark-modal-title::after {
| |
| content: " ✦";
| |
| color: #a78bfa;
| |
| } | | } |
|
| |
|
| | /* 闪光卡牌列表内的卡牌样式 */ |
| .spark-card-list { | | .spark-card-list { |
| display: flex; | | display: flex; |
| 第210行: |
第170行: |
| justify-content: center; | | justify-content: center; |
| gap: 15px; | | gap: 15px; |
| }
| |
|
| |
| .spark-card-list .card-wrapper {
| |
| display: inline-block;
| |
| } | | } |
|
| |
|
| .spark-card-list .card-deck-trans { | | .spark-card-list .card-deck-trans { |
| cursor: pointer; | | cursor: default; |
| } | | } |
|
| |
|
| .spark-card-list .card-deck-trans:hover { | | .spark-card-list .card-deck-trans:hover { |
| transform: translateY(-8px); | | transform: none; |
| filter: brightness(1.1); | | filter: none; |
| } | | } |
|
| |
|
| /* 加载提示 */ | | /* 加载动画 */ |
| .spark-loading { | | .spark-loading { |
| color: #fff; | | color: #fff; |
| font-size: 18px; | | font-size: 20px; |
| text-align: center; | | text-align: center; |
| padding: 40px; | | padding: 50px; |
| } | | } |
|
| |
|
| .spark-loading::after { | | .spark-loading::after { |
| content: ""; | | content: ''; |
| animation: loadingDots 1.5s infinite; | | animation: dots 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 {
| | @keyframes dots { |
| position: relative;
| | 0%, 20% { content: '.'; } |
| display: flex;
| | 40% { content: '..'; } |
| flex-direction: column;
| | 60%, 100% { content: '...'; } |
| 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);
| |
| }
| |
| | |
| /* 预加载指示器 */
| |
| .preload-indicator { | |
| position: fixed;
| |
| bottom: 10px;
| |
| right: 10px;
| |
| background: rgba(0, 0, 0, 0.7);
| |
| color: #fff;
| |
| padding: 5px 10px;
| |
| border-radius: 4px;
| |
| font-size: 12px;
| |
| z-index: 9998;
| |
| opacity: 0;
| |
| transition: opacity 0.3s ease;
| |
| pointer-events: none;
| |
| } | |
| | |
| .preload-indicator.active {
| |
| opacity: 1;
| |
| } | | } |
| </style> | | </style> |
| 第375行: |
第205行: |
| 'use strict'; | | 'use strict'; |
| | | |
| // ==================== 缓存管理器 ==================== | | // 等待DOM加载完成 |
| var SparkCache = { | | if (document.readyState === 'loading') { |
| CACHE_KEY: 'spark_card_cache', | | document.addEventListener('DOMContentLoaded', initCardWidget); |
| CACHE_VERSION: 'v1', | | } else { |
| CACHE_EXPIRE: 24 * 60 * 60 * 1000, // 24小时过期 | | initCardWidget(); |
| | } |
| | |
| | function initCardWidget() { |
| | // 创建主模态框 |
| | var mainModal = document.createElement('div'); |
| | mainModal.className = 'card-modal-overlay'; |
| | mainModal.id = 'cardMainModal'; |
| | mainModal.innerHTML = '<span class="card-modal-close">×</span><div class="card-modal-content"></div>'; |
| | document.body.appendChild(mainModal); |
| | | |
| // 获取完整的缓存键 | | // 创建闪光模态框 |
| getFullKey: function() { | | var sparkModal = document.createElement('div'); |
| return this.CACHE_KEY + '_' + this.CACHE_VERSION;
| | sparkModal.className = 'spark-modal-overlay'; |
| }, | | sparkModal.id = 'cardSparkModal'; |
| | sparkModal.innerHTML = '<span class="spark-modal-close">×</span><div class="spark-modal-content"><div class="spark-modal-title">✨ 闪光卡牌 ✨</div><div class="spark-card-container"></div></div>'; |
| | document.body.appendChild(sparkModal); |
| | | |
| // 获取所有缓存 | | // 获取所有卡牌元素 |
| getAll: function() { | | var cards = document.querySelectorAll('.card-deck-trans'); |
| try {
| |
| var data = localStorage.getItem(this.getFullKey());
| |
| if (!data) return {};
| |
| return JSON.parse(data);
| |
| } catch (e) {
| |
| console.warn('SparkCache: 读取缓存失败', e);
| |
| return {};
| |
| }
| |
| },
| |
| | | |
| // 保存所有缓存 | | cards.forEach(function(card) { |
| saveAll: function(data) {
| | // 跳过闪光列表中的卡牌 |
| try { | | if (card.closest('.spark-card-list') || card.closest('.card-modal-content')) { |
| localStorage.setItem(this.getFullKey(), JSON.stringify(data));
| | return; |
| } catch (e) {
| |
| console.warn('SparkCache: 保存缓存失败', e);
| |
| // 存储空间不足时,清理过期缓存
| |
| this.cleanup(); | |
| } | | } |
| },
| |
|
| |
| // 获取单个卡牌的闪光列表缓存
| |
| get: function(cardId) {
| |
| var cache = this.getAll();
| |
| var item = cache[cardId];
| |
| | | |
| if (!item) return null; | | card.addEventListener('click', function(e) { |
|
| | e.preventDefault(); |
| // 检查是否过期
| | e.stopPropagation(); |
| if (Date.now() - item.timestamp > this.CACHE_EXPIRE) {
| | openCardModal(this); |
| delete cache[cardId];
| | }); |
| this.saveAll(cache); | | }); |
| return null; | |
| } | |
|
| |
| return item.html;
| |
| }, | |
| | | |
| // 设置单个卡牌的闪光列表缓存 | | // 关闭主模态框 |
| set: function(cardId, html) { | | mainModal.querySelector('.card-modal-close').addEventListener('click', function() { |
| var cache = this.getAll();
| | closeMainModal(); |
| cache[cardId] = {
| | }); |
| html: html,
| |
| timestamp: Date.now()
| |
| }; | |
| this.saveAll(cache);
| |
| }, | |
| | | |
| // 检查是否有缓存 | | mainModal.addEventListener('click', function(e) { |
| has: function(cardId) {
| | if (e.target === mainModal) { |
| return this.get(cardId) !== null;
| | closeMainModal(); |
| },
| |
|
| |
| // 清理过期缓存
| |
| cleanup: function() {
| |
| var cache = this.getAll(); | |
| var now = Date.now();
| |
| var cleaned = false;
| |
|
| |
| for (var cardId in cache) {
| |
| if (cache.hasOwnProperty(cardId)) { | |
| if (now - cache[cardId].timestamp > this.CACHE_EXPIRE) {
| |
| delete cache[cardId];
| |
| cleaned = true;
| |
| }
| |
| }
| |
| } | | } |
|
| | }); |
| if (cleaned) {
| |
| this.saveAll(cache);
| |
| }
| |
| },
| |
| | | |
| // 清空所有缓存 | | // 关闭闪光模态框 |
| clear: function() { | | sparkModal.querySelector('.spark-modal-close').addEventListener('click', function() { |
| try {
| | closeSparkModal(); |
| localStorage.removeItem(this.getFullKey());
| | }); |
| } catch (e) {
| |
| console.warn('SparkCache: 清空缓存失败', e);
| |
| } | |
| } | |
| };
| |
|
| |
| // ==================== 预加载队列管理器 ====================
| |
| var PreloadQueue = {
| |
| queue: [],
| |
| isProcessing: false,
| |
| batchSize: 3, // 并发请求数
| |
| activeRequests: 0,
| |
| maxRetries: 2,
| |
| retryDelay: 1000,
| |
| | | |
| // 预加载指示器 | | sparkModal.addEventListener('click', function(e) { |
| indicator: null,
| | if (e.target === sparkModal) { |
|
| | closeSparkModal(); |
| // 初始化指示器
| |
| initIndicator: function() {
| |
| if (!this.indicator) { | |
| this.indicator = document.createElement('div');
| |
| this.indicator.className = 'preload-indicator';
| |
| this.indicator.textContent = '预加载中...';
| |
| document.body.appendChild(this.indicator); | |
| } | | } |
| }, | | }); |
| | | |
| // 显示/隐藏指示器 | | // ESC键关闭 |
| showIndicator: function(show) { | | document.addEventListener('keydown', function(e) { |
| this.initIndicator();
| | if (e.key === 'Escape') { |
| if (show) { | | if (sparkModal.classList.contains('active')) { |
| this.indicator.classList.add('active'); | | closeSparkModal(); |
| } else {
| | } else if (mainModal.classList.contains('active')) { |
| this.indicator.classList.remove('active');
| | closeMainModal(); |
| | } |
| } | | } |
| }, | | }); |
| | } |
| | |
| | function openCardModal(cardElement) { |
| | var modal = document.getElementById('cardMainModal'); |
| | var content = modal.querySelector('.card-modal-content'); |
| | | |
| // 更新指示器文本 | | // 克隆卡牌 |
| updateIndicator: function(current, total) { | | var cardClone = cardElement.cloneNode(true); |
| this.initIndicator();
| |
| this.indicator.textContent = '预加载: ' + current + '/' + total;
| |
| },
| |
| | | |
| // 添加到预加载队列 | | // 创建放大容器 |
| add: function(cardId, priority) { | | var enlargedContainer = document.createElement('div'); |
| // 如果已缓存,跳过
| | enlargedContainer.className = 'card-modal-card'; |
| if (SparkCache.has(cardId)) return;
| |
|
| |
| // 如果已在队列中,跳过
| |
| for (var i = 0; i < this.queue.length; i++) {
| |
| if (this.queue[i].cardId === cardId) return;
| |
| }
| |
|
| |
| this.queue.push({
| |
| cardId: cardId,
| |
| priority: priority || 0,
| |
| retries: 0
| |
| });
| |
|
| |
| // 按优先级排序(高优先级在前)
| |
| this.queue.sort(function(a, b) {
| |
| return b.priority - a.priority;
| |
| });
| |
|
| |
| this.process();
| |
| },
| |
| | | |
| // 批量添加到预加载队列 | | // 获取内部的card-deck |
| addBatch: function(cardIds, priority) { | | var cardDeck = cardClone.querySelector('.card-deck'); |
| var self = this;
| | if (cardDeck) { |
| cardIds.forEach(function(cardId) {
| | enlargedContainer.appendChild(cardDeck); |
| self.add(cardId, priority);
| | } else { |
| }); | | enlargedContainer.appendChild(cardClone); |
| }, | | } |
| | | |
| // 提升优先级(用于用户即将查看的卡牌) | | // 清空并添加新内容 |
| prioritize: function(cardId) { | | content.innerHTML = ''; |
| for (var i = 0; i < this.queue.length; i++) {
| | content.appendChild(enlargedContainer); |
| if (this.queue[i].cardId === cardId) {
| |
| this.queue[i].priority = 100;
| |
| break;
| |
| }
| |
| }
| |
| this.queue.sort(function(a, b) {
| |
| return b.priority - a.priority;
| |
| });
| |
| },
| |
| | | |
| // 处理队列 | | // 检查是否需要显示闪光按钮 |
| process: function() { | | var sparkEnable = cardElement.getAttribute('data-spark-enable'); |
| var self = this;
| | var sortType = cardElement.getAttribute('data-sort'); |
|
| |
| if (this.queue.length === 0) {
| |
| this.showIndicator(false);
| |
| return;
| |
| }
| |
|
| |
| // 控制并发数
| |
| while (this.activeRequests < this.batchSize && this.queue.length > 0) {
| |
| var item = this.queue.shift();
| |
| this.loadSparkList(item);
| |
| }
| |
|
| |
| if (this.activeRequests > 0) {
| |
| this.showIndicator(true);
| |
| this.updateIndicator(this.activeRequests, this.queue.length + this.activeRequests);
| |
| }
| |
| },
| |
| | | |
| // 加载闪光卡牌列表 | | // 如果没有data属性,尝试从其他地方获取 |
| loadSparkList: function(item) { | | if (!sparkEnable || !sortType) { |
| var self = this; | | // 尝试从页面数据或其他来源获取 |
| this.activeRequests++;
| | var cardData = getCardDataFromElement(cardElement); |
|
| | sparkEnable = cardData.sparkEnable; |
| var api = new mw.Api(); | | sortType = cardData.sort; |
| api.get({ | | } |
| action: 'parse',
| |
| text: '{{Card/spark/list|' + item.cardId + '}}',
| |
| prop: 'text',
| |
| disablelimitreport: true,
| |
| format: 'json'
| |
| }).done(function(data) {
| |
| self.activeRequests--;
| |
|
| |
| if (data.parse && data.parse.text) {
| |
| var html = data.parse.text['*'];
| |
| SparkCache.set(item.cardId, html);
| |
|
| |
| // 更新按钮状态
| |
| self.updateButtonState(item.cardId, true);
| |
| }
| |
|
| |
| self.process();
| |
| }).fail(function() { | |
| self.activeRequests--;
| |
|
| |
| // 重试逻辑
| |
| if (item.retries < self.maxRetries) {
| |
| item.retries++;
| |
| setTimeout(function() {
| |
| self.queue.unshift(item);
| |
| self.process();
| |
| }, self.retryDelay * item.retries);
| |
| }
| |
|
| |
| self.process();
| |
| });
| |
| }, | |
| | | |
| // 更新按钮缓存状态 | | // 判断是否显示闪光按钮 |
| updateButtonState: function(cardId, cached) { | | if (sparkEnable === '1' && sortType !== '中立卡牌' && sortType !== '怪物卡牌') { |
| var buttons = document.querySelectorAll('.spark-button[data-card-id="' + cardId + '"]'); | | var sparkButton = document.createElement('button'); |
| buttons.forEach(function(btn) { | | sparkButton.className = 'card-spark-button'; |
| if (cached) { | | sparkButton.innerHTML = '✨ 闪光 ✨'; |
| btn.classList.add('cached');
| | sparkButton.addEventListener('click', function() { |
| btn.classList.remove('loading');
| | openSparkModal(cardElement); |
| }
| |
| }); | | }); |
| | content.appendChild(sparkButton); |
| } | | } |
| }; | | |
| | modal.classList.add('active'); |
| | document.body.style.overflow = 'hidden'; |
| | } |
| | | |
| // ==================== 主逻辑 ==================== | | function closeMainModal() { |
|
| | var modal = document.getElementById('cardMainModal'); |
| // 确保DOM加载完成
| | modal.classList.remove('active'); |
| if (document.readyState === 'loading') {
| | document.body.style.overflow = ''; |
| document.addEventListener('DOMContentLoaded', initCardWidget); | |
| } else {
| |
| initCardWidget(); | |
| } | | } |
| | | |
| function initCardWidget() { | | function openSparkModal(cardElement) { |
| // 清理过期缓存 | | var modal = document.getElementById('cardSparkModal'); |
| SparkCache.cleanup(); | | var container = modal.querySelector('.spark-card-container'); |
| | | |
| // 创建主模态框元素(如果不存在) | | // 显示加载状态 |
| var modalOverlay = document.querySelector('.card-modal-overlay'); | | container.innerHTML = '<div class="spark-loading">正在加载闪光卡牌</div>'; |
| | | |
| if (!modalOverlay) { | | modal.classList.add('active'); |
| 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);
| |
| }
| |
| | | |
| // 创建闪光卡牌模态框(如果不存在) | | // 获取卡牌ID |
| var sparkModalOverlay = document.querySelector('.spark-modal-overlay'); | | var cardId = cardElement.getAttribute('data-card-id'); |
|
| | if (!cardId) { |
| if (!sparkModalOverlay) { | | cardId = getCardIdFromElement(cardElement); |
| 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);
| |
| } | | } |
| | | |
| var modalContent = modalOverlay.querySelector('.card-modal-content'); | | if (cardId) { |
| var closeButton = modalOverlay.querySelector('.card-modal-close');
| | // 通过API加载闪光卡牌列表 |
| var sparkModalContent = sparkModalOverlay.querySelector('.spark-modal-content');
| | loadSparkCards(cardId, container); |
| var sparkCloseButton = sparkModalOverlay.querySelector('.spark-modal-close');
| | } else { |
|
| | container.innerHTML = '<div style="color: #fff; text-align: center; padding: 50px;">无法获取卡牌信息</div>'; |
| // 当前放大的闪光卡牌容器
| |
| var currentEnlargedContainer = null;
| |
|
| |
| // 关闭主模态框函数
| |
| function closeModal() {
| |
| modalOverlay.classList.add('fade-out'); | |
| setTimeout(function() {
| |
| modalOverlay.classList.remove('active', 'fade-in', 'fade-out');
| |
| modalContent.innerHTML = '';
| |
| document.body.classList.remove('card-modal-open');
| |
| }, 300);
| |
| } | | } |
| | } |
| | |
| | function closeSparkModal() { |
| | var modal = document.getElementById('cardSparkModal'); |
| | modal.classList.remove('active'); |
| | } |
| | |
| | function getCardDataFromElement(element) { |
| | // 尝试从元素的class或子元素中推断数据 |
| | var data = { |
| | sparkEnable: '0', |
| | sort: '' |
| | }; |
| | | |
| // 关闭闪光模态框函数 | | // 检查是否有闪光标记 |
| function closeSparkModal() { | | if (element.querySelector('.card-spark-flag')) { |
| // 先关闭放大的卡牌
| | // 有闪光图标意味着spark_flag为真,但我们需要spark_enable |
| if (currentEnlargedContainer) {
| |
| currentEnlargedContainer.remove();
| |
| currentEnlargedContainer = null;
| |
| }
| |
|
| |
| sparkModalOverlay.classList.add('fade-out');
| |
| setTimeout(function() {
| |
| sparkModalOverlay.classList.remove('active', 'fade-in', 'fade-out');
| |
| sparkModalContent.innerHTML = '';
| |
| }, 300); | |
| } | | } |
| | | |
| // 关闭放大的闪光卡牌 | | // 检查是否有禁忌装饰 |
| function closeEnlargedCard() { | | if (element.querySelector('.card-deco-taboo')) { |
| if (currentEnlargedContainer) { | | data.sort = '禁忌卡牌'; |
| currentEnlargedContainer.remove();
| |
| currentEnlargedContainer = null;
| |
| }
| |
| } | | } |
| | | |
| // 打开主模态框函数 | | // 检查职业图标来判断是否为中立卡牌 |
| function openModal(cardElement, cardId, sparkEnable) { | | if (element.querySelector('.card-job1') && element.querySelector('.card-job2')) { |
| var cardClone = cardElement.cloneNode(true);
| | data.sort = '中立卡牌'; |
| // 移除克隆卡牌的点击事件标记
| |
| delete cardClone.dataset.cardBound;
| |
|
| |
| 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 {
| |
| // 提升优先级预加载
| |
| PreloadQueue.prioritize(cardId);
| |
| }
| |
|
| |
| sparkButton.addEventListener('click', function() {
| |
| openSparkModal(cardId);
| |
| });
| |
| modalContent.appendChild(sparkButton);
| |
| }
| |
|
| |
| modalOverlay.classList.add('active');
| |
| document.body.classList.add('card-modal-open');
| |
|
| |
| // 强制重绘后添加fade-in
| |
| requestAnimationFrame(function() {
| |
| modalOverlay.classList.add('fade-in');
| |
| });
| |
| } | | } |
| | | |
| // 打开闪光卡牌模态框 | | return data; |
| function openSparkModal(cardId) {
| | } |
| // 首先检查缓存
| | |
| var cachedHtml = SparkCache.get(cardId);
| | function getCardIdFromElement(element) { |
|
| | // 尝试从图片名称获取卡牌ID |
| if (cachedHtml) {
| | var artImg = element.querySelector('.card-art img'); |
| // 使用缓存,立即显示
| | if (artImg) { |
| showSparkContent(cachedHtml, true);
| | var src = artImg.getAttribute('src') || ''; |
| } else {
| | var match = src.match(/\/([^\/]+)\.png/); |
| // 无缓存,显示加载中并请求
| | if (match) { |
| sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-loading">加载中</div>';
| | return match[1]; |
|
| |
| sparkModalOverlay.classList.add('active');
| |
| requestAnimationFrame(function() {
| |
| sparkModalOverlay.classList.add('fade-in');
| |
| });
| |
|
| |
| // 发起请求
| |
| loadSparkListDirect(cardId);
| |
| }
| |
|
| |
| 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;
| |
| // 为闪光列表中的卡牌绑定事件
| |
| bindSparkListCardEvents();
| |
| } 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 loadSparkListDirect(cardId) { | |
| var api = new mw.Api();
| |
| api.get({
| |
| action: 'parse',
| |
| text: '{{Card/spark/list|' + cardId + '}}',
| |
| prop: 'text',
| |
| disablelimitreport: true,
| |
| format: 'json'
| |
| }).done(function(data) {
| |
| if (data.parse && data.parse.text) {
| |
| var html = data.parse.text['*'];
| |
|
| |
| // 存入缓存
| |
| SparkCache.set(cardId, html);
| |
|
| |
| // 更新按钮状态
| |
| PreloadQueue.updateButtonState(cardId, true);
| |
|
| |
| // 显示内容
| |
| showSparkContent(html, false);
| |
| }
| |
| }).fail(function() {
| |
| sparkModalContent.innerHTML = '<div class="spark-modal-title">闪光卡牌列表</div><div class="spark-empty">加载失败,请重试</div>';
| |
| }); | |
| } | | } |
| } | | } |
| | return null; |
| | } |
| | |
| | function loadSparkCards(cardId, container) { |
| | // 使用MediaWiki API进行解析 |
| | var api = mw.config.get('wgScriptPath') + '/api.php'; |
| | var wikitext = '{{Card/display/spark|' + cardId + '}}'; |
| | | |
| // 为闪光列表中的卡牌绑定点击事件 | | fetch(api + '?action=parse&text=' + encodeURIComponent(wikitext) + '&contentmodel=wikitext&format=json&disablelimitreport=1', { |
| function bindSparkListCardEvents() {
| | method: 'GET', |
| var sparkCards = sparkModalContent.querySelectorAll('.card-deck-trans');
| | headers: { |
| sparkCards.forEach(function(card) {
| | 'Accept': 'application/json' |
| if (card.dataset.sparkBound) return;
| |
| card.dataset.sparkBound = 'true';
| |
|
| |
| card.addEventListener('click', function(e) {
| |
| e.preventDefault();
| |
| e.stopPropagation();
| |
|
| |
| // 关闭之前放大的卡牌
| |
| closeEnlargedCard();
| |
|
| |
| // 创建放大容器
| |
| var enlargedContainer = document.createElement('div');
| |
| enlargedContainer.className = 'spark-enlarged-container';
| |
|
| |
| // 添加关闭按钮
| |
| var closeBtn = document.createElement('span');
| |
| closeBtn.className = 'spark-enlarged-close';
| |
| closeBtn.innerHTML = '×';
| |
| closeBtn.addEventListener('click', function(e) {
| |
| e.stopPropagation();
| |
| closeEnlargedCard();
| |
| });
| |
| enlargedContainer.appendChild(closeBtn);
| |
|
| |
| // 创建卡牌内容区
| |
| var cardContent = document.createElement('div');
| |
| cardContent.className = 'spark-enlarged-card';
| |
|
| |
| var cardClone = this.cloneNode(true);
| |
| delete cardClone.dataset.sparkBound;
| |
| cardContent.appendChild(cardClone);
| |
|
| |
| enlargedContainer.appendChild(cardContent);
| |
|
| |
| // 点击背景关闭
| |
| enlargedContainer.addEventListener('click', function(e) {
| |
| if (e.target === enlargedContainer) {
| |
| closeEnlargedCard();
| |
| }
| |
| });
| |
|
| |
| document.body.appendChild(enlargedContainer);
| |
| currentEnlargedContainer = enlargedContainer;
| |
| });
| |
| }); | |
| }
| |
|
| |
| // 绑定关闭按钮事件
| |
| closeButton.addEventListener('click', function(e) {
| |
| e.stopPropagation(); | |
| closeModal();
| |
| });
| |
|
| |
| sparkCloseButton.addEventListener('click', function(e) {
| |
| e.stopPropagation();
| |
| closeSparkModal();
| |
| });
| |
|
| |
| // 点击遮罩关闭
| |
| modalOverlay.addEventListener('click', function(e) {
| |
| if (e.target === modalOverlay) {
| |
| closeModal();
| |
| } | | } |
| }); | | }) |
|
| | .then(function(response) { |
| sparkModalOverlay.addEventListener('click', function(e) { | | return response.json(); |
| if (e.target === sparkModalOverlay) { | | }) |
| closeSparkModal();
| | .then(function(data) { |
| }
| | if (data.parse && data.parse.text) { |
| }); | | var html = data.parse.text['*']; |
|
| | container.innerHTML = html; |
| // ESC键关闭
| |
| document.addEventListener('keydown', function(e) { | |
| if (e.key === 'Escape') { | |
| // 优先关闭放大的闪光卡牌
| |
| if (currentEnlargedContainer) {
| |
| closeEnlargedCard();
| |
| } else if (sparkModalOverlay.classList.contains('active')) {
| |
| closeSparkModal();
| |
| } else if (modalOverlay.classList.contains('active')) {
| |
| closeModal();
| |
| }
| |
| }
| |
| });
| |
|
| |
| // 收集页面上所有启用闪光的卡牌ID
| |
| function collectSparkCardIds() {
| |
| var cardIds = [];
| |
| var cards = document.querySelectorAll('.card-deck-trans[data-spark-enable]');
| |
|
| |
| cards.forEach(function(card) {
| |
| var sparkEnable = card.dataset.sparkEnable;
| |
| if (sparkEnable && sparkEnable.trim() !== '') {
| |
| var wrapper = card.closest('.card-wrapper');
| |
| var cardId = wrapper ? wrapper.dataset.cardId : '';
| |
| if (cardId && cardIds.indexOf(cardId) === -1) {
| |
| cardIds.push(cardId);
| |
| }
| |
| } | |
| });
| |
|
| |
| return cardIds;
| |
| }
| |
|
| |
| // 为所有卡牌绑定点击事件
| |
| function bindCardEvents() {
| |
| var cards = document.querySelectorAll('.card-deck-trans');
| |
| var sparkCardIds = [];
| |
|
| |
| cards.forEach(function(card) {
| |
| // 避免重复绑定
| |
| if (card.dataset.cardBound) return; | |
| card.dataset.cardBound = 'true';
| |
| | | |
| // 排除模态框内的卡牌 | | // 如果没有卡牌 |
| if (card.closest('.card-modal-overlay') || card.closest('.spark-modal-overlay') || card.closest('.spark-enlarged-container')) return; | | if (!container.querySelector('.card-deck-trans')) { |
|
| | container.innerHTML = '<div style="color: #fff; text-align: center; padding: 50px;">暂无闪光卡牌</div>'; |
| // 收集启用闪光的卡牌ID用于预加载
| |
| 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);
| |
| }
| |
| }
| |
|
| |
| card.addEventListener('click', function(e) {
| |
| e.preventDefault();
| |
|
| |
| // 从card-deck-trans元素获取spark_enable
| |
| var sparkEnable = this.dataset.sparkEnable || '';
| |
|
| |
| // 获取card-id从父级card-wrapper
| |
| var wrapper = this.closest('.card-wrapper');
| |
| var cardId = wrapper ? wrapper.dataset.cardId : '';
| |
|
| |
| openModal(this, cardId, sparkEnable);
| |
| });
| |
|
| |
| // 鼠标悬停时预加载
| |
| card.addEventListener('mouseenter', function() {
| |
| var sparkEnable = this.dataset.sparkEnable || '';
| |
| if (sparkEnable && sparkEnable.trim() !== '') {
| |
| var wrapper = this.closest('.card-wrapper');
| |
| var cardId = wrapper ? wrapper.dataset.cardId : '';
| |
| if (cardId) {
| |
| // 高优先级预加载
| |
| PreloadQueue.add(cardId, 50);
| |
| }
| |
| }
| |
| });
| |
| });
| |
|
| |
| // 延迟低优先级预加载页面上所有闪光卡牌
| |
| if (sparkCardIds.length > 0) {
| |
| setTimeout(function() {
| |
| PreloadQueue.addBatch(sparkCardIds, 1);
| |
| }, 2000);
| |
| }
| |
| }
| |
|
| |
| // 初始绑定
| |
| bindCardEvents();
| |
|
| |
| // 监听DOM变化,为动态加载的卡牌绑定事件
| |
| var observer = new MutationObserver(function(mutations) {
| |
| var shouldBind = false;
| |
| mutations.forEach(function(mutation) {
| |
| if (mutation.addedNodes.length > 0) {
| |
| mutation.addedNodes.forEach(function(node) {
| |
| if (node.nodeType === 1) {
| |
| if (node.classList && node.classList.contains('card-deck-trans')) {
| |
| shouldBind = true;
| |
| } else if (node.querySelector && node.querySelector('.card-deck-trans')) {
| |
| shouldBind = true;
| |
| }
| |
| }
| |
| });
| |
| } | | } |
| });
| |
| if (shouldBind) {
| |
| bindCardEvents();
| |
| }
| |
| });
| |
|
| |
| observer.observe(document.body, {
| |
| childList: true,
| |
| subtree: true
| |
| });
| |
|
| |
| // 页面可见性变化时暂停/恢复预加载
| |
| document.addEventListener('visibilitychange', function() {
| |
| if (document.hidden) {
| |
| // 页面不可见时,暂停预加载(通过不处理队列)
| |
| } else { | | } else { |
| // 页面可见时,恢复预加载 | | container.innerHTML = '<div style="color: #fff; text-align: center; padding: 50px;">加载失败,请重试</div>'; |
| PreloadQueue.process();
| |
| } | | } |
| | }) |
| | .catch(function(error) { |
| | console.error('加载闪光卡牌失败:', error); |
| | container.innerHTML = '<div style="color: #fff; text-align: center; padding: 50px;">加载失败,请重试</div>'; |
| }); | | }); |
| } | | } |