1447 lines
52 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 云顶之弈阵容推荐器 - 前端脚本
// 页面加载完成后执行
$(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;
});
}