微件:BatchUpload:修订间差异
来自植物大战僵尸杂交版Wiki
更多操作
创建页面,内容为“<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-input { display: none; } .batch-upload-btn { display: inline-block; padding: 10px 30px; background: #4CAF50; color: #fff; border: none; border…” |
无编辑摘要 |
||
| (未显示同一用户的2个中间版本) | |||
| 第12行: | 第12行: | ||
border-color: #4CAF50; | border-color: #4CAF50; | ||
background: #f0fff0; | background: #f0fff0; | ||
} | } | ||
.batch-upload-btn { | .batch-upload-btn { | ||
display: inline-block; | display: inline-block; | ||
padding: | padding: 12px 30px; | ||
background: #4CAF50; | background: #4CAF50; | ||
color: #fff; | color: #fff; | ||
| 第25行: | 第22行: | ||
font-size: 16px; | font-size: 16px; | ||
cursor: pointer; | cursor: pointer; | ||
margin: | margin: 8px; | ||
} | } | ||
.batch-upload-btn:hover { background: #45a049; } | |||
.batch-upload-btn:disabled { background: #ccc; cursor: not-allowed; } | |||
.batch-upload-list { | .batch-upload-list { | ||
margin-top: 15px; | margin-top: 15px; | ||
| 第48行: | 第40行: | ||
gap: 10px; | gap: 10px; | ||
} | } | ||
.batch-upload-item .file-info { | .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-original { | |||
} | |||
.batch-upload-item .file-rename { | .batch-upload-item .file-rename { | ||
width: 200px; | width: 200px; padding: 4px 8px; border: 1px solid #ccc; | ||
border-radius: 4px; font-size: 13px; | |||
border-radius: 4px; | |||
} | } | ||
.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-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%; | width: 100%; height: 6px; background: #e0e0e0; | ||
border-radius: 3px; margin: 15px 0; overflow: hidden; display: none; | |||
border-radius: 3px; | |||
} | } | ||
.batch-upload-progress-bar { | .batch-upload-progress-bar { | ||
height: 100%; | height: 100%; background: #4CAF50; border-radius: 3px; | ||
transition: width 0.3s; width: 0%; | |||
transition: width 0.3s; | |||
} | } | ||
.batch-upload-actions { | .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> | </style> | ||
| 第125行: | 第92行: | ||
<h3>📤 批量上传图片</h3> | <h3>📤 批量上传图片</h3> | ||
<p style="color:#888;font-size:14px;">支持一次选择多张图片,或拖拽图片到此处</p> | <p style="color:#888;font-size:14px;">支持一次选择多张图片,或拖拽图片到此处</p> | ||
<div class="batch-upload-actions"> | <div class="batch-upload-actions"> | ||
<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"> | ||
| 第135行: | 第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 class="batch-upload-btn" id="batch-upload-start" disabled>检测并上传</button> | ||
</div> | </div> | ||
<script> | <script> | ||
(function() { | (function() { | ||
var $container = | var $container = document.getElementById('batch-upload-container'); | ||
var $selectBtn = document.getElementById('batch-upload-select'); | |||
var $selectBtn = | var $clearBtn = document.getElementById('batch-upload-clear'); | ||
var $clearBtn = | var $startBtn = document.getElementById('batch-upload-start'); | ||
var $startBtn = | var $list = document.getElementById('batch-upload-list'); | ||
var $list = | var $progress = document.getElementById('batch-upload-progress'); | ||
var $progress = | var $progressBar = document.getElementById('batch-upload-progress-bar'); | ||
var $progressBar = | var $summary = document.getElementById('batch-upload-summary'); | ||
var $summary = | var $conflictNotice = document.getElementById('conflict-notice'); | ||
var $conflictGlobal = document.getElementById('conflict-actions-global'); | |||
var files = []; | var files = []; | ||
| 第154行: | 第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) { | ||
var lastDot = filename.lastIndexOf('.'); | var lastDot = filename.lastIndexOf('.'); | ||
| 第161行: | 第135行: | ||
} | } | ||
function getNameWithoutExt(filename) { | function getNameWithoutExt(filename) { | ||
var lastDot = filename.lastIndexOf('.'); | var lastDot = filename.lastIndexOf('.'); | ||
| 第167行: | 第140行: | ||
} | } | ||
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. | $container.addEventListener('dragover', function(e) { | ||
e.preventDefault(); | e.preventDefault(); | ||
$container. | $container.classList.add('dragover'); | ||
}); | }); | ||
$container.addEventListener('dragleave', function() { | |||
$container. | $container.classList.remove('dragover'); | ||
$container. | |||
}); | }); | ||
$container.addEventListener('drop', function(e) { | |||
$container. | |||
e.preventDefault(); | e.preventDefault(); | ||
$container. | $container.classList.remove('dragover'); | ||
addFiles(e | addFiles(e.dataTransfer.files); | ||
}); | }); | ||
$clearBtn.click | $clearBtn.addEventListener('click', function() { | ||
files = []; | files = []; | ||
conflictResolved = false; | |||
retryCounts = {}; | |||
currentFailedFiles = []; | |||
$conflictNotice.style.display = 'none'; | |||
renderList(); | renderList(); | ||
updateStartButton(); | updateStartButton(); | ||
$summary. | $summary.style.display = 'none'; | ||
}); | }); | ||
| 第202行: | 第184行: | ||
var file = newFiles[i]; | var file = newFiles[i]; | ||
if (file.type.match(/^image\//)) { | if (file.type.match(/^image\//)) { | ||
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(); | ||
| 第213行: | 第199行: | ||
function renderList() { | function renderList() { | ||
$list. | $list.innerHTML = ''; | ||
if (files.length === 0) { | if (files.length === 0) { | ||
$list. | $list.innerHTML = '<p style="color:#999;text-align:center;">暂无文件</p>'; | ||
return; | return; | ||
} | } | ||
files.forEach(function(file, index) { | 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 sizeStr = file.size > 1024 * 1024 ? (file.size / (1024 * 1024)).toFixed(1) + ' MB' : (file.size / 1024).toFixed(0) + ' KB'; | ||
var | var item = document.createElement('div'); | ||
item.className = 'batch-upload-item'; | |||
item.setAttribute('data-index', index); | |||
var actionHtml = ''; | |||
var | 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>' + | |||
$list. | 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) { | |||
$('.file-rename'). | 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) { | |||
$('.batch-upload-remove'). | btn.addEventListener('click', function() { | ||
var idx = parseInt(this.getAttribute('data-index')); | |||
delete retryCounts[files[idx]._fileId]; | |||
files.splice(idx, 1); | |||
renderList(); | |||
updateStartButton(); | |||
}); | |||
}); | }); | ||
} | } | ||
function updateStartButton() { | function updateStartButton() { | ||
$startBtn. | 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(); | |||
}); | |||
} | } | ||
function uploadFile(file, index) { | function uploadFile(file, index) { | ||
return new Promise(function(resolve | return new Promise(function(resolve) { | ||
var targetName = (file._targetName || getNameWithoutExt(file.name)) + file._ext; | var targetName = (file._targetName || getNameWithoutExt(file.name)) + file._ext; | ||
var formData = new FormData(); | var formData = new FormData(); | ||
| 第274行: | 第302行: | ||
formData.append('ignorewarnings', '1'); | 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') { | 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) | 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.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 = | totalCount = 0; | ||
currentFailedFiles = []; | |||
updateStartButton(); | updateStartButton(); | ||
$selectBtn. | $selectBtn.disabled = true; | ||
$clearBtn. | $clearBtn.disabled = true; | ||
$progress. | $progress.style.display = 'block'; | ||
$summary. | $summary.style.display = 'none'; | ||
$progressBar. | $progressBar.style.width = '0%'; | ||
$('.file-rename'). | $list.querySelectorAll('.file-rename').forEach(function(i) { i.disabled = true; }); | ||
$('.batch-upload-remove'). | $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(); | ||
toUpload.forEach(function(item) { | |||
uploadQueue = uploadQueue.then(function() { | uploadQueue = uploadQueue.then(function() { | ||
$('.file-status[data-index="' + index + '"]') | 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() { | uploadQueue.then(function() { | ||
isUploading = false; | 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 = []; | files = []; | ||
conflictResolved = false; | |||
setTimeout(function() { | setTimeout(function() { | ||
if (files.length === 0 && !isUploading) { | if (files.length === 0 && !isUploading && currentFailedFiles.length === 0) { | ||
renderList(); | renderList(); | ||
$progress. | $progress.style.display = 'none'; | ||
} | } | ||
}, 5000); | }, 5000); | ||
}); | } | ||
// 绑定重试链接 | |||
var retryLink = document.getElementById('retry-link'); | |||
if (retryLink) { | |||
retryLink.addEventListener('click', function() { | |||
retryFailedFiles(); | |||
}); | |||
} | |||
} | |||
})(); | })(); | ||
</script> | </script> | ||
</includeonly> | </includeonly> | ||