|
@@ -52,6 +52,10 @@ export class CanvasPhotoEditor {
|
|
|
this.dragStartY = 0
|
|
this.dragStartY = 0
|
|
|
this.lastDrawOffsetX = 0
|
|
this.lastDrawOffsetX = 0
|
|
|
this.lastDrawOffsetY = 0
|
|
this.lastDrawOffsetY = 0
|
|
|
|
|
+ // 页面拖拽排序
|
|
|
|
|
+ this.isPageDragging = false
|
|
|
|
|
+ this.dragPageStartY = 0
|
|
|
|
|
+ this.dragPageMoved = false
|
|
|
//标引状态
|
|
//标引状态
|
|
|
this.indexing = false//是否开启
|
|
this.indexing = false//是否开启
|
|
|
this.indexingNum = 0 //0 未点击 1 已生成开始点位 2 结束点位已生成
|
|
this.indexingNum = 0 //0 未点击 1 已生成开始点位 2 结束点位已生成
|
|
@@ -180,6 +184,15 @@ export class CanvasPhotoEditor {
|
|
|
// --- 核心:拖拽仅改坐标,不重绘 ---
|
|
// --- 核心:拖拽仅改坐标,不重绘 ---
|
|
|
handleMouseDown(e) {
|
|
handleMouseDown(e) {
|
|
|
if (e.target !== this.canvas) return
|
|
if (e.target !== this.canvas) return
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ // 🔥 页面拖拽排序:按下时记录
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ // if (this.selectedPageIndex !== -1 && !this.indexing) {
|
|
|
|
|
+ // this.isPageDragging = true
|
|
|
|
|
+ // this.dragPageStartY = e.clientY
|
|
|
|
|
+ // this.dragPageMoved = false
|
|
|
|
|
+ // return
|
|
|
|
|
+ // }
|
|
|
// 记录拖拽起始状态
|
|
// 记录拖拽起始状态
|
|
|
if (this.indexing) {
|
|
if (this.indexing) {
|
|
|
if (this.indexingNum == 0) return this.starIindexing(e)
|
|
if (this.indexingNum == 0) return this.starIindexing(e)
|
|
@@ -214,13 +227,50 @@ export class CanvasPhotoEditor {
|
|
|
// if (this.indexing) {
|
|
// if (this.indexing) {
|
|
|
// return
|
|
// return
|
|
|
// }
|
|
// }
|
|
|
|
|
+
|
|
|
if (!this.isDragging) return
|
|
if (!this.isDragging) return
|
|
|
// 仅更新坐标偏移,不触发重绘(核心优化)
|
|
// 仅更新坐标偏移,不触发重绘(核心优化)
|
|
|
const deltaX = e.clientX - this.dragStartX
|
|
const deltaX = e.clientX - this.dragStartX
|
|
|
const deltaY = e.clientY - this.dragStartY
|
|
const deltaY = e.clientY - this.dragStartY
|
|
|
this.drawOffsetX = this.lastDrawOffsetX + deltaX
|
|
this.drawOffsetX = this.lastDrawOffsetX + deltaX
|
|
|
this.drawOffsetY = this.lastDrawOffsetY + deltaY
|
|
this.drawOffsetY = this.lastDrawOffsetY + deltaY
|
|
|
|
|
+ // const mouseX = (e.clientX - rect.left - this.drawOffsetX) / this.scale
|
|
|
|
|
+ // const mouseY = (e.clientY - rect.top - this.drawOffsetY) / this.scale
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+// 🔥 页面拖拽排序:移动时交换顺序
|
|
|
|
|
+// ==========================================
|
|
|
|
|
+if (this.isPageDragging && this.selectedPageIndex !== -1) {
|
|
|
|
|
+ const deltaY = e.clientY - this.dragPageStartY
|
|
|
|
|
+
|
|
|
|
|
+ // 向下拖 → 向后交换
|
|
|
|
|
+ if (deltaY > 80 && this.selectedPageIndex < this.pages.length - 1) {
|
|
|
|
|
+ const newPages = [...this.pages]
|
|
|
|
|
+ const temp = newPages[this.selectedPageIndex]
|
|
|
|
|
+ newPages[this.selectedPageIndex] = newPages[this.selectedPageIndex + 1]
|
|
|
|
|
+ newPages[this.selectedPageIndex + 1] = temp
|
|
|
|
|
+ this.pages = newPages
|
|
|
|
|
+ this.selectedPageIndex += 1
|
|
|
|
|
+ this.dragPageStartY = e.clientY
|
|
|
|
|
+ this.dragPageMoved = true
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
+ // 向上拖 → 向前交换
|
|
|
|
|
+ if (deltaY < -80 && this.selectedPageIndex > 0) {
|
|
|
|
|
+ const newPages = [...this.pages]
|
|
|
|
|
+ const temp = newPages[this.selectedPageIndex]
|
|
|
|
|
+ newPages[this.selectedPageIndex] = newPages[this.selectedPageIndex - 1]
|
|
|
|
|
+ newPages[this.selectedPageIndex - 1] = temp
|
|
|
|
|
+ this.pages = newPages
|
|
|
|
|
+ this.selectedPageIndex -= 1
|
|
|
|
|
+ this.dragPageStartY = e.clientY
|
|
|
|
|
+ this.dragPageMoved = true
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+ if(this.indexing && this.indexingNum == 1){
|
|
|
|
|
+ // 绘制中途轨迹
|
|
|
|
|
+ }
|
|
|
// 【关键】通过Canvas临时绘制偏移效果(替代重绘),避免拖拽时画面静止
|
|
// 【关键】通过Canvas临时绘制偏移效果(替代重绘),避免拖拽时画面静止
|
|
|
this.drawAllPages()
|
|
this.drawAllPages()
|
|
|
}
|
|
}
|
|
@@ -1152,18 +1202,18 @@ export class CanvasPhotoEditor {
|
|
|
const rules = {
|
|
const rules = {
|
|
|
a4: { perSheet: 1, orient: "portrait", format: "a4" },
|
|
a4: { perSheet: 1, orient: "portrait", format: "a4" },
|
|
|
a3: { perSheet: 2, orient: "landscape", format: "a3" },
|
|
a3: { perSheet: 2, orient: "landscape", format: "a3" },
|
|
|
- four: { perSheet: 4, orient: "portrait", format: "a4" },
|
|
|
|
|
|
|
+ four: { perSheet: 4, orient: "landscape", format: [840, 297]},
|
|
|
};
|
|
};
|
|
|
const { perSheet, orient, format } = rules[paperType];
|
|
const { perSheet, orient, format } = rules[paperType];
|
|
|
const pdf = new jsPDF({ orientation: orient, unit: "mm", format });
|
|
const pdf = new jsPDF({ orientation: orient, unit: "mm", format });
|
|
|
const pdfW = pdf.internal.pageSize.getWidth();
|
|
const pdfW = pdf.internal.pageSize.getWidth();
|
|
|
const pdfH = pdf.internal.pageSize.getHeight();
|
|
const pdfH = pdf.internal.pageSize.getHeight();
|
|
|
-
|
|
|
|
|
const groups = [];
|
|
const groups = [];
|
|
|
for (let i = 0; i < this.pages.length; i += perSheet) {
|
|
for (let i = 0; i < this.pages.length; i += perSheet) {
|
|
|
groups.push(this.pages.slice(i, i + perSheet));
|
|
groups.push(this.pages.slice(i, i + perSheet));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ console.log("groups", groups);
|
|
|
groups.forEach((group, sheetIndex) => {
|
|
groups.forEach((group, sheetIndex) => {
|
|
|
if (sheetIndex > 0) pdf.addPage();
|
|
if (sheetIndex > 0) pdf.addPage();
|
|
|
|
|
|
|
@@ -1197,6 +1247,7 @@ export class CanvasPhotoEditor {
|
|
|
|
|
|
|
|
if (photo) {
|
|
if (photo) {
|
|
|
const img = this.imgCache.get(photo.id);
|
|
const img = this.imgCache.get(photo.id);
|
|
|
|
|
+ console.log("img", img, img && img.complete);
|
|
|
if (img && img.complete) {
|
|
if (img && img.complete) {
|
|
|
// 图片裁切(不超出相框)
|
|
// 图片裁切(不超出相框)
|
|
|
ctx.save();
|
|
ctx.save();
|
|
@@ -1297,9 +1348,9 @@ export class CanvasPhotoEditor {
|
|
|
} else if (paperType === "a3") {
|
|
} else if (paperType === "a3") {
|
|
|
w = pdfW / 2; h = pdfH; x = idxInSheet * w; y = 0;
|
|
w = pdfW / 2; h = pdfH; x = idxInSheet * w; y = 0;
|
|
|
} else {
|
|
} else {
|
|
|
- w = pdfW / 2; h = pdfH / 2;
|
|
|
|
|
- x = (idxInSheet % 2) * w;
|
|
|
|
|
- y = Math.floor(idxInSheet / 2) * h;
|
|
|
|
|
|
|
+ w = pdfW / 4; h = pdfH;
|
|
|
|
|
+ x = idxInSheet * w;
|
|
|
|
|
+ y = 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
pdf.addImage(imgData, "PNG", x, y, w, h);
|
|
pdf.addImage(imgData, "PNG", x, y, w, h);
|
|
@@ -1349,8 +1400,8 @@ async exportPagesAsImages(paperType = "a4", name, fileType = 'pdf') {
|
|
|
|
|
|
|
|
// 创建画布(和PDF导出尺寸逻辑一致)
|
|
// 创建画布(和PDF导出尺寸逻辑一致)
|
|
|
const pageCanvas = document.createElement("canvas");
|
|
const pageCanvas = document.createElement("canvas");
|
|
|
- pageCanvas.width = this.pageWidth * DPR * (paperType === "a3" ? 2 : 1);
|
|
|
|
|
- pageCanvas.height = this.pageHeight * DPR * (paperType === "four" ? 2 : 1);
|
|
|
|
|
|
|
+ pageCanvas.width = this.pageWidth * DPR * (paperType === "a3" ? 2 : paperType === "a4" ? 1 : 4);
|
|
|
|
|
+ pageCanvas.height = this.pageHeight * DPR * 1;
|
|
|
const ctx = pageCanvas.getContext("2d");
|
|
const ctx = pageCanvas.getContext("2d");
|
|
|
ctx.scale(DPR, DPR);
|
|
ctx.scale(DPR, DPR);
|
|
|
|
|
|
|
@@ -1360,13 +1411,14 @@ async exportPagesAsImages(paperType = "a4", name, fileType = 'pdf') {
|
|
|
|
|
|
|
|
// 绘制本组页面
|
|
// 绘制本组页面
|
|
|
group.forEach((page, idxInSheet) => {
|
|
group.forEach((page, idxInSheet) => {
|
|
|
|
|
+ const pageIndex = groupIndex * perSheet + idxInSheet;
|
|
|
let offsetX = 0;
|
|
let offsetX = 0;
|
|
|
let offsetY = 0;
|
|
let offsetY = 0;
|
|
|
|
|
|
|
|
if (paperType === "a3") offsetX = idxInSheet * this.pageWidth;
|
|
if (paperType === "a3") offsetX = idxInSheet * this.pageWidth;
|
|
|
if (paperType === "four") {
|
|
if (paperType === "four") {
|
|
|
- offsetX = (idxInSheet % 2) * this.pageWidth;
|
|
|
|
|
- offsetY = Math.floor(idxInSheet / 2) * this.pageHeight;
|
|
|
|
|
|
|
+ offsetX = idxInSheet * this.pageWidth;
|
|
|
|
|
+ // offsetY = idxInSheet * this.pageHeight;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ctx.save();
|
|
ctx.save();
|
|
@@ -1419,51 +1471,63 @@ async exportPagesAsImages(paperType = "a4", name, fileType = 'pdf') {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- // 绘制标引(完全复用你修复好的逻辑)
|
|
|
|
|
- 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();
|
|
|
|
|
- points.forEach((p) => {
|
|
|
|
|
- const x = p.x - pageOffsetX;
|
|
|
|
|
- ctx.lineTo(x, p.y);
|
|
|
|
|
- });
|
|
|
|
|
- ctx.stroke();
|
|
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ // 🔥 1:1 还原你原 drawGuideLine 标引逻辑
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ this.indexingLineList.forEach(line => {
|
|
|
|
|
+ const { points, coordinate, indexingList } = line;
|
|
|
|
|
+ const startInfo = indexingList[0];
|
|
|
|
|
+ const endInfo = indexingList[1];
|
|
|
|
|
|
|
|
- if (isStart) {
|
|
|
|
|
- const first = points[0];
|
|
|
|
|
- ctx.beginPath();
|
|
|
|
|
- ctx.arc(first.x - pageOffsetX, first.y, 4, 0, Math.PI * 2);
|
|
|
|
|
- ctx.fill();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 当前正在导出的是哪一页
|
|
|
|
|
+ const currentIsStart = startInfo.pageIndex === pageIndex;
|
|
|
|
|
+ const currentIsEnd = endInfo.pageIndex === pageIndex;
|
|
|
|
|
+ if (!currentIsStart && !currentIsEnd) return;
|
|
|
|
|
+
|
|
|
|
|
+ ctx.save();
|
|
|
|
|
+ ctx.strokeStyle = '#ff0000';
|
|
|
|
|
+ ctx.fillStyle = '#ff0000';
|
|
|
|
|
+ ctx.lineWidth = 2;
|
|
|
|
|
+ ctx.lineCap = 'round';
|
|
|
|
|
+ ctx.lineJoin = 'round';
|
|
|
|
|
|
|
|
- if (isEnd) {
|
|
|
|
|
- const last = points[points.length - 1];
|
|
|
|
|
|
|
+ // 页面全局偏移(核心修正)
|
|
|
|
|
+ const pageOffsetX = pageIndex * (this.pageWidth + this.pageMargin);
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制连线
|
|
|
ctx.beginPath();
|
|
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);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ points.forEach((p, i) => {
|
|
|
|
|
+ const x = p.x - pageOffsetX;
|
|
|
|
|
+ const y = p.y;
|
|
|
|
|
+ i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
|
|
|
|
|
+ });
|
|
|
ctx.stroke();
|
|
ctx.stroke();
|
|
|
- }
|
|
|
|
|
- ctx.restore();
|
|
|
|
|
- });
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 起点圆点(只在起点页画)
|
|
|
|
|
+ if (currentIsStart) {
|
|
|
|
|
+ const first = points[0];
|
|
|
|
|
+ const fx = first.x - pageOffsetX;
|
|
|
|
|
+ ctx.beginPath();
|
|
|
|
|
+ ctx.arc(fx, first.y, 4, 0, Math.PI * 2);
|
|
|
|
|
+ ctx.fill();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // T型端线(只在终点页画)
|
|
|
|
|
+ if (currentIsEnd) {
|
|
|
|
|
+ const last = points[points.length - 1];
|
|
|
|
|
+ ctx.beginPath();
|
|
|
|
|
+ if (startInfo.pageIndex === endInfo.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();
|
|
ctx.restore();
|
|
|
});
|
|
});
|
|
@@ -1485,4 +1549,31 @@ async exportPagesAsImages(paperType = "a4", name, fileType = 'pdf') {
|
|
|
loading.close();
|
|
loading.close();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+/**
|
|
|
|
|
+ * 截取 Canvas 指定区域并返回图片 base64
|
|
|
|
|
+ * @param {HTMLCanvasElement} canvas - 原画布
|
|
|
|
|
+ * @param {number} x - 截取区域左上角 x
|
|
|
|
|
+ * @param {number} y - 截取区域左上角 y
|
|
|
|
|
+ * @param {number} width - 截取宽度
|
|
|
|
|
+ * @param {number} height - 截取高度
|
|
|
|
|
+ * @returns {string} 图片 dataURL
|
|
|
|
|
+ */
|
|
|
|
|
+captureCanvasArea(canvas, x, y, width, height) {
|
|
|
|
|
+ // 1. 创建一个临时小画布,大小就是你要截取的尺寸
|
|
|
|
|
+ const tempCanvas = document.createElement('canvas');
|
|
|
|
|
+ const tempCtx = tempCanvas.getContext('2d');
|
|
|
|
|
+
|
|
|
|
|
+ tempCanvas.width = width;
|
|
|
|
|
+ tempCanvas.height = height;
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 把原画布的指定区域 画到 临时画布
|
|
|
|
|
+ tempCtx.drawImage(
|
|
|
|
|
+ canvas,
|
|
|
|
|
+ x, y, width, height, // 原画布要截取的区域
|
|
|
|
|
+ 0, 0, width, height // 临时画布的绘制位置
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 导出图片
|
|
|
|
|
+ return tempCanvas.toDataURL('image/png');
|
|
|
|
|
+}
|
|
|
}
|
|
}
|