Files
PCFAnalysis/templates/index.html
T

171 lines
6.4 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">
<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>