Remove upload entry
This commit is contained in:
@@ -138,13 +138,6 @@
|
||||
<span class="arrow-button">→</span>
|
||||
</a>
|
||||
|
||||
<a class="service-card card-blue" href="/upload/" aria-label="进入文件上传">
|
||||
<span class="service-icon"><svg width="58" height="58" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 30v6a4 4 0 0 0 4 4h24a4 4 0 0 0 4-4v-6"/><path d="M24 10v20m-8-8 8 8 8-8"/></svg></span>
|
||||
<span class="service-title">文件上传</span>
|
||||
<span class="service-desc">文件共享服务</span>
|
||||
<span class="arrow-button">→</span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="service-card card-purple"
|
||||
href="https://gemini.google.com/app"
|
||||
|
||||
-524
@@ -1,524 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="sumi.work 文件上传 - 快速共享文件" />
|
||||
<meta name="theme-color" content="#f7fbff" />
|
||||
<title>上传 · sumi.work</title>
|
||||
<link rel="stylesheet" href="assets/css/base.css?v=20260509-4" />
|
||||
<link rel="stylesheet" href="assets/css/layout.css?v=20260509-4" />
|
||||
<link rel="stylesheet" href="assets/css/components.css?v=20260509-4" />
|
||||
<style>
|
||||
.upload-page {
|
||||
width: min(980px, calc(100% - 36px));
|
||||
margin: 30px auto;
|
||||
}
|
||||
.upload-panel {
|
||||
padding: 40px 44px 44px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
border: 1px solid rgba(215, 226, 242, 0.95);
|
||||
border-radius: 28px;
|
||||
box-shadow: 0 18px 50px rgba(38, 57, 96, 0.1);
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
.upload-panel h1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin: 0 0 6px;
|
||||
font-size: clamp(1.6rem, 2.5vw, 2rem);
|
||||
}
|
||||
.upload-panel h1 span {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 28px;
|
||||
background: var(--blue);
|
||||
border-radius: 999px;
|
||||
}
|
||||
.upload-panel .subtitle {
|
||||
margin: 0 0 28px;
|
||||
color: var(--muted);
|
||||
font-size: 1rem;
|
||||
}
|
||||
.drop-zone {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 14px;
|
||||
min-height: 200px;
|
||||
padding: 36px;
|
||||
cursor: pointer;
|
||||
background: rgba(247, 251, 255, 0.6);
|
||||
border: 2px dashed var(--blue);
|
||||
border-radius: 18px;
|
||||
transition: all 180ms ease;
|
||||
}
|
||||
.drop-zone:hover,
|
||||
.drop-zone.drag-over {
|
||||
background: rgba(35, 136, 255, 0.06);
|
||||
border-color: var(--blue-dark);
|
||||
}
|
||||
.drop-zone-icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
color: var(--blue);
|
||||
}
|
||||
.drop-zone-icon svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 1.8;
|
||||
}
|
||||
.drop-zone-text {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
color: #14213c;
|
||||
}
|
||||
.drop-zone-hint {
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.drop-zone input[type="file"] {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.drop-zone .browse-btn {
|
||||
display: inline-block;
|
||||
padding: 10px 28px;
|
||||
color: #fff;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 750;
|
||||
background: var(--blue);
|
||||
border-radius: 10px;
|
||||
transition: background 160ms ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
.drop-zone .browse-btn:hover {
|
||||
background: var(--blue-dark);
|
||||
}
|
||||
.upload-status {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
background: #e0e9f4;
|
||||
border-radius: 999px;
|
||||
}
|
||||
.progress-bar-fill {
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--blue), #5da5ff);
|
||||
border-radius: 999px;
|
||||
transition: width 200ms ease;
|
||||
}
|
||||
.status-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.status-text .spinner {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid #e0e9f4;
|
||||
border-top-color: var(--blue);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.status-text.success { color: var(--green); }
|
||||
.status-text.error { color: #e5483e; }
|
||||
.upload-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
padding: 12px 16px;
|
||||
background: rgba(232, 255, 244, 0.88);
|
||||
border: 1px solid rgba(33, 199, 137, 0.28);
|
||||
border-radius: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.upload-result .copy-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 6px 14px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
color: var(--blue);
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border: 1px solid rgba(35, 136, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 160ms ease;
|
||||
}
|
||||
.upload-result .copy-btn:hover {
|
||||
background: var(--blue);
|
||||
color: #fff;
|
||||
}
|
||||
.file-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 32px 0 14px;
|
||||
}
|
||||
.file-list-header h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
.file-list-header h2 span {
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.refresh-btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: var(--blue);
|
||||
background: rgba(35, 136, 255, 0.08);
|
||||
border: 1px solid rgba(35, 136, 255, 0.24);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 160ms ease;
|
||||
}
|
||||
.refresh-btn:hover {
|
||||
background: var(--blue);
|
||||
color: #fff;
|
||||
}
|
||||
.file-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 18px;
|
||||
background: rgba(247, 251, 255, 0.6);
|
||||
border: 1px solid rgba(215, 226, 242, 0.6);
|
||||
border-radius: 14px;
|
||||
transition: all 160ms ease;
|
||||
}
|
||||
.file-item:hover {
|
||||
background: rgba(247, 251, 255, 0.9);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
.file-icon {
|
||||
display: grid;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
place-items: center;
|
||||
color: var(--blue);
|
||||
background: rgba(35, 136, 255, 0.08);
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 900;
|
||||
}
|
||||
.file-icon.zip,
|
||||
.file-icon.gz,
|
||||
.file-icon.tar { color: var(--orange); background: rgba(240, 120, 69, 0.1); }
|
||||
.file-icon.jpg,
|
||||
.file-icon.png,
|
||||
.file-icon.webp,
|
||||
.file-icon.gif { color: var(--green); background: rgba(33, 199, 137, 0.1); }
|
||||
.file-icon.pdf { color: #e5483e; background: rgba(229, 72, 62, 0.1); }
|
||||
.file-icon.txt,
|
||||
.file-icon.md { color: var(--purple); background: rgba(119, 93, 246, 0.1); }
|
||||
.file-info { flex: 1; min-width: 0; }
|
||||
.file-name {
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
color: #14213c;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.file-meta {
|
||||
font-size: 0.82rem;
|
||||
color: var(--muted);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.file-actions { display: flex; gap: 6px; flex-shrink: 0; }
|
||||
.file-actions a,
|
||||
.file-actions button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 160ms ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
.file-actions .download-link {
|
||||
color: var(--blue);
|
||||
background: rgba(35, 136, 255, 0.08);
|
||||
border: 1px solid rgba(35, 136, 255, 0.24);
|
||||
}
|
||||
.file-actions .download-link:hover {
|
||||
background: var(--blue);
|
||||
color: #fff;
|
||||
}
|
||||
.file-actions .delete-btn {
|
||||
color: #e5483e;
|
||||
background: rgba(229, 72, 62, 0.08);
|
||||
border: 1px solid rgba(229, 72, 62, 0.2);
|
||||
}
|
||||
.file-actions .delete-btn:hover {
|
||||
background: #e5483e;
|
||||
color: #fff;
|
||||
}
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 48px;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
.empty-state-icon { width: 48px; height: 48px; opacity: 0.3; }
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
left: 50%;
|
||||
z-index: 999;
|
||||
padding: 12px 24px;
|
||||
color: #fff;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
background: #14213c;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.18);
|
||||
transform: translateX(-50%) translateY(80px);
|
||||
opacity: 0;
|
||||
transition: all 260ms ease;
|
||||
}
|
||||
.toast.show {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.upload-panel { padding: 24px 18px; }
|
||||
.file-item { flex-wrap: wrap; }
|
||||
.file-actions { width: 100%; justify-content: flex-end; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="upload-page">
|
||||
<div class="upload-panel">
|
||||
<h1><span></span>文件上传</h1>
|
||||
<p class="subtitle">拖放文件到此处或点击选择文件,上传后自动生成分享链接。</p>
|
||||
|
||||
<div class="drop-zone" id="dropZone">
|
||||
<div class="drop-zone-icon">
|
||||
<svg viewBox="0 0 48 48">
|
||||
<path d="M8 30v6a4 4 0 0 0 4 4h24a4 4 0 0 0 4-4v-6" />
|
||||
<path d="M24 10v20m-8-8 8 8 8-8" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="drop-zone-text">拖放文件到此处</div>
|
||||
<div class="drop-zone-hint">或点击下方按钮选择文件,最大 500MB</div>
|
||||
<span class="browse-btn" id="browseBtn">选择文件</span>
|
||||
<input type="file" id="fileInput" />
|
||||
</div>
|
||||
|
||||
<div class="upload-status" id="uploadStatus" style="display:none;">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar-fill" id="progressFill"></div>
|
||||
</div>
|
||||
<div class="status-text" id="statusText">
|
||||
<span class="spinner"></span>
|
||||
<span>正在上传...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="uploadResult"></div>
|
||||
|
||||
<div class="file-list-header">
|
||||
<h2>已上传文件 <span id="fileCount"></span></h2>
|
||||
<button class="refresh-btn" id="refreshBtn">⟳ 刷新</button>
|
||||
</div>
|
||||
|
||||
<div class="file-list" id="fileList">
|
||||
<div class="empty-state">
|
||||
<svg class="empty-state-icon" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M36 16V8a2 2 0 0 0-2-2H14l-6 6v28a2 2 0 0 0 2 2h12" />
|
||||
<path d="M14 6v6H8" />
|
||||
<path d="M30 28v12m-6-6h12" />
|
||||
</svg>
|
||||
<span>还没有上传文件</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
const API_BASE = '/upload/api';
|
||||
const FILE_BASE = '/upload/files';
|
||||
|
||||
const dropZone = document.getElementById('dropZone');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const uploadStatus = document.getElementById('uploadStatus');
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const statusText = document.getElementById('statusText');
|
||||
const uploadResult = document.getElementById('uploadResult');
|
||||
const fileList = document.getElementById('fileList');
|
||||
const fileCount = document.getElementById('fileCount');
|
||||
|
||||
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');
|
||||
if (e.dataTransfer.files.length) uploadFile(e.dataTransfer.files[0]);
|
||||
});
|
||||
|
||||
document.getElementById('browseBtn').addEventListener('click', () => fileInput.click());
|
||||
fileInput.addEventListener('change', () => {
|
||||
if (fileInput.files.length) uploadFile(fileInput.files[0]);
|
||||
});
|
||||
document.getElementById('refreshBtn').addEventListener('click', loadFiles);
|
||||
|
||||
function uploadFile(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.upload.addEventListener('progress', e => {
|
||||
if (e.lengthComputable) {
|
||||
progressFill.style.width = Math.round((e.loaded / e.total) * 100) + '%';
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('loadstart', () => {
|
||||
uploadStatus.style.display = 'block';
|
||||
progressFill.style.width = '0%';
|
||||
statusText.className = 'status-text';
|
||||
statusText.innerHTML = '<span class="spinner"></span><span>正在上传...</span>';
|
||||
uploadResult.innerHTML = '';
|
||||
});
|
||||
|
||||
xhr.addEventListener('load', () => {
|
||||
if (xhr.status === 200) {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
statusText.className = 'status-text success';
|
||||
statusText.innerHTML = '✓ 上传成功!';
|
||||
const link = window.location.origin + data.url;
|
||||
uploadResult.innerHTML = `
|
||||
<div class="upload-result">
|
||||
<span>${link}</span>
|
||||
<button class="copy-btn" onclick="copyLink('${link}')">复制链接</button>
|
||||
</div>
|
||||
`;
|
||||
loadFiles();
|
||||
} else {
|
||||
let msg = '上传失败';
|
||||
try { const d = JSON.parse(xhr.responseText); msg = d.error || msg; } catch {}
|
||||
statusText.className = 'status-text error';
|
||||
statusText.innerHTML = '✗ ' + msg;
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('error', () => {
|
||||
statusText.className = 'status-text error';
|
||||
statusText.innerHTML = '✗ 网络错误,请重试';
|
||||
});
|
||||
|
||||
xhr.open('POST', API_BASE + '/upload', true);
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
function loadFiles() {
|
||||
fetch(API_BASE + '/files')
|
||||
.then(r => r.json())
|
||||
.then(files => {
|
||||
fileCount.textContent = '(' + files.length + ')';
|
||||
if (!files.length) {
|
||||
fileList.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<svg class="empty-state-icon" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M36 16V8a2 2 0 0 0-2-2H14l-6 6v28a2 2 0 0 0 2 2h12" />
|
||||
<path d="M14 6v6H8" />
|
||||
<path d="M30 28v12m-6-6h12" />
|
||||
</svg>
|
||||
<span>还没有上传文件</span>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
fileList.innerHTML = files.map(f => {
|
||||
const ext = f.ext ? f.ext.toUpperCase() : '📄';
|
||||
return `
|
||||
<div class="file-item">
|
||||
<div class="file-icon ${f.ext}">${ext.slice(0, 3)}</div>
|
||||
<div class="file-info">
|
||||
<div class="file-name" title="${f.name}">${f.name}</div>
|
||||
<div class="file-meta">${f.sizeHuman} · ${f.modTime}</div>
|
||||
</div>
|
||||
<div class="file-actions">
|
||||
<a class="download-link" href="${FILE_BASE}/${f.storedName}" download>下载</a>
|
||||
<button class="delete-btn" onclick="deleteFile('${f.storedName}')">删除</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
})
|
||||
.catch(() => {
|
||||
fileList.innerHTML = '<div class="empty-state" style="color:#e5483e;">加载失败</div>';
|
||||
});
|
||||
}
|
||||
|
||||
function deleteFile(storedName) {
|
||||
fetch(API_BASE + '/files/' + encodeURIComponent(storedName), { method: 'DELETE' })
|
||||
.then(r => {
|
||||
if (r.ok) {
|
||||
showToast('已删除');
|
||||
loadFiles();
|
||||
} else {
|
||||
r.json().then(d => showToast('删除失败: ' + (d.error || 'unknown'), true));
|
||||
}
|
||||
})
|
||||
.catch(() => showToast('网络错误', true));
|
||||
}
|
||||
|
||||
function copyLink(url) {
|
||||
navigator.clipboard.writeText(url).then(() => showToast('链接已复制'));
|
||||
}
|
||||
|
||||
function showToast(msg, isError) {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg;
|
||||
t.style.background = isError ? '#e5483e' : '#14213c';
|
||||
t.classList.add('show');
|
||||
clearTimeout(t._hide);
|
||||
t._hide = setTimeout(() => t.classList.remove('show'), 3000);
|
||||
}
|
||||
|
||||
loadFiles();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user