TierListMaker:修订间差异
来自卡厄思梦境WIKI
小无编辑摘要 |
无编辑摘要 |
||
| 第1行: | 第1行: | ||
<includeonly> | <includeonly> | ||
<style> | <style> | ||
| 第62行: | 第61行: | ||
border: 2px dashed transparent; | border: 2px dashed transparent; | ||
transition: all 0.2s ease; | transition: all 0.2s ease; | ||
min-height: 120px; | |||
} | } | ||
| 第71行: | 第71行: | ||
#character-pool { | #character-pool { | ||
position: relative; | position: relative; | ||
min-height: 150px; | |||
} | } | ||
| 第84行: | 第85行: | ||
.tierlist-table th { | .tierlist-table th { | ||
width: 100px; | width: 100px; | ||
position: relative; | |||
} | } | ||
| 第119行: | 第121行: | ||
.tierlist-btn-reset:hover { | .tierlist-btn-reset:hover { | ||
background-color: #da190b; | background-color: #da190b; | ||
} | |||
.tierlist-btn-add { | |||
background-color: #2196F3; | |||
} | |||
.tierlist-btn-add:hover { | |||
background-color: #0b7dda; | |||
} | |||
.tierlist-btn-edit { | |||
background-color: #ff9800; | |||
} | |||
.tierlist-btn-edit:hover { | |||
background-color: #e68900; | |||
} | |||
.tierlist-btn-edit.active { | |||
background-color: #ff5722; | |||
} | } | ||
| 第136行: | 第158行: | ||
margin-top: 0; | margin-top: 0; | ||
color: #333; | color: #333; | ||
} | |||
/* 编辑模式下的样式 */ | |||
.tier-header.editable { | |||
cursor: pointer; | |||
position: relative; | |||
} | |||
.tier-header.editable:hover::after { | |||
content: '✎'; | |||
position: absolute; | |||
right: 5px; | |||
top: 50%; | |||
transform: translateY(-50%); | |||
font-size: 16px; | |||
} | |||
.delete-row-btn { | |||
position: absolute; | |||
right: 5px; | |||
top: 50%; | |||
transform: translateY(-50%); | |||
background-color: #f44336; | |||
color: white; | |||
border: none; | |||
border-radius: 3px; | |||
padding: 3px 8px; | |||
cursor: pointer; | |||
font-size: 12px; | |||
display: none; | |||
z-index: 100; | |||
} | |||
.edit-mode .delete-row-btn { | |||
display: block; | |||
} | |||
.delete-row-btn:hover { | |||
background-color: #da190b; | |||
} | |||
/* 颜色选择器容器 */ | |||
.color-picker-popup { | |||
position: fixed; | |||
top: 50%; | |||
left: 50%; | |||
transform: translate(-50%, -50%); | |||
background: white; | |||
padding: 20px; | |||
border-radius: 8px; | |||
box-shadow: 0 4px 20px rgba(0,0,0,0.3); | |||
z-index: 1000; | |||
min-width: 300px; | |||
} | |||
.color-picker-popup h4 { | |||
margin-top: 0; | |||
} | |||
.color-picker-popup label { | |||
display: block; | |||
margin: 10px 0 5px 0; | |||
} | |||
.color-picker-popup input[type="text"], | |||
.color-picker-popup input[type="color"] { | |||
width: 100%; | |||
padding: 5px; | |||
margin-bottom: 10px; | |||
box-sizing: border-box; | |||
} | |||
.color-picker-popup .button-group { | |||
display: flex; | |||
gap: 10px; | |||
margin-top: 15px; | |||
} | |||
.color-picker-popup button { | |||
flex: 1; | |||
padding: 8px; | |||
border: none; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
font-size: 14px; | |||
} | |||
.color-picker-popup .btn-confirm { | |||
background-color: #4CAF50; | |||
color: white; | |||
} | |||
.color-picker-popup .btn-cancel { | |||
background-color: #f44336; | |||
color: white; | |||
} | |||
.overlay { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
right: 0; | |||
bottom: 0; | |||
background: rgba(0,0,0,0.5); | |||
z-index: 999; | |||
} | } | ||
</style> | </style> | ||
| 第154行: | 第281行: | ||
draggedElement: null, | draggedElement: null, | ||
sourceContainer: null, | sourceContainer: null, | ||
editMode: false, | |||
init: function() { | init: function() { | ||
| 第167行: | 第295行: | ||
this.initDragAndDrop(); | this.initDragAndDrop(); | ||
this.initButtons(); | this.initButtons(); | ||
this.loadState(); | |||
}, | }, | ||
| 第173行: | 第302行: | ||
avatars.forEach(avatar => { | avatars.forEach(avatar => { | ||
if (!avatar.getAttribute('data-drag-initialized')) { | if (!avatar.getAttribute('data-drag-initialized')) { | ||
avatar.setAttribute('draggable', 'true'); | avatar.setAttribute('draggable', 'true'); | ||
| 第209行: | 第337行: | ||
element.classList.add('dragging'); | element.classList.add('dragging'); | ||
e.dataTransfer.effectAllowed = 'move'; | e.dataTransfer.effectAllowed = 'move'; | ||
e.dataTransfer.setData('text/plain', 'drag'); | e.dataTransfer.setData('text/plain', element.getAttribute('data-character-id') || 'drag'); | ||
}, | }, | ||
| 第219行: | 第347行: | ||
el.classList.remove('drag-over'); | el.classList.remove('drag-over'); | ||
}); | }); | ||
// 保存状态 | |||
this.saveState(); | |||
this.draggedElement = null; | this.draggedElement = null; | ||
| 第238行: | 第369行: | ||
handleDragLeave: function(e, element) { | handleDragLeave: function(e, element) { | ||
if (e.target === element) { | if (e.target === element) { | ||
element.classList.remove('drag-over'); | element.classList.remove('drag-over'); | ||
| 第272行: | 第402行: | ||
const saveBtn = document.getElementById('save-tierlist-btn'); | const saveBtn = document.getElementById('save-tierlist-btn'); | ||
const resetBtn = document.getElementById('reset-tierlist-btn'); | const resetBtn = document.getElementById('reset-tierlist-btn'); | ||
const addRowBtn = document.getElementById('add-row-btn'); | |||
const editModeBtn = document.getElementById('edit-mode-btn'); | |||
if (saveBtn) { | if (saveBtn) { | ||
| 第279行: | 第411行: | ||
if (resetBtn) { | if (resetBtn) { | ||
resetBtn.onclick = () => this.resetTierlist(); | resetBtn.onclick = () => this.resetTierlist(); | ||
} | |||
if (addRowBtn) { | |||
addRowBtn.onclick = () => this.addRow(); | |||
} | |||
if (editModeBtn) { | |||
editModeBtn.onclick = () => this.toggleEditMode(); | |||
} | |||
}, | |||
toggleEditMode: function() { | |||
this.editMode = !this.editMode; | |||
const btn = document.getElementById('edit-mode-btn'); | |||
const table = document.getElementById('tierlist-table'); | |||
if (this.editMode) { | |||
btn.classList.add('active'); | |||
btn.textContent = '完成编辑'; | |||
table.classList.add('edit-mode'); | |||
// 为所有tier header添加编辑功能 | |||
document.querySelectorAll('.tier-header').forEach(header => { | |||
header.classList.add('editable'); | |||
if (!header.querySelector('.delete-row-btn')) { | |||
const deleteBtn = document.createElement('button'); | |||
deleteBtn.className = 'delete-row-btn'; | |||
deleteBtn.textContent = '删除'; | |||
deleteBtn.onclick = (e) => { | |||
e.stopPropagation(); | |||
this.deleteRow(header.parentElement); | |||
}; | |||
header.appendChild(deleteBtn); | |||
} | |||
header.onclick = () => this.editTierHeader(header); | |||
}); | |||
} else { | |||
btn.classList.remove('active'); | |||
btn.textContent = '编辑模式'; | |||
table.classList.remove('edit-mode'); | |||
document.querySelectorAll('.tier-header').forEach(header => { | |||
header.classList.remove('editable'); | |||
header.onclick = null; | |||
}); | |||
} | |||
}, | |||
editTierHeader: function(header) { | |||
const currentText = header.textContent.replace('删除', '').trim(); | |||
const currentBgColor = header.style.backgroundColor || '#000000'; | |||
// 创建弹窗 | |||
const overlay = document.createElement('div'); | |||
overlay.className = 'overlay'; | |||
const popup = document.createElement('div'); | |||
popup.className = 'color-picker-popup'; | |||
popup.innerHTML = ` | |||
<h4>编辑Tier</h4> | |||
<label>Tier名称:</label> | |||
<input type="text" id="tier-name-input" value="${currentText}" maxlength="10"> | |||
<label>背景颜色:</label> | |||
<input type="color" id="tier-color-input" value="${this.rgbToHex(currentBgColor)}"> | |||
<div class="button-group"> | |||
<button class="btn-confirm">确定</button> | |||
<button class="btn-cancel">取消</button> | |||
</div> | |||
`; | |||
document.body.appendChild(overlay); | |||
document.body.appendChild(popup); | |||
const nameInput = popup.querySelector('#tier-name-input'); | |||
const colorInput = popup.querySelector('#tier-color-input'); | |||
popup.querySelector('.btn-confirm').onclick = () => { | |||
const newName = nameInput.value.trim(); | |||
const newColor = colorInput.value; | |||
if (newName) { | |||
header.setAttribute('data-tier', newName); | |||
header.querySelector('.delete-row-btn').remove(); | |||
header.textContent = newName; | |||
header.style.backgroundColor = newColor; | |||
// 重新添加删除按钮 | |||
const deleteBtn = document.createElement('button'); | |||
deleteBtn.className = 'delete-row-btn'; | |||
deleteBtn.textContent = '删除'; | |||
deleteBtn.onclick = (e) => { | |||
e.stopPropagation(); | |||
this.deleteRow(header.parentElement); | |||
}; | |||
header.appendChild(deleteBtn); | |||
// 更新对应的tier-row | |||
const row = header.parentElement.querySelector('.tier-row'); | |||
if (row) { | |||
row.setAttribute('data-tier', newName); | |||
} | |||
this.saveState(); | |||
} | |||
document.body.removeChild(overlay); | |||
document.body.removeChild(popup); | |||
}; | |||
popup.querySelector('.btn-cancel').onclick = () => { | |||
document.body.removeChild(overlay); | |||
document.body.removeChild(popup); | |||
}; | |||
overlay.onclick = () => { | |||
document.body.removeChild(overlay); | |||
document.body.removeChild(popup); | |||
}; | |||
nameInput.focus(); | |||
nameInput.select(); | |||
}, | |||
rgbToHex: function(rgb) { | |||
if (rgb.startsWith('#')) return rgb; | |||
const values = rgb.match(/\d+/g); | |||
if (!values || values.length < 3) return '#000000'; | |||
const r = parseInt(values[0]).toString(16).padStart(2, '0'); | |||
const g = parseInt(values[1]).toString(16).padStart(2, '0'); | |||
const b = parseInt(values[2]).toString(16).padStart(2, '0'); | |||
return '#' + r + g + b; | |||
}, | |||
addRow: function() { | |||
const table = document.getElementById('tierlist-table'); | |||
if (!table) return; | |||
const tbody = table.querySelector('tbody'); | |||
const newRow = document.createElement('tr'); | |||
const tierCount = document.querySelectorAll('.tier-row').length; | |||
const tierName = 'T' + (tierCount + 1); | |||
newRow.innerHTML = ` | |||
<th style="width: 100px; background-color: #9e9e9e; color: white; font-size: 20px; text-align: center" class="tier-header" data-tier="${tierName}">${tierName}</th> | |||
<td style="padding: 10px; min-height: 120px;" class="tier-row" data-tier="${tierName}"></td> | |||
`; | |||
tbody.appendChild(newRow); | |||
// 为新行添加拖放事件 | |||
const newTierRow = newRow.querySelector('.tier-row'); | |||
newTierRow.setAttribute('data-drop-initialized', 'true'); | |||
newTierRow.addEventListener('dragover', (e) => this.handleDragOver(e, newTierRow)); | |||
newTierRow.addEventListener('drop', (e) => this.handleDrop(e, newTierRow)); | |||
newTierRow.addEventListener('dragleave', (e) => this.handleDragLeave(e, newTierRow)); | |||
// 如果在编辑模式下,添加编辑功能 | |||
if (this.editMode) { | |||
const header = newRow.querySelector('.tier-header'); | |||
header.classList.add('editable'); | |||
const deleteBtn = document.createElement('button'); | |||
deleteBtn.className = 'delete-row-btn'; | |||
deleteBtn.textContent = '删除'; | |||
deleteBtn.onclick = (e) => { | |||
e.stopPropagation(); | |||
this.deleteRow(newRow); | |||
}; | |||
header.appendChild(deleteBtn); | |||
header.onclick = () => this.editTierHeader(header); | |||
} | |||
this.saveState(); | |||
}, | |||
deleteRow: function(row) { | |||
if (!confirm('确定要删除这一行吗?行内的角色将移回角色池。')) { | |||
return; | |||
} | |||
const tierRow = row.querySelector('.tier-row'); | |||
const avatars = tierRow.querySelectorAll('.avatar-frame'); | |||
const pool = document.getElementById('character-pool'); | |||
// 将角色移回角色池 | |||
avatars.forEach(avatar => { | |||
pool.appendChild(avatar); | |||
}); | |||
// 删除行 | |||
row.remove(); | |||
this.saveState(); | |||
}, | |||
saveState: function() { | |||
const state = { | |||
rows: [], | |||
pool: [] | |||
}; | |||
// 保存表格行信息 | |||
document.querySelectorAll('#tierlist-table tbody tr').forEach(row => { | |||
const header = row.querySelector('.tier-header'); | |||
const tierRow = row.querySelector('.tier-row'); | |||
if (!header || !tierRow) return; | |||
const rowData = { | |||
name: header.getAttribute('data-tier'), | |||
bgColor: header.style.backgroundColor, | |||
characters: [] | |||
}; | |||
tierRow.querySelectorAll('.avatar-frame').forEach(avatar => { | |||
const charId = this.getCharacterId(avatar); | |||
if (charId) { | |||
rowData.characters.push(charId); | |||
} | |||
}); | |||
state.rows.push(rowData); | |||
}); | |||
// 保存角色池信息 | |||
const pool = document.getElementById('character-pool'); | |||
if (pool) { | |||
pool.querySelectorAll('.avatar-frame').forEach(avatar => { | |||
const charId = this.getCharacterId(avatar); | |||
if (charId) { | |||
state.pool.push(charId); | |||
} | |||
}); | |||
} | |||
try { | |||
localStorage.setItem('tierlist-state', JSON.stringify(state)); | |||
} catch (e) { | |||
console.warn('无法保存状态:', e); | |||
} | } | ||
}, | |||
loadState: function() { | |||
try { | |||
const stateJson = localStorage.getItem('tierlist-state'); | |||
if (!stateJson) return; | |||
const state = JSON.parse(stateJson); | |||
// 先清空所有行(保留第一行作为模板) | |||
const tbody = document.querySelector('#tierlist-table tbody'); | |||
const rows = Array.from(tbody.querySelectorAll('tr')); | |||
// 恢复表格结构 | |||
if (state.rows && state.rows.length > 0) { | |||
rows.forEach((row, index) => { | |||
if (index >= state.rows.length) { | |||
row.remove(); | |||
} else { | |||
const header = row.querySelector('.tier-header'); | |||
const savedRow = state.rows[index]; | |||
if (header && savedRow) { | |||
header.textContent = savedRow.name; | |||
header.setAttribute('data-tier', savedRow.name); | |||
header.style.backgroundColor = savedRow.bgColor; | |||
const tierRow = row.querySelector('.tier-row'); | |||
if (tierRow) { | |||
tierRow.setAttribute('data-tier', savedRow.name); | |||
} | |||
} | |||
} | |||
}); | |||
// 如果保存的行数多于当前行数,添加新行 | |||
for (let i = rows.length; i < state.rows.length; i++) { | |||
const savedRow = state.rows[i]; | |||
const newRow = document.createElement('tr'); | |||
newRow.innerHTML = ` | |||
<th style="width: 100px; background-color: ${savedRow.bgColor}; color: white; font-size: 20px; text-align: center" class="tier-header" data-tier="${savedRow.name}">${savedRow.name}</th> | |||
<td style="padding: 10px; min-height: 120px;" class="tier-row" data-tier="${savedRow.name}"></td> | |||
`; | |||
tbody.appendChild(newRow); | |||
const newTierRow = newRow.querySelector('.tier-row'); | |||
newTierRow.setAttribute('data-drop-initialized', 'true'); | |||
newTierRow.addEventListener('dragover', (e) => this.handleDragOver(e, newTierRow)); | |||
newTierRow.addEventListener('drop', (e) => this.handleDrop(e, newTierRow)); | |||
newTierRow.addEventListener('dragleave', (e) => this.handleDragLeave(e, newTierRow)); | |||
} | |||
} | |||
// 恢复角色位置 | |||
setTimeout(() => { | |||
state.rows.forEach((rowData, index) => { | |||
const tierRow = document.querySelectorAll('.tier-row')[index]; | |||
if (!tierRow) return; | |||
rowData.characters.forEach(charId => { | |||
const avatar = this.findAvatarById(charId); | |||
if (avatar) { | |||
tierRow.appendChild(avatar); | |||
} | |||
}); | |||
}); | |||
}, 100); | |||
} catch (e) { | |||
console.warn('无法加载状态:', e); | |||
} | |||
}, | |||
getCharacterId: function(avatar) { | |||
// 尝试多种方式获取角色ID | |||
const img = avatar.querySelector('img'); | |||
if (img) { | |||
const src = img.getAttribute('src'); | |||
if (src) { | |||
const match = src.match(/\/(\d+)\./); | |||
if (match) return match[1]; | |||
} | |||
} | |||
const name = avatar.querySelector('.avatar-name'); | |||
if (name) { | |||
return name.textContent.trim(); | |||
} | |||
return avatar.outerHTML; | |||
}, | |||
findAvatarById: function(charId) { | |||
const allAvatars = document.querySelectorAll('.avatar-frame'); | |||
for (let avatar of allAvatars) { | |||
if (this.getCharacterId(avatar) === charId) { | |||
return avatar; | |||
} | |||
} | |||
return null; | |||
}, | }, | ||
| 第289行: | 第766行: | ||
} | } | ||
if (typeof html2canvas === 'undefined') { | if (typeof html2canvas === 'undefined') { | ||
alert('正在加载图片生成库,请稍后再试...'); | alert('正在加载图片生成库,请稍后再试...'); | ||
| 第295行: | 第771行: | ||
} | } | ||
const controls = document.querySelector('.tierlist-controls'); | const controls = document.querySelector('.tierlist-controls'); | ||
const poolContainer = document.querySelector('.character-pool-container'); | const poolContainer = document.querySelector('.character-pool-container'); | ||
| 第301行: | 第776行: | ||
const originalControlsDisplay = controls ? controls.style.display : ''; | const originalControlsDisplay = controls ? controls.style.display : ''; | ||
const originalPoolDisplay = poolContainer ? poolContainer.style.display : ''; | const originalPoolDisplay = poolContainer ? poolContainer.style.display : ''; | ||
// 隐藏编辑按钮 | |||
const deleteButtons = document.querySelectorAll('.delete-row-btn'); | |||
deleteButtons.forEach(btn => btn.style.display = 'none'); | |||
if (controls) controls.style.display = 'none'; | if (controls) controls.style.display = 'none'; | ||
| 第312行: | 第791行: | ||
allowTaint: true | allowTaint: true | ||
}).then(canvas => { | }).then(canvas => { | ||
if (controls) controls.style.display = originalControlsDisplay; | if (controls) controls.style.display = originalControlsDisplay; | ||
if (poolContainer) poolContainer.style.display = originalPoolDisplay; | if (poolContainer) poolContainer.style.display = originalPoolDisplay; | ||
deleteButtons.forEach(btn => btn.style.display = ''); | |||
const link = document.createElement('a'); | const link = document.createElement('a'); | ||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); | const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); | ||
| 第326行: | 第804行: | ||
if (controls) controls.style.display = originalControlsDisplay; | if (controls) controls.style.display = originalControlsDisplay; | ||
if (poolContainer) poolContainer.style.display = originalPoolDisplay; | if (poolContainer) poolContainer.style.display = originalPoolDisplay; | ||
deleteButtons.forEach(btn => btn.style.display = ''); | |||
alert('生成图片失败,请查看控制台了解详情'); | alert('生成图片失败,请查看控制台了解详情'); | ||
}); | }); | ||
| 第343行: | 第822行: | ||
const allAvatars = document.querySelectorAll('.avatar-frame'); | const allAvatars = document.querySelectorAll('.avatar-frame'); | ||
document.querySelectorAll('.tier-row').forEach(row => { | document.querySelectorAll('.tier-row').forEach(row => { | ||
row.innerHTML = ''; | row.innerHTML = ''; | ||
}); | }); | ||
allAvatars.forEach(avatar => { | allAvatars.forEach(avatar => { | ||
pool.appendChild(avatar); | pool.appendChild(avatar); | ||
const img = avatar.querySelector('img'); | const img = avatar.querySelector('img'); | ||
if (img) { | if (img) { | ||
| 第357行: | 第833行: | ||
} | } | ||
}); | }); | ||
// 清除保存的状态 | |||
try { | |||
localStorage.removeItem('tierlist-state'); | |||
} catch (e) { | |||
console.warn('无法清除状态:', e); | |||
} | |||
} | } | ||
}; | }; | ||