feat:静态上传页面制作
This commit is contained in:
parent
d28341cd24
commit
9b830a9c2f
396
static-upload.html
Normal file
396
static-upload.html
Normal 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>
|
||||
Loading…
Reference in New Issue
Block a user