chore: 完善开源项目文档与结构,移除敏感原始图片
- 重写 README.md,添加在线体验、API 文档、部署指南、技术栈等 - 添加 MIT LICENSE - 添加 CONTRIBUTING.md 贡献指南 - 添加 GitHub Issue/PR 模板 - 完善 .gitignore,防止原始图片和敏感文件误提交 - 从 git 中移除 25 个原始图片文件(.png/.jpg/非序号 webp) - 项目结构迁移:functions/ -> src/,根目录静态文件 -> public/ - 添加 wrangler.toml 与 package.json 配置
9
public/_headers
Normal file
@@ -0,0 +1,9 @@
|
||||
/manifest.json
|
||||
Access-Control-Allow-Origin: *
|
||||
Cache-Control: public, max-age=300
|
||||
|
||||
/mobile-image/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
|
||||
/desketop-image/*
|
||||
Cache-Control: public, max-age=31536000, immutable
|
||||
BIN
public/desketop-image/1.webp
Normal file
|
After Width: | Height: | Size: 918 KiB |
BIN
public/desketop-image/10.webp
Normal file
|
After Width: | Height: | Size: 340 KiB |
BIN
public/desketop-image/11.webp
Normal file
|
After Width: | Height: | Size: 647 KiB |
BIN
public/desketop-image/12.webp
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
public/desketop-image/13.webp
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
public/desketop-image/14.webp
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
public/desketop-image/15.webp
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
public/desketop-image/16.webp
Normal file
|
After Width: | Height: | Size: 258 KiB |
BIN
public/desketop-image/17.webp
Normal file
|
After Width: | Height: | Size: 707 KiB |
BIN
public/desketop-image/18.webp
Normal file
|
After Width: | Height: | Size: 609 KiB |
BIN
public/desketop-image/19.webp
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
public/desketop-image/2.webp
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/desketop-image/3.webp
Normal file
|
After Width: | Height: | Size: 513 KiB |
BIN
public/desketop-image/4.webp
Normal file
|
After Width: | Height: | Size: 918 KiB |
BIN
public/desketop-image/5.webp
Normal file
|
After Width: | Height: | Size: 545 KiB |
BIN
public/desketop-image/6.webp
Normal file
|
After Width: | Height: | Size: 207 KiB |
BIN
public/desketop-image/7.webp
Normal file
|
After Width: | Height: | Size: 608 KiB |
BIN
public/desketop-image/8.webp
Normal file
|
After Width: | Height: | Size: 595 KiB |
BIN
public/desketop-image/9.webp
Normal file
|
After Width: | Height: | Size: 338 KiB |
258
public/index.html
Normal file
@@ -0,0 +1,258 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>随机背景API</title>
|
||||
<meta name="description" content="Cloudflare Workers:随机背景 API 说明与预览。">
|
||||
<style>
|
||||
:root {
|
||||
--text: #1a1a1a;
|
||||
--muted: #666;
|
||||
--border: #ddd;
|
||||
--code-bg: #f5f5f5;
|
||||
--accent: #06c;
|
||||
}
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 1.5rem 1.25rem 3rem;
|
||||
color: var(--text);
|
||||
font: 16px/1.6 ui-sans-serif, system-ui, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: #fff;
|
||||
}
|
||||
a { color: var(--accent); }
|
||||
code {
|
||||
font: 0.92em ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
background: var(--code-bg);
|
||||
padding: 0.15em 0.35em;
|
||||
}
|
||||
pre, .pre-like {
|
||||
margin: 0.5rem 0 1rem;
|
||||
padding: 0.65rem 0.85rem;
|
||||
background: var(--code-bg);
|
||||
font: 13px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--border);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.doc {
|
||||
max-width: 40rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 0.5rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
margin: 2rem 0 0.65rem;
|
||||
padding-bottom: 0.25rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
p { margin: 0 0 0.85rem; }
|
||||
.muted { color: var(--muted); font-size: 0.95rem; }
|
||||
ul {
|
||||
margin: 0 0 1rem;
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
li { margin: 0.2rem 0; }
|
||||
.api-item { margin-bottom: 1.1rem; }
|
||||
.api-item p { margin: 0.25rem 0 0.35rem; }
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem 1rem;
|
||||
align-items: flex-end;
|
||||
margin: 0.75rem 0 1rem;
|
||||
}
|
||||
.field label {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted);
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
select {
|
||||
font: inherit;
|
||||
padding: 0.35rem 0.45rem;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
color: inherit;
|
||||
min-width: 6.5rem;
|
||||
}
|
||||
button {
|
||||
font: inherit;
|
||||
padding: 0.38rem 0.85rem;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover { filter: brightness(1.05); }
|
||||
#apiUrl {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font: 13px ui-monospace, Menlo, Consolas, monospace;
|
||||
padding: 0.45rem 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
background: #fafafa;
|
||||
}
|
||||
#previewImg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 14rem;
|
||||
object-fit: cover;
|
||||
margin-top: 0.5rem;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--code-bg);
|
||||
}
|
||||
footer {
|
||||
margin-top: 2.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--muted);
|
||||
border-top: 1px solid var(--border);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.controls { flex-direction: column; align-items: stretch; }
|
||||
button { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="doc">
|
||||
<h1>随机背景 API</h1>
|
||||
<p>Cloudflare Workers 接口。先用 <code>build.py</code> 在 <code>public/</code> 下生成序号 <code>.webp</code> 与 <code>manifest.json</code>,再通过 <code>/api/random</code>、<code>/api/list</code> 调用。</p>
|
||||
<p class="muted">约定:<code>mobile-image</code> 竖屏,<code>desketop-image</code> 横屏。默认响应为 302;加 <code>format=json</code> 时返回 JSON。</p>
|
||||
|
||||
<h2 id="links">快捷链接</h2>
|
||||
<ul>
|
||||
<li><a href="/api/list" target="_blank" rel="noreferrer"><code>/api/list</code></a></li>
|
||||
<li><a href="/manifest.json" target="_blank" rel="noreferrer"><code>/manifest.json</code></a></li>
|
||||
<li><a href="/mobile-image/1.webp" target="_blank" rel="noreferrer"><code>/mobile-image/1.webp</code></a>(示例)</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="api">接口说明</h2>
|
||||
|
||||
<div class="api-item">
|
||||
<p><strong><code>GET /api/random</code></strong></p>
|
||||
<p class="muted">按设备/方向选一个文件名,302 跳到对应图片。</p>
|
||||
<pre>GET /api/random</pre>
|
||||
</div>
|
||||
|
||||
<div class="api-item">
|
||||
<p><strong><code>GET /api/random?format=json</code></strong></p>
|
||||
<p class="muted">返回包含 <code>url</code> 等字段的 JSON。</p>
|
||||
<pre>GET /api/random?format=json</pre>
|
||||
</div>
|
||||
|
||||
<div class="api-item">
|
||||
<p><strong><code>GET /api/random?mode=mobile</code></strong> / <strong><code>?mode=desktop</code></strong></p>
|
||||
<p class="muted">强制使用竖屏或横屏素材目录。</p>
|
||||
<pre>GET /api/random?mode=mobile
|
||||
GET /api/random?mode=desktop</pre>
|
||||
</div>
|
||||
|
||||
<p class="muted">查询参数:<code>mode=auto|mobile|desktop</code>,<code>orientation=portrait|landscape</code>,<code>device=mobile|desktop</code>,<code>format=redirect|json</code>。</p>
|
||||
|
||||
<h2 id="preview">在线预览</h2>
|
||||
<div class="controls">
|
||||
<div class="field">
|
||||
<label for="modeSelect">mode</label>
|
||||
<select id="modeSelect">
|
||||
<option value="auto">auto</option>
|
||||
<option value="mobile">mobile</option>
|
||||
<option value="desktop">desktop</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="orientationSelect">orientation</label>
|
||||
<select id="orientationSelect">
|
||||
<option value="">auto</option>
|
||||
<option value="portrait">portrait</option>
|
||||
<option value="landscape">landscape</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="formatSelect">format</label>
|
||||
<select id="formatSelect">
|
||||
<option value="json">json</option>
|
||||
<option value="redirect">redirect</option>
|
||||
</select>
|
||||
</div>
|
||||
<button id="refreshBtn" type="button">刷新</button>
|
||||
</div>
|
||||
|
||||
<p><label for="apiUrl" class="muted" style="font-size: 0.75rem;">当前请求</label></p>
|
||||
<input id="apiUrl" readonly value="" autocomplete="off" spellcheck="false">
|
||||
<img id="previewImg" alt="随机背景预览" src="">
|
||||
<p><span class="muted" style="font-size: 0.75rem;">响应体</span></p>
|
||||
<pre class="pre-like" id="jsonOutput">{}</pre>
|
||||
|
||||
<footer>
|
||||
构建:<code>python3 build.py</code>。部署:<code>npm run deploy</code>(<code>wrangler deploy</code>)。Worker 仅含 API,静态页与图片来自 Assets。
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const modeSelect = document.getElementById('modeSelect');
|
||||
const orientationSelect = document.getElementById('orientationSelect');
|
||||
const formatSelect = document.getElementById('formatSelect');
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const apiUrl = document.getElementById('apiUrl');
|
||||
const previewImg = document.getElementById('previewImg');
|
||||
const jsonOutput = document.getElementById('jsonOutput');
|
||||
|
||||
function detectOrientation() {
|
||||
return window.matchMedia('(orientation: portrait)').matches ? 'portrait' : 'landscape';
|
||||
}
|
||||
|
||||
function detectDevice() {
|
||||
if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') {
|
||||
return navigator.userAgentData.mobile ? 'mobile' : 'desktop';
|
||||
}
|
||||
return /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent) ? 'mobile' : 'desktop';
|
||||
}
|
||||
|
||||
function buildApiPath() {
|
||||
const params = new URLSearchParams();
|
||||
params.set('mode', modeSelect.value);
|
||||
params.set('device', detectDevice());
|
||||
params.set('format', formatSelect.value);
|
||||
const orientation = orientationSelect.value || detectOrientation();
|
||||
params.set('orientation', orientation);
|
||||
return `/api/random?${params.toString()}`;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const url = buildApiPath();
|
||||
apiUrl.value = url;
|
||||
previewImg.style.opacity = '0.72';
|
||||
|
||||
if (formatSelect.value === 'json') {
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
jsonOutput.textContent = JSON.stringify(data, null, 2);
|
||||
previewImg.src = data.url || '';
|
||||
} else {
|
||||
jsonOutput.textContent = '{ "format": "redirect" }';
|
||||
previewImg.src = url;
|
||||
}
|
||||
}
|
||||
|
||||
previewImg.addEventListener('load', () => {
|
||||
previewImg.style.opacity = '1';
|
||||
});
|
||||
|
||||
refreshBtn.addEventListener('click', refresh);
|
||||
modeSelect.addEventListener('change', refresh);
|
||||
orientationSelect.addEventListener('change', refresh);
|
||||
formatSelect.addEventListener('change', refresh);
|
||||
refresh();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
42
public/manifest.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"generated_at": "2026-05-18T11:45:45.514521+00:00",
|
||||
"variants": {
|
||||
"mobile": {
|
||||
"folder": "mobile-image",
|
||||
"count": 6,
|
||||
"images": [
|
||||
"1.webp",
|
||||
"2.webp",
|
||||
"3.webp",
|
||||
"4.webp",
|
||||
"5.webp",
|
||||
"6.webp"
|
||||
]
|
||||
},
|
||||
"desktop": {
|
||||
"folder": "desketop-image",
|
||||
"count": 19,
|
||||
"images": [
|
||||
"1.webp",
|
||||
"2.webp",
|
||||
"3.webp",
|
||||
"4.webp",
|
||||
"5.webp",
|
||||
"6.webp",
|
||||
"7.webp",
|
||||
"8.webp",
|
||||
"9.webp",
|
||||
"10.webp",
|
||||
"11.webp",
|
||||
"12.webp",
|
||||
"13.webp",
|
||||
"14.webp",
|
||||
"15.webp",
|
||||
"16.webp",
|
||||
"17.webp",
|
||||
"18.webp",
|
||||
"19.webp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
public/mobile-image/1.webp
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
public/mobile-image/2.webp
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
public/mobile-image/3.webp
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
public/mobile-image/4.webp
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
public/mobile-image/5.webp
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
public/mobile-image/6.webp
Normal file
|
After Width: | Height: | Size: 157 KiB |