// 云顶之弈阵容推荐器 - 前端脚本 // 页面加载完成后执行 $(document).ready(function() { // 初始化抽屉 initDrawer(); // 加载本地存储的权重 loadWeightsFromLocalStorage(); // 初始化基础权重滑块 initBasicWeightSliders(); // 初始化羁绊权重滑块 initSynergyWeightSliders(); // 初始化棋子权重滑块 initChessWeightSliders(); // 监听人口数量变化 $('#population').on('input', function() { $('#population-value').text($(this).val()); }); // 监听推荐结果数量变化 $('#num-results').on('input', function() { $('#num-results-value').text($(this).val()); }); // 添加必要羁绊 $('#add-required-synergy').on('click', function() { const synergySelect = $('#required-synergy-select'); const synergyId = synergySelect.val(); const synergyName = synergySelect.find('option:selected').text(); if (!synergyId) return; // 检查是否已添加 if ($(`#required-synergy-${synergyId}`).length > 0) return; // 添加到列表 const synergyItem = $(` <div id="required-synergy-${synergyId}" class="flex items-center justify-between bg-indigo-50 p-2 rounded synergy-item" data-synergy-id="${synergyId}"> <span class="text-gray-800">${synergyName}</span> <button class="remove-synergy text-red-500 hover:text-red-700"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /> </svg> </button> </div> `); $('#required-synergies-list').append(synergyItem); // 重置选择框 synergySelect.val(''); }); // 移除必要羁绊 $(document).on('click', '.remove-synergy', function() { $(this).closest('div').remove(); }); // 添加必选棋子 $('#add-required-chess').on('click', function() { const chessSelect = $('#required-chess-select'); const chessId = chessSelect.val(); const chessName = chessSelect.find('option:selected').text(); if (!chessId) return; // 检查是否已添加 if ($(`#required-chess-${chessId}`).length > 0) return; // 添加到列表 const chessItem = $(` <div id="required-chess-${chessId}" class="flex items-center justify-between bg-indigo-50 p-2 rounded chess-item" data-chess-id="${chessId}"> <span class="text-gray-800">${chessName}</span> <button class="remove-chess text-red-500 hover:text-red-700"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /> </svg> </button> </div> `); $('#required-chess-list').append(chessItem); // 重置选择框 chessSelect.val(''); }); // 移除必选棋子 $(document).on('click', '.remove-chess', function() { $(this).closest('div').remove(); }); // 生成阵容 $('#generate-btn').on('click', function() { generateTeam(); }); // 仅显示已激活羁绊 $('#show-only-active').on('change', function() { const isChecked = $(this).is(':checked'); toggleActiveSynergiesVisibility(isChecked); }); // 仅显示已激活棋子 $('#show-only-active-chess').on('change', function() { const isChecked = $(this).is(':checked'); toggleActiveChessVisibility(isChecked); }); // 监听权重变化,保存到本地存储 $(document).on('change', '.noUi-target', function() { saveWeightsToLocalStorage(); }); // 恢复默认配置按钮 $('#reset-to-default').on('click', function() { resetToDefaultWeights(); }); // 初始化悬停提示 initTooltips(); }); /** * 初始化抽屉控制 */ function initDrawer() { // 打开抽屉 $('#open-weights-drawer').on('click', function() { $('#weights-drawer').addClass('open'); $('#drawer-backdrop').addClass('open'); }); // 关闭抽屉 $('#close-drawer, #drawer-backdrop').on('click', function() { $('#weights-drawer').removeClass('open'); $('#drawer-backdrop').removeClass('open'); }); } /** * 从本地存储加载权重设置 */ function loadWeightsFromLocalStorage() { const storedWeights = localStorage.getItem('tft-weights'); if (!storedWeights) return; try { const weights = JSON.parse(storedWeights); // 设置基础权重 if (weights.base_weights) { if (weights.base_weights.synergy_level_weight) { $('#synergy-level-weight-value').text(weights.base_weights.synergy_level_weight.toFixed(1)); } if (weights.base_weights.synergy_count_weight) { $('#synergy-count-weight-value').text(weights.base_weights.synergy_count_weight.toFixed(1)); } if (weights.base_weights.chess_cost_weight) { $('#chess-cost-weight-value').text(weights.base_weights.chess_cost_weight.toFixed(1)); } } // 设置羁绊权重 if (weights.synergy_weights) { for (const [synergyId, weight] of Object.entries(weights.synergy_weights)) { $(`.synergy-weight-item[data-synergy-id="${synergyId}"] .synergy-weight-value`).text(weight.toFixed(1)); } } // 设置棋子权重 if (weights.chess_weights) { for (const [chessId, weight] of Object.entries(weights.chess_weights)) { $(`.chess-weight-item[data-chess-id="${chessId}"] .chess-weight-value`).text(weight.toFixed(1)); } } } catch (e) { console.error('加载权重设置失败:', e); } } /** * 保存权重设置到本地存储 */ function saveWeightsToLocalStorage() { // 获取基础权重 const synergyLevelWeight = parseFloat($('#synergy-level-weight-value').text()); const synergyCountWeight = parseFloat($('#synergy-count-weight-value').text()); const chessCountWeight = parseFloat($('#chess-cost-weight-value').text()); // 获取羁绊权重 const synergyWeights = {}; $('.synergy-weight-item').each(function() { const synergyId = $(this).data('synergy-id'); const weight = parseFloat($(this).find('.synergy-weight-value').text()); synergyWeights[synergyId] = weight; }); // 获取棋子权重 const chessWeights = {}; $('.chess-weight-item').each(function() { const chessId = $(this).data('chess-id'); const weight = parseFloat($(this).find('.chess-weight-value').text()); chessWeights[chessId] = weight; }); // 构建权重对象 const weightsObj = { base_weights: { synergy_level_weight: synergyLevelWeight, synergy_count_weight: synergyCountWeight, chess_cost_weight: chessCountWeight }, synergy_weights: synergyWeights, chess_weights: chessWeights }; // 保存到本地存储 localStorage.setItem('tft-weights', JSON.stringify(weightsObj)); } /** * 重置为默认权重 */ function resetToDefaultWeights() { // 发送请求获取默认权重 $.ajax({ url: '/api/weights', type: 'GET', success: function(response) { // 更新基础权重 if (response.base_weights) { if (response.base_weights.synergy_level_weight) { const slider = document.getElementById('synergy-level-weight-slider').noUiSlider; slider.set(response.base_weights.synergy_level_weight); } if (response.base_weights.synergy_count_weight) { const slider = document.getElementById('synergy-count-weight-slider').noUiSlider; slider.set(response.base_weights.synergy_count_weight); } if (response.base_weights.chess_cost_weight) { const slider = document.getElementById('chess-cost-weight-slider').noUiSlider; slider.set(response.base_weights.chess_cost_weight); } } // 更新羁绊权重 if (response.synergy_weights) { for (const [synergyId, weight] of Object.entries(response.synergy_weights)) { const slider = $(`.synergy-weight-item[data-synergy-id="${synergyId}"] .synergy-weight-slider`)[0]; if (slider && slider.noUiSlider) { slider.noUiSlider.set(weight); } } } // 更新棋子权重 if (response.chess_weights) { for (const [chessId, weight] of Object.entries(response.chess_weights)) { const slider = $(`.chess-weight-item[data-chess-id="${chessId}"] .chess-weight-slider`)[0]; if (slider && slider.noUiSlider) { slider.noUiSlider.set(weight); } } } // 清除本地存储 localStorage.removeItem('tft-weights'); alert('已恢复默认权重设置'); }, error: function(xhr, status, error) { alert(`恢复默认权重失败: ${error}`); } }); } /** * 初始化基础权重滑块 */ function initBasicWeightSliders() { // 羁绊等级权重 const synergyLevelSlider = document.getElementById('synergy-level-weight-slider'); const synergyLevelValue = parseFloat($('#synergy-level-weight-value').text()); noUiSlider.create(synergyLevelSlider, { start: [synergyLevelValue], connect: [true, false], step: 0.1, range: { 'min': [0.0], 'max': [3.0] } }); // 更新羁绊等级权重值 synergyLevelSlider.noUiSlider.on('update', function(values, handle) { $('#synergy-level-weight-value').text(parseFloat(values[handle]).toFixed(1)); }); // 羁绊数量权重 const synergyCountSlider = document.getElementById('synergy-count-weight-slider'); const synergyCountValue = parseFloat($('#synergy-count-weight-value').text()); noUiSlider.create(synergyCountSlider, { start: [synergyCountValue], connect: [true, false], step: 0.1, range: { 'min': [0.0], 'max': [3.0] } }); // 更新羁绊数量权重值 synergyCountSlider.noUiSlider.on('update', function(values, handle) { $('#synergy-count-weight-value').text(parseFloat(values[handle]).toFixed(1)); }); // 棋子费用权重 const chessCountSlider = document.getElementById('chess-cost-weight-slider'); const chessCountValue = parseFloat($('#chess-cost-weight-value').text()); noUiSlider.create(chessCountSlider, { start: [chessCountValue], connect: [true, false], step: 0.1, range: { 'min': [0.0], 'max': [1.0] } }); // 更新棋子费用权重值 chessCountSlider.noUiSlider.on('update', function(values, handle) { $('#chess-cost-weight-value').text(parseFloat(values[handle]).toFixed(1)); }); } /** * 初始化羁绊权重滑块 */ function initSynergyWeightSliders() { $('.synergy-weight-item').each(function() { const slider = $(this).find('.synergy-weight-slider')[0]; const valueElement = $(this).find('.synergy-weight-value'); const initialValue = parseFloat(valueElement.text()); noUiSlider.create(slider, { start: [initialValue], connect: [true, false], step: 0.1, range: { 'min': [0.0], 'max': [3.0] } }); // 更新权重值 slider.noUiSlider.on('update', function(values, handle) { valueElement.text(parseFloat(values[handle]).toFixed(1)); }); }); } /** * 初始化棋子权重滑块 */ function initChessWeightSliders() { $('.chess-weight-item').each(function() { const slider = $(this).find('.chess-weight-slider')[0]; const valueElement = $(this).find('.chess-weight-value'); const initialValue = parseFloat(valueElement.text()); noUiSlider.create(slider, { start: [initialValue], connect: [true, false], step: 0.1, range: { 'min': [0.0], 'max': [3.0] } }); // 更新权重值 slider.noUiSlider.on('update', function(values, handle) { valueElement.text(parseFloat(values[handle]).toFixed(1)); }); }); } /** * 生成阵容 */ function generateTeam() { // 显示加载状态 $('#loading').removeClass('hidden'); $('#results-container').empty(); // 获取基础参数 const population = parseInt($('#population').val()); const numResults = parseInt($('#num-results').val()); // 获取基础权重 const synergyLevelWeight = parseFloat($('#synergy-level-weight-value').text()); const synergyCountWeight = parseFloat($('#synergy-count-weight-value').text()); const chessCountWeight = parseFloat($('#chess-cost-weight-value').text()); // 获取羁绊权重 const synergyWeights = {}; $('.synergy-weight-item').each(function() { const synergyId = $(this).data('synergy-id'); const weight = parseFloat($(this).find('.synergy-weight-value').text()); synergyWeights[synergyId] = weight; }); // 获取棋子权重 const chessWeights = {}; $('.chess-weight-item').each(function() { const chessId = $(this).data('chess-id'); const weight = parseFloat($(this).find('.chess-weight-value').text()); chessWeights[chessId] = weight; }); // 获取必选羁绊 const requiredSynergies = []; $('#required-synergies-list > div').each(function() { requiredSynergies.push($(this).data('synergy-id')); }); // 获取必选棋子 const requiredChess = []; $('#required-chess-list > div').each(function() { requiredChess.push($(this).data('chess-id')); }); // 构建请求体 const requestData = { population: population, num_results: numResults, base_weights: { synergy_level_weight: synergyLevelWeight, synergy_count_weight: synergyCountWeight, chess_cost_weight: chessCountWeight }, synergy_weights: synergyWeights, chess_weights: chessWeights, required_synergies: requiredSynergies, required_chess: requiredChess }; // 保存当前设置到本地存储 saveWeightsToLocalStorage(); // 发送请求 $.ajax({ url: '/api/recommend', type: 'POST', contentType: 'application/json', data: JSON.stringify(requestData), success: function(response) { // 隐藏加载状态 $('#loading').addClass('hidden'); if (response.status === 'success') { // 渲染结果 renderResults(response.results); // 更新羁绊和棋子激活状态 updateActiveStatus(response.results); } else { // 显示错误信息 $('#results-container').html(`<div class="text-red-600 p-4">生成阵容失败: ${response.message}</div>`); } }, error: function(xhr, status, error) { // 隐藏加载状态 $('#loading').addClass('hidden'); // 显示错误信息 $('#results-container').html(`<div class="text-red-600 p-4">请求失败: ${error}</div>`); } }); } /** * 渲染阵容结果 * @param {Array} results 阵容结果列表 */ function renderResults(results) { const container = $('#results-container'); container.empty(); results.forEach((result, index) => { // 克隆结果模板 const template = document.getElementById('team-result-template'); const teamElement = $(template.content.cloneNode(true)); // 设置阵容编号和评分 teamElement.find('.team-number').text(index + 1); teamElement.find('.team-score').text(result.score.toFixed(1)); // 渲染棋子列表 const chessList = teamElement.find('.chess-list'); result.chess_list.forEach(chess => { const chessCard = $(` <div class="chess-item chess-card p-2 chess-cost-${chess.cost}" data-chess-id="${chess.id}"> <div class="font-medium text-sm">${chess.name}</div> <div class="text-xs text-gray-500">${chess.cost}费</div> </div> `); chessList.append(chessCard); }); // 渲染羁绊列表 const synergyList = teamElement.find('.synergy-list'); result.active_synergies.forEach(synergy => { const synergyTag = $(` <div class="synergy-item synergy-tag" data-synergy-id="${synergy.id}"> <span>${synergy.name}</span> <span class="ml-1 text-indigo-700">(${synergy.level})</span> </div> `); synergyList.append(synergyTag); }); // 添加到结果容器 container.append(teamElement); }); } /** * 更新羁绊和棋子的激活状态 * @param {Array} results 阵容结果列表 */ function updateActiveStatus(results) { // 重置激活状态 $('.synergy-weight-item').removeClass('active'); $('.chess-weight-item').removeClass('active'); // 收集所有被激活的羁绊和棋子 const activeSynergies = new Set(); const activeSynergyNames = new Set(); const activeChess = new Set(); const activeChessNames = new Set(); results.forEach(result => { // 添加激活的羁绊 result.active_synergies.forEach(synergy => { if (synergy.id) activeSynergies.add(String(synergy.id)); if (synergy.name) activeSynergyNames.add(synergy.name); }); // 添加激活的棋子 result.chess_list.forEach(chess => { if (chess.id) activeChess.add(String(chess.id)); if (chess.name) activeChessNames.add(chess.name); }); }); // 处理羁绊激活状态 $('.synergy-weight-item').each(function() { const $item = $(this); const synergyId = String($item.data('synergy-id')); const synergyName = $item.data('synergy-name') || $item.find('label').text().trim(); // 通过ID或名称匹配 if (activeSynergies.has(synergyId) || activeSynergyNames.has(synergyName) || activeSynergyNames.has(synergyName.toLowerCase())) { $item.addClass('active'); } }); // 处理棋子激活状态 $('.chess-weight-item').each(function() { const $item = $(this); const chessId = String($item.data('chess-id')); const chessName = $item.data('chess-name') || $item.find('label').text().trim(); // 通过ID或名称匹配 if (activeChess.has(chessId) || activeChessNames.has(chessName) || activeChessNames.has(chessName.toLowerCase())) { $item.addClass('active'); } }); // 根据当前复选框状态更新显示 const showOnlyActiveSynergies = $('#show-only-active').is(':checked'); const showOnlyActiveChess = $('#show-only-active-chess').is(':checked'); // 更新显示状态 toggleActiveSynergiesVisibility(showOnlyActiveSynergies); toggleActiveChessVisibility(showOnlyActiveChess); } /** * 切换激活羁绊的可见性 * @param {boolean} showOnlyActive 是否只显示激活项 */ function toggleActiveSynergiesVisibility(showOnlyActive) { if (showOnlyActive) { // 隐藏所有羁绊权重项 $('.synergy-weight-item').hide(); // 显示已激活羁绊项 $('.synergy-weight-item.active').show(); } else { // 显示所有羁绊权重项 $('.synergy-weight-item').show(); } } /** * 获取当前激活的棋子ID列表 * @returns {Array} 激活的棋子ID列表 */ function getActiveChessIds() { const activeIds = []; $('.chess-weight-item.active').each(function() { const chessId = $(this).data('chess-id'); if (chessId) { activeIds.push(chessId); } }); return activeIds; } /** * 切换激活棋子的可见性 * @param {boolean} showOnlyActive 是否只显示激活项 */ function toggleActiveChessVisibility(showOnlyActive) { // 获取已激活的棋子ID列表 const activeChessIds = getActiveChessIds(); $('.chess-weight-item').each(function() { const chessId = $(this).data('chess-id'); if (showOnlyActive) { // 仅显示激活的棋子 if (activeChessIds.includes(chessId)) { $(this).show(); } else { $(this).hide(); } } else { // 显示所有棋子 $(this).show(); } }); } let tooltipHistory = []; /** * 创建提示框 */ function createTooltip() { // 如果已经存在提示框,不重复创建 if ($('#tft-tooltip').length) { return; } const tooltip = $(` <div id="tft-tooltip" class="fixed z-50 hidden bg-gray-800 text-white rounded-lg p-4 shadow-lg border border-gray-700 w-80"> <div class="tooltip-content pt-2"></div> <div class="loading-spinner absolute inset-0 flex items-center justify-center bg-gray-800 bg-opacity-90 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> </div> </div> <button class="tooltip-close-btn absolute top-3 right-3 text-gray-400 hover:text-white bg-gray-700 hover:bg-gray-600 rounded-full w-8 h-8 flex items-center justify-center shadow-md z-20"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div> `); // 添加到页面 $('body').append(tooltip); // 绑定关闭按钮事件 tooltip.find('.tooltip-close-btn').on('click', function(e) { e.stopPropagation(); tooltip.addClass('hidden'); currentHoveredId = null; $('.tooltip-active').removeClass('tooltip-active'); // 重置历史记录 tooltipHistory = []; }); // 防止点击提示框本身触发关闭 tooltip.on('click', function(e) { e.stopPropagation(); }); } /** * 显示提示的返回按钮(重构为返回功能) * @param {jQuery} tooltipContent 提示内容元素 */ function showTooltipBackButton(tooltipContent) { const tooltip = tooltipContent.closest('#tft-tooltip'); // 获取关闭按钮 const closeButton = tooltip.find('.tooltip-close-btn'); // 如果历史记录中有内容,则显示返回功能 if (tooltipHistory.length > 0) { // 修改按钮图标为返回箭头 closeButton.html(` <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /> </svg> `); // 移除之前的事件处理程序 closeButton.off('click'); // 绑定返回按钮事件 closeButton.on('click', function(e) { e.stopPropagation(); // 弹出上一个提示内容 const prevItem = tooltipHistory.pop(); if (prevItem) { // 恢复ID和内容 currentHoveredId = prevItem.id; tooltipContent.html(prevItem.content); // 如果仍有历史记录,保持返回按钮 if (tooltipHistory.length > 0) { showTooltipBackButton(tooltipContent); } else { // 没有更多历史,切换回关闭按钮 resetCloseButton(closeButton, tooltip); } // 重新绑定事件处理程序 bindTooltipInteractions(tooltipContent); } }); // 使用返回样式 closeButton.removeClass('bg-gray-700 hover:bg-gray-600').addClass('bg-indigo-600 hover:bg-indigo-700'); } else { // 没有历史,重置为关闭按钮 resetCloseButton(closeButton, tooltip); } } /** * 重置为关闭按钮 * @param {jQuery} closeButton 按钮元素 * @param {jQuery} tooltip 提示框元素 */ function resetCloseButton(closeButton, tooltip) { // 修改按钮图标为关闭图标 closeButton.html(` <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> `); // 移除之前的事件处理程序 closeButton.off('click'); // 绑定关闭按钮事件 closeButton.on('click', function(e) { e.stopPropagation(); tooltip.addClass('hidden'); currentHoveredId = null; $('.tooltip-active').removeClass('tooltip-active'); // 重置历史记录 tooltipHistory = []; }); // 使用关闭样式 closeButton.removeClass('bg-indigo-600 hover:bg-indigo-700').addClass('bg-gray-700 hover:bg-gray-600'); } /** * 绑定提示内容中的交互事件 * @param {jQuery} tooltipContent 提示内容元素 */ function bindTooltipInteractions(tooltipContent) { // 为羁绊小标签添加点击事件,显示对应的羁绊详情 tooltipContent.find('.synergy-item-mini').on('click', function(e) { e.stopPropagation(); const synergyId = $(this).data('synergy-id'); if (synergyId && synergyId !== currentHoveredId) { // 保存当前的提示内容到历史记录 tooltipHistory.push({ id: currentHoveredId, content: tooltipContent.html() }); // 更新当前悬停ID并显示新的提示 currentHoveredId = synergyId; showSynergyTooltipContent(synergyId, this, tooltipContent); } }); // 为棋子小标签添加点击事件,显示对应的棋子详情 tooltipContent.find('.chess-item-mini').on('click', function(e) { e.stopPropagation(); const chessId = $(this).data('chess-id'); if (chessId && chessId !== currentHoveredId) { // 保存当前的提示内容到历史记录 tooltipHistory.push({ id: currentHoveredId, content: tooltipContent.html() }); // 更新当前悬停ID并显示新的提示 currentHoveredId = chessId; showChessTooltipContent(chessId, this, tooltipContent); } }); } /** * 显示棋子提示内容(不重新创建提示框) * @param {string} chessId 棋子ID * @param {Element} element 触发元素 * @param {jQuery} tooltipContent 提示内容元素 */ function showChessTooltipContent(chessId, element, tooltipContent) { const tooltip = tooltipContent.closest('#tft-tooltip'); const loadingSpinner = tooltip.find('.loading-spinner'); const isMobile = window.matchMedia("(max-width: 768px)").matches || ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); // 先定位提示框但不显示 positionTooltip(tooltip, element); // 重置内容并显示加载中 tooltipContent.empty(); loadingSpinner.removeClass('hidden'); // 获取棋子详情 fetchItemDetails('chess', chessId).then(response => { if (response.status === 'success') { const details = response.details; // 构建提示内容,添加左侧内边距给返回按钮留出空间 let content = ` <div class="mb-2 flex items-center justify-between pl-10"> <div> <span class="font-bold text-lg">${details.name}</span> ${details.title ? `<span class="text-xs text-gray-300 ml-1">${details.title}</span>` : ''} <span class="ml-2 px-2 py-1 bg-yellow-600 rounded-full text-xs">${details.cost}费</span> </div> </div> `; // 棋子介绍 if (details.introduce) { content += ` <div class="mb-3 text-sm italic text-gray-300 bg-gray-700 p-2 rounded"> ${details.introduce} </div> `; } // 将属性分组显示 // 判断是否有任何属性数据可以显示 const hasAnyAttribute = details.life || details.attack || details.attackSpeed || details.attackRange || details.armor || details.spellBlock || details.crit || details.magic || details.startMagic || details.attackMag; if (hasAnyAttribute) { content += `<div class="mb-3">`; // 所有属性都归类到基础属性组 content += `<div> <div class="text-xs text-indigo-300 font-semibold mb-1">基础属性</div> <div class="grid ${isMobile ? "grid-cols-1" : "grid-cols-2"} gap-2 bg-gray-700 p-2 rounded">`; if (details.life) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">生命值:</span> <span class="text-sm ml-1">${details.life}</span> </div>`; } if (details.attack) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">攻击力:</span> <span class="text-sm ml-1">${details.attack}</span> </div>`; } if (details.attackSpeed) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">攻速:</span> <span class="text-sm ml-1">${details.attackSpeed}</span> </div>`; } if (details.attackRange) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">攻击范围:</span> <span class="text-sm ml-1">${details.attackRange}</span> </div>`; } if (details.armor) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">护甲:</span> <span class="text-sm ml-1">${details.armor}</span> </div>`; } if (details.spellBlock) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">魔抗:</span> <span class="text-sm ml-1">${details.spellBlock}</span> </div>`; } if (details.crit) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">暴击率:</span> <span class="text-sm ml-1">${details.crit}%</span> </div>`; } if (details.magic) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">法力值:</span> <span class="text-sm ml-1">${details.magic}</span> </div>`; } if (details.startMagic) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">初始法力值:</span> <span class="text-sm ml-1">${details.startMagic}</span> </div>`; } if (details.attackMag) { content += ` <div class="flex items-center"> <span class="text-xs text-gray-400">法术强度倍率:</span> <span class="text-sm ml-1">${details.attackMag}</span> </div>`; } content += `</div></div>`; content += `</div>`; } // 技能信息 if (details.skill_name) { content += ` <div class="mb-3 bg-gray-700 p-2 rounded"> <div class="flex items-center justify-between"> <div class="font-semibold text-indigo-300">${details.skill_name}</div> ${details.skill_mana ? `<span class="bg-blue-600 text-xs px-2 py-0.5 rounded">${details.skill_mana}法力</span>` : ''} </div> ${details.skill_type ? `<div class="text-xs text-blue-300 mt-1">${details.skill_type}</div>` : ''} <!-- 技能描述和介绍 --> <div class="mt-2"> ${details.skill_description ? ` <div class="text-sm text-gray-300"> <div class="text-xs text-gray-400 mb-1">技能效果:</div> ${details.skill_description} </div> ` : ''} ${details.skill_introduce ? ` <div class="text-sm italic text-gray-300 mt-2 pt-2 border-t border-gray-600 skill-introduce"> <div class="text-xs text-gray-400 mb-1">技能简介:</div> ${details.skill_introduce} </div> ` : ''} </div> </div> `; } // 羁绊信息 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">${synergy.name}</span> <span class="bg-gray-600 text-xs px-1.5 py-0.5 rounded">${synergyTypes[synergy.type] || synergy.type}</span> </div> `; }); } // 隐藏加载中状态 loadingSpinner.addClass('hidden'); // 设置内容并显示提示框 tooltipContent.html(content); // 显示返回按钮 showTooltipBackButton(tooltipContent); // 绑定交互事件 bindTooltipInteractions(tooltipContent); // 所有内容准备好后显示提示框 tooltip.removeClass('hidden'); } else { // 处理错误 tooltipContent.html(`<div class="text-red-400">获取详情失败</div>`); loadingSpinner.addClass('hidden'); tooltip.removeClass('hidden'); } }).catch(error => { // 处理错误 tooltipContent.html(`<div class="text-red-400">请求出错</div>`); loadingSpinner.addClass('hidden'); tooltip.removeClass('hidden'); console.error('获取棋子详情失败:', error); }); } /** * 显示羁绊提示内容(不重新创建提示框) * @param {string} synergyId 羁绊ID * @param {Element} element 触发元素 * @param {jQuery} tooltipContent 提示内容元素 */ function showSynergyTooltipContent(synergyId, element, tooltipContent) { const tooltip = tooltipContent.closest('#tft-tooltip'); const loadingSpinner = tooltip.find('.loading-spinner'); const isMobile = window.matchMedia("(max-width: 768px)").matches || ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); // 先定位提示框但不显示 positionTooltip(tooltip, element); // 显示加载中 tooltipContent.empty(); loadingSpinner.removeClass('hidden'); // 获取羁绊详情 fetchItemDetails('synergy', synergyId).then(response => { if (response.status === 'success') { const details = response.details; // 构建提示内容,添加左侧内边距给返回按钮留出空间 let content = ` <div class="mb-2 pl-10"> <span class="font-bold text-lg">${details.name}</span> <span class="ml-2 px-2 py-1 bg-indigo-600 rounded-full text-xs"> ${details.type === 'job' ? '职业' : '特质'} </span> </div> `; // 羁绊介绍 - 优先使用 introduce 字段 if (details.introduce) { content += ` <div class="mb-3 text-sm italic text-gray-300 bg-gray-700 p-2 rounded font-medium synergy-introduce"> ${details.introduce} </div> `; } // 羁绊描述作为补充信息 - 如果有额外的描述信息 if (details.description && details.description !== details.introduce) { content += `<div class="mb-3 text-sm text-gray-300">${details.description}</div>`; } // 等级效果 if (details.levels && details.levels.length > 0) { content += `<div class="text-sm font-semibold text-indigo-300 mb-1">等级效果:</div>`; // 按等级排序 const sortedLevels = [...details.levels].sort((a, b) => { return parseInt(a.level) - parseInt(b.level); }); sortedLevels.forEach(levelInfo => { content += ` <div class="mb-2 bg-gray-700 p-2 rounded"> <div class="text-xs text-yellow-400 font-medium">${levelInfo.level}人口:</div> <div class="text-sm mt-1">${levelInfo.effect}</div> </div> `; }); } // 相关棋子 if (details.related_chess && details.related_chess.length > 0) { content += `<div class="text-sm font-semibold text-indigo-300 mt-4 mb-1">相关棋子 (${details.related_chess.length}):</div>`; // 按费用排序 const sortedChess = [...details.related_chess].sort((a, b) => { return parseInt(a.cost) - parseInt(b.cost); }); // 保持两列布局,因为棋子卡片较小 content += `<div class="grid grid-cols-2 gap-1 mt-1">`; sortedChess.forEach(chess => { const costClass = `cost-${chess.cost}`; content += ` <div class="bg-gray-700 rounded p-1 flex items-center ${costClass} chess-item-mini" data-chess-id="${chess.id}"> <span class="text-sm flex-grow truncate">${chess.name}</span> <span class="text-xs px-1.5 py-0.5 bg-gray-600 rounded">${chess.cost}费</span> </div> `; }); content += `</div>`; } // 隐藏加载中状态 loadingSpinner.addClass('hidden'); // 设置内容并显示提示框 tooltipContent.html(content); // 显示返回按钮 showTooltipBackButton(tooltipContent); // 绑定交互事件 bindTooltipInteractions(tooltipContent); // 所有内容准备好后显示提示框 tooltip.removeClass('hidden'); } else { // 处理错误 tooltipContent.html(`<div class="text-red-400">获取详情失败</div>`); loadingSpinner.addClass('hidden'); tooltip.removeClass('hidden'); } }).catch(error => { // 处理错误 tooltipContent.html(`<div class="text-red-400">请求出错</div>`); loadingSpinner.addClass('hidden'); tooltip.removeClass('hidden'); console.error('获取羁绊详情失败:', error); }); } /** * 显示棋子悬停提示 * @param {string} chessId 棋子ID * @param {Element} element 触发元素 */ function showChessTooltip(chessId, element) { const tooltip = $('#tft-tooltip'); const tooltipContent = tooltip.find('.tooltip-content'); const loadingSpinner = tooltip.find('.loading-spinner'); // 先隐藏提示框,在数据加载完成后再显示 tooltip.addClass('hidden'); // 重置历史记录 tooltipHistory = []; // 使用共享方法显示内容 showChessTooltipContent(chessId, element, tooltipContent); } /** * 显示羁绊悬停提示 * @param {string} synergyId 羁绊ID * @param {Element} element 触发元素 */ function showSynergyTooltip(synergyId, element) { const tooltip = $('#tft-tooltip'); const tooltipContent = tooltip.find('.tooltip-content'); const loadingSpinner = tooltip.find('.loading-spinner'); // 先隐藏提示框,在数据加载完成后再显示 tooltip.addClass('hidden'); // 重置历史记录 tooltipHistory = []; // 使用共享方法显示内容 showSynergyTooltipContent(synergyId, element, tooltipContent); } /** * 定位提示框 * @param {jQuery} tooltip 提示元素 * @param {Element} element 触发元素 */ function positionTooltip(tooltip, element) { const rect = element.getBoundingClientRect(); const isMobile = window.matchMedia("(max-width: 768px)").matches || ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); // 移动设备始终居中显示 if (isMobile) { // 移动设备上使用CSS定位在屏幕中央 // 样式表中已经设置了固定位置,这里不需要设置left和top tooltip.css({ // 重置定位,让CSS的固定定位生效 transform: '', maxHeight: `${window.innerHeight * 0.7}px`, zIndex: 1000 }); } else { // 桌面设备的定位逻辑 const tooltipWidth = 320; // 提示框宽度 // 计算水平位置,优先显示在右侧 let left = rect.right + 10; if (left + tooltipWidth > window.innerWidth) { // 右侧空间不足,显示在左侧 left = rect.left - tooltipWidth - 10; // 如果左侧也没有足够空间 if (left < 0) { left = Math.max(10, (window.innerWidth - tooltipWidth) / 2); // 居中显示 } } // 计算垂直位置,考虑提示框不超出屏幕 let top = rect.top; const tooltipHeight = Math.min(500, window.innerHeight * 0.8); // 预估高度 if (top + tooltipHeight > window.innerHeight) { // 尝试显示在元素上方 top = Math.max(10, rect.top - tooltipHeight); } // 设置位置 tooltip.css({ left: `${left}px`, top: `${top}px`, width: 'auto', maxHeight: `${window.innerHeight - 20}px` // 防止溢出屏幕 }); } } // 防抖函数 function debounce(func, wait) { let timeout; return function(...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; } /** * 获取棋子或羁绊的详细信息 * @param {string} type 类型,'chess'或'synergy' * @param {string} id 棋子或羁绊ID * @returns {Promise} 详情数据 */ function fetchItemDetails(type, id) { // 使用缓存避免重复请求 if (!window.tftDetailsCache) { window.tftDetailsCache = {}; } const cacheKey = `${type}_${id}`; if (window.tftDetailsCache[cacheKey]) { return Promise.resolve(window.tftDetailsCache[cacheKey]); } return new Promise((resolve, reject) => { $.ajax({ url: `/api/details?type=${type}&id=${id}`, method: 'GET', dataType: 'json', success: (response) => { // 缓存结果 if (response.status === 'success') { window.tftDetailsCache[cacheKey] = response; } resolve(response); }, error: reject }); }); } // 存储当前悬停元素ID let currentHoveredId = null; /** * 初始化悬停提示 */ function initTooltips() { // 创建提示框 createTooltip(); const tooltipElement = $('#tft-tooltip'); // 检测是否为移动设备 const isMobile = window.matchMedia("(max-width: 768px)").matches || ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); // 点击文档其它位置关闭提示框 $(document).on('click touchend', function(e) { if (!$(e.target).closest('#tft-tooltip, .chess-item, .synergy-item').length) { tooltipElement.addClass('hidden'); currentHoveredId = null; $('.tooltip-active').removeClass('tooltip-active'); } }); // 处理棋子触发事件 $(document).on(isMobile ? 'click' : 'mouseenter', '.chess-item', function(e) { // 避免在点击移除按钮时显示提示 if ($(e.target).closest('.remove-chess').length) { return; } // 在移动设备上点击时阻止冒泡 if (isMobile) { e.stopPropagation(); // 如果已经点击过同一个元素,则关闭提示 if (currentHoveredId === $(this).data('chess-id') && tooltipElement.is(':visible')) { tooltipElement.addClass('hidden'); currentHoveredId = null; $(this).removeClass('tooltip-active'); return; } } const chessId = $(this).data('chess-id'); if (chessId) { // 关闭之前可能打开的提示 $('.tooltip-active').removeClass('tooltip-active'); currentHoveredId = chessId; // 在移动设备上直接显示,非移动设备使用防抖 if (isMobile) { showChessTooltip(chessId, this); } else { // 使用防抖函数延迟显示提示 debounce((id, element) => { if (currentHoveredId === id) { showChessTooltip(id, element); } }, 50)(chessId, this); } // 添加活跃状态 $(this).addClass('tooltip-active'); } }); // 处理羁绊触发事件 $(document).on(isMobile ? 'click' : 'mouseenter', '.synergy-item', function(e) { // 避免在点击移除按钮时显示提示 if ($(e.target).closest('.remove-synergy').length) { return; } // 在移动设备上点击时阻止冒泡 if (isMobile) { e.stopPropagation(); // 如果已经点击过同一个元素,则关闭提示 if (currentHoveredId === $(this).data('synergy-id') && tooltipElement.is(':visible')) { tooltipElement.addClass('hidden'); currentHoveredId = null; $(this).removeClass('tooltip-active'); return; } } const synergyId = $(this).data('synergy-id'); if (synergyId) { // 关闭之前可能打开的提示 $('.tooltip-active').removeClass('tooltip-active'); currentHoveredId = synergyId; // 在移动设备上直接显示,非移动设备使用防抖 if (isMobile) { showSynergyTooltip(synergyId, this); } else { // 使用防抖函数延迟显示提示 debounce((id, element) => { if (currentHoveredId === id) { showSynergyTooltip(id, element); } }, 50)(synergyId, this); } // 添加活跃状态 $(this).addClass('tooltip-active'); } }); // 非移动设备的鼠标离开事件 if (!isMobile) { $(document).on('mouseleave', '.chess-item, .synergy-item', function() { currentHoveredId = null; // 移除活跃状态 $(this).removeClass('tooltip-active'); // 检查鼠标是否在提示框上 if (!tooltipElement.is(':hover')) { tooltipElement.addClass('hidden'); } }); // 避免提示框本身触发悬停事件 tooltipElement.on('mouseenter', function() { // 保持显示 }).on('mouseleave', function() { $(this).addClass('hidden'); currentHoveredId = null; $('.tooltip-active').removeClass('tooltip-active'); }); } // 处理点击事件,避免与提示框冲突 $(document).on('click', '.remove-chess, .remove-synergy', function(e) { e.stopPropagation(); tooltipElement.addClass('hidden'); currentHoveredId = null; }); // 处理窗口滚动时隐藏提示 $(window).on('scroll', function() { tooltipElement.addClass('hidden'); currentHoveredId = null; }); }