This commit is contained in:
2026-03-11 14:13:57 +08:00
parent c5970da195
commit 16a2e4ff09

View File

@@ -127,6 +127,78 @@
color: var(--primary-dark);
transform: translateY(-1px);
}
/* 保存 PDF 按钮:固定悬浮,不随页面滚动,且不会进入截图中 */
.pdf-actions-fixed {
position: fixed;
top: 20px;
right: 24px;
z-index: 1000;
}
.pdf-fixed-btn {
display: inline-flex;
align-items: center;
padding: 10px 18px;
border-radius: 999px;
border: 1px solid var(--primary);
background: var(--bg-card);
color: var(--primary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
box-shadow: 0 2px 8px rgba(37, 99, 235, 0.2);
transition:
background 0.15s ease,
color 0.15s ease,
box-shadow 0.15s ease;
}
.pdf-fixed-btn:hover {
background: var(--primary);
color: #fff;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.35);
}
.pdf-fixed-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
/* 生成 PDF 时的全屏加载层 */
.pdf-loading-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
}
.pdf-loading-overlay[aria-hidden="true"] {
display: none;
}
.pdf-loading-content {
background: var(--bg-card);
padding: 28px 36px;
border-radius: 16px;
text-align: center;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
}
.pdf-loading-spinner {
width: 44px;
height: 44px;
border: 3px solid var(--border);
border-top-color: var(--primary);
border-radius: 50%;
margin: 0 auto 16px;
animation: pdf-spin 0.75s linear infinite;
}
.pdf-loading-content p {
margin: 0;
font-size: 15px;
color: var(--text);
}
@keyframes pdf-spin {
to {
transform: rotate(360deg);
}
}
.header-score .score {
font-size: 32px;
font-weight: 700;
@@ -382,6 +454,10 @@
.print-btn {
display: none !important;
}
.pdf-actions-fixed,
.pdf-loading-overlay {
display: none !important;
}
/* 抬头简化为白底、去圆角和阴影 */
.header {
background: #ffffff !important;
@@ -452,13 +528,21 @@
综合评分
<span id="riskLevelPill" class="pill low">风险:-</span>
</div>
<div class="header-actions">
<button id="btnSavePdf" class="print-btn">保存为 PDF</button>
</div>
</div>
</header>
<main id="reportSections" class="report-sections"></main>
</div>
<!-- 固定悬浮的保存 PDF 按钮(在 .page 外,不会被截进 PDF -->
<div class="pdf-actions-fixed">
<button id="btnSavePdf" class="pdf-fixed-btn" type="button">保存为 PDF</button>
</div>
<!-- 生成 PDF 时的加载浮层 -->
<div id="pdfLoadingOverlay" class="pdf-loading-overlay" aria-hidden="true">
<div class="pdf-loading-content">
<div class="pdf-loading-spinner" aria-hidden="true"></div>
<p>正在生成 PDF请稍候...</p>
</div>
</div>
<!-- 前端截图并生成 PDF 所需依赖 -->
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
@@ -2101,83 +2185,121 @@
}
loadReport();
// 绑定「保存为 PDF」按钮,使用 html2canvas + jsPDF 截图生成 PDF,避免依赖浏览器打印对话框
// 绑定「保存为 PDF」按钮html2canvas 截取 .page → 转 JPEG → jsPDF 分页生成 PDF(按钮在 .page 外固定悬浮,不会进 PDF
var saveBtn = document.getElementById("btnSavePdf");
var loadingOverlay = document.getElementById("pdfLoadingOverlay");
if (saveBtn && window.html2canvas && window.jspdf && window.jspdf.jsPDF) {
saveBtn.addEventListener("click", function () {
try {
var pageEl = document.querySelector(".page");
if (!pageEl) {
console.error("未找到 .page 容器");
return;
}
html2canvas(pageEl, {
scale: 1.5,
useCORS: true,
scrollX: 0,
scrollY: -window.scrollY,
})
.then(function (canvas) {
// 使用 JPEG 可以避免部分环境下 PNG 解析问题Incomplete or corrupt PNG file
var imgData = canvas.toDataURL(
"image/jpeg",
0.95,
);
var pdf = new jspdf.jsPDF("p", "mm", "a4");
var pageWidth = pdf.internal.pageSize.getWidth();
var pageHeight = pdf.internal.pageSize.getHeight();
var imgWidth = pageWidth;
var imgHeight =
(canvas.height * imgWidth) / canvas.width;
var position = 0;
var heightLeft = imgHeight;
pdf.addImage(
imgData,
"JPEG",
0,
position,
imgWidth,
imgHeight,
);
heightLeft -= pageHeight;
while (heightLeft > 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(
imgData,
"JPEG",
0,
position,
imgWidth,
imgHeight,
);
heightLeft -= pageHeight;
}
var fileName = "企业全景报告.pdf";
if (
reportData &&
reportData.entName &&
typeof reportData.entName === "string"
) {
fileName =
reportData.entName +
"_企业全景报告.pdf";
}
pdf.save(fileName);
})
.catch(function (e) {
console.error("生成 PDF 失败", e);
});
} catch (e) {
console.error("触发生成 PDF 失败", e);
var pageEl = document.querySelector(".page");
if (!pageEl) {
console.error("未找到 .page 容器");
return;
}
var btnText = saveBtn.textContent;
saveBtn.disabled = true;
saveBtn.textContent = "生成中...";
if (loadingOverlay) {
loadingOverlay.setAttribute("aria-hidden", "false");
}
function restoreBtn() {
saveBtn.disabled = false;
saveBtn.textContent = btnText;
if (loadingOverlay) {
loadingOverlay.setAttribute("aria-hidden", "true");
}
}
// 等待浏览器完成布局与绘制,再截取,避免截到白屏
function doCapture() {
requestAnimationFrame(function () {
requestAnimationFrame(function () {
try {
html2canvas(pageEl, {
scale: 1.5,
useCORS: true,
allowTaint: false,
backgroundColor: "#ffffff",
scrollX: 0,
scrollY: 0,
windowWidth: pageEl.scrollWidth,
windowHeight: pageEl.scrollHeight,
onclone: function (clonedDoc, node) {
var body = clonedDoc.body;
if (body) {
body.style.backgroundColor = "#ffffff";
body.style.overflow = "visible";
}
var clonePage = clonedDoc.querySelector(".page");
if (clonePage) {
clonePage.style.overflow = "visible";
clonePage.style.backgroundColor = "#ffffff";
}
},
})
.then(function (canvas) {
if (!canvas.width || !canvas.height) {
console.error("截图为空,宽或高为 0");
restoreBtn();
return;
}
var imgData = canvas.toDataURL(
"image/jpeg",
0.95,
);
var pdf = new jspdf.jsPDF("p", "mm", "a4");
var pageWidth = pdf.internal.pageSize.getWidth();
var pageHeight = pdf.internal.pageSize.getHeight();
var imgWidth = pageWidth;
var imgHeight =
(canvas.height * imgWidth) / canvas.width;
var position = 0;
var heightLeft = imgHeight;
pdf.addImage(
imgData,
"JPEG",
0,
position,
imgWidth,
imgHeight,
);
heightLeft -= pageHeight;
while (heightLeft > 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(
imgData,
"JPEG",
0,
position,
imgWidth,
imgHeight,
);
heightLeft -= pageHeight;
}
var fileName = "企业全景报告.pdf";
if (
reportData &&
reportData.entName &&
typeof reportData.entName === "string"
) {
fileName =
reportData.entName +
"_企业全景报告.pdf";
}
pdf.save(fileName);
restoreBtn();
})
.catch(function (e) {
console.error("生成 PDF 失败", e);
restoreBtn();
});
} catch (e) {
console.error("触发生成 PDF 失败", e);
restoreBtn();
}
});
});
}
doCapture();
});
}
})();