微件:BatchUpload:修订间差异
来自植物大战僵尸杂交版Wiki
更多操作
无编辑摘要 |
无编辑摘要 |
||
| 第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: | .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-conflict { color: #FF9800; } | ||
.status-skipped { color: #9E9E9E; } | .status-skipped { color: #9E9E9E; } | ||
| 第125行: | 第126行: | ||
var isUploading = false; | var isUploading = false; | ||
var conflictResolved = false; | var conflictResolved = false; | ||
var MAX_RETRIES = 3; | |||
var retryCounts = {}; | |||
var currentFailedFiles = []; | |||
function getExt(filename) { | function getExt(filename) { | ||
| 第168行: | 第172行: | ||
files = []; | files = []; | ||
conflictResolved = false; | conflictResolved = false; | ||
retryCounts = {}; | |||
currentFailedFiles = []; | |||
$conflictNotice.style.display = 'none'; | $conflictNotice.style.display = 'none'; | ||
renderList(); | renderList(); | ||
| 第180行: | 第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._conflictAction = 'ask'; | ||
file._fileId = Date.now() + '_' + i + '_' + Math.random().toString(36).substr(2, 5); | |||
files.push(file); | files.push(file); | ||
retryCounts[file._fileId] = 0; | |||
} | } | ||
} | } | ||
| 第236行: | 第244行: | ||
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(); | ||
| 第244行: | 第253行: | ||
function updateStartButton() { | function updateStartButton() { | ||
$startBtn.disabled = | if (currentFailedFiles.length > 0) { | ||
if (conflictResolved) { | $startBtn.disabled = false; | ||
$startBtn.textContent = '重试失败文件 (' + currentFailedFiles.length + ')'; | |||
$startBtn.style.background = '#FF9800'; | |||
} else if (conflictResolved) { | |||
$startBtn.disabled = false; | |||
$startBtn.textContent = '开始上传'; | $startBtn.textContent = '开始上传'; | ||
$startBtn.style.background = '#4CAF50'; | |||
} else { | } else { | ||
$startBtn.disabled = files.length === 0 || isUploading; | |||
$startBtn.textContent = '检测并上传'; | $startBtn.textContent = '检测并上传'; | ||
$startBtn.style.background = '#4CAF50'; | |||
} | } | ||
} | } | ||
| 第256行: | 第272行: | ||
} | } | ||
function checkFileExists(filename) { | function checkFileExists(filename) { | ||
return new Promise(function(resolve) { | return new Promise(function(resolve) { | ||
| 第293行: | 第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); | ||
| 第311行: | 第326行: | ||
} | } | ||
// | 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) { | function showConflictUI(conflicts) { | ||
$conflictNotice.style.display = 'block'; | $conflictNotice.style.display = 'block'; | ||
| 第333行: | 第401行: | ||
}); | }); | ||
conflicts.forEach(function(idx) { | conflicts.forEach(function(idx) { | ||
var item = $list.querySelector('.batch-upload-item[data-index="' + idx + '"]'); | var item = $list.querySelector('.batch-upload-item[data-index="' + idx + '"]'); | ||
| 第343行: | 第410行: | ||
statusEl.textContent = '⚠️ 重名'; | statusEl.textContent = '⚠️ 重名'; | ||
} | } | ||
var oldActions = item.querySelector('.conflict-actions'); | var oldActions = item.querySelector('.conflict-actions'); | ||
if (oldActions) oldActions.remove(); | if (oldActions) oldActions.remove(); | ||
| 第355行: | 第421行: | ||
}); | }); | ||
$list.querySelectorAll('.conflict-btn.overwrite').forEach(function(btn) { | $list.querySelectorAll('.conflict-btn.overwrite').forEach(function(btn) { | ||
btn.addEventListener('click', function(e) { | btn.addEventListener('click', function(e) { | ||
| 第400行: | 第465行: | ||
} | } | ||
$startBtn.addEventListener('click', function() { | $startBtn.addEventListener('click', function() { | ||
if (isUploading | if (isUploading) return; | ||
// 如果有失败文件,点击重试 | |||
if (currentFailedFiles.length > 0) { | |||
retryFailedFiles(); | |||
return; | |||
} | |||
if (files.length === 0) return; | |||
if (conflictResolved) { | if (conflictResolved) { | ||
startUpload(); | startUpload(); | ||
| 第410行: | 第481行: | ||
} | } | ||
isUploading = true; | isUploading = true; | ||
updateStartButton(); | updateStartButton(); | ||
| 第461行: | 第531行: | ||
uploadedCount = 0; | uploadedCount = 0; | ||
totalCount = 0; | totalCount = 0; | ||
currentFailedFiles = []; | |||
updateStartButton(); | updateStartButton(); | ||
$selectBtn.disabled = true; | $selectBtn.disabled = true; | ||
| 第471行: | 第542行: | ||
$list.querySelectorAll('.conflict-actions').forEach(function(i) { i.style.display = 'none'; }); | $list.querySelectorAll('.conflict-actions').forEach(function(i) { i.style.display = 'none'; }); | ||
var toUpload = []; | var toUpload = []; | ||
files.forEach(function(f, idx) { | files.forEach(function(f, idx) { | ||
| 第481行: | 第551行: | ||
totalCount = toUpload.length; | totalCount = toUpload.length; | ||
if (totalCount === 0) { | if (totalCount === 0) { | ||
finishUpload(); | |||
return; | return; | ||
} | } | ||
| 第501行: | 第564行: | ||
statusEl.textContent = '上传中...'; | statusEl.textContent = '上传中...'; | ||
} | } | ||
return | 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; | |||
$summary.innerHTML = | 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; | 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> | ||