实现权重配置更新和详细信息获取API,优化错误处理,新增棋子和羁绊的详细信息展示功能,提升用户体验和代码可维护性。
This commit is contained in:
parent
ee7daf9ad1
commit
79bd57b79d
121
src/web/app.py
121
src/web/app.py
@ -199,15 +199,120 @@ def create_app(config_path: Optional[str] = None):
|
||||
|
||||
@app.route('/api/weights', methods=['POST'])
|
||||
def update_weights():
|
||||
"""更新权重配置API - 已弃用,仅保留API兼容性"""
|
||||
"""更新权重配置API"""
|
||||
try:
|
||||
data = request.json
|
||||
weights_config.update_config_data(data)
|
||||
return jsonify({'status': 'success'})
|
||||
except Exception as e:
|
||||
logger.exception("更新权重配置失败")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': str(e)
|
||||
}), 500
|
||||
|
||||
@app.route('/api/details', methods=['GET'])
|
||||
def get_details():
|
||||
"""获取棋子或羁绊的详细信息API"""
|
||||
try:
|
||||
item_type = request.args.get('type') # 'chess' 或 'synergy'
|
||||
item_id = request.args.get('id')
|
||||
|
||||
if not item_type or not item_id:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': '缺少必要参数'
|
||||
}), 400
|
||||
|
||||
# 获取详细信息
|
||||
if item_type == 'chess':
|
||||
# 获取棋子详情
|
||||
chess = data_api.get_chess_by_id(item_id)
|
||||
if not chess:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'未找到ID为{item_id}的棋子'
|
||||
}), 404
|
||||
|
||||
# 获取棋子的所有羁绊
|
||||
synergies = data_api.get_synergies_of_chess(item_id)
|
||||
|
||||
# 构建详细信息
|
||||
details = {
|
||||
'id': chess.get('chessId'),
|
||||
'name': chess.get('displayName', ''),
|
||||
'cost': chess.get('price', 0),
|
||||
'skill_name': chess.get('skillName', ''),
|
||||
'skill_description': chess.get('skillDescription', ''),
|
||||
'skill_introduce': chess.get('skillIntroduce', ''),
|
||||
# 添加更多棋子属性
|
||||
'health': chess.get('health', ''),
|
||||
'attack_damage': chess.get('attackDamage', ''),
|
||||
'attack_speed': chess.get('attackSpeed', ''),
|
||||
'attack_range': chess.get('range', ''),
|
||||
'armor': chess.get('armor', ''),
|
||||
'magic_resist': chess.get('magicResist', ''),
|
||||
'synergies': [{
|
||||
'id': synergy.get('jobId') or synergy.get('raceId'),
|
||||
'name': synergy.get('name', ''),
|
||||
'type': 'job' if 'jobId' in synergy else 'race'
|
||||
} for synergy in synergies]
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': '权重配置已更新(本地存储模式)'
|
||||
'type': 'chess',
|
||||
'details': details
|
||||
})
|
||||
|
||||
elif item_type == 'synergy':
|
||||
# 获取羁绊详情
|
||||
synergy = data_api.get_synergy_by_id(item_id)
|
||||
if not synergy:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'未找到ID为{item_id}的羁绊'
|
||||
}), 404
|
||||
|
||||
# 获取羁绊的所有等级信息
|
||||
synergy_levels = synergy.get('level', {})
|
||||
|
||||
# 获取该羁绊的所有棋子
|
||||
related_chess = data_api.get_chess_by_synergy(item_id)
|
||||
|
||||
# 构建详细信息
|
||||
details = {
|
||||
'id': synergy.get('jobId') or synergy.get('raceId'),
|
||||
'name': synergy.get('name', ''),
|
||||
'description': synergy.get('description', ''),
|
||||
'introduce': synergy.get('introduce', ''),
|
||||
'type': 'job' if 'jobId' in synergy else 'race',
|
||||
'levels': [{
|
||||
'level': level,
|
||||
'effect': effect
|
||||
} for level, effect in synergy_levels.items()],
|
||||
# 添加相关棋子
|
||||
'related_chess': [{
|
||||
'id': chess.get('chessId'),
|
||||
'name': chess.get('displayName', ''),
|
||||
'cost': chess.get('price', 0)
|
||||
} for chess in related_chess]
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'type': 'synergy',
|
||||
'details': details
|
||||
})
|
||||
|
||||
else:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'不支持的类型: {item_type}'
|
||||
}), 400
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("更新权重配置时发生错误")
|
||||
logger.exception("获取详细信息失败")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': str(e)
|
||||
@ -220,16 +325,16 @@ def create_temp_config(base_config: Dict[str, Any],
|
||||
synergy_weights: Dict[str, float],
|
||||
chess_weights: Dict[str, float]) -> Dict[str, Any]:
|
||||
"""
|
||||
创建临时配置对象,不修改原始配置
|
||||
创建临时权重配置
|
||||
|
||||
Args:
|
||||
base_config: 基础配置
|
||||
base_weights: 用户自定义的基础权重
|
||||
synergy_weights: 用户自定义的羁绊权重
|
||||
chess_weights: 用户自定义的棋子权重
|
||||
base_weights: 基础权重
|
||||
synergy_weights: 羁绊权重
|
||||
chess_weights: 棋子权重
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 临时配置对象
|
||||
Dict[str, Any]: 临时配置
|
||||
"""
|
||||
# 深拷贝基础配置,避免修改原始配置
|
||||
temp_config = copy.deepcopy(base_config)
|
||||
|
@ -40,6 +40,127 @@
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 悬停提示样式 */
|
||||
.chess-item, .synergy-item {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 活跃状态样式 */
|
||||
.chess-item.tooltip-active, .synergy-item.tooltip-active {
|
||||
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.4);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* 增强提示框样式 */
|
||||
#tft-tooltip {
|
||||
transition: opacity 0.2s, transform 0.2s;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
||||
transform-origin: top left;
|
||||
animation: tooltipFadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* 提示框中的费用标识 */
|
||||
#tft-tooltip .cost-1 { border-left: 3px solid #94a3b8; } /* 1费 - 灰色 */
|
||||
#tft-tooltip .cost-2 { border-left: 3px solid #65a30d; } /* 2费 - 绿色 */
|
||||
#tft-tooltip .cost-3 { border-left: 3px solid #2563eb; } /* 3费 - 蓝色 */
|
||||
#tft-tooltip .cost-4 { border-left: 3px solid #7e22ce; } /* 4费 - 紫色 */
|
||||
#tft-tooltip .cost-5 { border-left: 3px solid #f59e0b; } /* 5费 - 金色 */
|
||||
|
||||
/* 提示框内交互元素样式 */
|
||||
.chess-item-mini, .synergy-item-mini {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chess-item-mini:hover, .synergy-item-mini:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.chess-item-mini:hover::after, .synergy-item-mini:hover::after {
|
||||
content: '查看详情';
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
right: 5px;
|
||||
background: rgba(79, 70, 229, 0.9);
|
||||
color: white;
|
||||
font-size: 8px;
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
@keyframes tooltipFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-5px) scale(0.98);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 提示框中的各部分样式 */
|
||||
#tft-tooltip .tooltip-content {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 提示框返回按钮 */
|
||||
.tooltip-back-button {
|
||||
opacity: 0.7;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.tooltip-back-button:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
background-color: rgba(79, 70, 229, 0.9);
|
||||
}
|
||||
|
||||
/* 特殊介绍文本样式 */
|
||||
#tft-tooltip .skill-introduce,
|
||||
#tft-tooltip .synergy-introduce {
|
||||
border-left: 3px solid rgba(79, 70, 229, 0.7);
|
||||
background-color: rgba(79, 70, 229, 0.1);
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
#tft-tooltip .loading-spinner {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 优化悬停标识 */
|
||||
.chess-item::after, .synergy-item::after {
|
||||
content: '?';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: rgba(79, 70, 229, 0.9);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s, transform 0.2s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.chess-item:hover::after, .synergy-item:hover::after {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 羁绊标签样式 */
|
||||
.synergy-tag {
|
||||
background-color: #e0e7ff;
|
||||
|
@ -40,7 +40,7 @@ $(document).ready(function() {
|
||||
|
||||
// 添加到列表
|
||||
const synergyItem = $(`
|
||||
<div id="required-synergy-${synergyId}" class="flex items-center justify-between bg-indigo-50 p-2 rounded" data-synergy-id="${synergyId}">
|
||||
<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">
|
||||
@ -74,7 +74,7 @@ $(document).ready(function() {
|
||||
|
||||
// 添加到列表
|
||||
const chessItem = $(`
|
||||
<div id="required-chess-${chessId}" class="flex items-center justify-between bg-indigo-50 p-2 rounded" data-chess-id="${chessId}">
|
||||
<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">
|
||||
@ -121,6 +121,9 @@ $(document).ready(function() {
|
||||
$('#reset-to-default').on('click', function() {
|
||||
resetToDefaultWeights();
|
||||
});
|
||||
|
||||
// 初始化悬停提示
|
||||
initTooltips();
|
||||
});
|
||||
|
||||
/**
|
||||
@ -506,7 +509,7 @@ function renderResults(results) {
|
||||
const chessList = teamElement.find('.chess-list');
|
||||
result.chess_list.forEach(chess => {
|
||||
const chessCard = $(`
|
||||
<div class="chess-card p-2 chess-cost-${chess.cost}">
|
||||
<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>
|
||||
@ -518,7 +521,7 @@ function renderResults(results) {
|
||||
const synergyList = teamElement.find('.synergy-list');
|
||||
result.active_synergies.forEach(synergy => {
|
||||
const synergyTag = $(`
|
||||
<div class="synergy-tag">
|
||||
<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>
|
||||
@ -613,18 +616,622 @@ function toggleActiveSynergiesVisibility(showOnlyActive) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前激活的棋子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) {
|
||||
// 隐藏所有棋子权重项
|
||||
$('.chess-weight-item').hide();
|
||||
// 显示已激活棋子项
|
||||
$('.chess-weight-item.active').show();
|
||||
// 仅显示激活的棋子
|
||||
if (activeChessIds.includes(chessId)) {
|
||||
$(this).show();
|
||||
} else {
|
||||
// 显示所有棋子权重项
|
||||
$('.chess-weight-item').show();
|
||||
$(this).hide();
|
||||
}
|
||||
} else {
|
||||
// 显示所有棋子
|
||||
$(this).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let tooltipHistory = [];
|
||||
|
||||
/**
|
||||
* 创建悬停提示
|
||||
*/
|
||||
function createTooltip() {
|
||||
// 创建提示元素
|
||||
if ($('#tft-tooltip').length === 0) {
|
||||
$('body').append(`
|
||||
<div id="tft-tooltip" class="hidden fixed z-50 p-4 bg-gray-800 text-white rounded-lg shadow-lg max-w-sm">
|
||||
<div class="tooltip-content relative"></div>
|
||||
<div class="loading-spinner hidden flex justify-center py-2">
|
||||
<div class="animate-spin rounded-full h-6 w-6 border-t-2 border-b-2 border-white"></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示的返回按钮
|
||||
* @param {jQuery} tooltipContent 提示内容元素
|
||||
*/
|
||||
function showTooltipBackButton(tooltipContent) {
|
||||
// 如果历史记录中有内容,则显示返回按钮
|
||||
if (tooltipHistory.length > 0) {
|
||||
const backButton = $(`
|
||||
<div class="tooltip-back-button absolute top-1 left-1 bg-gray-600 rounded-full w-6 h-6 flex items-center justify-center cursor-pointer">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" 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>
|
||||
</div>
|
||||
`);
|
||||
|
||||
tooltipContent.append(backButton);
|
||||
|
||||
// 添加返回按钮点击事件
|
||||
backButton.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);
|
||||
}
|
||||
|
||||
// 重新绑定事件处理程序
|
||||
bindTooltipInteractions(tooltipContent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定提示内容中的交互事件
|
||||
* @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');
|
||||
|
||||
// 显示加载中
|
||||
tooltipContent.empty();
|
||||
loadingSpinner.removeClass('hidden');
|
||||
|
||||
// 获取棋子详情
|
||||
fetchItemDetails('chess', chessId).then(response => {
|
||||
if (response.status === 'success') {
|
||||
const details = response.details;
|
||||
|
||||
// 构建提示内容,与showChessTooltip相同的内容构建逻辑
|
||||
let content = `
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<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 attributes = {
|
||||
'health': '生命值',
|
||||
'attack_damage': '攻击力',
|
||||
'attack_speed': '攻速',
|
||||
'attack_range': '攻击范围',
|
||||
'armor': '护甲',
|
||||
'magic_resist': '魔抗'
|
||||
};
|
||||
|
||||
// 检查是否有属性数据
|
||||
const hasAttributes = Object.keys(attributes).some(key => details[key]);
|
||||
|
||||
if (hasAttributes) {
|
||||
content += `<div class="grid grid-cols-2 gap-2 mb-3 bg-gray-700 p-2 rounded">`;
|
||||
for (const [key, label] of Object.entries(attributes)) {
|
||||
if (details[key]) {
|
||||
content += `
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs text-gray-400">${label}:</span>
|
||||
<span class="text-sm ml-1">${details[key]}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 附加属性(如果有)
|
||||
const extraAttributes = {
|
||||
'crit_rate': '暴击率',
|
||||
'crit_damage': '暴击伤害',
|
||||
'dodge': '闪避',
|
||||
'speed': '移速'
|
||||
};
|
||||
|
||||
for (const [key, label] of Object.entries(extraAttributes)) {
|
||||
if (details[key]) {
|
||||
content += `
|
||||
<div class="flex items-center">
|
||||
<span class="text-xs text-gray-400">${label}:</span>
|
||||
<span class="text-sm ml-1">${details[key]}</span>
|
||||
</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>${synergy.name}</span>
|
||||
<span class="bg-gray-600 text-xs px-1.5 py-0.5 rounded">${synergyTypes[synergy.type] || synergy.type}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
tooltipContent.html(content);
|
||||
|
||||
// 显示返回按钮
|
||||
showTooltipBackButton(tooltipContent);
|
||||
|
||||
// 绑定交互事件
|
||||
bindTooltipInteractions(tooltipContent);
|
||||
} else {
|
||||
tooltipContent.html(`<div class="text-red-400">获取详情失败</div>`);
|
||||
}
|
||||
|
||||
loadingSpinner.addClass('hidden');
|
||||
}).catch(error => {
|
||||
tooltipContent.html(`<div class="text-red-400">请求出错</div>`);
|
||||
loadingSpinner.addClass('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');
|
||||
|
||||
// 显示加载中
|
||||
tooltipContent.empty();
|
||||
loadingSpinner.removeClass('hidden');
|
||||
|
||||
// 获取羁绊详情
|
||||
fetchItemDetails('synergy', synergyId).then(response => {
|
||||
if (response.status === 'success') {
|
||||
const details = response.details;
|
||||
|
||||
// 构建提示内容,与showSynergyTooltip相同的内容构建逻辑
|
||||
let content = `
|
||||
<div class="mb-2">
|
||||
<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>
|
||||
${chess.title ? `<span class="text-xs text-gray-400 mr-1 truncate" title="${chess.title}">${chess.title}</span>` : ''}
|
||||
<span class="text-xs px-1.5 py-0.5 bg-gray-600 rounded">${chess.cost}费</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
content += `</div>`;
|
||||
}
|
||||
|
||||
tooltipContent.html(content);
|
||||
|
||||
// 显示返回按钮
|
||||
showTooltipBackButton(tooltipContent);
|
||||
|
||||
// 绑定交互事件
|
||||
bindTooltipInteractions(tooltipContent);
|
||||
} else {
|
||||
tooltipContent.html(`<div class="text-red-400">获取详情失败</div>`);
|
||||
}
|
||||
|
||||
loadingSpinner.addClass('hidden');
|
||||
}).catch(error => {
|
||||
tooltipContent.html(`<div class="text-red-400">请求出错</div>`);
|
||||
loadingSpinner.addClass('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');
|
||||
|
||||
// 定位提示框
|
||||
positionTooltip(tooltip, element);
|
||||
|
||||
// 重置历史记录
|
||||
tooltipHistory = [];
|
||||
|
||||
// 显示加载中
|
||||
tooltipContent.empty();
|
||||
loadingSpinner.removeClass('hidden');
|
||||
tooltip.removeClass('hidden');
|
||||
|
||||
// 使用共享方法显示内容
|
||||
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');
|
||||
|
||||
// 定位提示框
|
||||
positionTooltip(tooltip, element);
|
||||
|
||||
// 重置历史记录
|
||||
tooltipHistory = [];
|
||||
|
||||
// 显示加载中
|
||||
tooltipContent.empty();
|
||||
loadingSpinner.removeClass('hidden');
|
||||
tooltip.removeClass('hidden');
|
||||
|
||||
// 使用共享方法显示内容
|
||||
showSynergyTooltipContent(synergyId, element, tooltipContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定位提示框
|
||||
* @param {jQuery} tooltip 提示元素
|
||||
* @param {Element} element 触发元素
|
||||
*/
|
||||
function positionTooltip(tooltip, element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
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`,
|
||||
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 debouncedShowChessTooltip = debounce((chessId, element) => {
|
||||
if (currentHoveredId === chessId) {
|
||||
showChessTooltip(chessId, element);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
$(document).on('mouseenter', '.chess-item', function(e) {
|
||||
// 避免在点击移除按钮时显示提示
|
||||
if ($(e.target).closest('.remove-chess').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chessId = $(this).data('chess-id');
|
||||
if (chessId) {
|
||||
currentHoveredId = chessId;
|
||||
debouncedShowChessTooltip(chessId, this);
|
||||
|
||||
// 添加活跃状态
|
||||
$(this).addClass('tooltip-active');
|
||||
}
|
||||
});
|
||||
|
||||
// 羁绊悬停事件 - 使用防抖
|
||||
const debouncedShowSynergyTooltip = debounce((synergyId, element) => {
|
||||
if (currentHoveredId === synergyId) {
|
||||
showSynergyTooltip(synergyId, element);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
$(document).on('mouseenter', '.synergy-item', function(e) {
|
||||
// 避免在点击移除按钮时显示提示
|
||||
if ($(e.target).closest('.remove-synergy').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const synergyId = $(this).data('synergy-id');
|
||||
if (synergyId) {
|
||||
currentHoveredId = synergyId;
|
||||
debouncedShowSynergyTooltip(synergyId, this);
|
||||
|
||||
// 添加活跃状态
|
||||
$(this).addClass('tooltip-active');
|
||||
}
|
||||
});
|
||||
|
||||
// 离开时隐藏提示
|
||||
$(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;
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user