预览:https://spring-brook-2271.wexisliu.workers.dev/

使用方法
- 在 Cloudflare 后台创建一个 KV Namespace (例如命名为
URL_DB)。 - 在 Worker 的设置 (Settings) -> 变量 (Variables) -> KV Namespace Bindings 中,将变量名设为
LINKS并绑定刚才创建的 Namespace。 - 将下方代码完整复制到 Worker 编辑器中并部署。
export default {
async fetch(request, env) {
const url = new URL(request.url);
const path = url.pathname;
if (request.method === "POST" && path === "/api/create") {
try {
const { url: longUrl, slug: customSlug } = await request.json();
if (!longUrl) return new Response(JSON.stringify({ error: "URL is required" }), { status: 400 });
try { new URL(longUrl); } catch { return new Response(JSON.stringify({ error: "Invalid URL format" }), { status: 400 }); }
let slug = customSlug ? customSlug.trim() : crypto.randomUUID().slice(0, 8);
const exists = await env.LINKS.get(slug);
if (exists && customSlug) {
return new Response(JSON.stringify({ error: "Slug already exists" }), { status: 409 });
}
await env.LINKS.put(slug, longUrl);
return new Response(JSON.stringify({
slug,
shortUrl: `${url.origin}/${slug}`
}), { headers: { "Content-Type": "application/json" } });
} catch (e) {
return new Response(JSON.stringify({ error: e.message }), { status: 500 });
}
}
if (request.method === "GET" && path !== "/") {
const slug = path.slice(1);
const longUrl = await env.LINKS.get(slug);
if (longUrl) return Response.redirect(longUrl, 302);
return new Response("Not Found", { status: 404 });
}
return new Response(html, {
headers: { "Content-Type": "text/html;charset=UTF-8" },
});
},
};
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verdant Link</title>
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;1,400&family=Nunito:wght@300;400;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'bio-dark': '#2C4C3B',
'bio-med': '#4F775D',
'bio-light': '#8DAA91',
'bio-pale': '#E8EFE6',
'sand': '#F5F2EB',
},
fontFamily: {
sans: ['Nunito', 'sans-serif'],
serif: ['Lora', 'serif'],
},
animation: {
'breathe': 'breathe 8s ease-in-out infinite alternate',
},
keyframes: {
breathe: {
'0%': { transform: 'translate(0, 0) scale(1)' },
'100%': { transform: 'translate(10px, -10px) scale(1.05)' },
}
}
}
}
}
</script>
<style>
body {
background-color: #F5F2EB;
color: #2C4C3B;
overflow: hidden;
}
.noise-bg {
position: absolute;
inset: 0;
opacity: 0.06;
pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
}
.blob {
position: absolute;
filter: blur(60px);
z-index: 0;
opacity: 0.5;
animation: breathe 10s ease-in-out infinite alternate;
}
.blob-1 {
top: -10%; left: -10%; width: 50vw; height: 50vw;
background: #8DAA91;
border-radius: 40% 60% 70% 30% / 40% 50% 60% 50%;
}
.blob-2 {
bottom: -10%; right: -10%; width: 60vw; height: 60vw;
background: #4F775D;
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
animation-delay: -3s;
}
.glass-card {
background: rgba(255, 255, 255, 0.45);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 2rem;
box-shadow: 0 10px 40px -10px rgba(44, 76, 59, 0.1);
}
.organic-input {
border-radius: 1rem;
border: 1px solid rgba(44, 76, 59, 0.1);
background: rgba(255, 255, 255, 0.5);
transition: all 0.2s ease;
}
.organic-input:focus {
outline: none;
border-color: #4F775D;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 0 0 4px rgba(141, 170, 145, 0.2);
}
.leaf-btn {
border-radius: 20px 24px 24px 8px;
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.leaf-btn:hover {
border-radius: 24px 8px 24px 20px;
filter: brightness(1.05);
transform: translateY(-2px);
box-shadow: 0 10px 20px -5px rgba(44, 76, 59, 0.2);
}
.leaf-btn:active {
transform: scale(0.98);
}
</style>
</head>
<body class="h-screen w-screen flex items-center justify-center relative">
<div class="noise-bg"></div>
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<main class="relative z-10 w-full max-w-md p-6">
<div class="glass-card p-8 md:p-10 flex flex-col items-center text-center">
<div class="mb-6 text-bio-dark relative">
<i data-lucide="leaf" class="w-10 h-10 absolute -top-4 -left-6 text-bio-light opacity-60 rotate-[-15deg]"></i>
<h1 class="text-3xl font-serif font-medium tracking-wide">Verdant</h1>
<p class="text-sm font-sans text-bio-med mt-2 opacity-80">Organic URL Shortener</p>
</div>
<div class="w-full space-y-5">
<div class="text-left group">
<label class="block text-xs font-bold tracking-widest text-bio-med uppercase mb-2 ml-1 opacity-70">Origin</label>
<input type="url" id="longUrl" placeholder="https://..." class="organic-input w-full px-5 py-4 text-bio-dark placeholder-bio-med/40 font-sans">
</div>
<div class="text-left group">
<label class="block text-xs font-bold tracking-widest text-bio-med uppercase mb-2 ml-1 opacity-70">Slug (Optional)</label>
<input type="text" id="customSlug" placeholder="custom-name" class="organic-input w-full px-5 py-4 text-bio-dark placeholder-bio-med/40 font-sans">
</div>
<button onclick="createLink()" id="generateBtn" class="leaf-btn w-full bg-bio-dark text-sand py-4 font-serif text-lg flex items-center justify-center gap-2 mt-4">
<span>Shorten</span>
<i data-lucide="arrow-right" class="w-4 h-4"></i>
</button>
</div>
<div id="resultCard" class="hidden w-full mt-8 pt-6 border-t border-bio-dark/5 animate-[fade-in_0.5s_ease-out]">
<p class="text-xs text-bio-med uppercase tracking-widest mb-2">Your Seedling</p>
<div onclick="copyLink()" class="cursor-pointer group relative bg-white/40 border border-bio-light/20 rounded-xl p-4 flex items-center justify-between hover:bg-white/60 transition-colors">
<span id="shortUrl" class="text-bio-dark font-sans font-medium truncate mr-2 select-all"></span>
<i data-lucide="copy" class="w-5 h-5 text-bio-med group-hover:scale-110 transition-transform"></i>
</div>
<p id="copyMsg" class="h-4 mt-2 text-xs text-bio-med opacity-0 transition-opacity duration-300">Copied to clipboard</p>
</div>
</div>
</main>
<script>
lucide.createIcons();
async function createLink() {
const btn = document.getElementById('generateBtn');
const longUrl = document.getElementById('longUrl').value;
const customSlug = document.getElementById('customSlug').value;
const resultCard = document.getElementById('resultCard');
const shortUrlSpan = document.getElementById('shortUrl');
if(!longUrl) return;
const originalText = btn.innerHTML;
btn.innerHTML = '<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i>';
lucide.createIcons();
btn.disabled = true;
try {
const res = await fetch('/api/create', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ url: longUrl, slug: customSlug })
});
const data = await res.json();
if(res.ok) {
shortUrlSpan.textContent = data.shortUrl;
resultCard.classList.remove('hidden');
} else {
alert(data.error || 'Error creating link');
}
} catch(e) {
console.error(e);
} finally {
btn.innerHTML = originalText;
btn.disabled = false;
lucide.createIcons();
}
}
function copyLink() {
const text = document.getElementById('shortUrl').textContent;
navigator.clipboard.writeText(text);
const msg = document.getElementById('copyMsg');
msg.classList.remove('opacity-0');
setTimeout(() => msg.classList.add('opacity-0'), 2000);
}
</script>
</body>
</html>`;
谢谢楼主 不错
大家一起来把mjj的kv额度用光
删了删了
不明觉厉
绑定
page not found
关了,害怕被刷,佬可以自己部署玩一玩