部署前准备
你需要准备好:
Cloudflare 账号。
一个 KV 命名空间:用于存储你的无限量上传历史链接。
workers.js代码放在文章最后
详细部署步骤
第一步:创建 KV 命名空间(存储中心)
登录 Cloudflare 控制台。
在左侧菜单点击 “存储与数据库 (Storage & Databases)” -> “KV”。
点击 “创建命名空间 (Create Namespace)”。
输入名字:
IMG_LINKS(注意大小写,代码中引用了这个名字)。点击 “添加 (Add)”。
第二步:创建 Worker 服务
在左侧菜单点击 “计算 (Compute)” -> “Workers 和 Pages”。
点击 “创建应用程序 (Create application)” -> “创建 Worker”。
给你的 Worker 起个名字(例如
cloudshot-pro),点击 “部署 (Deploy)”。
第三步:绑定 KV 到 Worker
这是最关键的一步,否则无法记录历史:
进入你刚刚创建的 Worker 详情页。
点击上方选项卡中的 “设置 (Settings)”。
在左侧子菜单选择 “变量 (Variables)”。
滚动到 “KV 命名空间绑定 (KV Namespace Bindings)” 区域,点击 “添加绑定 (Add binding)”。
变量名称 (Variable name):填写
IMG_LINKS。KV 命名空间 (KV namespace):在下拉框选择你刚才创建的那个
IMG_LINKS。点击 “保存并部署 (Save and deploy)”。
第四步:写入代码
回到 Worker 的 “预览 (Preview)” 或点击右上角的 “编辑代码 (Edit Code)”。
清空编辑器中原有的所有代码。
将我上一条回复中提供的那份 “最终优化版”完整代码 粘贴进去。
点击右上角的 “部署 (Deploy)”。
const HTML_CONTENT = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer">
<title>CloudShot Pro - 智能图床工作台</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;800&display=swap');
body { font-family: 'Plus Jakarta Sans', sans-serif; background: #f8fafc; color: #1e293b; }
.glass-blur { background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(20px); border: 1px solid rgba(255,255,255,0.4); }
.drop-zone { border: 2px dashed #cbd5e1; border-radius: 24px; transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
.drop-zone.dragover { border-color: #3b82f6; background: #eff6ff; transform: scale(1.01); }
/* 相册图片卡片优化 */
.img-card { aspect-ratio: 1/1; border-radius: 16px; overflow: hidden; background: #f1f5f9; position: relative; cursor: zoom-in; box-shadow: 0 4px 12px rgba(0,0,0,0.03); }
.img-card img { width: 100%; height: 100%; object-fit: cover; transition: 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
.img-card:hover img { transform: scale(1.08); }
/* 复制按钮移至底部,解决遮挡问题 */
.img-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; justify-content: flex-end; opacity: 0; transition: 0.3s; padding: 8px; }
.img-card:hover .img-overlay { opacity: 1; background: linear-gradient(to top, rgba(0,0,0,0.6) 0%, transparent 50%); }
.action-bar { display: flex; gap: 4px; width: 100%; }
.action-btn { flex: 1; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(4px); font-size: 10px; font-weight: 700; padding: 6px 0; border-radius: 8px; color: #1e293b; transition: 0.2s; }
.action-btn:hover { background: #fff; transform: translateY(-1px); }
/* 灯箱预览 */
#lightbox { display: none; position: fixed; inset: 0; z-index: 9999; background: rgba(15, 23, 42, 0.95); backdrop-filter: blur(10px); align-items: center; justify-content: center; padding: 20px; cursor: zoom-out; }
#lightbox img { max-width: 95%; max-height: 90vh; border-radius: 12px; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5); transform: scale(0.9); transition: 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
#lightbox.active { display: flex; }
#lightbox.active img { transform: scale(1); }
/* 侧边栏按钮美化 */
.copy-btn-sm { font-size: 11px; font-weight: 700; padding: 8px 0; border-radius: 8px; background: #f1f5f9; color: #475569; width: 100%; text-align: center; transition: all 0.2s; border: 1px solid transparent; }
.copy-btn-sm:hover { background: #fff; border-color: #e2e8f0; color: #3b82f6; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05); }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
</style>
</head>
<body class="h-screen overflow-hidden flex flex-col md:flex-row">
<aside class="w-full md:w-80 h-[38vh] md:h-screen bg-white border-r border-slate-200 flex flex-col z-20 shadow-2xl overflow-hidden">
<div class="p-6 border-b border-slate-100 flex items-center justify-between">
<h1 class="text-xl font-800 tracking-tighter flex items-center gap-2">
<span class="bg-blue-600 text-white w-8 h-8 flex items-center justify-center rounded-lg shadow-lg italic">C</span>
CloudShot
</h1>
<button onclick="openGallery()" class="text-[11px] bg-blue-50 text-blue-600 px-3 py-1.5 rounded-full font-bold hover:bg-blue-100 transition-all">全量相册</button>
</div>
<div class="p-6 flex-1 bg-slate-50/30 overflow-hidden">
<h3 class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-4">最近同步 (6)</h3>
<div id="recentList" class="space-y-5">
</div>
</div>
</aside>
<main class="flex-1 overflow-y-auto p-6 md:p-16">
<div class="max-w-4xl mx-auto">
<header class="mb-10">
<h2 class="text-3xl font-800 text-slate-900 mb-2 tracking-tight">发布新的媒体资源</h2>
<p class="text-slate-500 font-medium italic">无限制云端记录 · 毫秒级外链分发</p>
</header>
<div class="glass-blur p-8 rounded-[40px] shadow-2xl shadow-slate-200/50">
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-8">
<label class="cursor-pointer"><input type="radio" name="api" value="360tc" class="peer sr-only" checked><div class="py-3 text-center rounded-2xl border border-slate-100 text-sm font-bold text-slate-500 peer-checked:bg-slate-900 peer-checked:text-white transition-all">360图床</div></label>
<label class="cursor-pointer"><input type="radio" name="api" value="jdtc" class="peer sr-only"><div class="py-3 text-center rounded-2xl border border-slate-100 text-sm font-bold text-slate-500 peer-checked:bg-slate-900 peer-checked:text-white transition-all">京东</div></label>
<label class="cursor-pointer"><input type="radio" name="api" value="sogotc" class="peer sr-only"><div class="py-3 text-center rounded-2xl border border-slate-100 text-sm font-bold text-slate-500 peer-checked:bg-slate-900 peer-checked:text-white transition-all">搜狗</div></label>
<label class="cursor-pointer"><input type="radio" name="api" value="yanxuantc" class="peer sr-only"><div class="py-3 text-center rounded-2xl border border-slate-100 text-sm font-bold text-slate-500 peer-checked:bg-slate-900 peer-checked:text-white transition-all">网易严选</div></label>
</div>
<div id="dropZone" class="drop-zone bg-slate-50/50 p-16 text-center mb-8 cursor-pointer group hover:bg-white transition-all">
<input type="file" id="fileInput" class="hidden" accept="image/*" multiple>
<div class="text-6xl text-slate-200 group-hover:text-blue-500 transition-all mb-4"><i class="fa-solid fa-cloud-arrow-up"></i></div>
<p class="text-slate-700 font-bold">将文件拖拽至此或点击选择</p>
<div id="pendingList" class="hidden mt-8 flex flex-wrap justify-center gap-3 pt-6 border-t border-slate-100"></div>
</div>
<button id="uploadButton" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-5 rounded-[24px] font-bold text-lg shadow-xl shadow-blue-200 transition-all active:scale-[0.98]">
<span id="btnText">开始同步任务</span>
</button>
</div>
</div>
</main>
<div id="galleryModal" class="fixed inset-0 bg-slate-900 z-[5000] hidden flex flex-col">
<div class="p-8 border-b border-white/10 flex justify-between items-center">
<h2 class="text-white text-2xl font-800 flex items-center gap-3"><i class="fa-solid fa-layer-group"></i> 全量媒体库</h2>
<div class="flex items-center gap-8">
<button onclick="clearHistory()" class="text-red-400 text-sm font-bold hover:text-red-300">清空所有记录</button>
<button onclick="closeGallery()" class="text-white/60 hover:text-white text-3xl transition-colors"><i class="fa-solid fa-times"></i></button>
</div>
</div>
<div id="fullGallery" class="p-8 overflow-y-auto grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-6">
</div>
</div>
<div id="lightbox" onclick="closeLightbox()">
<img id="lightboxImg" src="" onclick="event.stopPropagation()">
</div>
<script>
const fileInput = document.getElementById('fileInput');
const dropZone = document.getElementById('dropZone');
const recentList = document.getElementById('recentList');
const fullGallery = document.getElementById('fullGallery');
const uploadButton = document.getElementById('uploadButton');
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightboxImg');
let selectedFiles = [];
window.onload = async () => {
const resp = await fetch('/history');
const data = await resp.json();
const sorted = data.reverse();
// 核心改进:只保留侧边栏 6 张
sorted.slice(0, 6).forEach(item => renderRecentCard(item));
// 相册仍然显示全部
sorted.forEach(item => renderGalleryCard(item));
};
function showImage(url) {
lightboxImg.src = url.replace(/^http:\\/\\//i, 'https://');
lightbox.classList.add('active');
}
function closeLightbox() { lightbox.classList.remove('active'); }
function openGallery() { document.getElementById('galleryModal').classList.remove('hidden'); }
function closeGallery() { document.getElementById('galleryModal').classList.add('hidden'); }
function renderRecentCard(item) {
const safeUrl = item.url.replace(/^http:\\/\\//i, 'https://');
const div = document.createElement('div');
div.className = 'space-y-3';
div.innerHTML = \`
<div class="flex items-center gap-4 cursor-pointer group" onclick="showImage('\${safeUrl}')">
<div class="w-12 h-12 rounded-xl overflow-hidden bg-slate-200 flex-shrink-0 shadow-md ring-2 ring-white transition-all group-hover:ring-blue-100">
<img src="\${safeUrl}" class="w-full h-full object-cover">
</div>
<div class="flex-1 min-w-0">
<p class="text-[11px] font-bold text-slate-800 truncate mb-1">\${item.name}</p>
<p class="text-[9px] text-slate-400 uppercase tracking-tighter italic">Cloud Sync</p>
</div>
</div>
<div class="flex gap-2">
<button onclick="copy('\${safeUrl}', this)" class="copy-btn-sm">复制链接</button>
<button onclick="copyHTML('\${safeUrl}', '\${item.name}', this)" class="copy-btn-sm">复制HTML代码</button>
</div>\`;
recentList.appendChild(div);
}
function renderGalleryCard(item) {
const safeUrl = item.url.replace(/^http:\\/\\//i, 'https://');
const div = document.createElement('div');
div.className = 'img-card';
div.onclick = () => showImage(safeUrl);
div.innerHTML = \`
<img src="\${safeUrl}" loading="lazy">
<div class="img-overlay">
<div class="action-bar" onclick="event.stopPropagation()">
<button onclick="copy('\${safeUrl}', this)" class="action-btn">复制链接</button>
<button onclick="copyHTML('\${safeUrl}', '\${item.name}', this)" class="action-btn">复制HTML</button>
</div>
</div>\`;
fullGallery.appendChild(div);
}
function handleFiles(files) {
selectedFiles = Array.from(files);
const pending = document.getElementById('pendingList');
pending.innerHTML = ''; pending.classList.remove('hidden');
selectedFiles.forEach(f => {
const reader = new FileReader();
reader.onload = (e) => {
const d = document.createElement('div');
d.className = 'w-12 h-12 rounded-xl border-2 border-white shadow-lg overflow-hidden';
d.innerHTML = \`<img src="\${e.target.result}" class="w-full h-full object-cover">\`;
pending.appendChild(d);
};
reader.readAsDataURL(f);
});
}
dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('dragover'); });
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));
dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); handleFiles(e.dataTransfer.files); });
uploadButton.addEventListener('click', async function() {
if (!selectedFiles.length) return alert('请先选择图片');
const api = document.querySelector('input[name="api"]:checked').value;
this.disabled = true;
document.getElementById('btnText').innerText = '正在同步数据...';
for (const file of selectedFiles) {
const formData = new FormData();
formData.append('file', file);
formData.append('apiType', api);
try { await fetch('/upload', { method: 'POST', body: formData }); } catch(e) {}
}
location.reload();
});
async function clearHistory() {
if(confirm('此操作将永久抹除云端所有记录,确定吗?')) { await fetch('/clear', { method: 'POST' }); location.reload(); }
}
async function copy(t, b) {
await navigator.clipboard.writeText(t);
const o = b.innerText; b.innerText = '已复制';
setTimeout(() => b.innerText = o, 1000);
}
async function copyHTML(url, name, b) {
await navigator.clipboard.writeText(\`<img src="\${url}" alt="\${name}">\`);
const o = b.innerText; b.innerText = '已复制';
setTimeout(() => b.innerText = o, 1000);
}
</script>
</body>
</html>`;
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === '/history') {
const data = await env.IMG_LINKS.get('list');
return new Response(data || '[]', { headers: { 'Content-Type': 'application/json' } });
}
if (url.pathname === '/clear') {
await env.IMG_LINKS.put('list', '[]');
return new Response('ok');
}
if (url.pathname === '/upload' && request.method === 'POST') {
const formData = await request.formData();
const file = formData.get('file');
const apiType = formData.get('apiType');
const endpoints = {
'360tc': 'https://api.xinyew.cn/api/360tc',
'jdtc': 'https://api.xinyew.cn/api/jdtc',
'sogotc': 'https://api.xinyew.cn/api/sogotc',
'yanxuantc': 'https://api.xinyew.cn/api/yanxuantc'
};
try {
const resp = await fetch(endpoints[apiType], { method: 'POST', body: formData });
const result = await resp.json();
const finalUrl = result.url || result.data?.url;
if (finalUrl) {
let list = JSON.parse(await env.IMG_LINKS.get('list') || '[]');
list.push({ url: finalUrl, name: file.name, time: Date.now() });
await env.IMG_LINKS.put('list', JSON.stringify(list));
}
return new Response(JSON.stringify(result));
} catch (e) {
return new Response(JSON.stringify({ error: e.message }), { status: 500 });
}
}
return new Response(HTML_CONTENT, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
};
评论区