|
|
@@ -1318,138 +1318,166 @@ export class CanvasPhotoEditor {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async exportPagesAsImages(name) {
|
|
|
+async exportPagesAsImages(paperType = "a4", name, fileType = 'pdf') {
|
|
|
const loading = ElLoading.service({
|
|
|
lock: true,
|
|
|
text: "正在生成图片...",
|
|
|
background: "rgba(0, 0, 0, 0.7)",
|
|
|
});
|
|
|
|
|
|
- const DPR = 3; // 高清导出倍率,可改 2 / 3 / 4
|
|
|
+ const DPR = 3;
|
|
|
const zip = new JSZip();
|
|
|
|
|
|
+ // 完全和你 exportPagesToPDF 保持一致的规则
|
|
|
+ const rules = {
|
|
|
+ a4: { perSheet: 1 },
|
|
|
+ a3: { perSheet: 2 },
|
|
|
+ four: { perSheet: 4 },
|
|
|
+ };
|
|
|
+ const perSheet = rules[paperType]?.perSheet || 1;
|
|
|
+
|
|
|
try {
|
|
|
- // 遍历每一页
|
|
|
- for (let i = 0; i < this.pages.length; i++) {
|
|
|
- const pageIndex = i;
|
|
|
- const page = this.pages[pageIndex];
|
|
|
+ // 分组:几页拼一张图(a4=1,a3=2,four=4)
|
|
|
+ const groups = [];
|
|
|
+ for (let i = 0; i < this.pages.length; i += perSheet) {
|
|
|
+ groups.push(this.pages.slice(i, i + perSheet));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历每组 → 生成一张图片
|
|
|
+ for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
|
|
+ const group = groups[groupIndex];
|
|
|
|
|
|
- // 创建一页大小的画布
|
|
|
+ // 创建画布(和PDF导出尺寸逻辑一致)
|
|
|
const pageCanvas = document.createElement("canvas");
|
|
|
- pageCanvas.width = this.pageWidth * DPR;
|
|
|
- pageCanvas.height = this.pageHeight * DPR;
|
|
|
+ pageCanvas.width = this.pageWidth * DPR * (paperType === "a3" ? 2 : 1);
|
|
|
+ pageCanvas.height = this.pageHeight * DPR * (paperType === "four" ? 2 : 1);
|
|
|
const ctx = pageCanvas.getContext("2d");
|
|
|
ctx.scale(DPR, DPR);
|
|
|
|
|
|
// 白色背景
|
|
|
ctx.fillStyle = "#fff";
|
|
|
- ctx.fillRect(0, 0, this.pageWidth, this.pageHeight);
|
|
|
+ ctx.fillRect(0, 0, pageCanvas.width, pageCanvas.height);
|
|
|
|
|
|
- // 绘制页面内容(图片 + 文字 + 虚线框)
|
|
|
- const layout = this.getItemSize(page.layoutMode);
|
|
|
- const coords = this.getCoordinate(0, layout);
|
|
|
-
|
|
|
- coords.forEach((coord, idx) => {
|
|
|
- const photoId = page.list[idx];
|
|
|
- const photo = this.photos.find(p => p.id === photoId);
|
|
|
-
|
|
|
- // 相框底板
|
|
|
- ctx.fillStyle = "#D9D9D9";
|
|
|
- ctx.fillRect(coord.x, coord.y, coord.width, coord.height);
|
|
|
- ctx.strokeStyle = "#eee";
|
|
|
- ctx.strokeRect(coord.x, coord.y, coord.width, coord.height);
|
|
|
-
|
|
|
- if (photo) {
|
|
|
- const img = this.imgCache.get(photo.id);
|
|
|
- if (img && img.complete) {
|
|
|
- ctx.save();
|
|
|
- ctx.beginPath();
|
|
|
- ctx.rect(coord.x, coord.y, coord.width, coord.height);
|
|
|
- ctx.clip();
|
|
|
-
|
|
|
- const s = Math.max(coord.width / img.width, coord.height / img.height);
|
|
|
- const w = img.width * s;
|
|
|
- const h = img.height * s;
|
|
|
- const x = coord.x + (coord.width - w) / 2;
|
|
|
- const y = coord.y + (coord.height - h) / 2;
|
|
|
- ctx.drawImage(img, x, y, w, h);
|
|
|
- ctx.restore();
|
|
|
- }
|
|
|
+ // 绘制本组页面
|
|
|
+ group.forEach((page, idxInSheet) => {
|
|
|
+ let offsetX = 0;
|
|
|
+ let offsetY = 0;
|
|
|
|
|
|
- // 文字
|
|
|
- const text = photo.name || "说明文字";
|
|
|
- this.drawCenteredTextWithEllipsis(
|
|
|
- ctx, text,
|
|
|
- coord.x + coord.width / 2,
|
|
|
- coord.y + coord.height + 40,
|
|
|
- coord.width - 20, 24, 2, "16px Microsoft Yahei"
|
|
|
- );
|
|
|
-
|
|
|
- // 虚线框
|
|
|
- ctx.setLineDash([1, 1]);
|
|
|
- ctx.strokeRect(coord.x, coord.y + coord.height + 10, coord.width, 50);
|
|
|
- ctx.setLineDash([]);
|
|
|
+ if (paperType === "a3") offsetX = idxInSheet * this.pageWidth;
|
|
|
+ if (paperType === "four") {
|
|
|
+ offsetX = (idxInSheet % 2) * this.pageWidth;
|
|
|
+ offsetY = Math.floor(idxInSheet / 2) * this.pageHeight;
|
|
|
}
|
|
|
- });
|
|
|
-
|
|
|
- // ======================
|
|
|
- // 绘制标引(跨页也正常)
|
|
|
- // ======================
|
|
|
- this.indexingLineList.forEach(line => {
|
|
|
- const { points, coordinate, indexingList } = line;
|
|
|
- const s = indexingList[0];
|
|
|
- const e = indexingList[1];
|
|
|
- const currentIsStart = s.pageIndex === pageIndex;
|
|
|
- const currentIsEnd = e.pageIndex === pageIndex;
|
|
|
- if (!currentIsStart && !currentIsEnd) return;
|
|
|
|
|
|
ctx.save();
|
|
|
- ctx.strokeStyle = "#ff0000";
|
|
|
- ctx.fillStyle = "#ff0000";
|
|
|
- ctx.lineWidth = 2;
|
|
|
- ctx.lineCap = "round";
|
|
|
-
|
|
|
- const pageOffsetX = pageIndex * (this.pageWidth + this.pageMargin);
|
|
|
- ctx.beginPath();
|
|
|
- points.forEach(p => {
|
|
|
- const x = p.x - pageOffsetX;
|
|
|
- const y = p.y;
|
|
|
- ctx.lineTo(x, y);
|
|
|
+ ctx.translate(offsetX, offsetY);
|
|
|
+
|
|
|
+ // ==========================================
|
|
|
+ // 🔥 直接复用你 PDF 里一模一样的绘制逻辑
|
|
|
+ // ==========================================
|
|
|
+ const layout = this.getItemSize(page.layoutMode);
|
|
|
+ const coords = this.getCoordinate(0, layout);
|
|
|
+
|
|
|
+ coords.forEach((coord, i) => {
|
|
|
+ const photoId = page.list[i];
|
|
|
+ const photo = this.photos.find((p) => p.id === photoId);
|
|
|
+
|
|
|
+ ctx.fillStyle = "#D9D9D9";
|
|
|
+ ctx.fillRect(coord.x, coord.y, coord.width, coord.height);
|
|
|
+ ctx.strokeStyle = "#eee";
|
|
|
+ ctx.strokeRect(coord.x, coord.y, coord.width, coord.height);
|
|
|
+
|
|
|
+ if (photo) {
|
|
|
+ const img = this.imgCache.get(photo.id);
|
|
|
+ if (img && img.complete) {
|
|
|
+ ctx.save();
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.rect(coord.x, coord.y, coord.width, coord.height);
|
|
|
+ ctx.clip();
|
|
|
+
|
|
|
+ const s = Math.max(coord.width / img.width, coord.height / img.height);
|
|
|
+ const w = img.width * s;
|
|
|
+ const h = img.height * s;
|
|
|
+ const x = coord.x + (coord.width - w) / 2;
|
|
|
+ const y = coord.y + (coord.height - h) / 2;
|
|
|
+ ctx.drawImage(img, x, y, w, h);
|
|
|
+ ctx.restore();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文字
|
|
|
+ const text = photo.name || "说明文字";
|
|
|
+ this.drawCenteredTextWithEllipsis(
|
|
|
+ ctx, text,
|
|
|
+ coord.x + coord.width / 2,
|
|
|
+ coord.y + coord.height + 40,
|
|
|
+ coord.width - 20, 24, 2, "16px Microsoft Yahei"
|
|
|
+ );
|
|
|
+
|
|
|
+ ctx.setLineDash([1, 1]);
|
|
|
+ ctx.strokeRect(coord.x, coord.y + coord.height + 10, coord.width, 50);
|
|
|
+ ctx.setLineDash([]);
|
|
|
+ }
|
|
|
});
|
|
|
- ctx.stroke();
|
|
|
|
|
|
- if (currentIsStart) {
|
|
|
- const first = points[0];
|
|
|
+ // 绘制标引(完全复用你修复好的逻辑)
|
|
|
+ this.indexingLineList.forEach((line) => {
|
|
|
+ const { points, coordinate, indexingList } = line;
|
|
|
+ const s = indexingList[0];
|
|
|
+ const e = indexingList[1];
|
|
|
+ const realPageIndex = groupIndex * perSheet + idxInSheet;
|
|
|
+ const isStart = s.pageIndex === realPageIndex;
|
|
|
+ const isEnd = e.pageIndex === realPageIndex;
|
|
|
+ if (!isStart && !isEnd) return;
|
|
|
+
|
|
|
+ ctx.save();
|
|
|
+ ctx.strokeStyle = "#ff0000";
|
|
|
+ ctx.fillStyle = "#ff0000";
|
|
|
+ ctx.lineWidth = 2;
|
|
|
+ ctx.lineCap = "round";
|
|
|
+
|
|
|
+ const pageOffsetX = s.pageIndex * (this.pageWidth + this.pageMargin);
|
|
|
ctx.beginPath();
|
|
|
- ctx.arc(first.x - pageOffsetX, first.y, 4, 0, Math.PI * 2);
|
|
|
- ctx.fill();
|
|
|
- }
|
|
|
+ points.forEach((p) => {
|
|
|
+ const x = p.x - pageOffsetX;
|
|
|
+ ctx.lineTo(x, p.y);
|
|
|
+ });
|
|
|
+ ctx.stroke();
|
|
|
|
|
|
- if (currentIsEnd) {
|
|
|
- const last = points[points.length - 1];
|
|
|
- ctx.beginPath();
|
|
|
- if (s.pageIndex === e.pageIndex) {
|
|
|
- ctx.moveTo(coordinate.x - pageOffsetX, last.y);
|
|
|
- ctx.lineTo(coordinate.x - pageOffsetX + coordinate.width, last.y);
|
|
|
- } else {
|
|
|
- ctx.moveTo(last.x - pageOffsetX, coordinate.y);
|
|
|
- ctx.lineTo(last.x - pageOffsetX, coordinate.y + coordinate.height);
|
|
|
+ if (isStart) {
|
|
|
+ const first = points[0];
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(first.x - pageOffsetX, first.y, 4, 0, Math.PI * 2);
|
|
|
+ ctx.fill();
|
|
|
}
|
|
|
- ctx.stroke();
|
|
|
- }
|
|
|
+
|
|
|
+ if (isEnd) {
|
|
|
+ const last = points[points.length - 1];
|
|
|
+ ctx.beginPath();
|
|
|
+ if (s.pageIndex === e.pageIndex) {
|
|
|
+ ctx.moveTo(coordinate.x - pageOffsetX, last.y);
|
|
|
+ ctx.lineTo(coordinate.x - pageOffsetX + coordinate.width, last.y);
|
|
|
+ } else {
|
|
|
+ ctx.moveTo(last.x - pageOffsetX, coordinate.y);
|
|
|
+ ctx.lineTo(last.x - pageOffsetX, coordinate.y + coordinate.height);
|
|
|
+ }
|
|
|
+ ctx.stroke();
|
|
|
+ }
|
|
|
+ ctx.restore();
|
|
|
+ });
|
|
|
+
|
|
|
ctx.restore();
|
|
|
});
|
|
|
|
|
|
- // 转成图片并加入 ZIP
|
|
|
+ // 转图片并加入 ZIP
|
|
|
const base64 = pageCanvas.toDataURL("image/png");
|
|
|
- zip.file(`第${pageIndex + 1}页.png`, base64.split(",")[1], { base64: true });
|
|
|
+ zip.file(`第${groupIndex + 1}张.png`, base64.split(",")[1], { base64: true });
|
|
|
}
|
|
|
|
|
|
- // 生成 ZIP 并下载
|
|
|
- let filename = name || '排版图片_' + Date.now()
|
|
|
+ // 下载
|
|
|
+ const filename = name || "排版图片";
|
|
|
const blob = await zip.generateAsync({ type: "blob" });
|
|
|
- saveAs(blob, `filename.zip`);
|
|
|
- ElMessage.success("图片导出成功!");
|
|
|
+ saveAs(blob, `${filename}.zip`);
|
|
|
+ ElMessage.success("导出成功!");
|
|
|
} catch (err) {
|
|
|
console.error(err);
|
|
|
ElMessage.error("导出失败");
|