266 lines
7.0 KiB
HTML
266 lines
7.0 KiB
HTML
<!doctype html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>System Simulation App</title>
|
|
<style>
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
height: 100vh;
|
|
color: #18202a;
|
|
font-family:
|
|
system-ui,
|
|
-apple-system,
|
|
BlinkMacSystemFont,
|
|
"Segoe UI",
|
|
sans-serif;
|
|
background: #f6f8fb;
|
|
}
|
|
|
|
.app {
|
|
display: grid;
|
|
grid-template-rows: 48px minmax(0, 1fr);
|
|
height: 100vh;
|
|
}
|
|
|
|
.toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 0 14px;
|
|
border-bottom: 1px solid #d8dee8;
|
|
background: #ffffff;
|
|
}
|
|
|
|
.title {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
font-size: 15px;
|
|
font-weight: 650;
|
|
line-height: 1.2;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.status {
|
|
max-width: 220px;
|
|
overflow: hidden;
|
|
color: #5d6877;
|
|
font-size: 13px;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
button {
|
|
height: 32px;
|
|
padding: 0 12px;
|
|
border: 1px solid #1b6fb8;
|
|
border-radius: 6px;
|
|
color: #ffffff;
|
|
font: inherit;
|
|
font-size: 14px;
|
|
background: #1b6fb8;
|
|
cursor: pointer;
|
|
}
|
|
|
|
button:disabled {
|
|
border-color: #98a2af;
|
|
background: #98a2af;
|
|
cursor: wait;
|
|
}
|
|
|
|
iframe {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
border: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main class="app">
|
|
<header class="toolbar">
|
|
<div class="title">System Simulation App</div>
|
|
<div class="actions">
|
|
<span class="status" id="status">加载中</span>
|
|
<button id="export-button" type="button" disabled>生成 XML</button>
|
|
</div>
|
|
</header>
|
|
<iframe id="drawio-frame" title="draw.io"></iframe>
|
|
</main>
|
|
|
|
<script>
|
|
const DRAWIO_BASE_URL = __DRAWIO_BASE_URL__;
|
|
const frame = document.getElementById("drawio-frame");
|
|
const exportButton = document.getElementById("export-button");
|
|
const statusNode = document.getElementById("status");
|
|
const drawioUrl = new URL(DRAWIO_BASE_URL, window.location.href);
|
|
const componentLibraryUrl = new URL(
|
|
"/api/component-library.drawiolib",
|
|
window.location.href,
|
|
);
|
|
const componentLibraryKey = `U${componentLibraryUrl.href}`;
|
|
drawioUrl.searchParams.set("embed", "1");
|
|
drawioUrl.searchParams.set("proto", "json");
|
|
drawioUrl.searchParams.set("libraries", "1");
|
|
drawioUrl.searchParams.set("clibs", componentLibraryKey);
|
|
drawioUrl.searchParams.set(
|
|
"cors",
|
|
`^${escapeRegExp(window.location.origin)}/`,
|
|
);
|
|
drawioUrl.searchParams.set("spin", "1");
|
|
drawioUrl.searchParams.set("noSaveBtn", "1");
|
|
drawioUrl.searchParams.set("noExitBtn", "1");
|
|
drawioUrl.searchParams.set("saveAndExit", "0");
|
|
frame.src = drawioUrl.toString();
|
|
|
|
let pendingExport = false;
|
|
let exportTimeoutId = null;
|
|
|
|
const initialXml =
|
|
'<mxGraphModel dx="1200" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0"><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>';
|
|
|
|
function setStatus(text) {
|
|
statusNode.textContent = text;
|
|
}
|
|
|
|
function escapeRegExp(value) {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
}
|
|
|
|
function sendToDrawio(message) {
|
|
frame.contentWindow.postMessage(JSON.stringify(message), drawioUrl.origin);
|
|
}
|
|
|
|
function parseMessage(data) {
|
|
if (typeof data !== "string") {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(data);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function clearExportTimeout() {
|
|
if (exportTimeoutId !== null) {
|
|
window.clearTimeout(exportTimeoutId);
|
|
exportTimeoutId = null;
|
|
}
|
|
}
|
|
|
|
async function finishExport(diagramXml) {
|
|
clearExportTimeout();
|
|
pendingExport = false;
|
|
await exportSystemXml(diagramXml || initialXml);
|
|
}
|
|
|
|
function scheduleExportTimeout() {
|
|
clearExportTimeout();
|
|
exportTimeoutId = window.setTimeout(() => {
|
|
if (!pendingExport) {
|
|
return;
|
|
}
|
|
|
|
pendingExport = false;
|
|
exportButton.disabled = false;
|
|
setStatus("生成超时");
|
|
}, 5000);
|
|
}
|
|
|
|
window.addEventListener("message", async (event) => {
|
|
if (event.origin !== drawioUrl.origin) {
|
|
return;
|
|
}
|
|
|
|
const message = parseMessage(event.data);
|
|
if (!message) {
|
|
return;
|
|
}
|
|
|
|
if (message.event === "init") {
|
|
sendToDrawio({
|
|
action: "load",
|
|
xml: initialXml,
|
|
title: "system.drawio",
|
|
autosave: 1,
|
|
});
|
|
exportButton.disabled = false;
|
|
setStatus("已就绪");
|
|
return;
|
|
}
|
|
|
|
if (message.event === "autosave") {
|
|
if (!pendingExport) {
|
|
setStatus("已更新");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (message.event === "export" && pendingExport) {
|
|
await finishExport(message.xml);
|
|
}
|
|
});
|
|
|
|
exportButton.addEventListener("click", () => {
|
|
if (pendingExport) {
|
|
return;
|
|
}
|
|
|
|
pendingExport = true;
|
|
exportButton.disabled = true;
|
|
setStatus("生成中");
|
|
sendToDrawio({ action: "export", format: "xml" });
|
|
scheduleExportTimeout();
|
|
});
|
|
|
|
async function exportSystemXml(diagramXml) {
|
|
try {
|
|
const response = await fetch("/api/system-xml", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ xml: diagramXml }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(await response.text());
|
|
}
|
|
|
|
const systemXml = await response.text();
|
|
downloadText(systemXml, "system.xml", "application/xml");
|
|
setStatus("已生成");
|
|
} catch (error) {
|
|
console.error(error);
|
|
setStatus("生成失败");
|
|
} finally {
|
|
exportButton.disabled = false;
|
|
}
|
|
}
|
|
|
|
function downloadText(text, filename, mimeType) {
|
|
const blob = new Blob([text], { type: mimeType });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
link.download = filename;
|
|
link.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|