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