打开/关闭菜单
443
1222
41
4822
植物大战僵尸杂交版Wiki
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

微件:BatchUpload:修订间差异

来自植物大战僵尸杂交版Wiki
无编辑摘要
无编辑摘要
 
(未显示同一用户的1个中间版本)
第47行: 第47行:
}
}
.batch-upload-item .file-rename:focus { border-color: #4CAF50; outline: none; }
.batch-upload-item .file-rename:focus { border-color: #4CAF50; outline: none; }
.batch-upload-item .file-status { font-size: 12px; min-width: 60px; text-align: right; }
.batch-upload-item .file-status { font-size: 12px; min-width: 85px; text-align: right; }
.status-waiting { color: #999; }
.status-waiting { color: #999; }
.status-uploading { color: #2196F3; }
.status-uploading { color: #2196F3; }
.status-success { color: #4CAF50; }
.status-success { color: #4CAF50; }
.status-error { color: #f44336; }
.status-error { color: #f44336; }
.status-retrying { color: #FF9800; }
.status-conflict { color: #FF9800; }
.status-skipped { color: #9E9E9E; }
.batch-upload-progress {
.batch-upload-progress {
     width: 100%; height: 6px; background: #e0e0e0;
     width: 100%; height: 6px; background: #e0e0e0;
第65行: 第68行:
.batch-upload-remove:hover { color: #d32f2f; }
.batch-upload-remove:hover { color: #d32f2f; }
.batch-upload-ext { font-size: 12px; color: #999; margin-left: 2px; }
.batch-upload-ext { font-size: 12px; color: #999; margin-left: 2px; }
.conflict-actions { display: inline-flex; gap: 4px; margin-left: 8px; }
.conflict-btn {
    padding: 2px 8px; font-size: 11px; border: 1px solid #ccc;
    border-radius: 3px; cursor: pointer; background: #f9f9f9;
}
.conflict-btn.overwrite { border-color: #4CAF50; color: #4CAF50; }
.conflict-btn.overwrite:hover { background: #4CAF50; color: #fff; }
.conflict-btn.skip { border-color: #f44336; color: #f44336; }
.conflict-btn.skip:hover { background: #f44336; color: #fff; }
.conflict-notice {
    background: #fff3e0; border: 1px solid #FF9800; border-radius: 8px;
    padding: 10px 15px; margin: 10px 0; display: none; text-align: left; font-size: 14px;
}
.conflict-notice button {
    padding: 6px 15px; margin: 5px 5px 0 0; border: none;
    border-radius: 5px; cursor: pointer; font-size: 13px; color: #fff;
}
.conflict-notice .btn-overwrite-all { background: #4CAF50; }
.conflict-notice .btn-skip-all { background: #f44336; }
</style>
</style>


第73行: 第95行:
         <button class="batch-upload-btn" id="batch-upload-select">选择图片</button>
         <button class="batch-upload-btn" id="batch-upload-select">选择图片</button>
         <button class="batch-upload-btn" id="batch-upload-clear">清空列表</button>
         <button class="batch-upload-btn" id="batch-upload-clear">清空列表</button>
    </div>
    <div class="conflict-notice" id="conflict-notice">
        <strong>⚠️ 检测到重名文件</strong>,请为每个文件选择"覆盖"或"跳过":
        <div id="conflict-actions-global"></div>
     </div>
     </div>
     <div class="batch-upload-progress" id="batch-upload-progress">
     <div class="batch-upload-progress" id="batch-upload-progress">
第79行: 第105行:
     <div class="batch-upload-list" id="batch-upload-list"></div>
     <div class="batch-upload-list" id="batch-upload-list"></div>
     <div class="batch-upload-summary" id="batch-upload-summary"></div>
     <div class="batch-upload-summary" id="batch-upload-summary"></div>
     <button class="batch-upload-btn" id="batch-upload-start" disabled>开始上传</button>
     <button class="batch-upload-btn" id="batch-upload-start" disabled>检测并上传</button>
</div>
</div>


第92行: 第118行:
     var $progressBar = document.getElementById('batch-upload-progress-bar');
     var $progressBar = document.getElementById('batch-upload-progress-bar');
     var $summary = document.getElementById('batch-upload-summary');
     var $summary = document.getElementById('batch-upload-summary');
    var $conflictNotice = document.getElementById('conflict-notice');
    var $conflictGlobal = document.getElementById('conflict-actions-global');


     var files = [];
     var files = [];
第97行: 第125行:
     var totalCount = 0;
     var totalCount = 0;
     var isUploading = false;
     var isUploading = false;
    var conflictResolved = false;
    var MAX_RETRIES = 3;
    var retryCounts = {};
    var currentFailedFiles = [];


     function getExt(filename) {
     function getExt(filename) {
第108行: 第140行:
     }
     }


    // 创建隐藏的 input[type=file]
     var fileInput = document.createElement('input');
     var fileInput = document.createElement('input');
     fileInput.type = 'file';
     fileInput.type = 'file';
第125行: 第156行:
     });
     });


    // 拖拽
     $container.addEventListener('dragover', function(e) {
     $container.addEventListener('dragover', function(e) {
         e.preventDefault();
         e.preventDefault();
第141行: 第171行:
     $clearBtn.addEventListener('click', function() {
     $clearBtn.addEventListener('click', function() {
         files = [];
         files = [];
        conflictResolved = false;
        retryCounts = {};
        currentFailedFiles = [];
        $conflictNotice.style.display = 'none';
         renderList();
         renderList();
         updateStartButton();
         updateStartButton();
第152行: 第186行:
                 file._targetName = getNameWithoutExt(file.name);
                 file._targetName = getNameWithoutExt(file.name);
                 file._ext = getExt(file.name);
                 file._ext = getExt(file.name);
                file._conflictAction = 'ask';
                file._fileId = Date.now() + '_' + i + '_' + Math.random().toString(36).substr(2, 5);
                 files.push(file);
                 files.push(file);
                retryCounts[file._fileId] = 0;
             }
             }
         }
         }
        conflictResolved = false;
        $conflictNotice.style.display = 'none';
         renderList();
         renderList();
         updateStartButton();
         updateStartButton();
第169行: 第208行:
             var item = document.createElement('div');
             var item = document.createElement('div');
             item.className = 'batch-upload-item';
             item.className = 'batch-upload-item';
            item.setAttribute('data-index', index);
            var actionHtml = '';
            if (file._conflictAction === 'overwrite') {
                actionHtml = '<span class="conflict-actions"><span style="color:#4CAF50;font-size:12px;">覆盖</span></span>';
            } else if (file._conflictAction === 'skip') {
                actionHtml = '<span class="conflict-actions"><span style="color:#f44336;font-size:12px;">跳过</span></span>';
            }
             item.innerHTML =
             item.innerHTML =
                 '<div class="file-info">' +
                 '<div class="file-info">' +
第175行: 第223行:
                     '<div class="file-original">原始: ' + file.name + ' (' + sizeStr + ')</div>' +
                     '<div class="file-original">原始: ' + file.name + ' (' + sizeStr + ')</div>' +
                 '</div>' +
                 '</div>' +
                 '<span class="file-status status-waiting" data-index="' + index + '">等待上传</span>' +
                actionHtml +
                 '<span class="file-status status-waiting" data-index="' + index + '">等待检测</span>' +
                 '<span class="batch-upload-remove" data-index="' + index + '" title="移除">×</span>';
                 '<span class="batch-upload-remove" data-index="' + index + '" title="移除">×</span>';
             $list.appendChild(item);
             $list.appendChild(item);
         });
         });


        // 改名
         $list.querySelectorAll('.file-rename').forEach(function(input) {
         $list.querySelectorAll('.file-rename').forEach(function(input) {
             input.addEventListener('input', function() {
             input.addEventListener('input', function() {
                 var idx = parseInt(this.getAttribute('data-index'));
                 var idx = parseInt(this.getAttribute('data-index'));
                 if (files[idx]) files[idx]._targetName = this.value.trim();
                 if (files[idx]) {
                    files[idx]._targetName = this.value.trim();
                    files[idx]._conflictAction = 'ask';
                    conflictResolved = false;
                    $conflictNotice.style.display = 'none';
                }
             });
             });
         });
         });


        // 删除
         $list.querySelectorAll('.batch-upload-remove').forEach(function(btn) {
         $list.querySelectorAll('.batch-upload-remove').forEach(function(btn) {
             btn.addEventListener('click', function() {
             btn.addEventListener('click', function() {
                 var idx = parseInt(this.getAttribute('data-index'));
                 var idx = parseInt(this.getAttribute('data-index'));
                delete retryCounts[files[idx]._fileId];
                 files.splice(idx, 1);
                 files.splice(idx, 1);
                 renderList();
                 renderList();
第200行: 第253行:


     function updateStartButton() {
     function updateStartButton() {
         $startBtn.disabled = files.length === 0 || isUploading;
         if (currentFailedFiles.length > 0) {
            $startBtn.disabled = false;
            $startBtn.textContent = '重试失败文件 (' + currentFailedFiles.length + ')';
            $startBtn.style.background = '#FF9800';
        } else if (conflictResolved) {
            $startBtn.disabled = false;
            $startBtn.textContent = '开始上传';
            $startBtn.style.background = '#4CAF50';
        } else {
            $startBtn.disabled = files.length === 0 || isUploading;
            $startBtn.textContent = '检测并上传';
            $startBtn.style.background = '#4CAF50';
        }
     }
     }


     function getEditToken() {
     function getEditToken() {
         return mw.user.tokens.get('csrfToken');
         return mw.user.tokens.get('csrfToken');
    }
    function checkFileExists(filename) {
        return new Promise(function(resolve) {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', mw.util.wikiScript('api') + '?action=query&titles=File:' + encodeURIComponent(filename) + '&format=json&origin=*', true);
            xhr.onload = function() {
                try {
                    var data = JSON.parse(xhr.responseText);
                    var pages = data.query.pages;
                    for (var id in pages) {
                        if (parseInt(id) > 0) { resolve(true); return; }
                    }
                    resolve(false);
                } catch(e) { resolve(false); }
            };
            xhr.onerror = function() { resolve(false); };
            xhr.send();
        });
     }
     }


第224行: 第308行:
                     var data = JSON.parse(xhr.responseText);
                     var data = JSON.parse(xhr.responseText);
                     if (data.upload && data.upload.result === 'Success') {
                     if (data.upload && data.upload.result === 'Success') {
                         resolve({ success: true, name: targetName });
                         resolve({ success: true, name: targetName, fileId: file._fileId });
                     } else {
                     } else {
                         var errMsg = '未知错误';
                         var errMsg = '未知错误';
                         if (data.upload && data.upload.warnings) errMsg = JSON.stringify(data.upload.warnings);
                         if (data.upload && data.upload.warnings) errMsg = JSON.stringify(data.upload.warnings);
                         else if (data.error) errMsg = data.error.info || '未知错误';
                         else if (data.error) errMsg = data.error.info || '未知错误';
                         resolve({ success: false, name: targetName, error: errMsg });
                         resolve({ success: false, name: targetName, error: errMsg, fileId: file._fileId });
                     }
                     }
                 } catch(e) {
                 } catch(e) {
                     resolve({ success: false, name: targetName, error: '解析响应失败' });
                     resolve({ success: false, name: targetName, error: '解析响应失败', fileId: file._fileId });
                 }
                 }
             };
             };
             xhr.onerror = function() {
             xhr.onerror = function() {
                 resolve({ success: false, name: targetName, error: '网络错误' });
                 resolve({ success: false, name: targetName, error: '网络错误', fileId: file._fileId });
             };
             };
             xhr.send(formData);
             xhr.send(formData);
         });
         });
    }
    function delay(ms) {
        return new Promise(function(resolve) { setTimeout(resolve, ms); });
    }
    function uploadWithRetry(file, index, attemptNumber) {
        attemptNumber = attemptNumber || 1;
        var statusEl = $list.querySelector('.file-status[data-index="' + index + '"]');
        if (attemptNumber > 1 && statusEl) {
            statusEl.classList.remove('status-error', 'status-uploading');
            statusEl.classList.add('status-retrying');
            statusEl.textContent = '重试 ' + (attemptNumber - 1) + '/' + MAX_RETRIES;
        }
        return uploadFile(file, index).then(function(result) {
            if (result.success) {
                if (statusEl) {
                    statusEl.classList.remove('status-uploading', 'status-retrying', 'status-error');
                    statusEl.classList.add('status-success');
                    statusEl.textContent = '✅ 成功';
                }
                retryCounts[file._fileId] = 0;
                uploadedCount++;
                var progress = Math.round((uploadedCount / totalCount) * 100);
                $progressBar.style.width = progress + '%';
                return { success: true, index: index };
            } else {
                if (attemptNumber < MAX_RETRIES) {
                    if (statusEl) {
                        statusEl.classList.remove('status-uploading', 'status-error');
                        statusEl.classList.add('status-retrying');
                        statusEl.textContent = '重试 ' + attemptNumber + '/' + MAX_RETRIES;
                    }
                    var waitTime = attemptNumber * 2000;
                    return delay(waitTime).then(function() {
                        return uploadWithRetry(file, index, attemptNumber + 1);
                    });
                } else {
                    if (statusEl) {
                        statusEl.classList.remove('status-uploading', 'status-retrying');
                        statusEl.classList.add('status-error');
                        statusEl.textContent = '❌ 失败(' + MAX_RETRIES + '次)';
                    }
                    retryCounts[file._fileId] = MAX_RETRIES;
                    uploadedCount++;
                    var progress = Math.round((uploadedCount / totalCount) * 100);
                    $progressBar.style.width = progress + '%';
                    currentFailedFiles.push({ file: file, index: index, error: result.error });
                    return { success: false, index: index, error: result.error };
                }
            }
        });
    }
    function showConflictUI(conflicts) {
        $conflictNotice.style.display = 'block';
        $conflictGlobal.innerHTML =
            '<button class="btn-overwrite-all">全部覆盖</button>' +
            '<button class="btn-skip-all">全部跳过</button>';
        $conflictGlobal.querySelector('.btn-overwrite-all').addEventListener('click', function() {
            conflicts.forEach(function(idx) { files[idx]._conflictAction = 'overwrite'; });
            conflictResolved = true;
            $conflictNotice.style.display = 'none';
            renderList();
            updateStartButton();
        });
        $conflictGlobal.querySelector('.btn-skip-all').addEventListener('click', function() {
            conflicts.forEach(function(idx) { files[idx]._conflictAction = 'skip'; });
            conflictResolved = true;
            $conflictNotice.style.display = 'none';
            renderList();
            updateStartButton();
        });
        conflicts.forEach(function(idx) {
            var item = $list.querySelector('.batch-upload-item[data-index="' + idx + '"]');
            if (!item) return;
            var statusEl = item.querySelector('.file-status');
            if (statusEl) {
                statusEl.classList.remove('status-waiting');
                statusEl.classList.add('status-conflict');
                statusEl.textContent = '⚠️ 重名';
            }
            var oldActions = item.querySelector('.conflict-actions');
            if (oldActions) oldActions.remove();
            var actions = document.createElement('span');
            actions.className = 'conflict-actions';
            actions.innerHTML =
                '<button class="conflict-btn overwrite" data-idx="' + idx + '">覆盖</button>' +
                '<button class="conflict-btn skip" data-idx="' + idx + '">跳过</button>';
            item.appendChild(actions);
        });
        $list.querySelectorAll('.conflict-btn.overwrite').forEach(function(btn) {
            btn.addEventListener('click', function(e) {
                e.stopPropagation();
                var idx = parseInt(this.getAttribute('data-idx'));
                files[idx]._conflictAction = 'overwrite';
                var item = $list.querySelector('.batch-upload-item[data-index="' + idx + '"]');
                var statusEl = item.querySelector('.file-status');
                statusEl.classList.remove('status-conflict');
                statusEl.classList.add('status-waiting');
                statusEl.textContent = '等待上传';
                var actionsEl = item.querySelector('.conflict-actions');
                if (actionsEl) actionsEl.innerHTML = '<span style="color:#4CAF50;font-size:12px;">覆盖</span>';
                checkAllConflictsResolved();
            });
        });
        $list.querySelectorAll('.conflict-btn.skip').forEach(function(btn) {
            btn.addEventListener('click', function(e) {
                e.stopPropagation();
                var idx = parseInt(this.getAttribute('data-idx'));
                files[idx]._conflictAction = 'skip';
                var item = $list.querySelector('.batch-upload-item[data-index="' + idx + '"]');
                var statusEl = item.querySelector('.file-status');
                statusEl.classList.remove('status-conflict');
                statusEl.classList.add('status-skipped');
                statusEl.textContent = '⏭️ 跳过';
                var actionsEl = item.querySelector('.conflict-actions');
                if (actionsEl) actionsEl.innerHTML = '<span style="color:#f44336;font-size:12px;">跳过</span>';
                checkAllConflictsResolved();
            });
        });
    }
    function checkAllConflictsResolved() {
        var allResolved = true;
        files.forEach(function(f) {
            if (f._conflictAction === 'ask') allResolved = false;
        });
        if (allResolved) {
            conflictResolved = true;
            $conflictNotice.style.display = 'none';
            updateStartButton();
        }
     }
     }


     $startBtn.addEventListener('click', function() {
     $startBtn.addEventListener('click', function() {
        if (isUploading) return;
        // 如果有失败文件,点击重试
        if (currentFailedFiles.length > 0) {
            retryFailedFiles();
            return;
        }
        if (files.length === 0) return;
        if (conflictResolved) {
            startUpload();
            return;
        }
        isUploading = true;
        updateStartButton();
        $selectBtn.disabled = true;
        $clearBtn.disabled = true;
        $startBtn.textContent = '检测中...';
        $list.querySelectorAll('.file-rename').forEach(function(i) { i.disabled = true; });
        $list.querySelectorAll('.batch-upload-remove').forEach(function(i) { i.style.display = 'none'; });
        var checkQueue = Promise.resolve();
        var conflicts = [];
        files.forEach(function(file, index) {
            checkQueue = checkQueue.then(function() {
                var targetName = (file._targetName || getNameWithoutExt(file.name)) + file._ext;
                return checkFileExists(targetName).then(function(exists) {
                    if (exists) {
                        conflicts.push(index);
                    } else {
                        var statusEl = $list.querySelector('.file-status[data-index="' + index + '"]');
                        if (statusEl) {
                            statusEl.classList.remove('status-waiting');
                            statusEl.classList.add('status-waiting');
                            statusEl.textContent = '✅ 新文件';
                        }
                    }
                });
            });
        });
        checkQueue.then(function() {
            isUploading = false;
            $selectBtn.disabled = false;
            $clearBtn.disabled = false;
            updateStartButton();
            if (conflicts.length > 0) {
                showConflictUI(conflicts);
            } else {
                conflictResolved = true;
                updateStartButton();
                startUpload();
            }
        });
    });
    function startUpload() {
         if (isUploading || files.length === 0) return;
         if (isUploading || files.length === 0) return;
         isUploading = true;
         isUploading = true;
         uploadedCount = 0;
         uploadedCount = 0;
         totalCount = files.length;
         totalCount = 0;
        currentFailedFiles = [];
         updateStartButton();
         updateStartButton();
         $selectBtn.disabled = true;
         $selectBtn.disabled = true;
第255行: 第540行:
         $list.querySelectorAll('.file-rename').forEach(function(i) { i.disabled = true; });
         $list.querySelectorAll('.file-rename').forEach(function(i) { i.disabled = true; });
         $list.querySelectorAll('.batch-upload-remove').forEach(function(i) { i.style.display = 'none'; });
         $list.querySelectorAll('.batch-upload-remove').forEach(function(i) { i.style.display = 'none'; });
        $list.querySelectorAll('.conflict-actions').forEach(function(i) { i.style.display = 'none'; });
        var toUpload = [];
        files.forEach(function(f, idx) {
            if (f._conflictAction !== 'skip') {
                toUpload.push({ file: f, index: idx });
            }
        });
        totalCount = toUpload.length;
        if (totalCount === 0) {
            finishUpload();
            return;
        }


         var uploadQueue = Promise.resolve();
         var uploadQueue = Promise.resolve();
         files.forEach(function(file, index) {
         toUpload.forEach(function(item) {
             uploadQueue = uploadQueue.then(function() {
             uploadQueue = uploadQueue.then(function() {
                 var statusEl = $list.querySelector('.file-status[data-index="' + index + '"]');
                 var statusEl = $list.querySelector('.file-status[data-index="' + item.index + '"]');
                 if (statusEl) {
                 if (statusEl) {
                     statusEl.classList.remove('status-waiting');
                     statusEl.classList.remove('status-waiting', 'status-conflict', 'status-skipped');
                     statusEl.classList.add('status-uploading');
                     statusEl.classList.add('status-uploading');
                     statusEl.textContent = '上传中...';
                     statusEl.textContent = '上传中...';
                 }
                 }
                 return uploadFile(file, index).then(function(result) {
                 return uploadWithRetry(item.file, item.index);
                    uploadedCount++;
            });
                    var progress = Math.round((uploadedCount / totalCount) * 100);
        });
                    $progressBar.style.width = progress + '%';
 
                    if (statusEl) {
        uploadQueue.then(function() {
                        statusEl.classList.remove('status-uploading');
            finishUpload();
                        if (result.success) {
        });
                            statusEl.classList.add('status-success');
    }
                            statusEl.textContent = '✅ 成功';
 
                        } else {
    function retryFailedFiles() {
                            statusEl.classList.add('status-error');
        if (isUploading || currentFailedFiles.length === 0) return;
                            statusEl.textContent = '❌ 失败';
        isUploading = true;
                        }
        $selectBtn.disabled = true;
                    }
        $clearBtn.disabled = true;
                 });
        $progress.style.display = 'block';
        $progressBar.style.width = '0%';
        uploadedCount = 0;
        totalCount = currentFailedFiles.length;
        updateStartButton();
 
        var toRetry = currentFailedFiles.slice();
        currentFailedFiles = [];
 
        var uploadQueue = Promise.resolve();
        toRetry.forEach(function(item) {
            uploadQueue = uploadQueue.then(function() {
                retryCounts[item.file._fileId] = 0;
                var statusEl = $list.querySelector('.file-status[data-index="' + item.index + '"]');
                if (statusEl) {
                    statusEl.classList.remove('status-error');
                    statusEl.classList.add('status-uploading');
                    statusEl.textContent = '重试中...';
                }
                 return uploadWithRetry(item.file, item.index);
             });
             });
         });
         });


         uploadQueue.then(function() {
         uploadQueue.then(function() {
             isUploading = false;
             finishUpload();
            $selectBtn.disabled = false;
        });
            $clearBtn.disabled = false;
    }
            updateStartButton();
 
            var successCount = $list.querySelectorAll('.status-success').length;
    function finishUpload() {
            var failCount = $list.querySelectorAll('.status-error').length;
        isUploading = false;
            $summary.style.display = 'block';
        $selectBtn.disabled = false;
            $summary.innerHTML = '上传完成!成功 ' + successCount + ' 张,失败 ' + failCount + ' 张。';
        $clearBtn.disabled = false;
        updateStartButton();
 
        var successCount = $list.querySelectorAll('.status-success').length;
        var failCount = $list.querySelectorAll('.status-error').length;
        var skipCount = $list.querySelectorAll('.status-skipped').length;
 
        var msgParts = [];
        if (successCount > 0) msgParts.push('成功 ' + successCount + ' 张');
        if (failCount > 0) msgParts.push('失败 ' + failCount + ' 张');
        if (skipCount > 0) msgParts.push('跳过 ' + skipCount + ' 张');
 
        $summary.style.display = 'block';
        $summary.innerHTML = '上传完成!' + msgParts.join(',') + '';
 
        if (currentFailedFiles.length > 0) {
            $summary.innerHTML += ' <a href="javascript:void(0)" id="retry-link" style="color:#FF9800;font-weight:bold;">点击重试失败文件</a>';
        }
 
        if (currentFailedFiles.length === 0) {
             files = [];
             files = [];
            conflictResolved = false;
             setTimeout(function() {
             setTimeout(function() {
                 if (files.length === 0 && !isUploading) {
                 if (files.length === 0 && !isUploading && currentFailedFiles.length === 0) {
                     renderList();
                     renderList();
                     $progress.style.display = 'none';
                     $progress.style.display = 'none';
                 }
                 }
             }, 5000);
             }, 5000);
         });
         }
    });
 
        // 绑定重试链接
        var retryLink = document.getElementById('retry-link');
        if (retryLink) {
            retryLink.addEventListener('click', function() {
                retryFailedFiles();
            });
        }
    }
})();
})();
</script>
</script>
</includeonly>
</includeonly>

2026年6月12日 (五) 12:25的最新版本