391 lines
11 KiB
HTML
391 lines
11 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 Pages 随机背景 API 文档与在线预览。">
|
||
<style>
|
||
:root {
|
||
--bg: #f6f7fb;
|
||
--panel: #fff;
|
||
--text: #172033;
|
||
--muted: #5b657a;
|
||
--line: #e3e7ef;
|
||
--soft: #f3f5f9;
|
||
--accent: #2563eb;
|
||
--code: #0b1220;
|
||
--shadow: 0 10px 28px rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
background: linear-gradient(180deg, #fff 0%, var(--bg) 100%);
|
||
color: var(--text);
|
||
font: 14px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||
}
|
||
|
||
a { color: var(--accent); text-decoration: none; }
|
||
a:hover { text-decoration: underline; }
|
||
|
||
.wrap {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 14px;
|
||
}
|
||
|
||
.top {
|
||
display: grid;
|
||
gap: 10px;
|
||
grid-template-columns: minmax(0, 1fr) 250px;
|
||
align-items: start;
|
||
}
|
||
|
||
.main {
|
||
display: grid;
|
||
gap: 10px;
|
||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.card {
|
||
background: var(--panel);
|
||
border: 1px solid var(--line);
|
||
border-radius: 14px;
|
||
box-shadow: var(--shadow);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.pad { padding: 12px; }
|
||
|
||
h1, h2, p { margin: 0; }
|
||
h1 {
|
||
font-size: clamp(24px, 3vw, 34px);
|
||
line-height: 1.1;
|
||
letter-spacing: -0.03em;
|
||
}
|
||
h2 {
|
||
font-size: 15px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.lead {
|
||
margin-top: 6px;
|
||
color: var(--muted);
|
||
}
|
||
|
||
.meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 5px 9px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--line);
|
||
background: var(--soft);
|
||
font-size: 11px;
|
||
color: var(--text);
|
||
}
|
||
|
||
.links {
|
||
display: grid;
|
||
gap: 6px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.section {
|
||
display: grid;
|
||
gap: 8px;
|
||
}
|
||
|
||
.grid-2 {
|
||
display: grid;
|
||
gap: 8px;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.endpoint {
|
||
padding: 10px;
|
||
border-radius: 12px;
|
||
border: 1px solid var(--line);
|
||
background: #fff;
|
||
}
|
||
|
||
.endpoint strong {
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.code {
|
||
margin: 0;
|
||
padding: 9px 10px;
|
||
border-radius: 10px;
|
||
border: 1px solid var(--line);
|
||
background: var(--code);
|
||
color: #e7eefc;
|
||
overflow-x: auto;
|
||
font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||
}
|
||
|
||
label {
|
||
display: block;
|
||
margin-bottom: 4px;
|
||
font-size: 11px;
|
||
color: var(--muted);
|
||
}
|
||
|
||
select, button, input {
|
||
width: 100%;
|
||
padding: 9px 10px;
|
||
border-radius: 10px;
|
||
border: 1px solid var(--line);
|
||
background: #fff;
|
||
color: var(--text);
|
||
font: inherit;
|
||
}
|
||
|
||
button {
|
||
cursor: pointer;
|
||
border-color: var(--accent);
|
||
background: var(--accent);
|
||
color: #fff;
|
||
}
|
||
|
||
.row {
|
||
display: grid;
|
||
gap: 8px;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr)) 100px;
|
||
}
|
||
|
||
.preview-grid {
|
||
display: grid;
|
||
gap: 8px;
|
||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
|
||
align-items: start;
|
||
}
|
||
|
||
.preview-box {
|
||
aspect-ratio: 16 / 10;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--line);
|
||
background: #eef2f7;
|
||
}
|
||
|
||
.preview-box img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.small {
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.footer {
|
||
margin-top: 10px;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.top, .main, .grid-2, .preview-grid, .row {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main class="wrap">
|
||
<section class="top">
|
||
<div class="card pad">
|
||
<h1>随机背景 API</h1>
|
||
<p class="lead">Cloudflare Pages 随机背景接口。上传图片后先用 `build.py` 生成序号 `.webp`,然后可通过 `/api/random` 和 `/api/list` 调用。</p>
|
||
<div class="meta">
|
||
<span class="tag">竖屏:mobile-image</span>
|
||
<span class="tag">横屏:desketop-image</span>
|
||
<span class="tag">返回:302 / JSON</span>
|
||
<span class="tag">组件:widget.js</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card pad">
|
||
<h2>快速链接</h2>
|
||
<div class="links">
|
||
<a href="/api/list" target="_blank" rel="noreferrer">/api/list</a>
|
||
<a href="/manifest.json" target="_blank" rel="noreferrer">manifest.json</a>
|
||
<a href="/mobile-image/1.webp" target="_blank" rel="noreferrer">mobile-image/1.webp</a>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="main">
|
||
<div class="card pad section" id="api">
|
||
<h2>API 调用</h2>
|
||
<div class="grid-2">
|
||
<div class="endpoint">
|
||
<strong>随机图片</strong>
|
||
<div class="small">默认 302 跳转到随机图片。</div>
|
||
<pre class="code">GET /api/random</pre>
|
||
</div>
|
||
<div class="endpoint">
|
||
<strong>返回 JSON</strong>
|
||
<div class="small">适合前端自己处理图片地址。</div>
|
||
<pre class="code">GET /api/random?format=json</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid-2">
|
||
<div class="endpoint">
|
||
<strong>强制竖屏</strong>
|
||
<pre class="code">GET /api/random?mode=mobile</pre>
|
||
</div>
|
||
<div class="endpoint">
|
||
<strong>强制横屏</strong>
|
||
<pre class="code">GET /api/random?mode=desktop</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="small">支持参数:`mode=auto|mobile|desktop`、`orientation=portrait|landscape`、`device=mobile|desktop`、`format=redirect|json`。</p>
|
||
</div>
|
||
|
||
<div class="card pad section" id="preview">
|
||
<h2>在线预览</h2>
|
||
<div class="row">
|
||
<div>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
|
||
<div class="preview-grid">
|
||
<div>
|
||
<label for="apiUrl">当前地址</label>
|
||
<input id="apiUrl" readonly value="">
|
||
<div class="preview-box" style="margin-top:8px">
|
||
<img id="previewImg" alt="随机背景预览" src="">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label>返回结果</label>
|
||
<pre class="code" id="jsonOutput">{}</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="card pad section" style="margin-top:10px">
|
||
<h2>嵌入组件</h2>
|
||
<div class="grid-2">
|
||
<div class="endpoint">
|
||
<strong>直接引用</strong>
|
||
<pre class="code"><script src="https://random-background-api.pages.dev/widget.js"></script>
|
||
<random-background-widget
|
||
src="https://random-background-api.pages.dev"
|
||
blur="12"
|
||
radius="16px"
|
||
height="260px">
|
||
</random-background-widget></pre>
|
||
</div>
|
||
<div class="endpoint">
|
||
<strong>说明</strong>
|
||
<div class="small">组件只读静态 `manifest.json` 和图片,不消耗 Functions 额度;`blur` 控制高斯模糊像素值。</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="footer">
|
||
构建命令:<code>python3 build.py</code>。这里保留 Pages Functions,所以可以直接提供 HTTP 随机 API。
|
||
</div>
|
||
</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>
|