Files
asd-backend/static/js/afterbody.js
milky0217 54484482c3 feat: 抽测风速改为用户可自定义次数(增删按钮)
- models.rs:新增 spot_check_count 字段(默认 5)
- db.rs:INSERT/SELECT 添加 spotcheckcount 列
- migrations/007:ALTER TABLE weather_data ADD spotcheckcount
- afterbody.js:使用 data.spotCheckCount 动态渲染抽测数据
2026-05-18 09:51:43 +08:00

400 lines
15 KiB
JavaScript
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.
const WeatherReport = (function () {
const BASE_WIND_COUNT = 10;
const AREA_TYPE_MAP = {
urban: "城市",
rural: "农村",
};
const SUITABILITY_MAP = {
a: "不利于污染物的扩散和稀释,适宜于进行无组织排放监测",
b: "较不利于污染物的扩散和稀释,较适宜于进行无组织排放监测",
c: "有利于污染物的扩散和稀释,较不适宜于进行无组织排放监测",
d: "很有利于污染物的扩散和稀释,不适宜于进行无组织排放监测",
wind0: "静风",
overall0: "取消监测或改期",
};
function formatDate(dateStr) {
const d = new Date(dateStr);
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
}
function getSuitabilityText(value, type) {
const normalized = String(value).toLowerCase();
if (type === "wind" && normalized === "0") return SUITABILITY_MAP.wind0;
if (type === "overall" && normalized === "0") return SUITABILITY_MAP.overall0;
return SUITABILITY_MAP[normalized] || value;
}
function formatTime(hours, min) {
return `${hours}:${String(min).padStart(2, "0")}`;
}
function joinWithComma(arr, start, end) {
return arr.slice(start, end).map((v) => v ?? "").join("");
}
function buildReportHTML(data) {
const staticPrefix = window.staticPrefix || '/static';
const timeStr = `${formatDate(data.date)} ${formatTime(data.hours, data.min)}`;
const version = data.version || "unknown";
let html = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>总体适宜度测量报表</title>
<link rel="stylesheet" href="${staticPrefix}/css/report.css">
</head>
<body>
<div class="report-container">
<h1>总体适宜度测量报表</h1>
<div class="two-column">
<div class="field full-width">
<div class="field-label">项目名称:</div>
<div class="field-value">${data.title || "-"}</div>
</div>
<div class="field full-width">
<div class="field-label">测试时间:</div>
<div class="field-value">${timeStr}</div>
</div>
<div class="field">
<div class="field-label">检测类型:</div>
<div class="field-value">${data.inspectionType || "-"}</div>
</div>
<div class="field">
<div class="field-label">委托单号:</div>
<div class="field-value">${data.assignmentNumber || "-"}</div>
</div>
<div class="field">
<div class="field-label">经度:</div>
<div class="field-value">E ${data.longitude || "-"}</div>
</div>
<div class="field">
<div class="field-label">纬度:</div>
<div class="field-value">N ${data.latitude || "-"}</div>
</div>
</div>
<div class="divider"></div>
<div class="two-column">
<div class="field">
<div class="field-label">日期序数:</div>
<div class="field-value">${data.daySinceJanFirst}</div>
</div>
<div class="field">
<div class="field-label">太阳倾角:</div>
<div class="field-value">${Number(data.solarDeclination).toFixed(2)}°</div>
</div>
<div class="field">
<div class="field-label">太阳高度角:</div>
<div class="field-value">${Number(data.sunAltitude).toFixed(2)}°</div>
</div>
<div class="field">
<div class="field-label">总云量:</div>
<div class="field-value">${data.overallCloudiness}</div>
</div>
<div class="field">
<div class="field-label">低云量:</div>
<div class="field-value">${data.lowCloudiness}</div>
</div>
<div class="field">
<div class="field-label">辐射等级:</div>
<div class="field-value">${data.solarRadiationLevel}</div>
</div>
<div class="field">
<div class="field-label">平均风速:</div>
<div class="field-value">${data.averageWindSpeed} m/s</div>
</div>
<div class="field">
<div class="field-label">风速适宜度:</div>
<div class="field-value">${data.windSpeedSuitability}</div>
</div>`;
if (data.hasMeasuredWindSpeed === true) {
html += `
<div class="field">
<div class="field-label">实测风速:</div>
<div class="field-value">${data.measuredWindSpeed} m/s</div>
</div>
<div class="field">
<div class="field-label">转换风速:</div>
<div class="field-value">${data.convertedWindSpeed} m/s</div>
</div>`;
} else {
html += `
<div class="field">
<div class="field-label">区域类型:</div>
<div class="field-value">${AREA_TYPE_MAP[data.areaType] || data.areaType || "-"}</div>
</div>
<div class="field">
<div class="field-label">测点高度:</div>
<div class="field-value">${data.pointHeight} m</div>
</div>
<div class="field">
<div class="field-label">点位风速:</div>
<div class="field-value">${data.pointWindSpeed} m/s</div>
</div>
<div class="field">
<div class="field-label">推算风速:</div>
<div class="field-value">${data.calculatedWindSpeed} m/s</div>
</div>`;
}
html += `
<div class="field">
<div class="field-label">平均风向:</div>
<div class="field-value">${data.averageWindDirection}°</div>
</div>
<div class="field">
<div class="field-label">风向标准差:</div>
<div class="field-value">${data.windDirectionStandardDeviation}°</div>
</div>
<div class="field">
<div class="field-label">风向适宜度:</div>
<div class="field-value">${data.windDirectionSuitability}</div>
</div>
<div class="field">
<div class="field-label">大气稳定度:</div>
<div class="field-value">${data.atmosphericStability}</div>
</div>
<div class="field">
<div class="field-label">稳定度适宜度:</div>
<div class="field-value">${data.suitabilityDegree}</div>
</div>
<div class="field full-width">
<div class="field-label">总体适宜度:</div>
<div class="field-value">${getSuitabilityText(data.overallSuitability, "overall")}</div>
</div>
</div>
<div class="divider"></div>
<div class="two-column">
<div class="field full-width">
<div class="field-label">十次风速值:</div>
<div class="field-value">
<div class="ten-values">${joinWithComma(data.windSpeed, 0, BASE_WIND_COUNT)}</div>
</div>
</div>
<div class="field full-width">
<div class="field-label">十次风向值:</div>
<div class="field-value">
<div class="ten-values">${joinWithComma(data.windDirection, 0, BASE_WIND_COUNT)}</div>
</div>
</div>
</div>`;
const spotCount = data.spotCheckCount || 5;
if (data.hasSpotCheckWindSpeed && spotCount > 0) {
html += `
<div class="two-column">
<div class="field full-width">
<div class="field-label">抽测风速值:</div>
<div class="field-value">
<div class="ten-values">${joinWithComma(data.windSpeed, BASE_WIND_COUNT, BASE_WIND_COUNT + spotCount)}</div>
</div>
</div>
<div class="field full-width">
<div class="field-label">抽测风向值:</div>
<div class="field-value">
<div class="ten-values">${joinWithComma(data.windDirection, BASE_WIND_COUNT, BASE_WIND_COUNT + spotCount)}</div>
</div>
</div>
</div>`;
}
html += `
<div class="divider"></div>
<div class="footer">----报表结束 version:${version}----</div>
</div>
</body>
</html>`;
return html;
}
function downloadPDF(htmlContent, filename) {
const options = {
margin: 10,
filename: filename,
image: { type: "jpeg", quality: 0.99 },
html2canvas: { scale: 2 },
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
};
html2pdf().set(options).from(htmlContent).save();
}
// 下载状态管理(供重试使用)
let downloadState = {
ready: false,
html: null,
filename: null,
};
// 下载提示浮层
function showDownloadPrompt(type, message) {
let overlay = document.getElementById("download-overlay");
if (!overlay) {
overlay = document.createElement("div");
overlay.id = "download-overlay";
overlay.innerHTML = `
<style>
#download-overlay {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 9999;
padding: 16px;
text-align: center;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
transition: opacity 0.3s;
pointer-events: none;
}
#download-overlay .msg {
display: inline-block;
background: rgba(0,0,0,0.75);
color: #fff;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
}
#download-overlay .retry-btn {
display: inline-block;
margin-left: 12px;
background: #1677ff;
color: #fff;
border: none;
padding: 8px 16px;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
pointer-events: auto;
}
</style>
<span class="msg" id="dl-msg"></span>
<span class="retry-btn" id="dl-retry" style="display:none" onclick="WeatherReport.retryDownload()">重新下载</span>
`;
document.body.appendChild(overlay);
}
const msgEl = document.getElementById("dl-msg");
const retryEl = document.getElementById("dl-retry");
if (msgEl) msgEl.textContent = message;
if (retryEl) {
if (type === "loading" || type === "success") {
retryEl.style.display = "none";
} else {
retryEl.style.display = "inline-block";
}
}
}
function hideDownloadPrompt() {
const overlay = document.getElementById("download-overlay");
if (overlay) {
overlay.style.opacity = "0";
setTimeout(() => { if (overlay) overlay.remove(); }, 300);
}
}
function retryDownload() {
if (!downloadState.ready || !downloadState.html || !downloadState.filename) {
showDownloadPrompt("error", "数据未准备好,请刷新页面重试");
return;
}
downloadPDF(downloadState.html, downloadState.filename);
showDownloadPrompt("success", "正在重新下载,请稍候...");
}
function init() {
if (typeof window.weatherData === "undefined") {
console.error("错误window.weatherData 未定义");
showDownloadPrompt("error", "数据加载失败,请刷新页面重试");
return;
}
const data = window.weatherData;
console.log("【调试】从后端获取的 weatherData:", data);
const versionEl = document.querySelector(".version");
if (versionEl) {
versionEl.textContent = `版本号: ${data.version || "default"}`;
}
const html = buildReportHTML(data);
const filename = `${data.id}-${data.version || "unknown"}.pdf`;
// 保存下载状态,供重试使用
downloadState.ready = true;
downloadState.html = html;
downloadState.filename = filename;
// 显示下载中提示
showDownloadPrompt("loading", "正在生成 PDF请稍候...");
// 延迟执行下载,让用户看到提示
setTimeout(() => {
downloadPDF(html, filename);
// 显示成功提示3秒后消失
showDownloadPrompt("success", "PDF 开始下载...");
setTimeout(() => hideDownloadPrompt(), 3000);
// 额外等3秒如果提示已消失下载成功则不再显示重试
// 如果提示还在(下载失败),则替换为重试按钮
setTimeout(() => {
const overlay = document.getElementById("download-overlay");
if (overlay) {
showDownloadPrompt("retry", "下载未开始,请点击重试");
}
}, 3300);
}, 300);
}
document.addEventListener("DOMContentLoaded", function () {
if (typeof renderMathInElement === "function") {
renderMathInElement(document.body, {
delimiters: [
{ left: "$$", right: "$$", display: true },
{ left: "$", right: "$", display: false },
],
throwOnError: false,
});
}
init();
});
function toggleFormula(element) {
const content = element.nextElementSibling;
const isHidden = content.style.display === "none";
content.style.display = isHidden ? "block" : "none";
element.textContent = element.textContent.includes("▶")
? element.textContent.replace("▶", "▼")
: element.textContent.replace("▼", "▶");
}
function showNotification(message, type = "success") {
const notification = document.getElementById("notification");
if (!notification) return;
notification.textContent = message;
notification.className = `notification ${type}`;
notification.style.display = "block";
setTimeout(() => {
notification.style.display = "none";
}, 3000);
}
return {
buildReportHTML,
downloadPDF,
init,
toggleFormula,
showNotification,
retryDownload,
};
})();