copyparty/copyparty/web/browser.html
mengshon1-boop 154e3a5c9f feat(上传): 添加文件上传进度显示和测试用例
为文件上传功能添加进度条、状态显示和重试机制
添加全面的上传功能测试用例,包括大文件和多文件上传测试
2026-04-25 19:59:52 +08:00

410 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en" id="ht_brw">
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.8, minimum-scale=0.6">
<meta name="theme-color" content="#{{ tcolor }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/ui.css?_={{ ts }}">
<link rel="stylesheet" media="screen" href="{{ r }}/.cpr/w/browser.css?_={{ ts }}">
{{ html_head }}
{%- if css %}
<link rel="stylesheet" media="screen" href="{{ css }}_={{ ts }}">
{%- endif %}
</head>
<body>
<div id="ops"></div>
<div id="op_search" class="opview">
{%- if have_tags_idx %}
<div id="srch_form" class="tags opbox"></div>
{%- else %}
<div id="srch_form" class="opbox"></div>
{%- endif %}
<div id="srch_q"></div>
</div>
<div id="op_player" class="opview opbox opwide"></div>
<div id="op_bup" class="opview opbox {% if not ls0 %}act{% endif %}">
<div id="u2err"></div>
<form id="bup_form" method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
<input type="hidden" name="act" value="bput" />
<input type="file" id="bup_files" name="f" multiple /><br />
<input type="submit" id="bup_submit" value="start upload">
</form>
<div id="bup_progress" style="display:none; margin-top:10px;">
<div id="bup_status" style="margin-bottom:5px; font-weight:bold;"></div>
<div id="bup_fileinfo" style="margin-bottom:5px; font-size:0.9em;"></div>
<div style="width:100%; background:#333; height:20px; border-radius:10px; overflow:hidden;">
<div id="bup_bar" style="width:0%; height:100%; background:linear-gradient(90deg, #09d, #4b0); transition:width 0.1s;"></div>
</div>
<div id="bup_percent" style="text-align:center; margin-top:5px; font-size:0.9em;"></div>
<div id="bup_speed" style="text-align:center; margin-top:5px; font-size:0.9em; color:#888;"></div>
</div>
<div id="bup_result" style="display:none; margin-top:10px; padding:10px; border-radius:5px;"></div>
<a id="bbsw" href="?b=u" rel="nofollow"><br />switch to basic browser</a>
</div>
<div id="op_mkdir" class="opview opbox {% if not ls0 %}act{% endif %}">
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
<input type="hidden" name="act" value="mkdir" />
📂<input type="text" name="name" class="i" placeholder="awesome mix vol.1">
<input type="submit" value="make directory">
</form>
</div>
<div id="op_new_md" class="opview opbox">
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
<input type="hidden" name="act" value="new_md" />
📝<input type="text" name="name" class="i" placeholder="weekend-plans">
<input type="submit" value="new file">
</form>
<span id="new_mdi"></span>
</div>
<div id="op_msg" class="opview opbox {% if not ls0 %}act{% endif %}">
<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ url_suf }}">
📟<input type="text" name="msg" class="i" placeholder="lorem ipsum dolor sit amet">
<input type="submit" value="send msg to srv log">
</form>
</div>
<div id="op_unpost" class="opview opbox"></div>
<div id="op_up2k" class="opview"></div>
<div id="op_cfg" class="opview opbox opwide"></div>
<h1 id="path">
<a href="#" id="entree">🌲</a>
{%- for n in vpnodes %}
<a href="{{ r }}/{{ n[0] }}">{{ n[1] }}</a>
{%- endfor %}
</h1>
<div id="tree"></div>
<div id="wrap">
{%- if doc %}
<div id="bdoc"><pre>{{ doc|e }}</pre></div>
{%- else %}
<div id="bdoc"></div>
{%- endif %}
<div id="pro" class="logue">{{ "" if sb_lg else logues[0] }}</div>
<table id="files">
<thead>
<tr>
<th name="lead"><span>c</span></th>
<th name="href"><span>File Name</span></th>
<th name="sz" sort="int"><span>Size</span></th>
{%- for k in taglist %}
{%- if k.startswith('.') %}
<th name="tags/{{ k }}" sort="int"><span>{{ k[1:] }}</span></th>
{%- else %}
<th name="tags/{{ k }}"><span>{{ k[0]|upper }}{{ k[1:] }}</span></th>
{%- endif %}
{%- endfor %}
<th name="ext"><span>T</span></th>
<th name="ts"><span>Date</span></th>
</tr>
</thead>
<tbody>
{%- for f in files %}
<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td>
{%- if f.tags is defined %}
{%- for k in taglist %}<td>{{ f.tags[k]|e }}</td>{%- endfor %}
{%- endif %}<td>{{ f.ext }}</td><td>{{ f.dt }}</td></tr>
{%- endfor %}
</tbody>
</table>
<div id="epi" class="logue">{{ "" if sb_lg else logues[1] }}</div>
<h2 id="wfp"><a href="{{ r }}/?h" id="goh">control-panel</a></h2>
<a href="#" id="repl">π</a>
</div>
<div id="srv_info"><span>{{ srv_info }}</span></div>
<div id="widget"></div>
<div id="rcm" tabindex="0"></div>
<script>
var SR = "{{ r }}",
CGV1 = {{ cgv1 }},
CGV = {{ cgv|tojson }},
TS = "{{ ts }}",
dtheme = "{{ dtheme }}",
lang = "{{ lang }}",
dfavico = "{{ favico }}",
have_tags_idx = {{ have_tags_idx }},
logues = {{ logues|tojson if sb_lg else "[]" }},
ls0 = {{ ls0|tojson }};
var STG = window.localStorage;
document.documentElement.className = (STG && STG.cpp_thm) || dtheme;
</script>
<script src="{{ r }}/.cpr/w/util.js?_={{ ts }}"></script>
{%- if lang != "eng" %}
<script src="{{ r }}/.cpr/w/tl/{{ lang }}.js?_={{ ts }}"></script>
{%- endif %}
<script src="{{ r }}/.cpr/w/baguettebox.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/w/browser.js?_={{ ts }}"></script>
<script src="{{ r }}/.cpr/w/up2k.js?_={{ ts }}"></script>
{%- if js %}
<script src="{{ js }}_={{ ts }}"></script>
{%- endif %}
<script>
Date.now();function jsldp(a,b){2!=window[a]&&alert("FATAL ERROR: cannot load "+b+".js due to unreliable network or broken reverse-proxy; try CTRL-SHIFT-R")}
jsldp("J_UTL","util");
jsldp("J_BBX","baguettebox");
jsldp("J_BRW","browser");
jsldp("J_U2K","up2k");
</script>
<script>
(function() {
var MAX_RETRIES = 3,
RETRY_DELAY = 1000,
progressDiv = null,
progressBar = null,
progressPercent = null,
progressSpeed = null,
progressStatus = null,
progressFileinfo = null,
resultDiv = null,
form = null,
submitBtn = null,
fileInput = null,
uploadStartTime = 0,
currentRetry = 0,
currentFiles = [],
currentFileIndex = 0,
totalFilesSize = 0;
function formatSize(bytes) {
if (bytes === 0) return '0 B';
var k = 1024,
sizes = ['B', 'KB', 'MB', 'GB', 'TB'],
i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function formatSpeed(bytesPerSec) {
if (bytesPerSec === 0) return '0 B/s';
var k = 1024,
sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s'],
i = Math.floor(Math.log(bytesPerSec) / Math.log(k));
return parseFloat((bytesPerSec / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function showProgress(show) {
if (progressDiv) {
progressDiv.style.display = show ? 'block' : 'none';
}
}
function showResult(success, message) {
if (resultDiv) {
resultDiv.style.display = 'block';
resultDiv.style.background = success ? '#050' : '#500';
resultDiv.style.color = '#fff';
resultDiv.innerHTML = message;
}
}
function hideResult() {
if (resultDiv) {
resultDiv.style.display = 'none';
}
}
function updateProgress(loaded, total) {
if (!progressBar || !progressPercent) return;
var percent = total > 0 ? (loaded / total * 100) : 0;
progressBar.style.width = percent + '%';
progressPercent.textContent = percent.toFixed(1) + '%';
if (uploadStartTime > 0 && loaded > 0) {
var elapsed = (Date.now() - uploadStartTime) / 1000;
var speed = loaded / elapsed;
if (progressSpeed) {
progressSpeed.textContent = formatSpeed(speed);
}
}
}
function setStatus(text) {
if (progressStatus) {
progressStatus.textContent = text;
}
}
function setFileinfo(text) {
if (progressFileinfo) {
progressFileinfo.textContent = text;
}
}
function setFormEnabled(enabled) {
if (submitBtn) {
submitBtn.disabled = !enabled;
}
if (fileInput) {
fileInput.disabled = !enabled;
}
}
function uploadFile(file, formAction, callback) {
var xhr = new XMLHttpRequest();
var formData = new FormData();
formData.append('act', 'bput');
formData.append('f', file);
uploadStartTime = Date.now();
currentRetry = 0;
setStatus('Uploading: ' + file.name);
setFileinfo('Size: ' + formatSize(file.size) + ' | Retry: ' + currentRetry + '/' + MAX_RETRIES);
updateProgress(0, file.size);
function doUpload() {
xhr.open('POST', formAction, true);
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
updateProgress(e.loaded, e.total);
}
};
xhr.onload = function() {
if (xhr.status === 200) {
var responseText = xhr.responseText || '';
if (responseText.indexOf('error') === -1 && responseText.indexOf('ERR') === -1) {
callback(null, xhr);
} else {
callback(new Error('Server error: ' + responseText), xhr);
}
} else {
callback(new Error('HTTP error: ' + xhr.status), xhr);
}
};
xhr.onerror = function() {
if (currentRetry < MAX_RETRIES) {
currentRetry++;
setFileinfo('Size: ' + formatSize(file.size) + ' | Retry: ' + currentRetry + '/' + MAX_RETRIES + ' - retrying...');
setTimeout(doUpload, RETRY_DELAY * currentRetry);
} else {
callback(new Error('Network error after ' + MAX_RETRIES + ' retries'), xhr);
}
};
xhr.ontimeout = function() {
if (currentRetry < MAX_RETRIES) {
currentRetry++;
setFileinfo('Size: ' + formatSize(file.size) + ' | Retry: ' + currentRetry + '/' + MAX_RETRIES + ' - timeout, retrying...');
setTimeout(doUpload, RETRY_DELAY * currentRetry);
} else {
callback(new Error('Timeout after ' + MAX_RETRIES + ' retries'), xhr);
}
};
xhr.timeout = 300000;
xhr.send(formData);
}
doUpload();
}
function uploadNextFile(formAction) {
if (currentFileIndex >= currentFiles.length) {
showProgress(false);
showResult(true, 'All files uploaded successfully! (' + currentFiles.length + ' file(s), ' + formatSize(totalFilesSize) + ')');
setFormEnabled(true);
return;
}
var file = currentFiles[currentFileIndex];
uploadFile(file, formAction, function(err, xhr) {
if (err) {
showProgress(false);
showResult(false, 'Upload failed for "' + file.name + '": ' + err.message);
setFormEnabled(true);
} else {
currentFileIndex++;
uploadNextFile(formAction);
}
});
}
function initBupUpload() {
form = document.getElementById('bup_form');
submitBtn = document.getElementById('bup_submit');
fileInput = document.getElementById('bup_files');
progressDiv = document.getElementById('bup_progress');
progressBar = document.getElementById('bup_bar');
progressPercent = document.getElementById('bup_percent');
progressSpeed = document.getElementById('bup_speed');
progressStatus = document.getElementById('bup_status');
progressFileinfo = document.getElementById('bup_fileinfo');
resultDiv = document.getElementById('bup_result');
if (!form || !submitBtn || !fileInput) {
console.log('Basic upload form not found');
return;
}
form.addEventListener('submit', function(e) {
e.preventDefault();
if (!fileInput.files || fileInput.files.length === 0) {
showResult(false, 'Please select at least one file to upload.');
return;
}
hideResult();
showProgress(true);
setFormEnabled(false);
currentFiles = Array.from(fileInput.files);
currentFileIndex = 0;
totalFilesSize = 0;
for (var i = 0; i < currentFiles.length; i++) {
totalFilesSize += currentFiles[i].size;
}
setStatus('Preparing upload...');
setFileinfo('Total: ' + currentFiles.length + ' file(s), ' + formatSize(totalFilesSize));
var formAction = form.action || window.location.href;
uploadNextFile(formAction);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBupUpload);
} else {
initBupUpload();
}
})();
</script>
</body>
</html>