chore: 完善开源项目文档与结构,移除敏感原始图片

- 重写 README.md,添加在线体验、API 文档、部署指南、技术栈等
- 添加 MIT LICENSE
- 添加 CONTRIBUTING.md 贡献指南
- 添加 GitHub Issue/PR 模板
- 完善 .gitignore,防止原始图片和敏感文件误提交
- 从 git 中移除 25 个原始图片文件(.png/.jpg/非序号 webp)
- 项目结构迁移:functions/ -> src/,根目录静态文件 -> public/
- 添加 wrangler.toml 与 package.json 配置
This commit is contained in:
2026-06-06 20:52:59 +08:00
parent f68e19a3bd
commit f45a0068a2
69 changed files with 872 additions and 675 deletions

9
public/_headers Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

258
public/index.html Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
public/mobile-image/2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
public/mobile-image/3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
public/mobile-image/4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
public/mobile-image/5.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
public/mobile-image/6.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB