369 lines
10 KiB
HTML
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> |