hyapp-server/tools/baishun-game-test.html
2026-05-23 12:07:02 +08:00

371 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>百顺游戏测试页</title>
<style>
:root {
color-scheme: light;
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f4f7fb;
color: #172033;
}
* { box-sizing: border-box; }
body { margin: 0; padding: 24px; }
main { max-width: 1120px; margin: 0 auto; display: grid; gap: 16px; }
h1 { margin: 0; font-size: 24px; }
h2 { margin: 0 0 12px; font-size: 16px; }
section {
border: 1px solid #d9e2ef;
border-radius: 8px;
background: #fff;
padding: 16px;
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
}
label { display: grid; gap: 6px; color: #53627a; font-size: 13px; font-weight: 700; }
input, select, textarea {
width: 100%;
border: 1px solid #cad6e6;
border-radius: 8px;
padding: 10px 12px;
font: inherit;
color: #172033;
background: #fbfdff;
}
textarea { min-height: 88px; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
button {
border: 1px solid #2563eb;
border-radius: 8px;
padding: 10px 14px;
font: inherit;
font-weight: 800;
color: #fff;
background: #2563eb;
cursor: pointer;
}
button.secondary { color: #2563eb; background: #fff; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.muted { color: #697891; font-size: 13px; }
.status { min-height: 22px; color: #334155; font-size: 13px; }
.status.error { color: #dc2626; }
.status.ok { color: #16a34a; }
.games { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
.game {
display: grid;
gap: 8px;
align-content: start;
border: 1px solid #d9e2ef;
border-radius: 8px;
padding: 10px;
background: #fbfdff;
}
.game.selected { border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12); }
.game img { width: 64px; height: 64px; border-radius: 8px; object-fit: cover; background: #eef3fa; }
.game-title { font-weight: 850; overflow-wrap: anywhere; }
.pill { display: inline-flex; width: fit-content; border-radius: 999px; padding: 2px 8px; background: #eef3ff; color: #2563eb; font-size: 12px; font-weight: 800; }
iframe { width: 100%; min-height: 720px; border: 1px solid #d9e2ef; border-radius: 8px; background: #fff; }
@media (max-width: 760px) {
body { padding: 12px; }
.grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<main>
<header>
<h1>百顺游戏测试页</h1>
<div class="muted">测试服:<code>https://api-test.global-interaction.com</code>;本地代理打开时 API Base URL 留空即可</div>
</header>
<section>
<h2>1. API 与账号</h2>
<div class="grid">
<label>
API Base URL
<input id="apiBase" value="https://api-test.global-interaction.com" placeholder="本地代理模式留空,直接用 /api/v1/..." />
</label>
<label>
X-App-Code
<input id="appCode" value="lalu" />
</label>
<label>
用户名 / UID
<input id="account" placeholder="用于账号密码登录" />
</label>
<label>
密码
<input id="password" type="password" placeholder="用于登录或快捷创建" />
</label>
</div>
<label style="margin-top:12px">
Access Token
<textarea id="token" placeholder="可以直接粘贴 App 登录 access_token也可以点击下方登录/快捷创建自动填充"></textarea>
</label>
<div class="actions" style="margin-top:12px">
<button id="loginBtn" type="button">用户名密码登录</button>
<button id="quickCreateBtn" class="secondary" type="button">快捷创建测试账号</button>
<span id="authStatus" class="status"></span>
</div>
</section>
<section>
<h2>2. 获取游戏列表</h2>
<div class="grid">
<label>
scene
<input id="scene" value="voice_room" />
</label>
<label>
room_id
<input id="roomId" value="test_room_1" />
</label>
</div>
<div class="actions" style="margin-top:12px">
<button id="loadGamesBtn" type="button">获取 /api/v1/games</button>
<span id="gamesStatus" class="status"></span>
</div>
<div id="games" class="games" style="margin-top:12px"></div>
</section>
<section>
<h2>3. 启动游戏</h2>
<div class="actions">
<button id="launchBtn" type="button" disabled>启动选中游戏</button>
<button id="openBtn" class="secondary" type="button" disabled>新窗口打开</button>
<span id="launchStatus" class="status"></span>
</div>
<label style="margin-top:12px">
launch_url
<textarea id="launchUrl" readonly></textarea>
</label>
<iframe id="gameFrame" title="game preview"></iframe>
</section>
</main>
<script>
const state = {
games: [],
selectedGameId: "",
launchUrl: "",
};
const $ = (id) => document.getElementById(id);
const defaultApiBase = "https://api-test.global-interaction.com";
const isLocalProxy = ["localhost", "127.0.0.1", "::1"].includes(window.location.hostname);
$("token").value = localStorage.getItem("baishun_test_access_token") || "";
$("account").value = localStorage.getItem("baishun_test_account") || "";
const savedApiBase = localStorage.getItem("baishun_test_api_base") || "";
$("apiBase").value = isLocalProxy && (!savedApiBase || savedApiBase === defaultApiBase) ? "" : (savedApiBase || defaultApiBase);
$("appCode").value = localStorage.getItem("baishun_test_app_code") || $("appCode").value;
function apiBase() {
return $("apiBase").value.trim().replace(/\/+$/, "");
}
function appCode() {
return $("appCode").value.trim() || "lalu";
}
function token() {
return $("token").value.trim();
}
function setStatus(id, message, type = "") {
const el = $(id);
el.textContent = message || "";
el.className = ["status", type].filter(Boolean).join(" ");
}
async function request(path, options = {}) {
const headers = {
"Content-Type": "application/json",
"X-App-Code": appCode(),
"X-App-Platform": "web",
"X-App-Language": "en",
...(options.headers || {}),
};
if (options.auth !== false && token()) {
headers.Authorization = `Bearer ${token()}`;
}
const response = await fetch(`${apiBase()}${path}`, {
method: options.method || "GET",
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
});
const text = await response.text();
let payload = {};
try {
payload = text ? JSON.parse(text) : {};
} catch {
payload = { message: text };
}
if (!response.ok || payload.code !== 0) {
throw new Error(payload.message || response.statusText || `HTTP ${response.status}`);
}
return payload.data;
}
function saveToken(data) {
const accessToken = data?.access_token || data?.token?.access_token || "";
if (!accessToken) {
throw new Error("登录成功但响应里没有 access_token");
}
$("token").value = accessToken;
localStorage.setItem("baishun_test_access_token", accessToken);
if (data.account || data.uid) {
$("account").value = data.account || data.uid;
localStorage.setItem("baishun_test_account", $("account").value);
}
localStorage.setItem("baishun_test_api_base", apiBase());
localStorage.setItem("baishun_test_app_code", appCode());
}
async function login() {
setStatus("authStatus", "登录中...");
const account = $("account").value.trim();
const password = $("password").value;
if (!account || !password) {
setStatus("authStatus", "请填写用户名/UID 和密码", "error");
return;
}
try {
const data = await request("/api/v1/auth/account/login", {
method: "POST",
auth: false,
body: {
account,
password,
device_id: "baishun-html-test",
platform: "web",
language: "en",
timezone: "Asia/Shanghai",
},
});
saveToken(data);
setStatus("authStatus", "登录成功token 已填入", "ok");
} catch (err) {
setStatus("authStatus", err.message, "error");
}
}
async function quickCreate() {
setStatus("authStatus", "创建中...");
const password = $("password").value || "Test@123456";
try {
const data = await request("/api/v1/auth/account/quick-create", {
method: "POST",
auth: false,
body: {
password,
username: `baishun_html_${Date.now()}`,
device_id: "baishun-html-test",
platform: "web",
language: "en",
timezone: "Asia/Shanghai",
},
});
$("password").value = password;
saveToken(data);
setStatus("authStatus", `创建成功,账号/UID${data.account || data.uid || ""}`, "ok");
} catch (err) {
setStatus("authStatus", err.message, "error");
}
}
async function loadGames() {
setStatus("gamesStatus", "加载中...");
state.selectedGameId = "";
state.launchUrl = "";
$("launchBtn").disabled = true;
$("openBtn").disabled = true;
$("launchUrl").value = "";
$("gameFrame").src = "about:blank";
try {
const query = new URLSearchParams();
if ($("scene").value.trim()) query.set("scene", $("scene").value.trim());
if ($("roomId").value.trim()) query.set("room_id", $("roomId").value.trim());
const data = await request(`/api/v1/games?${query.toString()}`);
state.games = Array.isArray(data.games) ? data.games : [];
renderGames();
setStatus("gamesStatus", `已获取 ${state.games.length} 个游戏`, "ok");
} catch (err) {
$("games").innerHTML = "";
setStatus("gamesStatus", err.message, "error");
}
}
function renderGames() {
$("games").innerHTML = "";
for (const game of state.games) {
const card = document.createElement("button");
card.type = "button";
card.className = `game ${state.selectedGameId === game.game_id ? "selected" : ""}`;
card.innerHTML = `
${game.icon_url ? `<img src="${escapeHtml(game.icon_url)}" alt="">` : ""}
<span class="game-title">${escapeHtml(game.name || game.game_id)}</span>
<span class="muted">${escapeHtml(game.game_id || "")}</span>
<span class="pill">${escapeHtml(game.platform_code || "")}</span>
<span class="muted">${escapeHtml(game.enabled ? "可启动" : game.maintenance ? "维护" : "不可用")}</span>
`;
card.onclick = () => {
state.selectedGameId = game.game_id;
$("launchBtn").disabled = false;
renderGames();
};
$("games").appendChild(card);
}
}
async function launchSelected() {
const gameId = state.selectedGameId;
if (!gameId) {
setStatus("launchStatus", "请先选择游戏", "error");
return;
}
setStatus("launchStatus", "启动中...");
try {
const data = await request(`/api/v1/games/${encodeURIComponent(gameId)}/launch`, {
method: "POST",
body: {
scene: $("scene").value.trim() || "voice_room",
room_id: $("roomId").value.trim() || "test_room_1",
},
});
state.launchUrl = data.launch_url || "";
$("launchUrl").value = state.launchUrl;
$("openBtn").disabled = !state.launchUrl;
if (state.launchUrl) {
$("gameFrame").src = state.launchUrl;
setStatus("launchStatus", "启动成功", "ok");
} else {
setStatus("launchStatus", "接口成功,但 launch_url 为空", "error");
}
} catch (err) {
setStatus("launchStatus", err.message, "error");
}
}
function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
$("loginBtn").onclick = login;
$("quickCreateBtn").onclick = quickCreate;
$("loadGamesBtn").onclick = loadGames;
$("launchBtn").onclick = launchSelected;
$("openBtn").onclick = () => {
if (state.launchUrl) window.open(state.launchUrl, "_blank", "noopener,noreferrer");
};
</script>
</body>
</html>