Files
PCFAnalysis/templates/index.html
T

171 lines
6.4 KiB
HTML
Raw Normal View History

2026-06-30 14:29:02 +08:00
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PCF 焊接数据导入生成工具</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<main class="shell">
<section class="panel">
<header>
<h1>PCF 焊接数据导入生成工具</h1>
<p>上传多个 .pcf 文件,生成基于焊接数据导入模板的 .xls 文件。</p>
</header>
<form id="uploadForm">
<label class="dropzone">
<input id="files" name="files" type="file" accept=".pcf" multiple required>
<span>选择 PCF 文件</span>
<small>支持一次选择多个 .pcf 文件</small>
</label>
<section id="fileList" class="file-list hidden" aria-live="polite"></section>
<button id="submitBtn" type="submit">开始生成</button>
</form>
<section id="result" class="result hidden"></section>
</section>
</main>
<script>
const form = document.getElementById("uploadForm");
const result = document.getElementById("result");
const submitBtn = document.getElementById("submitBtn");
const fileInput = document.getElementById("files");
const fileList = document.getElementById("fileList");
fileInput.addEventListener("change", () => {
const files = Array.from(fileInput.files || []);
if (!files.length) {
fileList.classList.add("hidden");
fileList.innerHTML = "";
return;
}
fileList.classList.remove("hidden");
fileList.innerHTML = `
<h2>已选择文件(${files.length})</h2>
<ul>
${files.map(file => `<li><span>${escapeHtml(file.name)}</span><small>${formatBytes(file.size)}</small></li>`).join("")}
</ul>
`;
});
form.addEventListener("submit", async (event) => {
event.preventDefault();
const files = document.getElementById("files").files;
if (!files.length) return;
const data = new FormData();
for (const file of files) data.append("files", file);
submitBtn.disabled = true;
submitBtn.textContent = "生成中...";
result.classList.remove("hidden");
result.innerHTML = "<p>正在解析 PCF 并生成 Excel...</p>";
try {
const response = await fetch("/api/jobs", { method: "POST", body: data });
const payload = await response.json();
if (!response.ok) throw new Error(payload.detail || "生成失败");
result.innerHTML = `
<h2>生成完成</h2>
<dl>
<dt>任务编号</dt><dd>${payload.job_id}</dd>
<dt>文件数</dt><dd>${payload.input_files}</dd>
<dt>焊口数</dt><dd>${payload.weld_count}</dd>
<dt>警告数</dt><dd>${payload.warning_count}</dd>
<dt>错误数</dt><dd>${payload.error_count}</dd>
</dl>
<div class="downloads">
<a href="${payload.downloads.xls}">下载导入文件 .xls</a>
<a href="${payload.downloads.issues}">下载问题清单 .csv</a>
<a href="${payload.downloads.report}">下载解析报告 .json</a>
</div>
${(payload.messages || []).length ? `<ul>${payload.messages.map(m => `<li>${m}</li>`).join("")}</ul>` : ""}
${renderTabs(payload.tables || {})}
`;
} catch (error) {
result.innerHTML = `<p class="error">${error.message}</p>`;
} finally {
submitBtn.disabled = false;
submitBtn.textContent = "开始生成";
}
});
function renderTabs(tables) {
const importColumns = tables.import_columns || (tables.import_headers || []).map(h => ({key: h, label: h}));
const importRows = tables.import_rows || [];
const issueRows = tables.issue_rows || [];
return `
<section class="tabs" data-active="import">
<div class="tab-buttons">
<button type="button" class="tab-button active" data-tab="import">导入数据(${importRows.length})</button>
<button type="button" class="tab-button" data-tab="issues">问题清单(${issueRows.length})</button>
</div>
<div class="tab-panel" data-panel="import">
${renderColumnTable(importColumns, importRows, "暂无导入数据")}
</div>
<div class="tab-panel hidden" data-panel="issues">
${renderTable(["类型", "文件", "焊口代号", "字段", "内容", "处理状态"], issueRows, "暂无问题")}
</div>
</section>
`;
}
function renderColumnTable(columns, rows, emptyText) {
if (!rows.length) return `<p>${emptyText}</p>`;
return `
<div class="table-wrap">
<table>
<thead><tr>${columns.map(col => `<th>${escapeHtml(col.label || col.key)}</th>`).join("")}</tr></thead>
<tbody>
${rows.map(row => `<tr>${columns.map(col => `<td>${escapeHtml(row[col.key] ?? "")}</td>`).join("")}</tr>`).join("")}
</tbody>
</table>
</div>
`;
}
function renderTable(headers, rows, emptyText) {
if (!rows.length) return `<p>${emptyText}</p>`;
return `
<div class="table-wrap">
<table>
<thead><tr>${headers.map(h => `<th>${escapeHtml(h)}</th>`).join("")}</tr></thead>
<tbody>
${rows.map(row => `<tr>${headers.map(h => `<td>${escapeHtml(row[h] ?? "")}</td>`).join("")}</tr>`).join("")}
</tbody>
</table>
</div>
`;
}
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function formatBytes(size) {
if (size < 1024) return `${size} B`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
return `${(size / 1024 / 1024).toFixed(1)} MB`;
}
result.addEventListener("click", (event) => {
const button = event.target.closest(".tab-button");
if (!button) return;
const tabs = button.closest(".tabs");
const name = button.dataset.tab;
tabs.querySelectorAll(".tab-button").forEach(btn => btn.classList.toggle("active", btn === button));
tabs.querySelectorAll(".tab-panel").forEach(panel => panel.classList.toggle("hidden", panel.dataset.panel !== name));
});
</script>
</body>
</html>