微件

TierListMaker:修订间差异

来自卡厄思梦境WIKI

律Rhyme留言 | 贡献
无编辑摘要
律Rhyme留言 | 贡献
无编辑摘要
第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 => {
                // 不使用 cloneNode,直接设置属性
                 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行:
             }
             }
              
              
            // 检查 html2canvas 是否加载
             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');
              
              
            // 清空所有tier行
             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);
            }
         }
         }
     };
     };

2025年10月20日 (一) 21:12的版本