f
This commit is contained in:
@@ -2185,16 +2185,11 @@
|
||||
}
|
||||
loadReport();
|
||||
|
||||
// 绑定「保存为 PDF」按钮:html2canvas 截取 .page → 转 JPEG → jsPDF 分页生成 PDF(按钮在 .page 外固定悬浮,不会进 PDF)
|
||||
// 绑定「保存为 PDF」按钮:调用后端 /reports/qygl/:id/pdf 接口,由服务器端 headless Chrome 生成高质量 PDF
|
||||
var saveBtn = document.getElementById("btnSavePdf");
|
||||
var loadingOverlay = document.getElementById("pdfLoadingOverlay");
|
||||
if (saveBtn && window.html2canvas && window.jspdf && window.jspdf.jsPDF) {
|
||||
if (saveBtn) {
|
||||
saveBtn.addEventListener("click", function () {
|
||||
var pageEl = document.querySelector(".page");
|
||||
if (!pageEl) {
|
||||
console.error("未找到 .page 容器");
|
||||
return;
|
||||
}
|
||||
var btnText = saveBtn.textContent;
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = "生成中...";
|
||||
@@ -2208,135 +2203,68 @@
|
||||
loadingOverlay.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
}
|
||||
// 等待浏览器完成布局与绘制,再截取,避免截到白屏
|
||||
function doCapture() {
|
||||
requestAnimationFrame(function () {
|
||||
requestAnimationFrame(function () {
|
||||
try {
|
||||
(function () {
|
||||
var scale = 1.25;
|
||||
var pdf = new jspdf.jsPDF("p", "mm", "a4");
|
||||
var pageWidthMm = pdf.internal.pageSize.getWidth();
|
||||
var pageHeightMm = pdf.internal.pageSize.getHeight();
|
||||
|
||||
// 以元素宽度为基准,计算每一页对应的像素高度(避免生成超长大图导致 jsPDF 白页)
|
||||
var targetWidthPx = Math.max(
|
||||
1,
|
||||
Math.floor(pageEl.scrollWidth),
|
||||
);
|
||||
var pageHeightPx = Math.max(
|
||||
1,
|
||||
Math.floor((targetWidthPx * pageHeightMm) / pageWidthMm),
|
||||
);
|
||||
var totalHeightPx = Math.max(
|
||||
1,
|
||||
Math.floor(pageEl.scrollHeight),
|
||||
);
|
||||
try {
|
||||
// 从当前 URL 中解析报告编号,路径形如 /reports/qygl/:id
|
||||
var path = window.location.pathname || "";
|
||||
var match = path.match(/\\/reports\\/qygl\\/([^/]+)/);
|
||||
if (!match || !match[1]) {
|
||||
console.error("无法从当前 URL 解析报告编号,路径为", path);
|
||||
restoreBtn();
|
||||
return;
|
||||
}
|
||||
var reportId = match[1];
|
||||
var pdfUrl = "/reports/qygl/" + encodeURIComponent(reportId) + "/pdf";
|
||||
|
||||
var offsetY = 0;
|
||||
var pageIndex = 0;
|
||||
|
||||
function renderSlice() {
|
||||
var sliceHeight = Math.min(
|
||||
pageHeightPx,
|
||||
totalHeightPx - offsetY,
|
||||
);
|
||||
if (sliceHeight <= 0) {
|
||||
// 结束,保存
|
||||
var fileName = "企业全景报告.pdf";
|
||||
if (
|
||||
reportData &&
|
||||
reportData.entName &&
|
||||
typeof reportData.entName === "string"
|
||||
) {
|
||||
fileName =
|
||||
reportData.entName +
|
||||
"_企业全景报告.pdf";
|
||||
}
|
||||
pdf.save(fileName);
|
||||
restoreBtn();
|
||||
return;
|
||||
}
|
||||
|
||||
html2canvas(pageEl, {
|
||||
scale: scale,
|
||||
useCORS: true,
|
||||
allowTaint: false,
|
||||
backgroundColor: "#ffffff",
|
||||
x: 0,
|
||||
y: offsetY,
|
||||
width: targetWidthPx,
|
||||
height: sliceHeight,
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
windowWidth: targetWidthPx,
|
||||
windowHeight: sliceHeight,
|
||||
onclone: function (clonedDoc) {
|
||||
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", {
|
||||
offsetY: offsetY,
|
||||
sliceHeight: sliceHeight,
|
||||
});
|
||||
restoreBtn();
|
||||
return;
|
||||
}
|
||||
// 调试:输出每页分片信息
|
||||
console.info("PDF分片截图信息", {
|
||||
page: pageIndex + 1,
|
||||
w: canvas.width,
|
||||
h: canvas.height,
|
||||
offsetY: offsetY,
|
||||
sliceHeight: sliceHeight,
|
||||
totalHeightPx: totalHeightPx,
|
||||
});
|
||||
|
||||
var imgData = canvas.toDataURL("image/jpeg", 0.9);
|
||||
|
||||
if (pageIndex > 0) pdf.addPage();
|
||||
pdf.addImage(
|
||||
imgData,
|
||||
"JPEG",
|
||||
0,
|
||||
0,
|
||||
pageWidthMm,
|
||||
pageHeightMm,
|
||||
);
|
||||
|
||||
pageIndex += 1;
|
||||
offsetY += sliceHeight;
|
||||
|
||||
// 给 UI 一点喘息,避免长任务卡死
|
||||
setTimeout(renderSlice, 0);
|
||||
})
|
||||
.catch(function (e) {
|
||||
console.error("生成 PDF 失败(分片截图阶段)", e);
|
||||
restoreBtn();
|
||||
});
|
||||
}
|
||||
|
||||
renderSlice();
|
||||
})();
|
||||
} catch (e) {
|
||||
console.error("触发生成 PDF 失败", e);
|
||||
restoreBtn();
|
||||
// 通过 fetch 获取 PDF 二进制并触发下载
|
||||
fetch(pdfUrl, {
|
||||
method: "GET",
|
||||
})
|
||||
.then(function (resp) {
|
||||
if (!resp.ok) {
|
||||
throw new Error("生成 PDF 接口返回错误状态:" + resp.status);
|
||||
}
|
||||
var contentType = resp.headers.get("Content-Type") || "";
|
||||
if (
|
||||
contentType.indexOf("application/pdf") === -1 &&
|
||||
contentType.indexOf("application/octet-stream") === -1
|
||||
) {
|
||||
// 可能返回的是错误字符串
|
||||
return resp.text().then(function (txt) {
|
||||
throw new Error("生成 PDF 失败:" + txt);
|
||||
});
|
||||
}
|
||||
return resp.blob();
|
||||
})
|
||||
.then(function (blob) {
|
||||
var fileName = "企业全景报告.pdf";
|
||||
if (
|
||||
reportData &&
|
||||
reportData.entName &&
|
||||
typeof reportData.entName === "string"
|
||||
) {
|
||||
fileName =
|
||||
reportData.entName + "_企业全景报告.pdf";
|
||||
}
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
var a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
restoreBtn();
|
||||
})
|
||||
.catch(function (e) {
|
||||
console.error("生成 PDF 失败(后端接口)", e);
|
||||
restoreBtn();
|
||||
alert("生成 PDF 失败,请稍后重试");
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("触发生成 PDF 失败", e);
|
||||
restoreBtn();
|
||||
}
|
||||
doCapture();
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user