- 重写 README.md,添加在线体验、API 文档、部署指南、技术栈等 - 添加 MIT LICENSE - 添加 CONTRIBUTING.md 贡献指南 - 添加 GitHub Issue/PR 模板 - 完善 .gitignore,防止原始图片和敏感文件误提交 - 从 git 中移除 25 个原始图片文件(.png/.jpg/非序号 webp) - 项目结构迁移:functions/ -> src/,根目录静态文件 -> public/ - 添加 wrangler.toml 与 package.json 配置
259 lines
8.6 KiB
HTML
259 lines
8.6 KiB
HTML
<!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>
|