From 1336512244bc1353ce08c77698c73c18d21e2b91 Mon Sep 17 00:00:00 2001 From: hxuanyu <2252193204@qq.com> Date: Thu, 3 Apr 2025 10:56:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E6=80=81=E6=A1=86?= =?UTF-8?q?=E7=9A=84=E6=A0=B7=E5=BC=8F=E5=92=8C=E4=BA=A4=E4=BA=92=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=96=B0=E5=A2=9E=E5=8A=A0=E8=BD=BD=E5=8A=A8?= =?UTF-8?q?=E7=94=BB=E6=95=88=E6=9E=9C=EF=BC=8C=E6=8F=90=E5=8D=87=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82=E9=87=8D=E6=9E=84=E7=9B=B8?= =?UTF-8?q?=E5=85=B3JavaScript=E9=80=BB=E8=BE=91=E4=BB=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=96=B0=E7=9A=84=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=9C=A8=E4=B8=8D=E5=90=8C=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E4=B8=8A=E8=89=AF=E5=A5=BD=E6=98=BE=E7=A4=BA=E5=92=8C?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/web/static/css/style.css | 125 ++++++---- src/web/static/js/main.js | 431 +++++++++++++++++++++++------------ src/web/templates/index.html | 2 +- 3 files changed, 365 insertions(+), 193 deletions(-) diff --git a/src/web/static/css/style.css b/src/web/static/css/style.css index bd003c6..27313e3 100644 --- a/src/web/static/css/style.css +++ b/src/web/static/css/style.css @@ -63,18 +63,20 @@ z-index: 50; display: none; backdrop-filter: blur(2px); - animation: backdropFadeIn 0.3s ease; + transition: opacity 0.25s ease; + opacity: 0; } #tft-modal-backdrop.open { display: block; + opacity: 1; } #tft-modal { position: fixed; top: 50%; left: 50%; - transform: translate(-50%, -50%); + transform: translate(-50%, -50%) scale(0.98); z-index: 51; max-width: 90%; width: 450px; @@ -85,12 +87,15 @@ display: none; overflow: hidden; /* 改为hidden,让滚动发生在内部容器 */ border: 1px solid rgba(99, 102, 241, 0.3); - animation: modalFadeIn 0.3s cubic-bezier(0.16, 1, 0.3, 1); color: #f3f4f6; /* 设置模态框内默认文字颜色为浅灰色 */ + opacity: 0; + transition: all 0.25s ease; } #tft-modal.open { display: block; + opacity: 1; + transform: translate(-50%, -50%) scale(1); } /* 模态框内部包装容器 */ @@ -110,17 +115,6 @@ to { opacity: 1; } } -@keyframes modalFadeIn { - from { - opacity: 0; - transform: translate(-50%, -48%) scale(0.96); - } - to { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } -} - /* 自定义滚动条样式 */ .modal-wrapper::-webkit-scrollbar { width: 6px; @@ -174,9 +168,6 @@ /* 模态框内容淡入效果 */ #tft-modal .modal-content { word-break: break-word; - opacity: 0; - animation: contentFadeIn 0.2s ease forwards; - animation-delay: 0.1s; } /* 模态框中的标题和文本颜色 */ @@ -206,17 +197,6 @@ background-color: #4b5563; } -@keyframes contentFadeIn { - from { - opacity: 0; - transform: translateY(5px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - /* 提示框中的费用标识 */ #tft-modal .cost-1 { border-left: 3px solid #94a3b8; } /* 1费 - 灰色 */ #tft-modal .cost-2 { border-left: 3px solid #65a30d; } /* 2费 - 绿色 */ @@ -252,36 +232,37 @@ #tft-modal .modal-close-btn { position: absolute; top: 12px; - right: 12px; - width: 36px; - height: 36px; - background-color: #374151; - color: #9ca3af; - border-radius: 9999px; + left: 12px; + width: 28px; + height: 28px; + border-radius: 50%; display: flex; align-items: center; justify-content: center; + background-color: rgba(55, 65, 81, 0.9); + color: rgb(209, 213, 219); + border: none; cursor: pointer; + z-index: 5; transition: all 0.2s ease; - opacity: 0.9; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - z-index: 2; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } #tft-modal .modal-close-btn:hover { - opacity: 1; - color: #ffffff; - background-color: #4b5563; + background-color: rgba(99, 102, 241, 0.9); + color: white; transform: scale(1.05); } /* 模态框返回按钮样式 */ #tft-modal .modal-back-btn { - background-color: #4f46e5; + background-color: rgba(79, 70, 229, 0.9); + color: white; } #tft-modal .modal-back-btn:hover { - background-color: #4338ca; + background-color: rgba(99, 102, 241, 0.9); + transform: scale(1.05); } /* 特殊介绍文本样式 */ @@ -292,8 +273,56 @@ padding-left: 8px; } +/* 加载动画样式 */ #tft-modal .loading-spinner { - opacity: 0.8; + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(31, 41, 55, 0.85); /* 深灰色背景,半透明 */ + z-index: 2; + backdrop-filter: blur(1px); + border-radius: 0.5rem; + transition: opacity 0.2s ease, visibility 0.2s ease; +} + +#tft-modal .loading-spinner.hidden { + opacity: 0; + visibility: hidden; +} + +#tft-modal .loading-spinner.visible { + opacity: 1; + visibility: visible; +} + +#tft-modal .loading-spinner .animate-spin { + height: 2.5rem; + width: 2.5rem; + border-width: 3px; + border-color: transparent; + border-top-color: rgba(99, 102, 241, 0.7); + border-bottom-color: rgba(99, 102, 241, 0.7); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +#tft-modal .loading-spinner .text-xs { + margin-top: 0.75rem; + color: rgba(165, 180, 252, 0.8); + animation: pulse 1.5s ease infinite; +} + +@keyframes pulse { + 0% { opacity: 0.6; } + 50% { opacity: 1; } + 100% { opacity: 0.6; } } /* 优化悬停标识 */ @@ -510,4 +539,14 @@ flex-wrap: wrap; gap: 0.3rem; } +} + +/* 模态框内容淡入淡出动画 */ +@keyframes fadeInOut { + 0% { opacity: 0; transform: translateY(10px); } + 100% { opacity: 1; transform: translateY(0); } +} + +.fade-in-content { + animation: fadeInOut 0.3s ease forwards; } \ No newline at end of file diff --git a/src/web/static/js/main.js b/src/web/static/js/main.js index 27d60dd..9d835c3 100644 --- a/src/web/static/js/main.js +++ b/src/web/static/js/main.js @@ -687,17 +687,23 @@ function createTooltip() { * 关闭模态框 */ function closeModal() { - $('#tft-modal').removeClass('open'); - $('#tft-modal-backdrop').removeClass('open'); + const modal = $('#tft-modal'); + const modalBackdrop = $('#tft-modal-backdrop'); + + // 关闭模态框 + modal.removeClass('open'); + modalBackdrop.removeClass('open'); + + // 重置历史记录和状态 currentHoveredId = null; $('.tooltip-active').removeClass('tooltip-active'); - // 重置历史记录 tooltipHistory = []; - // 重置滚动位置 + // 延迟后重置滚动位置 setTimeout(() => { $('.modal-wrapper').scrollTop(0); - }, 300); // 等待模态框关闭动画完成 + modal.find('.modal-content').empty(); + }, 250); // 等待模态框关闭动画完成 } /** @@ -840,24 +846,15 @@ function showChessTooltipContent(chessId, element, modalContent) { ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); - // 先打开模态框,再加载内容,这样可以避免滚动条突然出现 + // 先打开模态框,再加载内容 $('#tft-modal').addClass('open'); $('#tft-modal-backdrop').addClass('open'); // 滚动到顶部 $('.modal-wrapper').scrollTop(0); - // 重置内容并显示加载中 - 添加占位符内容以维持高度 - modalContent.empty(); - modalContent.html(` -
-
-
加载中...
-
正在获取棋子信息
-
-
- `); - loadingSpinner.removeClass('hidden'); + // 显示加载动画 + loadingSpinner.removeClass('hidden').addClass('visible'); // 获取棋子详情 fetchItemDetails('chess', chessId).then(response => { @@ -975,7 +972,29 @@ function showChessTooltipContent(chessId, element, modalContent) { } // 技能信息 - if (details.skill_name) { + if (details.skill) { + content += ` +
+
${details.skill.name || '技能'}
+ + +
+ ${details.skill.detail ? ` +
+ ${details.skill.detail} +
+ ` : ''} + + ${details.skill.introduce ? ` +
+ ${details.skill.introduce} +
+ ` : ''} +
+
+ `; + } else if (details.skill_name) { + // 兼容原有数据结构 content += `
@@ -1005,49 +1024,107 @@ function showChessTooltipContent(chessId, element, modalContent) { } // 羁绊信息 - if (details.synergies && details.synergies.length > 0) { - content += `
羁绊:
`; - const synergyTypes = { - 'job': '职业', - 'race': '特质' - }; - - details.synergies.forEach(synergy => { - content += ` -
- ${synergy.name} - ${synergyTypes[synergy.type] || synergy.type} -
- `; - }); + let hasSynergies = (details.jobs && details.jobs.length) || (details.races && details.races.length); + if (!hasSynergies && details.synergies && details.synergies.length > 0) { + hasSynergies = true; } - // 使用淡入效果更新内容,避免内容突然变化 - setTimeout(() => { - // 隐藏加载动画 - loadingSpinner.addClass('hidden'); + if (hasSynergies) { + content += `
+
羁绊
+
`; - // 模拟内容加载完成的淡入效果 - modalContent.fadeOut(100, function() { - $(this).html(content).fadeIn(150); + // 处理新的数据结构 + // 职业羁绊 + if (details.jobs && details.jobs.length) { + for (const job of details.jobs) { + content += ` +
+
+
${job.name}
+
${job.description || ''}
+
+
职业
+
+ `; + } + } + + // 种族羁绊 + if (details.races && details.races.length) { + for (const race of details.races) { + content += ` +
+
+
${race.name}
+
${race.description || ''}
+
+
种族
+
+ `; + } + } + + // 兼容原有数据结构 + if (details.synergies && details.synergies.length > 0) { + const synergyTypes = { + 'job': '职业', + 'race': '特质' + }; - // 显示返回按钮(如果有历史记录) - showTooltipBackButton(modalContent); - - // 绑定交互事件 - bindTooltipInteractions(modalContent); - }); - }, 100); + details.synergies.forEach(synergy => { + content += ` +
+
+
${synergy.name}
+
${synergy.description || ''}
+
+
${synergyTypes[synergy.type] || synergy.type}
+
+ `; + }); + } + + content += `
`; + } + + // 淡出加载动画 + loadingSpinner.removeClass('visible').addClass('hidden'); + + // 直接更新内容,减少不必要的过渡 + modalContent.html(content); + + // 设置返回按钮 + showTooltipBackButton(modalContent); + + // 绑定新内容的交互事件 + bindTooltipInteractions(modalContent); + + } else { + // 加载失败 + loadingSpinner.removeClass('visible').addClass('hidden'); + + modalContent.html(` +
+
+
加载失败
+
无法获取棋子信息,请稍后重试
+
+
+ `); } }).catch(error => { - // 处理错误 - setTimeout(() => { - loadingSpinner.addClass('hidden'); - modalContent.html(`
请求出错: ${error.message || '未知错误'}
`); - modalContent.fadeIn(150); - }, 100); + // 加载失败 + loadingSpinner.removeClass('visible').addClass('hidden'); - console.error('获取棋子详情失败:', error); + modalContent.html(` +
+
+
加载失败
+
发生错误: ${error.message || '未知错误'}
+
+
+ `); }); } @@ -1061,43 +1138,42 @@ function showSynergyTooltipContent(synergyId, element, modalContent) { const modal = $('#tft-modal'); const loadingSpinner = modal.find('.loading-spinner'); - // 先打开模态框,再加载内容,这样可以避免滚动条突然出现 + // 先打开模态框,再加载内容 $('#tft-modal').addClass('open'); $('#tft-modal-backdrop').addClass('open'); // 滚动到顶部 $('.modal-wrapper').scrollTop(0); - // 重置内容并显示加载中 - 添加占位符内容以维持高度 - modalContent.empty(); - modalContent.html(` -
-
-
加载中...
-
正在获取羁绊信息
-
-
- `); - loadingSpinner.removeClass('hidden'); + // 显示加载动画 + loadingSpinner.removeClass('hidden').addClass('visible'); // 获取羁绊详情 fetchItemDetails('synergy', synergyId).then(response => { if (response.status === 'success') { const details = response.details; - console.log("羁绊详情:", details); // 调试用,查看响应数据结构 - // 构建提示内容 + // 构建提示内容,添加左侧内边距给返回按钮留出空间 let content = ` -
+
${details.name} - ${details.type === 'job' ? '职业' : '种族'} +
+
+ ${details.jobId ? '职业' : (details.raceId ? '种族' : (details.type === 'job' ? '职业' : '种族'))}
`; - // 羁绊介绍 - if (details.introduce) { + // 羁绊描述 + if (details.description) { + content += ` +
+ ${details.description} +
+ `; + } else if (details.introduce) { + // 兼容原有数据结构 content += `
${details.introduce} @@ -1105,99 +1181,156 @@ function showSynergyTooltipContent(synergyId, element, modalContent) { `; } - // 羁绊激活条件 - if (details.levels && details.levels.length > 0) { - content += `
激活条件:
`; - - // 添加激活条件卡片 - details.levels.forEach(level => { - // 检查激活数量可能的字段名 - let activateCount = null; - if (level.level) { - activateCount = level.level; - } else if (level.count) { - activateCount = level.count; - } else if (level.num) { - activateCount = level.num; - } else if (level.number) { - activateCount = level.number; - } - - content += ` -
-
- ${activateCount}个 - - ${level.level ? `等级 ${level.level}` : ''} - -
-
${level.effect}
-
- `; - }); - - content += `
`; + // 羁绊效果 + let hasLevels = details.setEffects && details.setEffects.length; + if (!hasLevels && details.levels && details.levels.length) { + hasLevels = true; } - // 相关棋子 - if (details.related_chess && details.related_chess.length > 0) { - content += `
相关棋子:
`; + if (hasLevels) { + content += ` +
+
激活效果
+
+ `; - // 根据费用对棋子进行分组 - const chessByPrice = {}; - details.related_chess.forEach(chess => { - const price = chess.cost || 0; - if (!chessByPrice[price]) { - chessByPrice[price] = []; - } - chessByPrice[price].push(chess); - }); - - // 按费用从低到高排序并添加棋子 - Object.keys(chessByPrice).sort((a, b) => Number(a) - Number(b)).forEach(price => { - const priceChess = chessByPrice[price]; + // 处理新的数据结构 + if (details.setEffects && details.setEffects.length) { + // 按照人口从小到大排序 + const sortedEffects = [...details.setEffects].sort((a, b) => a.count - b.count); - content += `
`; - content += `
`; - - priceChess.forEach(chess => { + for (const effect of sortedEffects) { content += ` -
-
${chess.cost}
- ${chess.name} +
+
${effect.count}人口:
+
${effect.effect}
+
+ `; + } + } + + // 兼容原有数据结构 + if (details.levels && details.levels.length > 0) { + details.levels.forEach(level => { + // 检查激活数量可能的字段名 + let activateCount = null; + if (level.level) { + activateCount = level.level; + } else if (level.count) { + activateCount = level.count; + } else if (level.num) { + activateCount = level.num; + } else if (level.number) { + activateCount = level.number; + } + + content += ` +
+
${activateCount}个:
+
${level.effect}
`; }); - - content += `
`; - }); + } + + content += `
`; } - // 隐藏加载动画并更新内容 - setTimeout(() => { - loadingSpinner.addClass('hidden'); + // 拥有此羁绊的棋子列表 + let hasChess = details.heros && details.heros.length; + if (!hasChess && details.related_chess && details.related_chess.length) { + hasChess = true; + } + + if (hasChess) { + content += ` +
+
拥有该羁绊的棋子
+
+ `; - // 模拟内容加载完成的淡入效果 - modalContent.fadeOut(100, function() { - $(this).html(content).fadeIn(150); + // 处理新的数据结构 + if (details.heros && details.heros.length) { + // 按照费用从小到大排序 + const sortedHeros = [...details.heros].sort((a, b) => a.cost - b.cost); - // 显示返回按钮(如果有历史记录) - showTooltipBackButton(modalContent); + for (const hero of sortedHeros) { + content += ` +
+
${hero.name}
+
${hero.cost}费
+
+ `; + } + } + + // 兼容原有数据结构 + if (details.related_chess && details.related_chess.length > 0) { + // 根据费用对棋子进行分组 + const chessByPrice = {}; + details.related_chess.forEach(chess => { + const price = chess.cost || 0; + if (!chessByPrice[price]) { + chessByPrice[price] = []; + } + chessByPrice[price].push(chess); + }); - // 绑定棋子点击事件 - bindTooltipInteractions(modalContent); - }); - }, 100); + // 按费用从低到高排序并添加棋子 + Object.keys(chessByPrice).sort((a, b) => Number(a) - Number(b)).forEach(price => { + const priceChess = chessByPrice[price]; + + priceChess.forEach(chess => { + content += ` +
+
${chess.name}
+
${chess.cost}费
+
+ `; + }); + }); + } + + content += `
`; + } + + // 淡出加载动画 + loadingSpinner.removeClass('visible').addClass('hidden'); + + // 直接更新内容,减少不必要的过渡 + modalContent.html(content); + + // 设置返回按钮 + showTooltipBackButton(modalContent); + + // 绑定新内容的交互事件 + bindTooltipInteractions(modalContent); + + } else { + // 加载失败 + loadingSpinner.removeClass('visible').addClass('hidden'); + + modalContent.html(` +
+
+
加载失败
+
无法获取羁绊信息,请稍后重试
+
+
+ `); } }).catch(error => { - // 处理错误 - setTimeout(() => { - loadingSpinner.addClass('hidden'); - modalContent.html(`
请求出错: ${error.message || '未知错误'}
`); - modalContent.fadeIn(150); - }, 100); + // 加载失败 + loadingSpinner.removeClass('visible').addClass('hidden'); - console.error('获取羁绊详情失败:', error); + modalContent.html(` +
+
+
加载失败
+
发生错误: ${error.message || '未知错误'}
+
+
+ `); }); } diff --git a/src/web/templates/index.html b/src/web/templates/index.html index bebd70e..e45a75d 100644 --- a/src/web/templates/index.html +++ b/src/web/templates/index.html @@ -145,7 +145,7 @@