打开/关闭搜索
搜索
打开/关闭菜单
443
1222
41
4822
植物大战僵尸杂交版Wiki
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
上传文件
打开/关闭外观设置菜单
notifications
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
user-interface-preferences
个人工具
创建账号
登录
查看“︁微件:BatchUpload”︁的源代码
来自植物大战僵尸杂交版Wiki
查看
阅读
查看源代码
查看历史
associated-pages
微件
讨论
更多操作
←
微件:BatchUpload
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
您没有权限编辑
微件
命名空间内的页面。
您可以查看和复制此页面的源代码。
<includeonly> <style> .batch-upload-container { margin: 20px 0; padding: 20px; border: 2px dashed #ccc; border-radius: 12px; text-align: center; background: #fafafa; } .batch-upload-container.dragover { border-color: #4CAF50; background: #f0fff0; } .batch-upload-btn { display: inline-block; padding: 12px 30px; background: #4CAF50; color: #fff; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; margin: 8px; } .batch-upload-btn:hover { background: #45a049; } .batch-upload-btn:disabled { background: #ccc; cursor: not-allowed; } .batch-upload-list { margin-top: 15px; text-align: left; max-height: 400px; overflow-y: auto; } .batch-upload-item { display: flex; align-items: center; padding: 8px 12px; border-bottom: 1px solid #eee; font-size: 14px; gap: 10px; } .batch-upload-item .file-info { flex: 1; min-width: 0; } .batch-upload-item .file-original { font-size: 12px; color: #999; word-break: break-all; } .batch-upload-item .file-rename { width: 200px; padding: 4px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 13px; } .batch-upload-item .file-rename:focus { border-color: #4CAF50; outline: none; } .batch-upload-item .file-status { font-size: 12px; min-width: 85px; text-align: right; } .status-waiting { color: #999; } .status-uploading { color: #2196F3; } .status-success { color: #4CAF50; } .status-error { color: #f44336; } .status-retrying { color: #FF9800; } .status-conflict { color: #FF9800; } .status-skipped { color: #9E9E9E; } .batch-upload-progress { width: 100%; height: 6px; background: #e0e0e0; border-radius: 3px; margin: 15px 0; overflow: hidden; display: none; } .batch-upload-progress-bar { height: 100%; background: #4CAF50; border-radius: 3px; transition: width 0.3s; width: 0%; } .batch-upload-summary { margin-top: 10px; font-size: 14px; color: #555; display: none; } .batch-upload-actions { margin-top: 10px; display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; } .batch-upload-remove { color: #f44336; cursor: pointer; font-size: 18px; line-height: 1; } .batch-upload-remove:hover { color: #d32f2f; } .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> <div class="batch-upload-container" id="batch-upload-container"> <h3>📤 批量上传图片</h3> <p style="color:#888;font-size:14px;">支持一次选择多张图片,或拖拽图片到此处</p> <div class="batch-upload-actions"> <button class="batch-upload-btn" id="batch-upload-select">选择图片</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 class="batch-upload-progress" id="batch-upload-progress"> <div class="batch-upload-progress-bar" id="batch-upload-progress-bar"></div> </div> <div class="batch-upload-list" id="batch-upload-list"></div> <div class="batch-upload-summary" id="batch-upload-summary"></div> <button class="batch-upload-btn" id="batch-upload-start" disabled>检测并上传</button> </div> <script> (function() { var $container = document.getElementById('batch-upload-container'); var $selectBtn = document.getElementById('batch-upload-select'); var $clearBtn = document.getElementById('batch-upload-clear'); var $startBtn = document.getElementById('batch-upload-start'); var $list = document.getElementById('batch-upload-list'); var $progress = document.getElementById('batch-upload-progress'); var $progressBar = document.getElementById('batch-upload-progress-bar'); var $summary = document.getElementById('batch-upload-summary'); var $conflictNotice = document.getElementById('conflict-notice'); var $conflictGlobal = document.getElementById('conflict-actions-global'); var files = []; var uploadedCount = 0; var totalCount = 0; var isUploading = false; var conflictResolved = false; var MAX_RETRIES = 3; var retryCounts = {}; var currentFailedFiles = []; function getExt(filename) { var lastDot = filename.lastIndexOf('.'); return lastDot > -1 ? filename.substring(lastDot) : ''; } function getNameWithoutExt(filename) { var lastDot = filename.lastIndexOf('.'); return lastDot > -1 ? filename.substring(0, lastDot) : filename; } var fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.multiple = true; fileInput.accept = 'image/*'; fileInput.style.display = 'none'; document.body.appendChild(fileInput); fileInput.addEventListener('change', function() { addFiles(this.files); this.value = ''; }); $selectBtn.addEventListener('click', function() { fileInput.click(); }); $container.addEventListener('dragover', function(e) { e.preventDefault(); $container.classList.add('dragover'); }); $container.addEventListener('dragleave', function() { $container.classList.remove('dragover'); }); $container.addEventListener('drop', function(e) { e.preventDefault(); $container.classList.remove('dragover'); addFiles(e.dataTransfer.files); }); $clearBtn.addEventListener('click', function() { files = []; conflictResolved = false; retryCounts = {}; currentFailedFiles = []; $conflictNotice.style.display = 'none'; renderList(); updateStartButton(); $summary.style.display = 'none'; }); function addFiles(newFiles) { for (var i = 0; i < newFiles.length; i++) { var file = newFiles[i]; if (file.type.match(/^image\//)) { file._targetName = getNameWithoutExt(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); retryCounts[file._fileId] = 0; } } conflictResolved = false; $conflictNotice.style.display = 'none'; renderList(); updateStartButton(); } function renderList() { $list.innerHTML = ''; if (files.length === 0) { $list.innerHTML = '<p style="color:#999;text-align:center;">暂无文件</p>'; return; } files.forEach(function(file, index) { var sizeStr = file.size > 1024 * 1024 ? (file.size / (1024 * 1024)).toFixed(1) + ' MB' : (file.size / 1024).toFixed(0) + ' KB'; var item = document.createElement('div'); 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 = '<div class="file-info">' + '<input type="text" class="file-rename" value="' + file._targetName + '" data-index="' + index + '">' + '<span class="batch-upload-ext">' + file._ext + '</span>' + '<div class="file-original">原始: ' + file.name + ' (' + sizeStr + ')</div>' + '</div>' + actionHtml + '<span class="file-status status-waiting" data-index="' + index + '">等待检测</span>' + '<span class="batch-upload-remove" data-index="' + index + '" title="移除">×</span>'; $list.appendChild(item); }); $list.querySelectorAll('.file-rename').forEach(function(input) { input.addEventListener('input', function() { var idx = parseInt(this.getAttribute('data-index')); 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) { btn.addEventListener('click', function() { var idx = parseInt(this.getAttribute('data-index')); delete retryCounts[files[idx]._fileId]; files.splice(idx, 1); renderList(); updateStartButton(); }); }); } function updateStartButton() { 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() { 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(); }); } function uploadFile(file, index) { return new Promise(function(resolve) { var targetName = (file._targetName || getNameWithoutExt(file.name)) + file._ext; var formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', targetName); formData.append('file', file); formData.append('format', 'json'); formData.append('token', getEditToken()); formData.append('ignorewarnings', '1'); var xhr = new XMLHttpRequest(); xhr.open('POST', mw.util.wikiScript('api'), true); xhr.onload = function() { try { var data = JSON.parse(xhr.responseText); if (data.upload && data.upload.result === 'Success') { resolve({ success: true, name: targetName, fileId: file._fileId }); } else { var errMsg = '未知错误'; if (data.upload && data.upload.warnings) errMsg = JSON.stringify(data.upload.warnings); else if (data.error) errMsg = data.error.info || '未知错误'; resolve({ success: false, name: targetName, error: errMsg, fileId: file._fileId }); } } catch(e) { resolve({ success: false, name: targetName, error: '解析响应失败', fileId: file._fileId }); } }; xhr.onerror = function() { resolve({ success: false, name: targetName, error: '网络错误', fileId: file._fileId }); }; 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() { 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; isUploading = true; uploadedCount = 0; totalCount = 0; currentFailedFiles = []; updateStartButton(); $selectBtn.disabled = true; $clearBtn.disabled = true; $progress.style.display = 'block'; $summary.style.display = 'none'; $progressBar.style.width = '0%'; $list.querySelectorAll('.file-rename').forEach(function(i) { i.disabled = true; }); $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(); toUpload.forEach(function(item) { uploadQueue = uploadQueue.then(function() { var statusEl = $list.querySelector('.file-status[data-index="' + item.index + '"]'); if (statusEl) { statusEl.classList.remove('status-waiting', 'status-conflict', 'status-skipped'); statusEl.classList.add('status-uploading'); statusEl.textContent = '上传中...'; } return uploadWithRetry(item.file, item.index); }); }); uploadQueue.then(function() { finishUpload(); }); } function retryFailedFiles() { if (isUploading || currentFailedFiles.length === 0) return; 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() { finishUpload(); }); } function finishUpload() { isUploading = false; $selectBtn.disabled = false; $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 = []; conflictResolved = false; setTimeout(function() { if (files.length === 0 && !isUploading && currentFailedFiles.length === 0) { renderList(); $progress.style.display = 'none'; } }, 5000); } // 绑定重试链接 var retryLink = document.getElementById('retry-link'); if (retryLink) { retryLink.addEventListener('click', function() { retryFailedFiles(); }); } } })(); </script> </includeonly>
返回
微件:BatchUpload
。
查看“︁微件:BatchUpload”︁的源代码
来自植物大战僵尸杂交版Wiki