TierListMaker.js:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 标签:已被回退 |
无编辑摘要 标签:已被回退 |
||
| 第1行: | 第1行: | ||
/** | |||
* Tier List Maker | |||
* 为角色列表添加拖拽排序功能 | |||
*/ | |||
(function() { | (function() { | ||
'use strict'; | 'use strict'; | ||
// | // 检查是否在正确的页面 | ||
if ( | if (!$('.wikitable').length) { | ||
return; | return; | ||
} | } | ||
// | // 加载必要的库 | ||
loadDragula(function() { | |||
initTierList(); | |||
}); | }); | ||
function | function loadDragula(callback) { | ||
// | // 加载CSS | ||
if (!$('#dragula-css').length) { | |||
$('<link>') | |||
.attr('id', 'dragula-css') | |||
.attr('rel', 'stylesheet') | |||
.attr('href', 'https://cdn.jsdelivr.net/npm/dragula@3.7.3/dist/dragula.min.css') | |||
.appendTo('head'); | |||
} | |||
// | // 加载JS | ||
if (typeof dragula === 'undefined') { | |||
$.getScript('https://cdn.jsdelivr.net/npm/dragula@3.7.3/dist/dragula.min.js') | |||
.done(function() { | |||
callback(); | |||
}) | |||
.fail(function() { | |||
console.error('加载Dragula失败'); | |||
}); | |||
} else { | |||
callback(); | |||
} | |||
} | } | ||
function | function initTierList() { | ||
addStyles(); | |||
createAvatarPool(); | |||
processAvatars(); | |||
setupDragAndDrop(); | |||
addExportButton(); | |||
addSaveLoadButtons(); | |||
checkUrlForTierList(); | |||
} | } | ||
function | function addStyles() { | ||
var | var css = ` | ||
#avatar-pool { | |||
. | background: #f5f5f5; | ||
. | border: 2px dashed #ccc; | ||
border-radius: 8px; | |||
padding: 15px; | |||
margin: 20px 0; | |||
min-height: 120px; | |||
display: flex; | |||
flex-wrap: wrap; | |||
gap: 10px; | |||
align-items: flex-start; | |||
} | |||
#avatar-pool.gu-over { | |||
background: #e8f4f8; | |||
border-color: #4a9eff; | |||
} | |||
.avatar-frame { | |||
display: inline-flex; | |||
flex-direction: column; | |||
align-items: center; | |||
padding: 8px; | |||
background: white; | |||
border: 2px solid #ddd; | |||
border-radius: 8px; | |||
cursor: move; | |||
transition: all 0.2s; | |||
margin: 4px; | |||
min-width: 80px; | |||
} | |||
.avatar-frame:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 8px rgba(0,0,0,0.15); | |||
border-color: #4a9eff; | |||
} | |||
.avatar-frame.gu-transit { | |||
opacity: 0.8; | |||
transform: scale(1.05); | |||
} | |||
.avatar-frame img { | |||
width: 64px; | |||
height: 64px; | |||
border-radius: 4px; | |||
margin-bottom: 5px; | |||
} | |||
.avatar-name { | |||
font-size: 12px; | |||
text-align: center; | |||
color: #333; | |||
word-break: break-word; | |||
max-width: 80px; | |||
} | |||
.wikitable td { | |||
vertical-align: top; | |||
min-height: 100px; | |||
} | |||
.wikitable td:nth-child(2) { | |||
background: #fafafa; | |||
padding: 10px; | |||
display: flex; | |||
flex-wrap: wrap; | |||
gap: 8px; | |||
align-content: flex-start; | |||
} | |||
.wikitable td:nth-child(2).gu-over { | |||
background: #e8f4f8; | |||
} | |||
.gu-mirror { | |||
cursor: grabbing !important; | |||
opacity: 0.9; | |||
z-index: 9999 !important; | |||
} | |||
#export-tierlist-btn, | |||
#save-tierlist-btn, | |||
#load-tierlist-btn, | |||
#share-tierlist-btn { | |||
margin: 10px 5px; | |||
padding: 12px 24px; | |||
font-size: 16px; | |||
font-weight: bold; | |||
border: none; | |||
border-radius: 6px; | |||
cursor: pointer; | |||
transition: all 0.3s; | |||
color: white; | |||
} | |||
#export-tierlist-btn { | |||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |||
} | |||
#export-tierlist-btn:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); | |||
} | |||
#save-tierlist-btn { | |||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |||
} | |||
#save-tierlist-btn:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.4); | |||
} | |||
#load-tierlist-btn { | |||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); | |||
} | |||
#load-tierlist-btn:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 12px rgba(79, 172, 254, 0.4); | |||
} | |||
#share-tierlist-btn { | |||
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); | |||
} | |||
#share-tierlist-btn:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 12px rgba(67, 233, 123, 0.4); | |||
} | |||
.tier-dialog-overlay { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
right: 0; | |||
bottom: 0; | |||
background: rgba(0, 0, 0, 0.7); | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
z-index: 10000; | |||
animation: fadeIn 0.3s; | |||
} | |||
@keyframes fadeIn { | |||
from { opacity: 0; } | |||
to { opacity: 1; } | |||
} | |||
.tier-dialog { | |||
background: white; | |||
border-radius: 12px; | |||
padding: 30px; | |||
max-width: 500px; | |||
width: 90%; | |||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); | |||
animation: slideUp 0.3s; | |||
} | |||
@keyframes slideUp { | |||
from { | |||
transform: translateY(50px); | |||
opacity: 0; | |||
} | } | ||
to { | |||
transform: translateY(0); | |||
opacity: 1; | |||
} | } | ||
} | |||
.tier- | |||
.tier-dialog h3 { | |||
margin: 0 0 20px 0; | |||
color: #333; | |||
font-size: 24px; | |||
} | |||
.tier-dialog input { | |||
width: 100%; | |||
padding: 12px; | |||
border: 2px solid #ddd; | |||
border-radius: 6px; | |||
font-size: 16px; | |||
margin-bottom: 20px; | |||
box-sizing: border-box; | |||
} | |||
.tier-dialog input:focus { | |||
outline: none; | |||
border-color: #4a9eff; | |||
} | |||
.tier-dialog-buttons { | |||
display: flex; | |||
gap: 10px; | |||
justify-content: flex-end; | |||
} | |||
.tier-dialog-btn { | |||
padding: 10px 20px; | |||
border: none; | |||
border-radius: 6px; | |||
font-size: 16px; | |||
cursor: pointer; | |||
transition: all 0.3s; | |||
} | |||
.tier-dialog-btn-primary { | |||
background: #4a9eff; | |||
color: white; | |||
} | |||
.tier-dialog-btn-primary:hover { | |||
background: #3a8eef; | |||
} | |||
.tier-dialog-btn-secondary { | |||
background: #e0e0e0; | |||
color: #333; | |||
} | |||
.tier-dialog-btn-secondary:hover { | |||
background: #d0d0d0; | |||
} | |||
.tier-info-box { | |||
background: #f0f8ff; | |||
border: 2px solid #4a9eff; | |||
border-radius: 8px; | |||
padding: 15px; | |||
margin: 15px 0; | |||
} | |||
.tier-info-box p { | |||
margin: 8px 0; | |||
color: #333; | |||
} | |||
.tier-info-box strong { | |||
color: #4a9eff; | |||
} | |||
.tier-copy-btn { | |||
background: #4a9eff; | |||
color: white; | |||
border: none; | |||
padding: 6px 12px; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
margin-left: 10px; | |||
font-size: 14px; | |||
} | |||
.tier-copy-btn:hover { | |||
background: #3a8eef; | |||
} | |||
.tier-notification { | |||
position: fixed; | |||
top: 20px; | |||
right: 20px; | |||
padding: 15px 25px; | |||
border-radius: 8px; | |||
color: white; | |||
font-size: 16px; | |||
z-index: 10001; | |||
animation: slideInRight 0.3s; | |||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); | |||
} | |||
@keyframes slideInRight { | |||
from { | |||
transform: translateX(100%); | |||
opacity: 0; | |||
} | } | ||
to { | |||
transform: translateX(0); | |||
opacity: 1; | |||
} | } | ||
} | |||
.tier-notification.success { | |||
background: #4caf50; | |||
} | |||
.tier-notification.error { | |||
background: #f44336; | |||
} | |||
.tier-notification.info { | |||
background: #2196f3; | |||
} | |||
`; | `; | ||
$(' | |||
$('<style>').text(css).appendTo('head'); | |||
} | } | ||
function | function createAvatarPool() { | ||
var $pool = $('<div>') | |||
var $ | |||
.attr('id', 'avatar-pool') | .attr('id', 'avatar-pool') | ||
. | .html('<h3 style="width: 100%; margin: 0 0 10px 0; color: #666;">角色池 - 拖拽角色到对应层级</h3>'); | ||
$('.wikitable').before($pool); | |||
} | |||
function processAvatars() { | |||
var avatarSelector = '.wikitable td a[href*="character"] img, .wikitable td img[src*="Face_character"]'; | |||
$ | $(avatarSelector).each(function() { | ||
var $img = $(this); | |||
var $link = $img.closest('a'); | |||
var $td = $img.closest('td'); | |||
// 获取角色名称 | |||
var name = $img.attr('alt') || $img.attr('title') || $link.attr('title') || ''; | |||
// 创建头像框架 | |||
var $frame = $('<div>') | |||
.addClass('avatar-frame') | |||
.append($img.clone()) | |||
.append($('<div>').addClass('avatar-name').text(name)); | |||
// 添加唯一标识 | |||
var uniqueId = 'avatar_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); | |||
$frame.attr('data-tier-id', uniqueId); | |||
// 移除原始内容 | |||
if ($link.length) { | |||
$link.remove(); | |||
} else { | |||
$img.remove(); | |||
} | |||
// 添加到头像池 | |||
$('#avatar-pool').append($frame); | |||
}); | |||
// | // 清理空的td | ||
$('.wikitable td').each(function() { | |||
var $td = $(this); | |||
$(this). | if ($td.children().length === 0 && $td.text().trim() === '') { | ||
// 不删除,只是清空 | |||
} | |||
}); | }); | ||
} | |||
function setupDragAndDrop() { | |||
var containers = [$('#avatar-pool')[0]].concat($('.wikitable td:nth-child(2)').toArray()); | |||
$('.wikitable'). | var drake = dragula(containers, { | ||
isContainer: function (el) { | |||
return $(el).is('#avatar-pool') || $(el).is('.wikitable td:nth-child(2)'); | |||
}, | |||
moves: function (el, container, handle) { | |||
return $(el).hasClass('avatar-frame'); | |||
}, | |||
accepts: function (el, target) { | |||
return $(target).is('#avatar-pool') || $(target).is('.wikitable td:nth-child(2)'); | |||
}, | |||
copy: false, | |||
revertOnSpill: true | |||
}); | |||
// | drake.on('drop', function(el, target, source, sibling) { | ||
var $el = $(el); | |||
var $target = $(target); | |||
console.log('角色被拖放'); | |||
console.log('目标容器:', $target.prop('tagName'), $target.attr('class') || $target.attr('id')); | |||
// 如果放到tier中,添加标记 | |||
if ($target.is('.wikitable td:nth-child(2)')) { | |||
$el.addClass('in-tier'); | |||
console.log('角色放入层级,添加 in-tier 类'); | |||
} else if ($target.is('#avatar-pool')) { | |||
$el.removeClass('in-tier'); | |||
console.log('角色放回池中,移除 in-tier 类'); | |||
} | |||
}); | |||
} | } | ||
function | function addExportButton() { | ||
var $button = $('<button>') | |||
.attr('id', 'export-tierlist-btn') | |||
.text('📸 导出为图片') | |||
.click(function() { | |||
loadHtml2Canvas(function() { | |||
captureTierList(); | |||
}); | |||
}); | }); | ||
$('.wikitable').after($button); | |||
} | } | ||
function | function addSaveLoadButtons() { | ||
$('. | var $saveBtn = $('<button>') | ||
.attr('id', 'save-tierlist-btn') | |||
.text('💾 保存榜单') | |||
.click(function() { | |||
saveTierList(); | |||
}); | }); | ||
var $loadBtn = $('<button>') | |||
.attr('id', 'load-tierlist-btn') | |||
.text('📂 加载榜单') | |||
.click(function() { | |||
showLoadDialog(); | |||
}); | }); | ||
var $shareBtn = $('<button>') | |||
.attr('id', 'share-tierlist-btn') | |||
.text('🔗 分享榜单') | |||
.click(function() { | |||
showShareDialog(); | |||
}); | }); | ||
$('#export-tierlist-btn').after($saveBtn).after($loadBtn).after($shareBtn); | |||
} | } | ||
function | function saveTierList() { | ||
var tierData = collectTierData(); | |||
console.log('=== 开始保存榜单 ==='); | |||
console.log('收集到的层级数:', tierData.tiers.length); | |||
console.log('池中角色数:', tierData.pool.length); | |||
// | // 检查是否有数据要保存 | ||
var totalAvatars = tierData.pool.length; | |||
tierData.tiers.forEach(function(tier) { | |||
totalAvatars += tier.avatars.length; | |||
}); | }); | ||
if ( | console.log('总角色数:', totalAvatars); | ||
alert(' | |||
if (totalAvatars === 0) { | |||
alert('没有检测到任何角色数据!'); | |||
return; | return; | ||
} | |||
if (tierData.tiers.length === 0) { | |||
var confirm = window.confirm('您还没有将任何角色放入层级中,确定要保存空榜单吗?'); | |||
if (!confirm) { | |||
return; | |||
} | |||
} | } | ||
| 第460行: | 第510行: | ||
}; | }; | ||
console.log('保存的数据:', saveData); | |||
localStorage.setItem('tierlist_' + listId, JSON.stringify(saveData)); | localStorage.setItem('tierlist_' + listId, JSON.stringify(saveData)); | ||
// | console.log('已保存到 localStorage,key:', 'tierlist_' + listId); | ||
// 验证保存 | |||
var saved = localStorage.getItem('tierlist_' + listId); | |||
console.log('验证保存成功:', saved ? '是' : '否'); | |||
showSaveSuccessDialog(listId); | showSaveSuccessDialog(listId); | ||
} | } | ||
function collectTierData() { | function collectTierData() { | ||
var data = { | var data = { | ||
| 第474行: | 第529行: | ||
}; | }; | ||
// | // 收集各个tier中的数据 | ||
$('.wikitable tr').each(function( | $('.wikitable tr').each(function() { | ||
var $cells = $(this).find('td'); | var $cells = $(this).find('td'); | ||
if ($cells.length > | if ($cells.length >= 2) { | ||
var tierName = $cells.eq(0).text().trim(); | var tierName = $cells.eq(0).text().trim(); | ||
var | var $tierCell = $cells.eq(1); | ||
$ | // 检查这个单元格是否包含头像 | ||
var $avatarsInTier = $tierCell.find('.avatar-frame'); | |||
console.log('检查层级:', tierName, '包含', $avatarsInTier.length, '个角色'); | |||
if ( | if ($avatarsInTier.length > 0) { | ||
var avatars = []; | |||
$avatarsInTier.each(function() { | |||
var avatarId = getAvatarIdentifier($(this)); | |||
if (avatarId) { | |||
avatars.push(avatarId); | |||
console.log('收集角色:', avatarId); | |||
} | |||
}); | }); | ||
if (avatars.length > 0) { | |||
data.tiers.push({ | |||
name: tierName, | |||
avatars: avatars | |||
}); | |||
console.log('保存层级:', tierName, '共', avatars.length, '个角色'); | |||
} | |||
} | } | ||
} | } | ||
| 第504行: | 第570行: | ||
} | } | ||
}); | }); | ||
console.log('最终收集的数据:', data); | |||
console.log('层级数量:', data.tiers.length); | |||
console.log('池中角色数量:', data.pool.length); | |||
return data; | return data; | ||
} | } | ||
function getAvatarIdentifier($avatar) { | |||
// 优先使用稳定的标识符 | |||
function getAvatarIdentifier($avatar) { | var identifier = {}; | ||
// 获取图片信息 | |||
var $img = $avatar.find('img'); | |||
if ($img.length > 0) { | |||
identifier.src = $img.attr('src') || ''; | |||
identifier.alt = $img.attr('alt') || ''; | |||
identifier.title = $img.attr('title') || ''; | |||
} | |||
} | |||
// 获取文本内容 | |||
var $text = $avatar.find('.avatar-name'); | |||
if ($text.length > 0) { | |||
identifier.name = $text.text().trim(); | |||
} | |||
// 获取data属性 | |||
if ($avatar.attr('data-avatar-id')) { | |||
identifier.dataId = $avatar.attr('data-avatar-id'); | |||
} | |||
// 如果没有找到任何标识,使用outerHTML | |||
if (Object.keys(identifier).length === 0) { | |||
identifier.html = $avatar.prop('outerHTML'); | |||
} | |||
// 添加一个唯一的data属性用于后续查找 | |||
if (!$avatar.attr('data-tier-id')) { | |||
var uniqueId = 'avatar_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); | |||
$avatar.attr('data-tier-id', uniqueId); | |||
identifier.tierId = uniqueId; | |||
} else { | |||
identifier.tierId = $avatar.attr('data-tier-id'); | |||
} | |||
return JSON.stringify(identifier); | |||
} | |||
function showSaveSuccessDialog(listId) { | |||
var pageUrl = window.location.origin + window.location.pathname; | |||
var fullUrl = pageUrl + '?tierlist=' + listId; | |||
var $ | var $overlay = $('<div>') | ||
.addClass('tier-dialog-overlay') | |||
.click(function(e) { | |||
if (e.target === this) { | |||
.click(function() { | $(this).remove(); | ||
} | |||
$(this). | |||
} | |||
}); | }); | ||
var $dialog = $('<div>').addClass('tier-dialog'); | |||
var $ | |||
$dialog.html(` | |||
<h3>✅ 榜单保存成功!</h3> | |||
<div class="tier-info-box"> | |||
<p><strong>榜单ID:</strong> ${listId}</p> | |||
<p><strong>分享链接:</strong></p> | |||
<input type="text" value="${fullUrl}" readonly style="margin-bottom: 10px;"> | |||
</div> | |||
<p style="color: #666; font-size: 14px;">您可以通过以下方式访问这个榜单:</p> | |||
<ul style="color: #666; font-size: 14px; margin: 10px 0;"> | |||
<li>点击"加载榜单"按钮,输入ID:<code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">${listId}</code></li> | |||
<li>复制分享链接发送给其他人</li> | |||
</ul> | |||
<div class="tier-dialog-buttons"> | |||
<button class="tier-dialog-btn tier-dialog-btn-secondary" onclick="this.closest('.tier-dialog-overlay').remove()">关闭</button> | |||
<button class="tier-dialog-btn tier-dialog-btn-primary" id="copy-url-btn">复制链接</button> | |||
</div> | |||
`); | |||
$overlay.append($dialog); | $overlay.append($dialog); | ||
$('body').append($overlay); | $('body').append($overlay); | ||
// 复制链接按钮 | |||
$('#copy-url-btn').click(function() { | |||
var $input = $dialog.find('input'); | |||
$input.select(); | |||
document.execCommand('copy'); | |||
showNotification('链接已复制到剪贴板!', 'success'); | |||
}); | |||
// 自动选中链接 | |||
setTimeout(function() { | |||
$dialog.find('input').select(); | |||
}, 100); | |||
} | } | ||
function showLoadDialog() { | function showLoadDialog() { | ||
var $overlay = $('<div>').addClass('tier-dialog-overlay'); | var $overlay = $('<div>') | ||
.addClass('tier-dialog-overlay') | |||
.click(function(e) { | |||
if (e.target === this) { | |||
$(this).remove(); | |||
} | |||
}); | |||
var $dialog = $('<div>').addClass('tier-dialog'); | var $dialog = $('<div>').addClass('tier-dialog'); | ||
var | // 获取所有保存的榜单 | ||
var savedLists = []; | |||
for (var i = 0; i < localStorage.length; i++) { | |||
var key = localStorage.key(i); | |||
if (key.startsWith('tierlist_')) { | |||
try { | |||
var data = JSON.parse(localStorage.getItem(key)); | |||
savedLists.push(data); | |||
} catch (e) { | |||
console.error('解析榜单失败:', key, e); | |||
} | |||
} | |||
} | |||
// 按时间排序 | |||
savedLists.sort(function(a, b) { | |||
return new Date(b.timestamp) - new Date(a.timestamp); | |||
. | }); | ||
var | var listHtml = ''; | ||
if (savedLists.length > 0) { | |||
. | listHtml = '<div style="max-height: 300px; overflow-y: auto; margin: 15px 0;">'; | ||
savedLists.forEach(function(list) { | |||
var date = new Date(list.timestamp).toLocaleString('zh-CN'); | |||
var tierCount = list.data.tiers.length; | |||
var avatarCount = list.data.pool.length; | |||
list.data.tiers.forEach(function(tier) { | |||
avatarCount += tier.avatars.length; | |||
var | }); | ||
} | |||
listHtml += ` | |||
<div style="border: 1px solid #ddd; border-radius: 6px; padding: 10px; margin-bottom: 10px; cursor: pointer; transition: all 0.2s;" | |||
class="saved-list-item" | |||
data-list-id="${list.id}" | |||
onmouseover="this.style.background='#f0f8ff'; this.style.borderColor='#4a9eff';" | |||
onmouseout="this.style.background='white'; this.style.borderColor='#ddd';"> | |||
<div style="font-weight: bold; margin-bottom: 5px;">ID: ${list.id}</div> | |||
<div style="font-size: 12px; color: #666;">保存时间: ${date}</div> | |||
<div style="font-size: 12px; color: #666;">包含 ${tierCount} 个层级,${avatarCount} 个角色</div> | |||
</div> | |||
`; | |||
}); | }); | ||
listHtml += '</div>'; | |||
} | |||
$dialog.html(` | |||
<h3>📂 加载榜单</h3> | |||
<p style="color: #666; margin-bottom: 15px;">输入榜单ID或从下方选择:</p> | |||
<input type="text" id="tierlist-id-input" placeholder="输入榜单ID" style="margin-bottom: 15px;"> | |||
${listHtml || '<p style="color: #999; text-align: center; padding: 20px;">暂无保存的榜单</p>'} | |||
<div class="tier-dialog-buttons"> | |||
<button class="tier-dialog-btn tier-dialog-btn-secondary" onclick="this.closest('.tier-dialog-overlay').remove()">取消</button> | |||
<button class="tier-dialog-btn tier-dialog-btn-primary" id="load-btn">加载</button> | |||
</div> | |||
`); | |||
$overlay.append($dialog); | $overlay.append($dialog); | ||
$('body').append($overlay); | $('body').append($overlay); | ||
// | // 点击列表项选择 | ||
$('.saved-list-item').click(function() { | |||
$('.saved-list-item').css('background', 'white').css('border-color', '#ddd'); | |||
$(this).css('background', '#e8f4f8').css('border-color', '#4a9eff'); | |||
$('#tierlist-id-input').val($(this).data('list-id')); | |||
}); | |||
// 加载按钮 | |||
$('#load-btn').click(function() { | |||
var listId = $('#tierlist-id-input').val().trim(); | |||
if (!listId) { | |||
alert('请输入或选择榜单ID'); | |||
return; | |||
} | |||
if (loadTierListData(listId)) { | |||
$overlay.remove(); | |||
} else { | |||
alert('加载失败:未找到该榜单'); | |||
} | |||
}); | |||
// | // 回车加载 | ||
$input. | $('#tierlist-id-input').keypress(function(e) { | ||
if (e.which === 13) { | if (e.which === 13) { | ||
$ | $('#load-btn').click(); | ||
} | } | ||
}); | }); | ||
} | } | ||
function loadTierListData(listId) { | |||
function loadTierListData(listId) { | var savedData = localStorage.getItem('tierlist_' + listId); | ||
console.log('尝试加载榜单 ID:', listId); | |||
console.log('本地存储数据:', savedData); | |||
if (!savedData) { | |||
console.error('未找到榜单数据'); | |||
return false; | |||
} | |||
try { | |||
var saveData = JSON.parse(savedData); | |||
console.log('解析后的数据:', saveData); | |||
var | applyTierData(saveData.data); | ||
showNotification('榜单加载成功!', 'success'); | |||
return true; | |||
} catch (e) { | |||
console.error('加载榜单失败:', e); | |||
return false; | |||
} | } | ||
} | } | ||
function applyTierData(tierData) { | function applyTierData(tierData) { | ||
// | console.log('应用榜单数据:', tierData); | ||
$('.wikitable td .avatar-frame').each(function() { | |||
// 首先清空所有层级,将所有角色移回池中 | |||
$('.wikitable td:nth-child(2) .avatar-frame').each(function() { | |||
$(this).removeClass('in-tier'); | $(this).removeClass('in-tier'); | ||
$('#avatar-pool').append($(this)); | $('#avatar-pool').append($(this)); | ||
}); | }); | ||
console.log('总共需要恢复', tierData.tiers.length, '个层级'); | |||
// 应用tier数据 | // 应用tier数据 | ||
tierData.tiers.forEach(function(tier) { | tierData.tiers.forEach(function(tier, index) { | ||
console.log('处理层级', index, ':', tier.name, '包含', tier.avatars.length, '个角色'); | |||
var $targetCell = null; | var $targetCell = null; | ||
| 第821行: | 第816行: | ||
if (tierName === tier.name) { | if (tierName === tier.name) { | ||
$targetCell = $cells.eq(1); | $targetCell = $cells.eq(1); | ||
console.log('找到目标单元格:', tierName); | |||
return false; | return false; | ||
} | } | ||
| 第827行: | 第823行: | ||
if ($targetCell) { | if ($targetCell) { | ||
tier.avatars.forEach(function(avatarId) { | tier.avatars.forEach(function(avatarId, avatarIndex) { | ||
console.log('查找角色', avatarIndex, ':', avatarId); | |||
var $avatar = findAvatarByIdentifier(avatarId); | var $avatar = findAvatarByIdentifier(avatarId); | ||
if ($avatar) { | if ($avatar) { | ||
console.log('找到角色,移动到', tier.name); | |||
$avatar.addClass('in-tier'); | $avatar.addClass('in-tier'); | ||
$targetCell.append($avatar); | $targetCell.append($avatar); | ||
} else { | |||
console.warn('未找到角色:', avatarId); | |||
} | } | ||
}); | }); | ||
} else { | |||
console.warn('未找到层级单元格:', tier.name); | |||
} | } | ||
}); | }); | ||
console.log('榜单应用完成'); | |||
} | } | ||
function findAvatarByIdentifier(identifierStr) { | |||
function | try { | ||
var identifier = JSON.parse(identifierStr); | |||
var $found = null; | |||
var | |||
var | |||
var | $('.avatar-frame').each(function() { | ||
var $avatar = $(this); | |||
data | // 优先通过 tierId 匹配 | ||
if (identifier.tierId && $avatar.attr('data-tier-id') === identifier.tierId) { | |||
$found = $avatar; | |||
return false; | |||
} | |||
// 通过图片src匹配 | |||
var $img = $avatar.find('img'); | |||
if (identifier.src && $img.attr('src') === identifier.src) { | |||
// 进一步验证name | |||
var $name = $avatar.find('.avatar-name'); | |||
if (identifier.name && $name.text().trim() === identifier.name) { | |||
$found = $avatar; | |||
// 设置tierId以便下次快速查找 | |||
if (identifier.tierId) { | |||
$avatar.attr('data-tier-id', identifier.tierId); | |||
} | |||
return false; | |||
} | |||
} | |||
// 通过name匹配(备用方案) | |||
if (identifier.name) { | |||
var $name = $avatar.find('.avatar-name'); | |||
if ($name.text().trim() === identifier.name) { | |||
$found = $avatar; | |||
return false; | |||
} | |||
} | |||
}); | |||
return $found; | |||
} catch (e) { | |||
console.error('解析角色标识符失败:', e); | |||
return null; | |||
} | } | ||
} | } | ||
function showShareDialog() { | |||
function showShareDialog( | // 先保存当前榜单 | ||
var | var tierData = collectTierData(); | ||
var | var listId = generateUniqueId(); | ||
var timestamp = new Date().toISOString(); | |||
var | var saveData = { | ||
id: listId, | |||
timestamp: timestamp, | |||
data: tierData | |||
}; | |||
localStorage.setItem('tierlist_' + listId, JSON.stringify(saveData)); | |||
// 生成分享链接 | |||
var pageUrl = window.location.origin + window.location.pathname; | |||
var fullUrl = pageUrl + '?tierlist=' + listId; | |||
var $ | var $overlay = $('<div>') | ||
.addClass('tier-dialog-overlay') | |||
.click(function(e) { | |||
if (e.target === this) { | |||
.click(function() { | $(this).remove(); | ||
} | |||
$(this). | |||
} | |||
}); | }); | ||
$ | var $dialog = $('<div>').addClass('tier-dialog'); | ||
$dialog.html(` | |||
<h3>🔗 分享榜单</h3> | |||
<div class="tier-info-box"> | |||
<p><strong>分享链接:</strong></p> | |||
<input type="text" value="${fullUrl}" readonly id="share-url-input"> | |||
<p style="margin-top: 10px; font-size: 14px; color: #666;"> | |||
将此链接发送给好友,他们打开后可以看到您的榜单排序。 | |||
</p> | |||
</div> | |||
<div class="tier-dialog-buttons"> | |||
<button class="tier-dialog-btn tier-dialog-btn-secondary" onclick="this.closest('.tier-dialog-overlay').remove()">关闭</button> | |||
<button class="tier-dialog-btn tier-dialog-btn-primary" id="share-copy-btn">复制链接</button> | |||
</div> | |||
`); | |||
$overlay.append($dialog); | $overlay.append($dialog); | ||
$('body').append($overlay); | $('body').append($overlay); | ||
// 复制按钮 | |||
$('#share-copy-btn').click(function() { | |||
var $input = $('#share-url-input'); | |||
$input.select(); | |||
document.execCommand('copy'); | |||
showNotification('分享链接已复制!', 'success'); | |||
}); | |||
// 自动选中 | |||
setTimeout(function() { | |||
$('#share-url-input').select(); | |||
}, 100); | |||
} | } | ||
function checkUrlForTierList() { | |||
function | |||
var urlParams = new URLSearchParams(window.location.search); | var urlParams = new URLSearchParams(window.location.search); | ||
var | var tierlistId = urlParams.get('tierlist'); | ||
if ( | if (tierlistId) { | ||
console.log('检测到URL中的榜单ID:', tierlistId); | |||
setTimeout(function() { | setTimeout(function() { | ||
if (loadTierListData( | if (loadTierListData(tierlistId)) { | ||
showNotification(' | showNotification('已自动加载分享的榜单', 'info'); | ||
} else { | } else { | ||
showNotification(' | showNotification('无法加载分享的榜单', 'error'); | ||
} | } | ||
}, 500); | }, 500); | ||
| 第959行: | 第964行: | ||
} | } | ||
function generateUniqueId() { | |||
function | return Date.now().toString(36) + Math.random().toString(36).substr(2, 9); | ||
} | } | ||
function showNotification(message, type) { | function showNotification(message, type) { | ||
var $notification = $('<div>') | var $notification = $('<div>') | ||
. | .addClass('tier-notification') | ||
.addClass(type || 'info') | |||
.text(message); | .text(message); | ||
| 第1,003行: | 第983行: | ||
} | } | ||
function | function loadHtml2Canvas(callback) { | ||
if (typeof html2canvas === 'undefined') { | if (typeof html2canvas === 'undefined') { | ||
$.getScript('https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js') | $.getScript('https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js') | ||
.done(function() { | .done(function() { | ||
| 第1,021行: | 第990行: | ||
}) | }) | ||
.fail(function() { | .fail(function() { | ||
alert(' | alert('加载html2canvas失败,请检查网络连接'); | ||
}); | }); | ||
}); | } else { | ||
callback(); | |||
} | |||
} | } | ||
function captureTierList() { | function captureTierList() { | ||
var $table = $('.wikitable'); | var $table = $('.wikitable').first().clone(); | ||
if ($ | // 移除空的tier单元格中的占位内容 | ||
$table.find('td:nth-child(2)').each(function() { | |||
if ($(this).find('.avatar-frame').length === 0) { | |||
} | $(this).empty(); | ||
} | |||
}); | |||
// | // 创建临时容器 | ||
var $ | var $container = $('<div>') | ||
.css({ | .css({ | ||
position: 'fixed', | |||
left: '-9999px', | |||
top: '0', | |||
background: 'white', | |||
padding: '20px', | |||
minWidth: '800px' | |||
}) | }) | ||
. | .append($table); | ||
. | |||
$('body').append($container); | |||
// 添加标题 | |||
var $title = $('<h2>') | |||
.text('我的Tier榜单') | |||
.css({ | |||
textAlign: 'center', | |||
marginBottom: '20px', | |||
color: '#333' | |||
}); | |||
$container.prepend($title); | |||
html2canvas($ | html2canvas($container[0], { | ||
backgroundColor: '#ffffff', | backgroundColor: '#ffffff', | ||
scale: 2, | scale: 2, | ||
| 第1,058行: | 第1,039行: | ||
allowTaint: true | allowTaint: true | ||
}).then(function(canvas) { | }).then(function(canvas) { | ||
$ | $container.remove(); | ||
// 转换为blob并下载 | // 转换为blob并下载 | ||
canvas.toBlob(function(blob) { | canvas.toBlob(function(blob) { | ||
var url = URL.createObjectURL(blob); | var url = URL.createObjectURL(blob); | ||
var | var a = document.createElement('a'); | ||
a.href = url; | |||
a.download = 'tierlist_' + Date.now() + '.png'; | |||
document.body.appendChild(a); | |||
a.click(); | |||
document.body.removeChild(a); | |||
URL.revokeObjectURL(url); | URL.revokeObjectURL(url); | ||
showNotification('榜单图片已导出!', 'success'); | |||
}); | }); | ||
}).catch(function(error) { | }).catch(function(error) { | ||
$ | $container.remove(); | ||
console.error(' | console.error('导出失败:', error); | ||
alert(' | alert('导出图片失败:' + error.message); | ||
}); | }); | ||
} | } | ||
})(); | })(); | ||
/** | |||
* 使用说明: | |||
* | |||
* 1. 拖拽功能: | |||
* - 从角色池拖拽角色到对应的层级 | |||
* - 在层级之间拖拽调整 | |||
* - 拖回角色池移除 | |||
* | |||
* 2. 保存功能: | |||
* - 点击"保存榜单"保存当前排序 | |||
* - 系统会生成唯一ID | |||
* - 数据保存在浏览器本地存储中 | |||
* | |||
* 3. 加载功能: | |||
* - 点击"加载榜单"查看所有已保存的榜单 | |||
* - 可以选择榜单或输入ID加载 | |||
* - 支持从URL参数自动加载 | |||
* | |||
* 4. 分享功能: | |||
* - 点击"分享榜单"生成分享链接 | |||
* - 复制链接发送给他人 | |||
* - 他人打开链接自动加载您的榜单 | |||
* | |||
* 5. 导出功能: | |||
* - 点击"导出为图片"将榜单保存为PNG图片 | |||
* - 图片包含所有层级和角色排序 | |||
* | |||
* 技术特性: | |||
* - 使用 dragula.js 实现拖拽 | |||
* - 使用 html2canvas 实现截图 | |||
* - 使用 localStorage 保存数据 | |||
* - 支持 URL 参数传递榜单 | |||
* - 响应式设计,支持移动端 | |||
* | |||
* 数据格式: | |||
* { | |||
* "id": "唯一标识", | |||
* "timestamp": "保存时间", | |||
* "data": { | |||
* "tiers": [ | |||
* { | |||
* "name": "层级名称", | |||
* "avatars": ["角色标识1", "角色标识2", ...] | |||
* } | |||
* ], | |||
* "pool": ["未分配的角色标识"] | |||
* } | |||
* } | |||
* | |||
* 角色标识格式: | |||
* { | |||
* "src": "图片路径", | |||
* "name": "角色名称", | |||
* "tierId": "唯一ID" | |||
* } | |||
*/ | |||
2025年10月8日 (三) 12:51的版本
/**
* Tier List Maker
* 为角色列表添加拖拽排序功能
*/
(function() {
'use strict';
// 检查是否在正确的页面
if (!$('.wikitable').length) {
return;
}
// 加载必要的库
loadDragula(function() {
initTierList();
});
function loadDragula(callback) {
// 加载CSS
if (!$('#dragula-css').length) {
$('<link>')
.attr('id', 'dragula-css')
.attr('rel', 'stylesheet')
.attr('href', 'https://cdn.jsdelivr.net/npm/dragula@3.7.3/dist/dragula.min.css')
.appendTo('head');
}
// 加载JS
if (typeof dragula === 'undefined') {
$.getScript('https://cdn.jsdelivr.net/npm/dragula@3.7.3/dist/dragula.min.js')
.done(function() {
callback();
})
.fail(function() {
console.error('加载Dragula失败');
});
} else {
callback();
}
}
function initTierList() {
addStyles();
createAvatarPool();
processAvatars();
setupDragAndDrop();
addExportButton();
addSaveLoadButtons();
checkUrlForTierList();
}
function addStyles() {
var css = `
#avatar-pool {
background: #f5f5f5;
border: 2px dashed #ccc;
border-radius: 8px;
padding: 15px;
margin: 20px 0;
min-height: 120px;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: flex-start;
}
#avatar-pool.gu-over {
background: #e8f4f8;
border-color: #4a9eff;
}
.avatar-frame {
display: inline-flex;
flex-direction: column;
align-items: center;
padding: 8px;
background: white;
border: 2px solid #ddd;
border-radius: 8px;
cursor: move;
transition: all 0.2s;
margin: 4px;
min-width: 80px;
}
.avatar-frame:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
border-color: #4a9eff;
}
.avatar-frame.gu-transit {
opacity: 0.8;
transform: scale(1.05);
}
.avatar-frame img {
width: 64px;
height: 64px;
border-radius: 4px;
margin-bottom: 5px;
}
.avatar-name {
font-size: 12px;
text-align: center;
color: #333;
word-break: break-word;
max-width: 80px;
}
.wikitable td {
vertical-align: top;
min-height: 100px;
}
.wikitable td:nth-child(2) {
background: #fafafa;
padding: 10px;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-content: flex-start;
}
.wikitable td:nth-child(2).gu-over {
background: #e8f4f8;
}
.gu-mirror {
cursor: grabbing !important;
opacity: 0.9;
z-index: 9999 !important;
}
#export-tierlist-btn,
#save-tierlist-btn,
#load-tierlist-btn,
#share-tierlist-btn {
margin: 10px 5px;
padding: 12px 24px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
color: white;
}
#export-tierlist-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
#export-tierlist-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
#save-tierlist-btn {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
#save-tierlist-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.4);
}
#load-tierlist-btn {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
#load-tierlist-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(79, 172, 254, 0.4);
}
#share-tierlist-btn {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
#share-tierlist-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(67, 233, 123, 0.4);
}
.tier-dialog-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.tier-dialog {
background: white;
border-radius: 12px;
padding: 30px;
max-width: 500px;
width: 90%;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
animation: slideUp 0.3s;
}
@keyframes slideUp {
from {
transform: translateY(50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.tier-dialog h3 {
margin: 0 0 20px 0;
color: #333;
font-size: 24px;
}
.tier-dialog input {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 6px;
font-size: 16px;
margin-bottom: 20px;
box-sizing: border-box;
}
.tier-dialog input:focus {
outline: none;
border-color: #4a9eff;
}
.tier-dialog-buttons {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.tier-dialog-btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
}
.tier-dialog-btn-primary {
background: #4a9eff;
color: white;
}
.tier-dialog-btn-primary:hover {
background: #3a8eef;
}
.tier-dialog-btn-secondary {
background: #e0e0e0;
color: #333;
}
.tier-dialog-btn-secondary:hover {
background: #d0d0d0;
}
.tier-info-box {
background: #f0f8ff;
border: 2px solid #4a9eff;
border-radius: 8px;
padding: 15px;
margin: 15px 0;
}
.tier-info-box p {
margin: 8px 0;
color: #333;
}
.tier-info-box strong {
color: #4a9eff;
}
.tier-copy-btn {
background: #4a9eff;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
margin-left: 10px;
font-size: 14px;
}
.tier-copy-btn:hover {
background: #3a8eef;
}
.tier-notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
border-radius: 8px;
color: white;
font-size: 16px;
z-index: 10001;
animation: slideInRight 0.3s;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.tier-notification.success {
background: #4caf50;
}
.tier-notification.error {
background: #f44336;
}
.tier-notification.info {
background: #2196f3;
}
`;
$('<style>').text(css).appendTo('head');
}
function createAvatarPool() {
var $pool = $('<div>')
.attr('id', 'avatar-pool')
.html('<h3 style="width: 100%; margin: 0 0 10px 0; color: #666;">角色池 - 拖拽角色到对应层级</h3>');
$('.wikitable').before($pool);
}
function processAvatars() {
var avatarSelector = '.wikitable td a[href*="character"] img, .wikitable td img[src*="Face_character"]';
$(avatarSelector).each(function() {
var $img = $(this);
var $link = $img.closest('a');
var $td = $img.closest('td');
// 获取角色名称
var name = $img.attr('alt') || $img.attr('title') || $link.attr('title') || '';
// 创建头像框架
var $frame = $('<div>')
.addClass('avatar-frame')
.append($img.clone())
.append($('<div>').addClass('avatar-name').text(name));
// 添加唯一标识
var uniqueId = 'avatar_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
$frame.attr('data-tier-id', uniqueId);
// 移除原始内容
if ($link.length) {
$link.remove();
} else {
$img.remove();
}
// 添加到头像池
$('#avatar-pool').append($frame);
});
// 清理空的td
$('.wikitable td').each(function() {
var $td = $(this);
if ($td.children().length === 0 && $td.text().trim() === '') {
// 不删除,只是清空
}
});
}
function setupDragAndDrop() {
var containers = [$('#avatar-pool')[0]].concat($('.wikitable td:nth-child(2)').toArray());
var drake = dragula(containers, {
isContainer: function (el) {
return $(el).is('#avatar-pool') || $(el).is('.wikitable td:nth-child(2)');
},
moves: function (el, container, handle) {
return $(el).hasClass('avatar-frame');
},
accepts: function (el, target) {
return $(target).is('#avatar-pool') || $(target).is('.wikitable td:nth-child(2)');
},
copy: false,
revertOnSpill: true
});
drake.on('drop', function(el, target, source, sibling) {
var $el = $(el);
var $target = $(target);
console.log('角色被拖放');
console.log('目标容器:', $target.prop('tagName'), $target.attr('class') || $target.attr('id'));
// 如果放到tier中,添加标记
if ($target.is('.wikitable td:nth-child(2)')) {
$el.addClass('in-tier');
console.log('角色放入层级,添加 in-tier 类');
} else if ($target.is('#avatar-pool')) {
$el.removeClass('in-tier');
console.log('角色放回池中,移除 in-tier 类');
}
});
}
function addExportButton() {
var $button = $('<button>')
.attr('id', 'export-tierlist-btn')
.text('📸 导出为图片')
.click(function() {
loadHtml2Canvas(function() {
captureTierList();
});
});
$('.wikitable').after($button);
}
function addSaveLoadButtons() {
var $saveBtn = $('<button>')
.attr('id', 'save-tierlist-btn')
.text('💾 保存榜单')
.click(function() {
saveTierList();
});
var $loadBtn = $('<button>')
.attr('id', 'load-tierlist-btn')
.text('📂 加载榜单')
.click(function() {
showLoadDialog();
});
var $shareBtn = $('<button>')
.attr('id', 'share-tierlist-btn')
.text('🔗 分享榜单')
.click(function() {
showShareDialog();
});
$('#export-tierlist-btn').after($saveBtn).after($loadBtn).after($shareBtn);
}
function saveTierList() {
var tierData = collectTierData();
console.log('=== 开始保存榜单 ===');
console.log('收集到的层级数:', tierData.tiers.length);
console.log('池中角色数:', tierData.pool.length);
// 检查是否有数据要保存
var totalAvatars = tierData.pool.length;
tierData.tiers.forEach(function(tier) {
totalAvatars += tier.avatars.length;
});
console.log('总角色数:', totalAvatars);
if (totalAvatars === 0) {
alert('没有检测到任何角色数据!');
return;
}
if (tierData.tiers.length === 0) {
var confirm = window.confirm('您还没有将任何角色放入层级中,确定要保存空榜单吗?');
if (!confirm) {
return;
}
}
var listId = generateUniqueId();
var timestamp = new Date().toISOString();
var saveData = {
id: listId,
timestamp: timestamp,
data: tierData
};
console.log('保存的数据:', saveData);
localStorage.setItem('tierlist_' + listId, JSON.stringify(saveData));
console.log('已保存到 localStorage,key:', 'tierlist_' + listId);
// 验证保存
var saved = localStorage.getItem('tierlist_' + listId);
console.log('验证保存成功:', saved ? '是' : '否');
showSaveSuccessDialog(listId);
}
function collectTierData() {
var data = {
tiers: [],
pool: []
};
// 收集各个tier中的数据
$('.wikitable tr').each(function() {
var $cells = $(this).find('td');
if ($cells.length >= 2) {
var tierName = $cells.eq(0).text().trim();
var $tierCell = $cells.eq(1);
// 检查这个单元格是否包含头像
var $avatarsInTier = $tierCell.find('.avatar-frame');
console.log('检查层级:', tierName, '包含', $avatarsInTier.length, '个角色');
if ($avatarsInTier.length > 0) {
var avatars = [];
$avatarsInTier.each(function() {
var avatarId = getAvatarIdentifier($(this));
if (avatarId) {
avatars.push(avatarId);
console.log('收集角色:', avatarId);
}
});
if (avatars.length > 0) {
data.tiers.push({
name: tierName,
avatars: avatars
});
console.log('保存层级:', tierName, '共', avatars.length, '个角色');
}
}
}
});
// 收集头像池中的数据
$('#avatar-pool .avatar-frame').each(function() {
var avatarId = getAvatarIdentifier($(this));
if (avatarId) {
data.pool.push(avatarId);
}
});
console.log('最终收集的数据:', data);
console.log('层级数量:', data.tiers.length);
console.log('池中角色数量:', data.pool.length);
return data;
}
function getAvatarIdentifier($avatar) {
// 优先使用稳定的标识符
var identifier = {};
// 获取图片信息
var $img = $avatar.find('img');
if ($img.length > 0) {
identifier.src = $img.attr('src') || '';
identifier.alt = $img.attr('alt') || '';
identifier.title = $img.attr('title') || '';
}
// 获取文本内容
var $text = $avatar.find('.avatar-name');
if ($text.length > 0) {
identifier.name = $text.text().trim();
}
// 获取data属性
if ($avatar.attr('data-avatar-id')) {
identifier.dataId = $avatar.attr('data-avatar-id');
}
// 如果没有找到任何标识,使用outerHTML
if (Object.keys(identifier).length === 0) {
identifier.html = $avatar.prop('outerHTML');
}
// 添加一个唯一的data属性用于后续查找
if (!$avatar.attr('data-tier-id')) {
var uniqueId = 'avatar_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
$avatar.attr('data-tier-id', uniqueId);
identifier.tierId = uniqueId;
} else {
identifier.tierId = $avatar.attr('data-tier-id');
}
return JSON.stringify(identifier);
}
function showSaveSuccessDialog(listId) {
var pageUrl = window.location.origin + window.location.pathname;
var fullUrl = pageUrl + '?tierlist=' + listId;
var $overlay = $('<div>')
.addClass('tier-dialog-overlay')
.click(function(e) {
if (e.target === this) {
$(this).remove();
}
});
var $dialog = $('<div>').addClass('tier-dialog');
$dialog.html(`
<h3>✅ 榜单保存成功!</h3>
<div class="tier-info-box">
<p><strong>榜单ID:</strong> ${listId}</p>
<p><strong>分享链接:</strong></p>
<input type="text" value="${fullUrl}" readonly style="margin-bottom: 10px;">
</div>
<p style="color: #666; font-size: 14px;">您可以通过以下方式访问这个榜单:</p>
<ul style="color: #666; font-size: 14px; margin: 10px 0;">
<li>点击"加载榜单"按钮,输入ID:<code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px;">${listId}</code></li>
<li>复制分享链接发送给其他人</li>
</ul>
<div class="tier-dialog-buttons">
<button class="tier-dialog-btn tier-dialog-btn-secondary" onclick="this.closest('.tier-dialog-overlay').remove()">关闭</button>
<button class="tier-dialog-btn tier-dialog-btn-primary" id="copy-url-btn">复制链接</button>
</div>
`);
$overlay.append($dialog);
$('body').append($overlay);
// 复制链接按钮
$('#copy-url-btn').click(function() {
var $input = $dialog.find('input');
$input.select();
document.execCommand('copy');
showNotification('链接已复制到剪贴板!', 'success');
});
// 自动选中链接
setTimeout(function() {
$dialog.find('input').select();
}, 100);
}
function showLoadDialog() {
var $overlay = $('<div>')
.addClass('tier-dialog-overlay')
.click(function(e) {
if (e.target === this) {
$(this).remove();
}
});
var $dialog = $('<div>').addClass('tier-dialog');
// 获取所有保存的榜单
var savedLists = [];
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key.startsWith('tierlist_')) {
try {
var data = JSON.parse(localStorage.getItem(key));
savedLists.push(data);
} catch (e) {
console.error('解析榜单失败:', key, e);
}
}
}
// 按时间排序
savedLists.sort(function(a, b) {
return new Date(b.timestamp) - new Date(a.timestamp);
});
var listHtml = '';
if (savedLists.length > 0) {
listHtml = '<div style="max-height: 300px; overflow-y: auto; margin: 15px 0;">';
savedLists.forEach(function(list) {
var date = new Date(list.timestamp).toLocaleString('zh-CN');
var tierCount = list.data.tiers.length;
var avatarCount = list.data.pool.length;
list.data.tiers.forEach(function(tier) {
avatarCount += tier.avatars.length;
});
listHtml += `
<div style="border: 1px solid #ddd; border-radius: 6px; padding: 10px; margin-bottom: 10px; cursor: pointer; transition: all 0.2s;"
class="saved-list-item"
data-list-id="${list.id}"
onmouseover="this.style.background='#f0f8ff'; this.style.borderColor='#4a9eff';"
onmouseout="this.style.background='white'; this.style.borderColor='#ddd';">
<div style="font-weight: bold; margin-bottom: 5px;">ID: ${list.id}</div>
<div style="font-size: 12px; color: #666;">保存时间: ${date}</div>
<div style="font-size: 12px; color: #666;">包含 ${tierCount} 个层级,${avatarCount} 个角色</div>
</div>
`;
});
listHtml += '</div>';
}
$dialog.html(`
<h3>📂 加载榜单</h3>
<p style="color: #666; margin-bottom: 15px;">输入榜单ID或从下方选择:</p>
<input type="text" id="tierlist-id-input" placeholder="输入榜单ID" style="margin-bottom: 15px;">
${listHtml || '<p style="color: #999; text-align: center; padding: 20px;">暂无保存的榜单</p>'}
<div class="tier-dialog-buttons">
<button class="tier-dialog-btn tier-dialog-btn-secondary" onclick="this.closest('.tier-dialog-overlay').remove()">取消</button>
<button class="tier-dialog-btn tier-dialog-btn-primary" id="load-btn">加载</button>
</div>
`);
$overlay.append($dialog);
$('body').append($overlay);
// 点击列表项选择
$('.saved-list-item').click(function() {
$('.saved-list-item').css('background', 'white').css('border-color', '#ddd');
$(this).css('background', '#e8f4f8').css('border-color', '#4a9eff');
$('#tierlist-id-input').val($(this).data('list-id'));
});
// 加载按钮
$('#load-btn').click(function() {
var listId = $('#tierlist-id-input').val().trim();
if (!listId) {
alert('请输入或选择榜单ID');
return;
}
if (loadTierListData(listId)) {
$overlay.remove();
} else {
alert('加载失败:未找到该榜单');
}
});
// 回车加载
$('#tierlist-id-input').keypress(function(e) {
if (e.which === 13) {
$('#load-btn').click();
}
});
}
function loadTierListData(listId) {
var savedData = localStorage.getItem('tierlist_' + listId);
console.log('尝试加载榜单 ID:', listId);
console.log('本地存储数据:', savedData);
if (!savedData) {
console.error('未找到榜单数据');
return false;
}
try {
var saveData = JSON.parse(savedData);
console.log('解析后的数据:', saveData);
applyTierData(saveData.data);
showNotification('榜单加载成功!', 'success');
return true;
} catch (e) {
console.error('加载榜单失败:', e);
return false;
}
}
function applyTierData(tierData) {
console.log('应用榜单数据:', tierData);
// 首先清空所有层级,将所有角色移回池中
$('.wikitable td:nth-child(2) .avatar-frame').each(function() {
$(this).removeClass('in-tier');
$('#avatar-pool').append($(this));
});
console.log('总共需要恢复', tierData.tiers.length, '个层级');
// 应用tier数据
tierData.tiers.forEach(function(tier, index) {
console.log('处理层级', index, ':', tier.name, '包含', tier.avatars.length, '个角色');
var $targetCell = null;
// 查找对应的tier单元格
$('.wikitable tr').each(function() {
var $cells = $(this).find('td');
if ($cells.length > 0) {
var tierName = $cells.eq(0).text().trim();
if (tierName === tier.name) {
$targetCell = $cells.eq(1);
console.log('找到目标单元格:', tierName);
return false;
}
}
});
if ($targetCell) {
tier.avatars.forEach(function(avatarId, avatarIndex) {
console.log('查找角色', avatarIndex, ':', avatarId);
var $avatar = findAvatarByIdentifier(avatarId);
if ($avatar) {
console.log('找到角色,移动到', tier.name);
$avatar.addClass('in-tier');
$targetCell.append($avatar);
} else {
console.warn('未找到角色:', avatarId);
}
});
} else {
console.warn('未找到层级单元格:', tier.name);
}
});
console.log('榜单应用完成');
}
function findAvatarByIdentifier(identifierStr) {
try {
var identifier = JSON.parse(identifierStr);
var $found = null;
$('.avatar-frame').each(function() {
var $avatar = $(this);
// 优先通过 tierId 匹配
if (identifier.tierId && $avatar.attr('data-tier-id') === identifier.tierId) {
$found = $avatar;
return false;
}
// 通过图片src匹配
var $img = $avatar.find('img');
if (identifier.src && $img.attr('src') === identifier.src) {
// 进一步验证name
var $name = $avatar.find('.avatar-name');
if (identifier.name && $name.text().trim() === identifier.name) {
$found = $avatar;
// 设置tierId以便下次快速查找
if (identifier.tierId) {
$avatar.attr('data-tier-id', identifier.tierId);
}
return false;
}
}
// 通过name匹配(备用方案)
if (identifier.name) {
var $name = $avatar.find('.avatar-name');
if ($name.text().trim() === identifier.name) {
$found = $avatar;
return false;
}
}
});
return $found;
} catch (e) {
console.error('解析角色标识符失败:', e);
return null;
}
}
function showShareDialog() {
// 先保存当前榜单
var tierData = collectTierData();
var listId = generateUniqueId();
var timestamp = new Date().toISOString();
var saveData = {
id: listId,
timestamp: timestamp,
data: tierData
};
localStorage.setItem('tierlist_' + listId, JSON.stringify(saveData));
// 生成分享链接
var pageUrl = window.location.origin + window.location.pathname;
var fullUrl = pageUrl + '?tierlist=' + listId;
var $overlay = $('<div>')
.addClass('tier-dialog-overlay')
.click(function(e) {
if (e.target === this) {
$(this).remove();
}
});
var $dialog = $('<div>').addClass('tier-dialog');
$dialog.html(`
<h3>🔗 分享榜单</h3>
<div class="tier-info-box">
<p><strong>分享链接:</strong></p>
<input type="text" value="${fullUrl}" readonly id="share-url-input">
<p style="margin-top: 10px; font-size: 14px; color: #666;">
将此链接发送给好友,他们打开后可以看到您的榜单排序。
</p>
</div>
<div class="tier-dialog-buttons">
<button class="tier-dialog-btn tier-dialog-btn-secondary" onclick="this.closest('.tier-dialog-overlay').remove()">关闭</button>
<button class="tier-dialog-btn tier-dialog-btn-primary" id="share-copy-btn">复制链接</button>
</div>
`);
$overlay.append($dialog);
$('body').append($overlay);
// 复制按钮
$('#share-copy-btn').click(function() {
var $input = $('#share-url-input');
$input.select();
document.execCommand('copy');
showNotification('分享链接已复制!', 'success');
});
// 自动选中
setTimeout(function() {
$('#share-url-input').select();
}, 100);
}
function checkUrlForTierList() {
var urlParams = new URLSearchParams(window.location.search);
var tierlistId = urlParams.get('tierlist');
if (tierlistId) {
console.log('检测到URL中的榜单ID:', tierlistId);
setTimeout(function() {
if (loadTierListData(tierlistId)) {
showNotification('已自动加载分享的榜单', 'info');
} else {
showNotification('无法加载分享的榜单', 'error');
}
}, 500);
}
}
function generateUniqueId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
}
function showNotification(message, type) {
var $notification = $('<div>')
.addClass('tier-notification')
.addClass(type || 'info')
.text(message);
$('body').append($notification);
setTimeout(function() {
$notification.fadeOut(300, function() {
$(this).remove();
});
}, 3000);
}
function loadHtml2Canvas(callback) {
if (typeof html2canvas === 'undefined') {
$.getScript('https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js')
.done(function() {
callback();
})
.fail(function() {
alert('加载html2canvas失败,请检查网络连接');
});
} else {
callback();
}
}
function captureTierList() {
var $table = $('.wikitable').first().clone();
// 移除空的tier单元格中的占位内容
$table.find('td:nth-child(2)').each(function() {
if ($(this).find('.avatar-frame').length === 0) {
$(this).empty();
}
});
// 创建临时容器
var $container = $('<div>')
.css({
position: 'fixed',
left: '-9999px',
top: '0',
background: 'white',
padding: '20px',
minWidth: '800px'
})
.append($table);
$('body').append($container);
// 添加标题
var $title = $('<h2>')
.text('我的Tier榜单')
.css({
textAlign: 'center',
marginBottom: '20px',
color: '#333'
});
$container.prepend($title);
html2canvas($container[0], {
backgroundColor: '#ffffff',
scale: 2,
logging: false,
useCORS: true,
allowTaint: true
}).then(function(canvas) {
$container.remove();
// 转换为blob并下载
canvas.toBlob(function(blob) {
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'tierlist_' + Date.now() + '.png';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showNotification('榜单图片已导出!', 'success');
});
}).catch(function(error) {
$container.remove();
console.error('导出失败:', error);
alert('导出图片失败:' + error.message);
});
}
})();
/**
* 使用说明:
*
* 1. 拖拽功能:
* - 从角色池拖拽角色到对应的层级
* - 在层级之间拖拽调整
* - 拖回角色池移除
*
* 2. 保存功能:
* - 点击"保存榜单"保存当前排序
* - 系统会生成唯一ID
* - 数据保存在浏览器本地存储中
*
* 3. 加载功能:
* - 点击"加载榜单"查看所有已保存的榜单
* - 可以选择榜单或输入ID加载
* - 支持从URL参数自动加载
*
* 4. 分享功能:
* - 点击"分享榜单"生成分享链接
* - 复制链接发送给他人
* - 他人打开链接自动加载您的榜单
*
* 5. 导出功能:
* - 点击"导出为图片"将榜单保存为PNG图片
* - 图片包含所有层级和角色排序
*
* 技术特性:
* - 使用 dragula.js 实现拖拽
* - 使用 html2canvas 实现截图
* - 使用 localStorage 保存数据
* - 支持 URL 参数传递榜单
* - 响应式设计,支持移动端
*
* 数据格式:
* {
* "id": "唯一标识",
* "timestamp": "保存时间",
* "data": {
* "tiers": [
* {
* "name": "层级名称",
* "avatars": ["角色标识1", "角色标识2", ...]
* }
* ],
* "pool": ["未分配的角色标识"]
* }
* }
*
* 角色标识格式:
* {
* "src": "图片路径",
* "name": "角色名称",
* "tierId": "唯一ID"
* }
*/