|
|
| 第1行: |
第1行: |
| <includeonly> | | <includeonly> |
| <style> | | <style> |
| .tier-row { | | .avatar-frame { |
| position: relative; | | position: relative; |
| background-color: #fafafa; | | display: inline-block; |
| border: 2px dashed transparent; | | vertical-align: top; |
| transition: all 0.2s ease; | | border: 3px solid #ccc; |
| display: flex; | | border-radius: 5px; |
| flex-wrap: wrap;
| | overflow: hidden; |
| gap: 5px; | | box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
| align-content: flex-start; | | background: #f5f5f5; |
| padding: 10px; | | transition: transform 0.3s ease, box-shadow 0.3s ease; |
| min-height: 80px; | | cursor: move; |
| | | margin: 2px; |
| .tier-row.drag-over { | |
| background-color: #e3f2fd; | |
| border-color: #2196F3; | |
| } | | } |
| | | .avatar-frame:hover { |
| #character-pool {
| | transform: scale(1.05); |
| position: relative; | | box-shadow: 0 4px 8px rgba(0,0,0,0.2); |
| display: flex;
| | z-index: 10; |
| flex-wrap: wrap;
| |
| gap: 5px;
| |
| align-content: flex-start; | |
| padding: 10px;
| |
| min-height: 100px; | |
| } | | } |
| | | .avatar-frame.dragging { |
| #character-pool.drag-over {
| | opacity: 0.5; |
| background-color: #fff3e0; | |
| } | | } |
| | | .avatar-frame img { |
| .tierlist-table { | | display: block; |
| width: 100%; | |
| table-layout: fixed;
| |
| border-collapse: collapse;
| |
| }
| |
| | |
| .tierlist-table th {
| |
| width: 100px; | | width: 100px; |
| position: relative; | | height: 100px; |
| vertical-align: top; | | object-fit: cover; |
| | pointer-events: none; |
| } | | } |
| | | .avatar-name { |
| .tierlist-table td { | | position: absolute; |
| padding: 0 !important; | | left: 0; |
| vertical-align: top; | | bottom: 0; |
| | | padding: 2px 8px; |
| .tierlist-controls {
| |
| margin-bottom: 20px; | |
| display: flex;
| |
| gap: 10px;
| |
| }
| |
| | |
| .tierlist-btn {
| |
| padding: 10px 20px; | |
| font-size: 16px;
| |
| color: white; | | color: white; |
| border: none; | | font-size: 12px; |
| border-radius: 5px; | | font-weight: bold; |
| cursor: pointer; | | text-shadow: 0 0 2px black, 0 0 2px black; |
| transition: all 0.2s ease; | | white-space: nowrap; |
| display: inline-block; | | max-width: 100%; |
| text-align: center; | | overflow: hidden; |
| user-select: none; | | text-overflow: ellipsis; |
| | border-top-right-radius: 3px; |
| | pointer-events: none; |
| } | | } |
| | | .tier-content { |
| .tierlist-btn-save { | | min-height: 120px; |
| background-color: #4CAF50; | | transition: background-color 0.3s; |
| } | | } |
| | | .tier-content.drag-over { |
| .tierlist-btn-save:hover { | | background-color: #e3f2fd !important; |
| background-color: #45a049; | | border: 2px dashed #2196F3; |
| } | | } |
| | | .tier-header { |
| .tierlist-btn-reset { | | position: relative; |
| background-color: #f44336; | | user-select: none; |
| } | | } |
| | | .tier-label { |
| .tierlist-btn-reset:hover { | | display: inline-block; |
| background-color: #da190b; | | padding: 5px; |
| } | | } |
| | | .tier-label:focus { |
| .tierlist-btn-add { | | outline: 2px solid white; |
| background-color: #2196F3; | | outline-offset: -2px; |
| } | | } |
| | | .color-picker-trigger { |
| .tierlist-btn-add:hover { | | position: absolute; |
| background-color: #0b7dda; | | top: 5px; |
| }
| | right: 5px; |
| | | font-size: 16px; |
| .tierlist-btn-edit {
| | cursor: pointer; |
| background-color: #ff9800; | | opacity: 0.7; |
| }
| | transition: opacity 0.3s; |
| | |
| .tierlist-btn-edit:hover { | |
| background-color: #e68900; | |
| } | | } |
| | | .color-picker-trigger:hover { |
| .tierlist-btn-edit.active { | | opacity: 1; |
| background-color: #ff5722; | |
| } | | } |
| | | .control-button:hover { |
| .tierlist-btn:active { | | opacity: 0.9; |
| transform: scale(0.98); | | transform: translateY(-1px); |
| } | | } |
| | | .control-button:active { |
| .character-pool-container { | | transform: translateY(0); |
| background-color: #f9f9f9; | |
| padding: 15px;
| |
| border: 2px dashed #ccc;
| |
| border-radius: 5px;
| |
| margin-top: 20px;
| |
| } | | } |
| | | #tierlist-table { |
| .character-pool-container h3 {
| | border: 2px solid #ddd; |
| margin-top: 0; | |
| color: #333;
| |
| } | | } |
| | | .tier-remove-btn { |
| /* 编辑模式下的样式 */
| |
| .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; | | position: absolute; |
| right: 5px; | | top: 5px; |
| top: 50%; | | left: 5px; |
| transform: translateY(-50%);
| | background: #f44336; |
| background-color: #f44336; | |
| color: white; | | color: white; |
| border: none;
| |
| border-radius: 3px; | | border-radius: 3px; |
| padding: 3px 8px; | | padding: 2px 6px; |
| cursor: pointer;
| |
| font-size: 12px; | | 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; | | cursor: pointer; |
| font-size: 14px; | | opacity: 0.7; |
| }
| |
| | |
| .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;
| |
| }
| |
| | |
| /* 确保avatar-frame正确显示 */
| |
| .avatar-frame {
| |
| display: inline-block;
| |
| margin: 0;
| |
| cursor: move; /* 显示可移动光标 */
| |
| } | | } |
| | | .tier-remove-btn:hover { |
| .avatar-frame.dragging { | | opacity: 1; |
| opacity: 0.5;
| |
| }
| |
| | |
| /* 空的tier-row显示提示 */
| |
| .tier-row:empty::before {
| |
| content: '拖放角色到这里'; | |
| color: #999;
| |
| font-size: 14px;
| |
| position: absolute;
| |
| left: 50%;
| |
| top: 50%;
| |
| transform: translate(-50%, -50%);
| |
| pointer-events: none;
| |
| } | | } |
| </style> | | </style> |
|
| |
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
| |
|
| |
|
| <script> | | <script> |
| (function() {
| | let draggedElement = null; |
| 'use strict'; | | let currentEditingTier = null; |
| | | // 初始化拖拽功能 |
| if (window.TierlistMakerInitialized) {
| | function initDragAndDrop() { |
| console.log('TierlistMaker already initialized');
| | const avatars = document.querySelectorAll('.avatar-frame'); |
| return;
| | const tierContents = document.querySelectorAll('.tier-content'); |
| }
| |
| | | |
| const TierlistMaker = { | | avatars.forEach(avatar => { |
| draggedElement: null, | | avatar.setAttribute('draggable', 'true'); |
| sourceContainer: null,
| |
| editMode: false,
| |
| initRetryCount: 0,
| |
| maxRetries: 10,
| |
| | | |
| init: function() { | | avatar.addEventListener('dragstart', function(e) { |
| if (document.readyState === 'loading') {
| | draggedElement = this; |
| document.addEventListener('DOMContentLoaded', () => this.setup());
| | this.classList.add('dragging'); |
| } else {
| |
| this.setup();
| |
| }
| |
| },
| |
|
| |
| setup: function() {
| |
| const characterPool = document.getElementById('character-pool');
| |
| const avatars = characterPool ? characterPool.querySelectorAll('.avatar-frame') : [];
| |
|
| |
| if (avatars.length === 0 && this.initRetryCount < this.maxRetries) {
| |
| this.initRetryCount++;
| |
| console.log('等待角色加载...', this.initRetryCount);
| |
| setTimeout(() => this.setup(), 300);
| |
| return;
| |
| }
| |
|
| |
| console.log('找到角色数量:', avatars.length);
| |
|
| |
| this.cleanupContainers();
| |
| this.initDragAndDrop();
| |
| this.initButtons();
| |
| },
| |
|
| |
| cleanupContainers: function() {
| |
| const containers = document.querySelectorAll('.tier-row, #character-pool');
| |
| containers.forEach(container => {
| |
| const children = Array.from(container.childNodes);
| |
| children.forEach(child => {
| |
| if (child.nodeType === Node.TEXT_NODE && !child.textContent.trim()) {
| |
| container.removeChild(child);
| |
| } else if (child.nodeType === Node.ELEMENT_NODE) {
| |
| if (!child.classList.contains('avatar-frame') &&
| |
| !child.querySelector('.avatar-frame') &&
| |
| !child.textContent.trim()) {
| |
| container.removeChild(child);
| |
| }
| |
| }
| |
| });
| |
| });
| |
| },
| |
|
| |
| initDragAndDrop: function() {
| |
| console.log('初始化拖拽功能...');
| |
|
| |
| // 初始化所有avatar的拖拽
| |
| this.initAvatarsDraggable();
| |
|
| |
| // 初始化所有drop区域
| |
| this.initDropZones();
| |
| },
| |
|
| |
| // 初始化所有avatar元素的拖拽功能
| |
| initAvatarsDraggable: function() {
| |
| const allAvatars = document.querySelectorAll('.avatar-frame');
| |
| console.log('设置可拖拽的角色数量:', allAvatars.length);
| |
|
| |
| allAvatars.forEach(avatar => {
| |
| if (!avatar.hasAttribute('draggable')) {
| |
| avatar.setAttribute('draggable', 'true');
| |
|
| |
| // 移除旧的事件监听器(如果存在)
| |
| avatar.ondragstart = null;
| |
| avatar.ondragend = null;
| |
|
| |
| // 添加新的事件监听器
| |
| avatar.addEventListener('dragstart', (e) => {
| |
| this.handleDragStart(e, avatar);
| |
| });
| |
|
| |
| avatar.addEventListener('dragend', (e) => {
| |
| this.handleDragEnd(e, avatar);
| |
| });
| |
|
| |
| console.log('已设置拖拽:', avatar);
| |
| }
| |
| });
| |
| },
| |
|
| |
| // 初始化所有drop区域
| |
| initDropZones: function() {
| |
| // 为所有tier行添加drop事件
| |
| const tierRows = document.querySelectorAll('.tier-row');
| |
| console.log('初始化drop区域数量:', tierRows.length);
| |
|
| |
| tierRows.forEach(row => {
| |
| // 移除旧的事件监听器
| |
| const newRow = row.cloneNode(true);
| |
| row.parentNode.replaceChild(newRow, row);
| |
|
| |
| // 添加新的事件监听器
| |
| newRow.addEventListener('dragover', (e) => {
| |
| e.preventDefault();
| |
| e.stopPropagation();
| |
| e.dataTransfer.dropEffect = 'move';
| |
| newRow.classList.add('drag-over');
| |
| });
| |
|
| |
| newRow.addEventListener('dragleave', (e) => {
| |
| e.preventDefault();
| |
| e.stopPropagation();
| |
| // 检查是否真的离开了该元素
| |
| const rect = newRow.getBoundingClientRect();
| |
| if (e.clientX < rect.left || e.clientX >= rect.right ||
| |
| e.clientY < rect.top || e.clientY >= rect.bottom) {
| |
| newRow.classList.remove('drag-over');
| |
| }
| |
| });
| |
|
| |
| newRow.addEventListener('drop', (e) => {
| |
| e.preventDefault();
| |
| e.stopPropagation();
| |
| this.handleDrop(e, newRow);
| |
| });
| |
|
| |
| // 重新设置该行中已有avatar的拖拽
| |
| newRow.querySelectorAll('.avatar-frame').forEach(avatar => {
| |
| if (!avatar.hasAttribute('draggable')) {
| |
| avatar.setAttribute('draggable', 'true');
| |
| avatar.addEventListener('dragstart', (e) => this.handleDragStart(e, avatar));
| |
| avatar.addEventListener('dragend', (e) => this.handleDragEnd(e, avatar));
| |
| }
| |
| });
| |
| }); | |
|
| |
| // 为角色池添加drop事件
| |
| const characterPool = document.getElementById('character-pool');
| |
| if (characterPool) {
| |
| // 移除旧的事件监听器
| |
| const newPool = characterPool.cloneNode(true);
| |
| characterPool.parentNode.replaceChild(newPool, characterPool);
| |
|
| |
| newPool.addEventListener('dragover', (e) => {
| |
| e.preventDefault();
| |
| e.stopPropagation();
| |
| e.dataTransfer.dropEffect = 'move';
| |
| newPool.classList.add('drag-over');
| |
| });
| |
|
| |
| newPool.addEventListener('dragleave', (e) => {
| |
| e.preventDefault();
| |
| e.stopPropagation();
| |
| const rect = newPool.getBoundingClientRect();
| |
| if (e.clientX < rect.left || e.clientX >= rect.right ||
| |
| e.clientY < rect.top || e.clientY >= rect.bottom) {
| |
| newPool.classList.remove('drag-over');
| |
| }
| |
| });
| |
|
| |
| newPool.addEventListener('drop', (e) => {
| |
| e.preventDefault();
| |
| e.stopPropagation();
| |
| this.handleDrop(e, newPool);
| |
| });
| |
|
| |
| // 重新设置角色池中avatar的拖拽
| |
| newPool.querySelectorAll('.avatar-frame').forEach(avatar => {
| |
| if (!avatar.hasAttribute('draggable')) {
| |
| avatar.setAttribute('draggable', 'true');
| |
| avatar.addEventListener('dragstart', (e) => this.handleDragStart(e, avatar));
| |
| avatar.addEventListener('dragend', (e) => this.handleDragEnd(e, avatar));
| |
| }
| |
| });
| |
| }
| |
| },
| |
|
| |
| handleDragStart: function(e, element) {
| |
| console.log('开始拖拽:', element);
| |
| this.draggedElement = element;
| |
| this.sourceContainer = element.parentElement; | |
| element.classList.add('dragging');
| |
| e.dataTransfer.effectAllowed = 'move'; | | e.dataTransfer.effectAllowed = 'move'; |
| e.dataTransfer.setData('text/html', element.innerHTML); | | e.dataTransfer.setData('text/html', this.outerHTML); |
| }, | | }); |
| | | |
| handleDragEnd: function(e, element) { | | avatar.addEventListener('dragend', function(e) { |
| console.log('结束拖拽'); | | this.classList.remove('dragging'); |
| element.classList.remove('dragging');
| | }); |
|
| | }); |
| document.querySelectorAll('.drag-over').forEach(el => {
| | |
| el.classList.remove('drag-over');
| | tierContents.forEach(content => { |
| }); | | content.addEventListener('dragover', function(e) { |
|
| | e.preventDefault(); |
| this.cleanupContainers();
| | e.dataTransfer.dropEffect = 'move'; |
| this.saveState(); | | this.classList.add('drag-over'); |
|
| | }); |
| this.draggedElement = null;
| |
| this.sourceContainer = null; | |
| }, | |
| | | |
| handleDrop: function(e, targetContainer) { | | content.addEventListener('dragleave', function(e) { |
| console.log('放置到:', targetContainer);
| | this.classList.remove('drag-over'); |
| | | }); |
| targetContainer.classList.remove('drag-over');
| |
|
| |
| if (this.draggedElement && this.draggedElement.parentElement !== targetContainer) {
| |
| // 从原位置移除
| |
| if (this.draggedElement.parentElement) {
| |
| this.draggedElement.parentElement.removeChild(this.draggedElement);
| |
| }
| |
|
| |
| // 添加到新位置
| |
| targetContainer.appendChild(this.draggedElement);
| |
|
| |
| // 确保样式正常
| |
| this.draggedElement.style.opacity = '1';
| |
| const img = this.draggedElement.querySelector('img');
| |
| if (img) {
| |
| img.style.opacity = '1';
| |
| }
| |
|
| |
| this.cleanupContainers();
| |
|
| |
| console.log('放置成功');
| |
| }
| |
| },
| |
| | | |
| initButtons: function() { | | content.addEventListener('drop', function(e) { |
| const saveBtn = document.getElementById('save-tierlist-btn');
| | e.preventDefault(); |
| const resetBtn = document.getElementById('reset-tierlist-btn'); | | this.classList.remove('drag-over'); |
| const addRowBtn = document.getElementById('add-row-btn'); | |
| const editModeBtn = document.getElementById('edit-mode-btn');
| |
| | | |
| if (saveBtn) { | | if (draggedElement) { |
| saveBtn.onclick = () => this.saveTierlist(); | | this.appendChild(draggedElement); |
| | draggedElement = null; |
| } | | } |
|
| | }); |
| if (resetBtn) {
| | }); |
| resetBtn.onclick = () => this.resetTierlist();
| | } |
| }
| | // 添加新行 |
|
| | function addNewTier() { |
| if (addRowBtn) {
| | const table = document.getElementById('tierlist-table'); |
| addRowBtn.onclick = () => this.addRow();
| | const tbody = table.querySelector('tbody'); |
| }
| | const tierCount = document.querySelectorAll('.tier-header').length; |
|
| | |
| if (editModeBtn) {
| | const newRow = tbody.insertRow(-1); |
| editModeBtn.onclick = () => this.toggleEditMode();
| | |
| }
| | const headerCell = newRow.insertCell(0); |
| }, | | headerCell.className = 'tier-header'; |
| | headerCell.setAttribute('data-tier', tierCount); |
| | headerCell.style = 'width: 100px; background-color: #9e9e9e; color: white; font-size: 20px; text-align: center; cursor: pointer; position: relative;'; |
| | headerCell.innerHTML = '<div class="tier-label" contenteditable="true">新行</div><div class="color-picker-trigger" onclick="showColorPicker(this, ' + tierCount + ')">🎨</div><div class="tier-remove-btn" onclick="removeTier(this)">删除</div>'; |
| | |
| | const contentCell = newRow.insertCell(1); |
| | contentCell.className = 'tier-content'; |
| | contentCell.setAttribute('data-tier', tierCount); |
| | contentCell.style = 'min-height: 120px; padding: 10px; background: #fff;'; |
| | |
| | initDragAndDrop(); |
| | } |
| | // 删除行 |
| | function removeTier(btn) { |
| | if (confirm('确定要删除这一行吗?')) { |
| | const row = btn.closest('tr'); |
| | row.remove(); |
| | } |
| | } |
| | // 显示颜色选择器 |
| | function showColorPicker(trigger, tier) { |
| | event.stopPropagation(); |
| | currentEditingTier = tier; |
| | |
| | const header = document.querySelector('.tier-header[data-tier="' + tier + '"]'); |
| | const bgColor = rgbToHex(header.style.backgroundColor); |
| | const textColor = rgbToHex(header.style.color); |
| | |
| | document.getElementById('bg-color-picker').value = bgColor; |
| | document.getElementById('text-color-picker').value = textColor; |
| | document.getElementById('color-picker-modal').style.display = 'block'; |
| | document.getElementById('modal-overlay').style.display = 'block'; |
| | } |
| | // 应用颜色 |
| | function applyColor() { |
| | if (currentEditingTier !== null) { |
| | const bgColor = document.getElementById('bg-color-picker').value; |
| | const textColor = document.getElementById('text-color-picker').value; |
| | const header = document.querySelector('.tier-header[data-tier="' + currentEditingTier + '"]'); |
| | | |
| toggleEditMode: function() { | | header.style.backgroundColor = bgColor; |
| this.editMode = !this.editMode;
| | header.style.color = textColor; |
| const btn = document.getElementById('edit-mode-btn');
| | } |
| const table = document.getElementById('tierlist-table');
| | closeColorPicker(); |
|
| | } |
| if (this.editMode) {
| | // 关闭颜色选择器 |
| btn.classList.add('active');
| | function closeColorPicker() { |
| btn.textContent = '完成编辑';
| | document.getElementById('color-picker-modal').style.display = 'none'; |
| table.classList.add('edit-mode');
| | document.getElementById('modal-overlay').style.display = 'none'; |
|
| | currentEditingTier = null; |
| document.querySelectorAll('.tier-header').forEach(header => {
| | } |
| header.classList.add('editable');
| | // RGB转HEX |
| if (!header.querySelector('.delete-row-btn')) {
| | function rgbToHex(rgb) { |
| const deleteBtn = document.createElement('button');
| | if (!rgb || rgb.indexOf('rgb') === -1) return '#000000'; |
| deleteBtn.className = 'delete-row-btn';
| | |
| deleteBtn.textContent = '删除';
| | const values = rgb.match(/\d+/g); |
| deleteBtn.onclick = (e) => {
| | if (!values) return '#000000'; |
| e.stopPropagation();
| |
| this.deleteRow(header.closest('tr'));
| |
| };
| |
| 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);
| |
| const deleteBtn = header.querySelector('.delete-row-btn');
| |
| if (deleteBtn) deleteBtn.remove();
| |
| header.textContent = newName;
| |
| header.style.backgroundColor = newColor;
| |
|
| |
| const newDeleteBtn = document.createElement('button');
| |
| newDeleteBtn.className = 'delete-row-btn';
| |
| newDeleteBtn.textContent = '删除';
| |
| newDeleteBtn.onclick = (e) => {
| |
| e.stopPropagation();
| |
| this.deleteRow(header.closest('tr'));
| |
| };
| |
| header.appendChild(newDeleteBtn);
| |
|
| |
| const row = header.closest('tr').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 tierCount = document.querySelectorAll('.tier-row').length;
| |
| const tierName = 'T' + (tierCount + 1);
| |
|
| |
| const newRow = document.createElement('tr');
| |
| 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 class="tier-row" data-tier="${tierName}"></td>
| |
| `;
| |
|
| |
| tbody.appendChild(newRow);
| |
|
| |
| console.log('添加新行:', tierName);
| |
|
| |
| // 重新初始化所有drop区域(包括新行)
| |
| this.initDropZones();
| |
|
| |
| // 如果在编辑模式下,添加编辑功能
| |
| 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 = Array.from(tierRow.querySelectorAll('.avatar-frame'));
| |
| const pool = document.getElementById('character-pool');
| |
|
| |
| avatars.forEach(avatar => {
| |
| pool.appendChild(avatar);
| |
| });
| |
|
| |
| row.remove();
| |
|
| |
| this.cleanupContainers();
| |
| 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));
| |
| console.log('状态已保存');
| |
| } catch (e) {
| |
| console.warn('无法保存状态:', e);
| |
| }
| |
| },
| |
|
| |
| loadState: function() {
| |
| try {
| |
| const stateJson = localStorage.getItem('tierlist-state');
| |
| if (!stateJson) {
| |
| console.log('没有保存的状态');
| |
| return;
| |
| }
| |
|
| |
| const state = JSON.parse(stateJson);
| |
| console.log('加载保存的状态:', state);
| |
|
| |
| 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);
| |
| tierRow.innerHTML = '';
| |
| }
| |
| }
| |
| }
| |
| });
| |
|
| |
| 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 class="tier-row" data-tier="${savedRow.name}"></td>
| |
| `;
| |
| tbody.appendChild(newRow);
| |
| }
| |
| }
| |
|
| |
| // 重新初始化所有drop区域
| |
| this.initDropZones();
| |
|
| |
| 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 && avatar.parentElement) {
| |
| tierRow.appendChild(avatar);
| |
| }
| |
| });
| |
| });
| |
|
| |
| // 重新初始化拖拽
| |
| this.initAvatarsDraggable();
| |
| this.cleanupContainers();
| |
| console.log('状态加载完成');
| |
| }, 200);
| |
|
| |
| } catch (e) {
| |
| console.warn('无法加载状态:', e);
| |
| }
| |
| },
| |
|
| |
| getCharacterId: function(avatar) {
| |
| 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;
| |
| },
| |
|
| |
| saveTierlist: function() {
| |
| const table = document.getElementById('tierlist-table');
| |
| if (!table) {
| |
| alert('未找到 Tierlist 表格!');
| |
| return;
| |
| }
| |
|
| |
| if (typeof html2canvas === 'undefined') {
| |
| alert('正在加载图片生成库,请稍后再试...');
| |
| return;
| |
| }
| |
|
| |
| const controls = document.querySelector('.tierlist-controls');
| |
| const poolContainer = document.querySelector('.character-pool-container');
| |
|
| |
| const originalControlsDisplay = controls ? controls.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 (poolContainer) poolContainer.style.display = 'none';
| |
|
| |
| html2canvas(table, {
| |
| scale: 2,
| |
| backgroundColor: '#ffffff',
| |
| logging: false,
| |
| useCORS: true,
| |
| allowTaint: true
| |
| }).then(canvas => {
| |
| if (controls) controls.style.display = originalControlsDisplay;
| |
| if (poolContainer) poolContainer.style.display = originalPoolDisplay;
| |
| deleteButtons.forEach(btn => btn.style.display = '');
| |
|
| |
| const link = document.createElement('a');
| |
| const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
| |
| link.download = 'tierlist_' + timestamp + '.png';
| |
| link.href = canvas.toDataURL('image/png');
| |
| link.click();
| |
| }).catch(err => {
| |
| console.error('生成图片失败:', err);
| |
| if (controls) controls.style.display = originalControlsDisplay;
| |
| if (poolContainer) poolContainer.style.display = originalPoolDisplay;
| |
| deleteButtons.forEach(btn => btn.style.display = '');
| |
| alert('生成图片失败,请查看控制台了解详情');
| |
| });
| |
| },
| |
|
| |
| resetTierlist: function() {
| |
| if (!confirm('确定要重置所有排列吗?此操作不可撤销。')) {
| |
| return;
| |
| }
| |
|
| |
| const pool = document.getElementById('character-pool');
| |
| if (!pool) {
| |
| alert('未找到角色池!');
| |
| return;
| |
| }
| |
|
| |
| const allAvatars = Array.from(document.querySelectorAll('.avatar-frame'));
| |
|
| |
| document.querySelectorAll('.tier-row').forEach(row => {
| |
| row.innerHTML = '';
| |
| });
| |
|
| |
| pool.innerHTML = '';
| |
|
| |
| allAvatars.forEach(avatar => {
| |
| pool.appendChild(avatar);
| |
| avatar.style.opacity = '1';
| |
| const img = avatar.querySelector('img');
| |
| if (img) {
| |
| img.style.opacity = '1';
| |
| }
| |
| });
| |
|
| |
| // 重新初始化拖拽功能
| |
| this.initAvatarsDraggable();
| |
| this.cleanupContainers();
| |
|
| |
| try {
| |
| localStorage.removeItem('tierlist-state');
| |
| console.log('状态已清除');
| |
| } catch (e) {
| |
| console.warn('无法清除状态:', e);
| |
| }
| |
|
| |
| alert('重置完成!');
| |
| }
| |
| };
| |
| | | |
| window.TierlistMakerInitialized = true; | | const hex = values.map(x => { |
| window.TierlistMaker = TierlistMaker;
| | const hexValue = parseInt(x).toString(16); |
| | return hexValue.length === 1 ? '0' + hexValue : hexValue; |
| | }); |
| | | |
| TierlistMaker.init(); | | return '#' + hex.join(''); |
| | } |
| | // 导出为PNG |
| | function exportToPNG() { |
| | const container = document.getElementById('tier-list-container'); |
| | | |
| // 添加手动加载状态的功能 | | // 使用html2canvas库 |
| window.loadTierlistState = function() { | | if (typeof html2canvas === 'undefined') { |
| if (window.TierlistMaker) { | | // 动态加载html2canvas |
| window.TierlistMaker.loadState(); | | const script = document.createElement('script'); |
| } | | script.src = 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js'; |
| }; | | script.onload = function() { |
| })(); | | captureAndDownload(container); |
| | }; |
| | document.head.appendChild(script); |
| | } else { |
| | captureAndDownload(container); |
| | } |
| | } |
| | function captureAndDownload(container) { |
| | html2canvas(container, { |
| | backgroundColor: '#ffffff', |
| | scale: 2, |
| | logging: false, |
| | useCORS: true |
| | }).then(canvas => { |
| | canvas.toBlob(function(blob) { |
| | const url = URL.createObjectURL(blob); |
| | const a = document.createElement('a'); |
| | a.href = url; |
| | a.download = 'tier-list-' + new Date().getTime() + '.png'; |
| | a.click(); |
| | URL.revokeObjectURL(url); |
| | }); |
| | }); |
| | } |
| | // 页面加载完成后初始化 |
| | if (document.readyState === 'loading') { |
| | document.addEventListener('DOMContentLoaded', initDragAndDrop); |
| | } else { |
| | initDragAndDrop(); |
| | } |
| | // 监听动态添加的元素 |
| | const observer = new MutationObserver(function(mutations) { |
| | initDragAndDrop(); |
| | }); |
| | observer.observe(document.body, { |
| | childList: true, |
| | subtree: true |
| | }); |
| </script> | | </script> |
| </includeonly> | | </includeonly> |