feat:静态上传页面制作

This commit is contained in:
李进 2025-09-15 15:25:51 +08:00
parent d28341cd24
commit 9b830a9c2f

396
static-upload.html Normal file
View File

@ -0,0 +1,396 @@
<!DOCTYPE html>
<html lang="zh-CN" data-theme="auto">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
<style>
:root {
--primary-color: #4f46e5;
--background-color: #f8fafc;
--card-background-color: #ffffff;
--text-color: #334155;
--text-light-color: #64748b;
--border-color: #e2e8f0;
--drop-zone-border-color: #cbd5e1;
--success-color: #22c55e;
--danger-color: #ef4444;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--background-color);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
color: var(--text-color);
}
.upload-card {
background-color: var(--card-background-color);
border-radius: 1.5rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1);
border: 1px solid var(--border-color);
width: 100%;
max-width: 600px;
padding: 2.5rem;
text-align: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.upload-card:hover {
transform: translateY(-5px);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
}
.upload-icon {
width: 60px;
height: 60px;
color: var(--primary-color);
}
h1 {
font-size: 1.875rem;
font-weight: 700;
margin-top: 1.5rem;
}
p {
color: var(--text-light-color);
margin-top: 0.5rem;
font-size: 1rem;
}
.drop-zone {
border: 2px dashed var(--drop-zone-border-color);
border-radius: 1rem;
padding: 2.5rem 1.5rem;
margin-top: 2rem;
cursor: pointer;
transition: border-color 0.3s ease, background-color 0.3s ease;
}
.drop-zone.drag-over {
border-color: var(--primary-color);
background-color: rgba(79, 70, 229, 0.05);
}
.file-input {
display: none;
}
.btn {
background-color: var(--primary-color);
color: white;
border: none;
padding: 0.875rem 2rem;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease;
display: inline-block;
margin-top: 1rem;
}
.btn:hover {
background-color: #4338ca;
transform: translateY(-2px);
}
#file-list {
margin-top: 2rem;
text-align: left;
}
.file-item {
display: flex;
align-items: center;
padding: 0.75rem 0;
border-bottom: 1px solid var(--border-color);
}
.file-item:last-child {
border-bottom: none;
}
.file-name {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 1rem;
}
.file-size {
color: var(--text-light-color);
margin-left: 1rem;
}
.remove-btn {
background: none;
border: none;
cursor: pointer;
color: var(--danger-color);
margin-left: 1rem;
font-size: 1.25rem;
}
.upload-btn-container {
text-align: right;
margin-top: 2rem;
}
</style>
<style>
/* Futuristic override theme (glass + neon) */
:root {
--aurora-1: #6ee7ff;
--aurora-2: #a78bfa;
--aurora-3: #22d3ee;
--panel-bg: rgba(255,255,255,0.08);
--panel-stroke: rgba(255,255,255,0.16);
--glow: rgba(79,70,229,0.65);
--grid-color: rgba(100,116,139,0.08);
}
[data-theme="dark"] body { background-color: #0b1220; color: #e5e7eb; }
[data-theme="dark"] .upload-card { background-color: rgba(17,24,39,0.55); border-color: rgba(148,163,184,0.14); }
[data-theme="dark"] .btn { box-shadow: 0 10px 25px -10px rgba(79,70,229,0.6); }
/* full-scene background */
.scene-bg {
position: fixed; inset: 0; pointer-events: none; z-index: -1;
background:
radial-gradient(1200px 600px at 10% 10%, rgba(79,70,229,0.25), transparent),
radial-gradient(900px 500px at 90% 80%, rgba(34,211,238,0.25), transparent),
radial-gradient(700px 400px at 50% 30%, rgba(167,139,250,0.22), transparent),
linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
}
.grid-overlay::before{
content:""; position: fixed; inset:0; pointer-events:none; z-index:-1;
background-image: linear-gradient(var(--grid-color) 1px, transparent 1px), linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 40px 40px; mask-image: radial-gradient(circle at 50% 50%, rgba(0,0,0,0.6), rgba(0,0,0,0.05));
}
/* panel upgrade */
.upload-card{
position: relative;
background: var(--panel-bg);
border: 1px solid var(--panel-stroke);
backdrop-filter: blur(14px) saturate(120%);
transform-style: preserve-3d;
}
.upload-card::before{
content:""; position:absolute; inset:-1px; border-radius:inherit;
background: conic-gradient(from 180deg at 50% 50%, rgba(79,70,229,0.35), rgba(34,211,238,0.35), rgba(167,139,250,0.35), rgba(79,70,229,0.35));
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor; mask-composite: exclude; padding:1px; opacity:.7;
filter: blur(.3px);
}
.upload-card:hover{ box-shadow: 0 25px 70px -20px var(--glow); }
.brand-bar{
position: fixed; top: 24px; left: 50%; transform: translateX(-50%);
display:flex; align-items:center; gap:12px; padding:.5rem 1rem; border-radius: 999px;
background: rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.14); backdrop-filter: blur(10px);
}
.brand-bar .dot{ width:10px; height:10px; border-radius:50%; background: radial-gradient(circle at 30% 30%, #fff, #a78bfa); box-shadow: 0 0 14px #a78bfa; }
.brand-title{ font-weight:700; letter-spacing:.2px; }
.theme-toggle{ position: fixed; right: 24px; top: 24px; background: rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.15); color: inherit; padding:.6rem .8rem; border-radius:12px; cursor:pointer; backdrop-filter: blur(10px); }
.theme-toggle:hover{ box-shadow: 0 8px 30px -12px var(--glow); }
/* drop zone upgrade */
.drop-zone{
border-radius: 1.25rem;
position: relative;
background: rgba(255,255,255,0.04);
border: 1px dashed rgba(148,163,184,0.35);
}
.drop-zone::after{
content:""; position:absolute; inset:-2px; border-radius:inherit; opacity:0; transition:.3s ease; pointer-events:none;
background: linear-gradient(90deg, rgba(79,70,229,.55), rgba(34,211,238,.55)); filter: blur(18px);
}
.drop-zone.drag-over{ border-color: rgba(79,70,229,.85); background: linear-gradient(180deg, rgba(79,70,229,0.08), rgba(34,211,238,0.08)); }
.drop-zone.drag-over::after{ opacity:.6; }
.btn-primary{ background: linear-gradient(90deg, #6366f1, #22d3ee); box-shadow: 0 10px 25px -12px rgba(99,102,241,.6); }
.btn-primary:hover{ background: linear-gradient(90deg, #4f46e5, #06b6d4); }
/* file list */
.file-item{ border:1px solid rgba(148,163,184,0.15); border-radius: 12px; padding:.75rem; margin-bottom:.75rem; background: rgba(255,255,255,0.05); }
.file-meta{ display:flex; align-items:center; gap:.5rem; }
.file-ext{ width:28px; height:28px; border-radius:8px; display:grid; place-items:center; font-weight:700; font-size:.7rem; color:#111827; background: linear-gradient(180deg,#fff,#c7d2fe); }
.file-actions{ margin-left:auto; display:flex; align-items:center; gap:.5rem; }
.chip{ font-size:.75rem; color: var(--text-light-color); opacity:.85; }
.remove-neo{ background: none; border:1px solid rgba(239,68,68,0.35); color:#ef4444; padding:.25rem .5rem; border-radius:8px; cursor:pointer; }
.remove-neo:hover{ background: rgba(239,68,68,0.08); }
.progress{ margin-top:.5rem; height:8px; width:100%; border-radius:999px; background: rgba(148,163,184,0.18); overflow:hidden; }
.bar{ height:100%; width:0%; background: linear-gradient(90deg,#22d3ee,#6366f1,#a78bfa); transition: width .25s ease; box-shadow: inset 0 0 8px rgba(255,255,255,0.35); }
.upload-actions{ display:flex; justify-content:space-between; align-items:center; gap:12px; }
.stats{ font-size:.85rem; color: var(--text-light-color); }
</style>
</head>
<body>
<div class="scene-bg grid-overlay"></div>
<div class="brand-bar"><span class="dot"></span><span class="brand-title">Nebula Uploader</span></div>
<button class="theme-toggle" id="theme-toggle" aria-label="切换主题">🌗</button>
<div class="upload-card">
<svg class="upload-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 16.5V9.75m0 0l-3.75 3.75M12 9.75l3.75 3.75M3 17.25V18a2.25 2.25 0 002.25 2.25h13.5A2.25 2.25 0 0021 18v-.75M3 16.5V7.5A2.25 2.25 0 015.25 5.25h13.5A2.25 2.25 0 0121 7.5v9" />
</svg>
<h1>文件上传</h1>
<p>拖拽文件至此, 或点击下方按钮选择</p>
<div class="drop-zone" id="drop-zone">
<span>将文件拖到此处</span><br>
<button type="button" class="btn btn-primary" id="browse-btn">选择文件</button>
<input type="file" multiple class="file-input" id="file-input">
</div>
<div id="file-list"></div>
<div class="upload-btn-container upload-actions">
<div class="stats" id="stats">未选择文件</div>
<button type="button" class="btn btn-primary" id="upload-btn" style="display: none;">开始上传</button>
</div>
</div>
<script>
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const browseBtn = document.getElementById('browse-btn');
const fileListContainer = document.getElementById('file-list');
const uploadBtn = document.getElementById('upload-btn');
const statsEl = document.getElementById('stats');
const themeToggle = document.getElementById('theme-toggle');
let filesToUpload = [];
let uploading = false;
// theme bootstrap
(function initTheme(){
try {
const saved = localStorage.getItem('nebula-theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = saved || (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
} catch(e) {}
})();
themeToggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme') || 'light';
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
try { localStorage.setItem('nebula-theme', next); } catch(e) {}
});
browseBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', () => {
addFiles(fileInput.files);
});
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
addFiles(e.dataTransfer.files);
});
function addFiles(files) {
for (const file of files) {
if (!filesToUpload.some(f => f.name === file.name && f.size === file.size && f.lastModified === file.lastModified)) {
filesToUpload.push(file);
}
}
renderFileList();
}
function renderFileList() {
fileListContainer.innerHTML = '';
if (filesToUpload.length > 0) {
uploadBtn.style.display = 'inline-block';
} else {
uploadBtn.style.display = 'none';
}
const total = filesToUpload.reduce((a,f)=>a+f.size,0);
statsEl.textContent = filesToUpload.length ? `已选择 ${filesToUpload.length} 个文件 · 总计 ${formatBytes(total)}` : '未选择文件';
filesToUpload.forEach((file, index) => {
const ext = (file.name.split('.').pop() || '').slice(0,4).toUpperCase();
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
fileItem.dataset.index = index;
fileItem.innerHTML = `
<div class="file-meta">
<div class="file-ext">${ext || 'FILE'}</div>
<div class="file-name" title="${file.name}">${file.name}</div>
<div class="chip">${formatBytes(file.size)}</div>
<div class="file-actions">
<button class="remove-neo" onclick="removeFile(${index})">移除</button>
</div>
</div>
<div class="progress"><div class="bar"></div></div>
`;
fileListContainer.appendChild(fileItem);
});
}
window.removeFile = function (index) {
filesToUpload.splice(index, 1);
renderFileList();
}
uploadBtn.addEventListener('click', () => {
if (uploading || filesToUpload.length === 0) return;
uploading = true;
const rows = Array.from(document.querySelectorAll('.file-item'));
rows.forEach((row, i) => simulateProgress(row.querySelector('.bar'), i));
});
function simulateProgress(barEl, delayIndex){
let p = 0;
const tick = () => {
if (!barEl) return;
p += Math.min(2 + Math.random() * 8, 100 - p);
barEl.style.width = p + '%';
if (p < 100) {
setTimeout(tick, 80 + delayIndex * 15);
} else if (Array.from(document.querySelectorAll('.bar')).every(b => b.style.width === '100%')) {
uploading = false;
}
};
setTimeout(tick, 150 + delayIndex * 100);
}
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
</script>
</body>
</html>