TkAstral3D/Test/index.html
2026-04-09 00:20:33 +08:00

425 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.0" />
<title>TkAstral3D Offline Package Test</title>
<style>
:root {
--bg: #0b1020;
--panel: rgba(15, 23, 42, 0.88);
--line: rgba(148, 163, 184, 0.24);
--text: #e2e8f0;
--muted: #94a3b8;
--accent: #22c55e;
--accent-2: #38bdf8;
--danger: #f87171;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background:
radial-gradient(circle at top left, rgba(56, 189, 248, 0.12), transparent 32%),
radial-gradient(circle at top right, rgba(34, 197, 94, 0.14), transparent 28%),
linear-gradient(180deg, #0b1020, #050814 72%);
color: var(--text);
font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
}
.app {
display: grid;
grid-template-columns: 360px 1fr;
width: 100%;
height: 100%;
}
.sidebar {
padding: 20px;
border-right: 1px solid var(--line);
background: var(--panel);
backdrop-filter: blur(10px);
overflow: auto;
}
.viewer-wrap {
position: relative;
min-width: 0;
min-height: 0;
}
#viewer {
width: 100%;
height: 100%;
}
h1 {
margin: 0 0 10px;
font-size: 24px;
line-height: 1.2;
}
.desc,
.hint,
.meta,
.log {
color: var(--muted);
line-height: 1.6;
}
.desc {
margin-bottom: 16px;
font-size: 14px;
}
.card {
margin-top: 16px;
padding: 14px;
border: 1px solid var(--line);
border-radius: 14px;
background: rgba(2, 6, 23, 0.36);
}
.card h2 {
margin: 0 0 10px;
font-size: 15px;
color: #f8fafc;
}
.row {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
margin-top: 12px;
}
button,
input[type="file"] {
font: inherit;
}
button {
padding: 10px 14px;
border: 1px solid var(--line);
border-radius: 10px;
background: linear-gradient(180deg, rgba(56, 189, 248, 0.16), rgba(34, 197, 94, 0.16));
color: var(--text);
cursor: pointer;
}
button.secondary {
background: rgba(15, 23, 42, 0.9);
}
input[type="file"] {
width: 100%;
color: var(--muted);
}
.status {
margin-top: 12px;
padding: 10px 12px;
border-radius: 10px;
border: 1px solid var(--line);
background: rgba(15, 23, 42, 0.72);
font-size: 13px;
white-space: pre-wrap;
}
.status.success {
border-color: rgba(34, 197, 94, 0.35);
color: #bbf7d0;
}
.status.error {
border-color: rgba(248, 113, 113, 0.35);
color: #fecaca;
}
.file-list {
margin: 10px 0 0;
padding-left: 18px;
font-size: 13px;
color: var(--muted);
}
.tag {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
border-radius: 999px;
background: rgba(56, 189, 248, 0.1);
border: 1px solid rgba(56, 189, 248, 0.18);
font-size: 12px;
color: #bae6fd;
}
@media (max-width: 900px) {
.app {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
}
.sidebar {
border-right: 0;
border-bottom: 1px solid var(--line);
}
}
</style>
</head>
<body>
<div class="app">
<aside class="sidebar">
<h1>Tk 离线包测试</h1>
<div class="desc">
这个页面直接使用 Tk 构建产物来验证离线包加载。
支持单包与多包 .astral 文件,直接通过 SDK 的离线参数加载,不依赖编辑器页面。
</div>
<div class="row">
<span class="tag">SDK: TkAstral3D/Test/dist/astral3d.umd.js</span>
</div>
<div class="card">
<h2>选择离线包</h2>
<input id="fileInput" type="file" multiple accept=".astral" />
<div class="row">
<button id="loadBtn">加载离线包</button>
<button id="loadLocalSplitBtn" class="secondary">直接加载本目录(分包)</button>
<button id="loadLocalSingleBtn" class="secondary">直接加载本目录(单包)</button>
<button id="resetBtn" class="secondary">重置场景</button>
</div>
<div id="status" class="status">等待选择 .astral 文件,或使用“直接加载本目录”</div>
<ul id="fileList" class="file-list"></ul>
</div>
<div class="card">
<h2>说明</h2>
<div class="hint">
1. 多包模式:直接选中全部 .astral 文件后加载。\n
2. 单包模式:只需要选择导出的单个 .astral 文件。\n
3. 本目录快捷加载:分包=1121212.astral + 7ff6dd32-25b3-4eea-abdc-6f316ecd668e.astral单包=1121212.astral。\n
4. 建议通过本地静态服务器打开此页面,而不是直接 file:// 打开。
</div>
</div>
</aside>
<main class="viewer-wrap">
<div id="viewer"></div>
</main>
</div>
<script src="./dist/astral3d.umd.js"></script>
<script>
(function () {
const SDK = globalThis.Astral3D;
if (!SDK) {
document.getElementById("status").textContent = "Tk SDK 加载失败,请检查 astral3d.umd.js 路径";
document.getElementById("status").className = "status error";
return;
}
const viewerEl = document.getElementById("viewer");
const fileInput = document.getElementById("fileInput");
const loadBtn = document.getElementById("loadBtn");
const loadLocalSplitBtn = document.getElementById("loadLocalSplitBtn");
const loadLocalSingleBtn = document.getElementById("loadLocalSingleBtn");
const resetBtn = document.getElementById("resetBtn");
const statusEl = document.getElementById("status");
const fileListEl = document.getElementById("fileList");
const LOCAL_SPLIT_FILES = [
"1121212.astral",
"7ff6dd32-25b3-4eea-abdc-6f316ecd668e.astral",
];
const LOCAL_SINGLE_FILE = "1121212-offline.astral";
let viewer = null;
function setStatus(text, type) {
statusEl.textContent = text;
statusEl.className = "status" + (type ? ` ${type}` : "");
}
function guessEntryFile(files) {
const uuidReg = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
return files.find(f => !uuidReg.test(f.name.replace(/\.[^.]+$/, ""))) || files[0];
}
function ensureViewer() {
if (viewer) return viewer;
viewer = new SDK.Viewer({
container: viewerEl,
edit: { enabled: false },
grid: { enabled: true },
request: { baseUrl: "" },
});
globalThis.viewer = viewer;
return viewer;
}
function resetViewer() {
if (viewer && typeof viewer.dispose === "function") {
viewer.dispose();
}
viewer = null;
viewerEl.innerHTML = "";
setStatus("场景已重置", "success");
}
function renderFileList(files) {
fileListEl.innerHTML = "";
Array.from(files || []).forEach(file => {
const li = document.createElement("li");
li.textContent = `${file.name} (${Math.round(file.size / 1024)} KB)`;
fileListEl.appendChild(li);
});
}
async function loadByOfflineFiles(files, entryName) {
setStatus(`开始加载离线包:${entryName}`, "");
const currentViewer = ensureViewer();
const packer = currentViewer.package || new SDK.Package(currentViewer);
currentViewer.package = packer;
await packer.unpack({
url: entryName,
offlineFiles: files,
offlineEntry: entryName,
onProgress: progress => {
setStatus(`离线包加载中... ${Number(progress || 0).toFixed(2)}%`, "");
},
onSceneLoad: () => {
setStatus("场景首包已解析,正在继续加载资源...", "");
},
onComplete: () => {
setStatus(`离线包加载完成:${entryName}`, "success");
},
});
}
async function loadByOfflineBundle(bundle, entryName) {
setStatus(`开始加载离线包:${entryName}`, "");
const currentViewer = ensureViewer();
const packer = currentViewer.package || new SDK.Package(currentViewer);
currentViewer.package = packer;
console.log({
url: "/",
offlineBundle: bundle,
offlineEntry: entryName,
onProgress: progress => {
setStatus(`离线包加载中... ${Number(progress || 0).toFixed(2)}%`, "");
},
onSceneLoad: () => {
setStatus("场景首包已解析,正在继续加载资源...", "");
},
onComplete: () => {
setStatus(`离线包加载完成:${entryName}`, "success");
},
})
await packer.unpack({
url: "/",
offlineBundle: bundle,
offlineEntry: entryName,
onProgress: progress => {
setStatus(`离线包加载中... ${Number(progress || 0).toFixed(2)}%`, "");
},
onSceneLoad: () => {
setStatus("场景首包已解析,正在继续加载资源...", "");
},
onComplete: () => {
setStatus(`离线包加载完成:${entryName}`, "success");
},
});
}
async function fetchLocalAstralFile(name) {
const response = await fetch(`./${encodeURIComponent(name)}`);
if (!response.ok) {
throw new Error(`读取本地文件失败:${name}`);
}
const blob = await response.blob();
return new File([blob], name, { type: "application/octet-stream" });
}
async function loadOfflinePackage() {
const files = Array.from(fileInput.files || []).filter(f => f.name.toLowerCase().endsWith(".astral"));
if (files.length === 0) {
setStatus("请选择 .astral 文件", "error");
return;
}
renderFileList(files);
try {
if (files.length === 1) {
await loadByOfflineBundle(files[0], files[0].name);
} else {
const entry = guessEntryFile(files);
await loadByOfflineFiles(files, entry.name);
}
} catch (error) {
console.error(error);
setStatus(`加载失败:${error && error.message ? error.message : error}`, "error");
}
}
async function loadLocalSplitPackage() {
try {
renderFileList(LOCAL_SPLIT_FILES.map(name => ({ name, size: 0 })));
const files = [];
for (const name of LOCAL_SPLIT_FILES) {
files.push(await fetchLocalAstralFile(name));
}
await loadByOfflineFiles(files, LOCAL_SPLIT_FILES[0]);
} catch (error) {
console.error(error);
setStatus(`本目录分包加载失败:${error && error.message ? error.message : error}`, "error");
}
}
async function loadLocalSinglePackage() {
try {
renderFileList([{ name: LOCAL_SINGLE_FILE, size: 0 }]);
const file = await fetchLocalAstralFile(LOCAL_SINGLE_FILE);
await loadByOfflineBundle(file, LOCAL_SINGLE_FILE);
} catch (error) {
console.error(error);
setStatus(`本目录单包加载失败:${error && error.message ? error.message : error}`, "error");
}
}
fileInput.addEventListener("change", () => {
renderFileList(fileInput.files);
setStatus("文件已选择,点击“加载离线包”开始测试", "");
});
loadBtn.addEventListener("click", () => {
loadOfflinePackage();
});
loadLocalSplitBtn.addEventListener("click", () => {
loadLocalSplitPackage();
});
loadLocalSingleBtn.addEventListener("click", () => {
loadLocalSinglePackage();
});
resetBtn.addEventListener("click", () => {
resetViewer();
});
ensureViewer();
setStatus("Tk SDK 已加载,等待选择离线包", "success");
})();
</script>
</body>
</html>