优化模态框的样式和交互逻辑,新增加载动画效果,提升用户体验。重构相关JavaScript逻辑以支持新的数据结构,确保在不同设备上良好显示和操作。

This commit is contained in:
hxuanyu 2025-04-03 10:56:55 +08:00
parent 09c39afff4
commit 1336512244
3 changed files with 365 additions and 193 deletions

View File

@ -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; }
}
/* 优化悬停标识 */
@ -511,3 +540,13 @@
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;
}

View File

@ -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(`
<div class="min-h-[400px] flex items-center justify-center">
<div class="text-center text-gray-400">
<div class="text-lg mb-2">加载中...</div>
<div class="text-sm">正在获取棋子信息</div>
</div>
</div>
`);
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 += `
<div class="mb-3 bg-gray-700 p-2 rounded">
<div class="font-semibold text-indigo-300 mb-1">${details.skill.name || '技能'}</div>
<!-- 技能描述和介绍 -->
<div>
${details.skill.detail ? `
<div class="text-sm text-gray-300">
${details.skill.detail}
</div>
` : ''}
${details.skill.introduce ? `
<div class="text-sm italic text-gray-300 mt-2 pt-2 border-t border-gray-600 skill-introduce">
${details.skill.introduce}
</div>
` : ''}
</div>
</div>
`;
} else if (details.skill_name) {
// 兼容原有数据结构
content += `
<div class="mb-3 bg-gray-700 p-2 rounded">
<div class="flex items-center justify-between">
@ -1005,49 +1024,107 @@ function showChessTooltipContent(chessId, element, modalContent) {
}
// 羁绊信息
if (details.synergies && details.synergies.length > 0) {
content += `<div class="text-sm font-semibold text-indigo-300 mb-1">羁绊:</div>`;
const synergyTypes = {
'job': '职业',
'race': '特质'
};
details.synergies.forEach(synergy => {
content += `
<div class="text-sm mb-1 px-2 py-1 bg-gray-700 rounded flex items-center justify-between synergy-item-mini" data-synergy-id="${synergy.id}">
<span class="flex-grow text-gray-200">${synergy.name}</span>
<span class="bg-gray-600 text-xs px-1.5 py-0.5 rounded text-gray-300">${synergyTypes[synergy.type] || synergy.type}</span>
</div>
`;
});
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 += `<div class="mb-3">
<div class="text-sm font-semibold mb-2">羁绊</div>
<div class="space-y-2">`;
// 模拟内容加载完成的淡入效果
modalContent.fadeOut(100, function() {
$(this).html(content).fadeIn(150);
// 处理新的数据结构
// 职业羁绊
if (details.jobs && details.jobs.length) {
for (const job of details.jobs) {
content += `
<div class="synergy-item-mini bg-gray-700 p-2 rounded flex justify-between items-center" data-synergy-id="${job.jobId}">
<div>
<div class="text-sm font-medium">${job.name}</div>
<div class="text-xs text-gray-400">${job.description || ''}</div>
</div>
<div class="bg-gray-600 text-xs px-1.5 py-0.5 rounded">职业</div>
</div>
`;
}
}
// 显示返回按钮(如果有历史记录)
showTooltipBackButton(modalContent);
// 种族羁绊
if (details.races && details.races.length) {
for (const race of details.races) {
content += `
<div class="synergy-item-mini bg-gray-700 p-2 rounded flex justify-between items-center" data-synergy-id="${race.raceId}">
<div>
<div class="text-sm font-medium">${race.name}</div>
<div class="text-xs text-gray-400">${race.description || ''}</div>
</div>
<div class="bg-gray-600 text-xs px-1.5 py-0.5 rounded">种族</div>
</div>
`;
}
}
// 绑定交互事件
bindTooltipInteractions(modalContent);
});
}, 100);
// 兼容原有数据结构
if (details.synergies && details.synergies.length > 0) {
const synergyTypes = {
'job': '职业',
'race': '特质'
};
details.synergies.forEach(synergy => {
content += `
<div class="synergy-item-mini bg-gray-700 p-2 rounded flex justify-between items-center" data-synergy-id="${synergy.id}">
<div>
<div class="text-sm font-medium">${synergy.name}</div>
<div class="text-xs text-gray-400">${synergy.description || ''}</div>
</div>
<div class="bg-gray-600 text-xs px-1.5 py-0.5 rounded">${synergyTypes[synergy.type] || synergy.type}</div>
</div>
`;
});
}
content += `</div></div>`;
}
// 淡出加载动画
loadingSpinner.removeClass('visible').addClass('hidden');
// 直接更新内容,减少不必要的过渡
modalContent.html(content);
// 设置返回按钮
showTooltipBackButton(modalContent);
// 绑定新内容的交互事件
bindTooltipInteractions(modalContent);
} else {
// 加载失败
loadingSpinner.removeClass('visible').addClass('hidden');
modalContent.html(`
<div class="flex items-center justify-center h-40">
<div class="text-center text-red-500">
<div class="text-lg mb-2">加载失败</div>
<div class="text-sm">无法获取棋子信息请稍后重试</div>
</div>
</div>
`);
}
}).catch(error => {
// 处理错误
setTimeout(() => {
loadingSpinner.addClass('hidden');
modalContent.html(`<div class="text-red-400 p-4">请求出错: ${error.message || '未知错误'}</div>`);
modalContent.fadeIn(150);
}, 100);
// 加载失败
loadingSpinner.removeClass('visible').addClass('hidden');
console.error('获取棋子详情失败:', error);
modalContent.html(`
<div class="flex items-center justify-center h-40">
<div class="text-center text-red-500">
<div class="text-lg mb-2">加载失败</div>
<div class="text-sm">发生错误: ${error.message || '未知错误'}</div>
</div>
</div>
`);
});
}
@ -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(`
<div class="min-h-[400px] flex items-center justify-center">
<div class="text-center text-gray-400">
<div class="text-lg mb-2">加载中...</div>
<div class="text-sm">正在获取羁绊信息</div>
</div>
</div>
`);
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 = `
<div class="mb-2 flex items-center pl-10">
<div class="mb-2 flex items-center justify-between pl-10">
<div>
<span class="font-bold text-lg text-white">${details.name}</span>
<span class="text-xs text-gray-300 ml-1">${details.type === 'job' ? '职业' : '种族'}</span>
</div>
<div class="bg-gray-600 px-2 py-1 rounded text-xs">
${details.jobId ? '职业' : (details.raceId ? '种族' : (details.type === 'job' ? '职业' : '种族'))}
</div>
</div>
`;
// 羁绊介绍
if (details.introduce) {
// 羁绊描述
if (details.description) {
content += `
<div class="mb-3 text-sm italic text-gray-300 bg-gray-700 p-2 rounded synergy-introduce">
${details.description}
</div>
`;
} else if (details.introduce) {
// 兼容原有数据结构
content += `
<div class="mb-3 text-sm italic text-gray-300 bg-gray-700 p-2 rounded synergy-introduce">
${details.introduce}
@ -1105,99 +1181,156 @@ function showSynergyTooltipContent(synergyId, element, modalContent) {
`;
}
// 羁绊激活条件
if (details.levels && details.levels.length > 0) {
content += `<div class="mb-3"><div class="text-sm text-indigo-300 font-semibold mb-1">激活条件:</div>`;
// 添加激活条件卡片
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 += `
<div class="bg-gray-700 p-2 mb-2 rounded flex flex-wrap">
<div class="w-full flex items-center justify-between mb-1">
<span class="text-yellow-400 font-semibold">${activateCount}</span>
<span class="text-xs bg-indigo-900 text-indigo-200 px-2 py-0.5 rounded">
${level.level ? `等级 ${level.level}` : ''}
</span>
</div>
<div class="text-sm text-white">${level.effect}</div>
</div>
`;
});
content += `</div>`;
// 羁绊效果
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 += `<div class="text-sm text-indigo-300 font-semibold mb-1">相关棋子:</div>`;
if (hasLevels) {
content += `
<div class="mb-3">
<div class="text-sm font-semibold mb-2">激活效果</div>
<div class="space-y-2">
`;
// 根据费用对棋子进行分组
const chessByPrice = {};
details.related_chess.forEach(chess => {
const price = chess.cost || 0;
if (!chessByPrice[price]) {
chessByPrice[price] = [];
}
chessByPrice[price].push(chess);
});
// 处理新的数据结构
if (details.setEffects && details.setEffects.length) {
// 按照人口从小到大排序
const sortedEffects = [...details.setEffects].sort((a, b) => a.count - b.count);
// 按费用从低到高排序并添加棋子
Object.keys(chessByPrice).sort((a, b) => Number(a) - Number(b)).forEach(price => {
const priceChess = chessByPrice[price];
content += `<div class="mb-2">`;
content += `<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-1">`;
priceChess.forEach(chess => {
for (const effect of sortedEffects) {
content += `
<div class="bg-gray-700 p-1 rounded flex items-center chess-item-mini" data-chess-id="${chess.id}">
<div class="bg-yellow-600 text-xs rounded px-1 text-white">${chess.cost}</div>
<span class="ml-1 text-sm text-gray-200 truncate">${chess.name}</span>
<div class="bg-gray-700 p-2 rounded">
<div class="text-xs mb-1 text-indigo-300 font-semibold">${effect.count}人口:</div>
<div class="text-sm">${effect.effect}</div>
</div>
`;
}
}
// 兼容原有数据结构
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 += `
<div class="bg-gray-700 p-2 rounded">
<div class="text-xs mb-1 text-indigo-300 font-semibold">${activateCount}:</div>
<div class="text-sm">${level.effect}</div>
</div>
`;
});
}
content += `</div></div>`;
});
content += `</div></div>`;
}
// 隐藏加载动画并更新内容
setTimeout(() => {
loadingSpinner.addClass('hidden');
// 拥有此羁绊的棋子列表
let hasChess = details.heros && details.heros.length;
if (!hasChess && details.related_chess && details.related_chess.length) {
hasChess = true;
}
// 模拟内容加载完成的淡入效果
modalContent.fadeOut(100, function() {
$(this).html(content).fadeIn(150);
if (hasChess) {
content += `
<div class="mb-3">
<div class="text-sm font-semibold mb-2">拥有该羁绊的棋子</div>
<div class="grid grid-cols-2 gap-2">
`;
// 显示返回按钮(如果有历史记录)
showTooltipBackButton(modalContent);
// 处理新的数据结构
if (details.heros && details.heros.length) {
// 按照费用从小到大排序
const sortedHeros = [...details.heros].sort((a, b) => a.cost - b.cost);
// 绑定棋子点击事件
bindTooltipInteractions(modalContent);
});
}, 100);
for (const hero of sortedHeros) {
content += `
<div class="chess-item-mini bg-gray-700 p-2 rounded flex justify-between items-center cost-${hero.cost}" data-chess-id="${hero.chessId}">
<div class="text-sm">${hero.name}</div>
<div class="text-xs px-1.5 py-0.5 bg-gray-600 rounded">${hero.cost}</div>
</div>
`;
}
}
// 兼容原有数据结构
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);
});
// 按费用从低到高排序并添加棋子
Object.keys(chessByPrice).sort((a, b) => Number(a) - Number(b)).forEach(price => {
const priceChess = chessByPrice[price];
priceChess.forEach(chess => {
content += `
<div class="chess-item-mini bg-gray-700 p-2 rounded flex justify-between items-center cost-${chess.cost}" data-chess-id="${chess.id}">
<div class="text-sm">${chess.name}</div>
<div class="text-xs px-1.5 py-0.5 bg-gray-600 rounded">${chess.cost}</div>
</div>
`;
});
});
}
content += `</div></div>`;
}
// 淡出加载动画
loadingSpinner.removeClass('visible').addClass('hidden');
// 直接更新内容,减少不必要的过渡
modalContent.html(content);
// 设置返回按钮
showTooltipBackButton(modalContent);
// 绑定新内容的交互事件
bindTooltipInteractions(modalContent);
} else {
// 加载失败
loadingSpinner.removeClass('visible').addClass('hidden');
modalContent.html(`
<div class="flex items-center justify-center h-40">
<div class="text-center text-red-500">
<div class="text-lg mb-2">加载失败</div>
<div class="text-sm">无法获取羁绊信息请稍后重试</div>
</div>
</div>
`);
}
}).catch(error => {
// 处理错误
setTimeout(() => {
loadingSpinner.addClass('hidden');
modalContent.html(`<div class="text-red-400 p-4">请求出错: ${error.message || '未知错误'}</div>`);
modalContent.fadeIn(150);
}, 100);
// 加载失败
loadingSpinner.removeClass('visible').addClass('hidden');
console.error('获取羁绊详情失败:', error);
modalContent.html(`
<div class="flex items-center justify-center h-40">
<div class="text-center text-red-500">
<div class="text-lg mb-2">加载失败</div>
<div class="text-sm">发生错误: ${error.message || '未知错误'}</div>
</div>
</div>
`);
});
}

View File

@ -145,7 +145,7 @@
<div id="tft-modal">
<div class="modal-wrapper">
<div class="modal-content"></div>
<div class="loading-spinner absolute inset-0 flex items-center justify-center bg-gray-800 bg-opacity-90 hidden rounded-lg">
<div class="loading-spinner absolute inset-0 flex items-center justify-center hidden rounded-lg">
<div class="flex flex-col items-center">
<div class="animate-spin rounded-full h-10 w-10 border-b-2 border-t-2 border-indigo-500 mb-2"></div>
<div class="text-xs text-indigo-300">加载中...</div>