微件: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…” |
无编辑摘要 |
||
| 第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: 60px; text-align: right; } | |||
.status-waiting { color: #999; } | .status-waiting { color: #999; } | ||
.status-uploading { color: #2196F3; } | .status-uploading { color: #2196F3; } | ||
| 第78行: | 第53行: | ||
.status-error { color: #f44336; } | .status-error { color: #f44336; } | ||
.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-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; } | |||
</style> | </style> | ||
| 第125行: | 第70行: | ||
<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> | ||
| 第140行: | 第84行: | ||
<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 files = []; | var files = []; | ||
| 第155行: | 第98行: | ||
var isUploading = false; | var isUploading = false; | ||
function getExt(filename) { | function getExt(filename) { | ||
var lastDot = filename.lastIndexOf('.'); | var lastDot = filename.lastIndexOf('.'); | ||
| 第161行: | 第103行: | ||
} | } | ||
function getNameWithoutExt(filename) { | function getNameWithoutExt(filename) { | ||
var lastDot = filename.lastIndexOf('.'); | var lastDot = filename.lastIndexOf('.'); | ||
| 第167行: | 第108行: | ||
} | } | ||
// 创建隐藏的 input[type=file] | |||
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 = []; | ||
renderList(); | renderList(); | ||
updateStartButton(); | updateStartButton(); | ||
$summary. | $summary.style.display = 'none'; | ||
}); | }); | ||
| 第202行: | 第150行: | ||
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); | ||
| 第213行: | 第160行: | ||
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.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>' + | |||
'<span class="file-status status-waiting" data-index="' + index + '">等待上传</span>' + | |||
'<span class="batch-upload-remove" data-index="' + index + '" title="移除">×</span>'; | |||
$list.appendChild(item); | |||
$list. | |||
}); | }); | ||
// | // 改名 | ||
$('.file-rename'). | $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(); | |||
} | }); | ||
}); | }); | ||
// | // 删除 | ||
$('.batch-upload-remove'). | $list.querySelectorAll('.batch-upload-remove').forEach(function(btn) { | ||
btn.addEventListener('click', function() { | |||
var idx = parseInt(this.getAttribute('data-index')); | |||
files.splice(idx, 1); | |||
renderList(); | |||
updateStartButton(); | |||
}); | |||
}); | }); | ||
} | } | ||
function updateStartButton() { | function updateStartButton() { | ||
$startBtn. | $startBtn.disabled = files.length === 0 || isUploading; | ||
} | } | ||
| 第264行: | 第208行: | ||
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行: | 第218行: | ||
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 }); | ||
} 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 }); | resolve({ success: false, name: targetName, error: errMsg }); | ||
} | } | ||
} | } catch(e) { | ||
resolve({ success: false, name: targetName, error: '解析响应失败' }); | |||
resolve({ success: false, name: targetName, error: ' | |||
} | } | ||
}); | }; | ||
xhr.onerror = function() { | |||
resolve({ success: false, name: targetName, error: '网络错误' }); | |||
}; | |||
xhr.send(formData); | |||
}); | }); | ||
} | } | ||
$startBtn.click | $startBtn.addEventListener('click', function() { | ||
if (isUploading || files.length === 0) return; | if (isUploading || files.length === 0) return; | ||
isUploading = true; | isUploading = true; | ||
| 第306行: | 第248行: | ||
totalCount = files.length; | totalCount = files.length; | ||
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'; }); | ||
var uploadQueue = Promise.resolve(); | var uploadQueue = Promise.resolve(); | ||
files.forEach(function(file, index) { | files.forEach(function(file, index) { | ||
uploadQueue = uploadQueue.then(function() { | uploadQueue = uploadQueue.then(function() { | ||
$('.file-status[data-index="' + index + '"]') | var statusEl = $list.querySelector('.file-status[data-index="' + index + '"]'); | ||
. | if (statusEl) { | ||
. | statusEl.classList.remove('status-waiting'); | ||
. | statusEl.classList.add('status-uploading'); | ||
statusEl.textContent = '上传中...'; | |||
} | |||
return uploadFile(file, index).then(function(result) { | return uploadFile(file, index).then(function(result) { | ||
uploadedCount++; | uploadedCount++; | ||
var progress = Math.round((uploadedCount / totalCount) * 100); | var progress = Math.round((uploadedCount / totalCount) * 100); | ||
$progressBar. | $progressBar.style.width = progress + '%'; | ||
if (statusEl) { | |||
if ( | statusEl.classList.remove('status-uploading'); | ||
if (result.success) { | |||
statusEl.classList.add('status-success'); | |||
. | statusEl.textContent = '✅ 成功'; | ||
. | } else { | ||
statusEl.classList.add('status-error'); | |||
statusEl.textContent = '❌ 失败'; | |||
. | } | ||
. | |||
} | } | ||
}); | }); | ||
| 第345行: | 第285行: | ||
uploadQueue.then(function() { | uploadQueue.then(function() { | ||
isUploading = false; | isUploading = false; | ||
$selectBtn. | $selectBtn.disabled = false; | ||
$clearBtn. | $clearBtn.disabled = false; | ||
updateStartButton(); | updateStartButton(); | ||
var successCount = $('.status-success').length; | var successCount = $list.querySelectorAll('.status-success').length; | ||
var failCount = $('.status-error').length; | var failCount = $list.querySelectorAll('.status-error').length; | ||
$summary. | $summary.style.display = 'block'; | ||
$summary.innerHTML = '上传完成!成功 ' + successCount + ' 张,失败 ' + failCount + ' 张。'; | |||
files = []; | files = []; | ||
setTimeout(function() { | setTimeout(function() { | ||
if (files.length === 0 && !isUploading) { | if (files.length === 0 && !isUploading) { | ||
renderList(); | renderList(); | ||
$progress. | $progress.style.display = 'none'; | ||
} | } | ||
}, 5000); | }, 5000); | ||