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

369 lines
10 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TkAstral3D 分包离线测试</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 文件并加载。
</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="loadLocalBtn" 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. 至少包含入口包和依赖包后再点击加载。\n
3. 也可点击“直接加载本目录”,默认读取 1121212.astral + 7ff6dd32-25b3-4eea-abdc-6f316ecd668e.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 loadLocalBtn = document.getElementById("loadLocalBtn");
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",
];
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}$/;
const findBaseName = name => name.replace(/\.[^.]+$/, "");
return files.find(file => !uuidReg.test(findBaseName(file.name))) || 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 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 < 2) {
setStatus("分包模式请至少选择 2 个 .astral 文件", "error");
return;
}
renderFileList(files);
try {
const entry = guessEntryFile(files);
await loadByOfflineFiles(files, entry.name);
} catch (error) {
console.error(error);
setStatus(`加载失败:${error && error.message ? error.message : error}`, "error");
}
}
async function loadLocalPackages() {
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");
}
}
fileInput.addEventListener("change", () => {
renderFileList(fileInput.files);
setStatus("文件已选择,点击“加载离线包”开始测试", "");
});
loadBtn.addEventListener("click", () => {
loadOfflinePackage();
});
loadLocalBtn.addEventListener("click", () => {
loadLocalPackages();
});
resetBtn.addEventListener("click", () => {
resetViewer();
});
ensureViewer();
setStatus("Tk SDK 已加载(分包模式),等待选择离线包", "success");
})();
</script>
</body>
</html>