|
|
@@ -8,36 +8,31 @@
|
|
|
</div>
|
|
|
<div class="preview-panel">
|
|
|
<div class="canvas-result">
|
|
|
- <canvas
|
|
|
- id="outputCanvas"
|
|
|
- style="width: 1000px"
|
|
|
- v-show="canvasGenerated"
|
|
|
- ></canvas>
|
|
|
- <div class="canvas-buttons" v-if="canvasGenerated">
|
|
|
+ <canvas id="outputCanvas" v-show="canvasGenerated"></canvas>
|
|
|
+ <!-- <div class="canvas-buttons" v-if="canvasGenerated">
|
|
|
<button @click="downloadCanvasAsImage" class="download">
|
|
|
📥 下载图片
|
|
|
</button>
|
|
|
<button @click="exportToPDF" class="pdf"> 📄 导出PDF </button>
|
|
|
<button @click="printCanvas" class="print"> 🖨️ 打印 </button>
|
|
|
- </div>
|
|
|
+ </div> -->
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 图片素材区域 -->
|
|
|
- <div class="images-panel">
|
|
|
- <div class="panel-title">图片素材</div>
|
|
|
- <div class="images-grid">
|
|
|
- <div
|
|
|
- v-for="(img, index) in dragImages"
|
|
|
- :key="index"
|
|
|
- class="drag-item"
|
|
|
- draggable="true"
|
|
|
- @dragstart="handleDragStart($event, img, index)"
|
|
|
- >
|
|
|
- <img :src="img.url" :alt="img.name" />
|
|
|
- <div class="drag-item-name">{{ img.name }}</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 图片素材区域 -->
|
|
|
+ <div class="images-panel">
|
|
|
+ <div class="panel-title">图片素材</div>
|
|
|
+ <div class="images-grid">
|
|
|
+ <div
|
|
|
+ v-for="(img, index) in dragImages"
|
|
|
+ :key="index"
|
|
|
+ class="drag-item"
|
|
|
+ draggable="true"
|
|
|
+ @dragstart="handleDragStart($event, img, index)"
|
|
|
+ >
|
|
|
+ <img :src="img.url" :alt="img.name" />
|
|
|
+ <div class="drag-item-name">{{ img.name }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -90,17 +85,95 @@
|
|
|
{ name: '图片3', url: require('@/assets/ic-500.svg') }
|
|
|
],
|
|
|
droppedImages: [],
|
|
|
- selectedImage: null
|
|
|
+ selectedImage: null,
|
|
|
+ baseCanvasData: null // 缓存基础内容的 Canvas 数据
|
|
|
};
|
|
|
},
|
|
|
mounted() {
|
|
|
// 组件挂载后自动渲染一次
|
|
|
this.$nextTick(() => {
|
|
|
- // this.renderToCanvas();
|
|
|
+ this.renderToCanvas();
|
|
|
});
|
|
|
+
|
|
|
+ // 保存组件实例引用,用于事件处理器
|
|
|
+ window._aaComponentInstance = this;
|
|
|
+
|
|
|
+ // 创建全局安全移除函数
|
|
|
+ window._safeRemoveElement = function (element) {
|
|
|
+ try {
|
|
|
+ if (
|
|
|
+ element &&
|
|
|
+ element.parentNode &&
|
|
|
+ document.body.contains(element)
|
|
|
+ ) {
|
|
|
+ element.parentNode.removeChild(element);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.warn('安全移除元素时发生警告:', err);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ // 清理引用
|
|
|
+ if (window._aaComponentInstance === this) {
|
|
|
+ window._aaComponentInstance = null;
|
|
|
+ }
|
|
|
+ if (window._safeRemoveElement) {
|
|
|
+ window._safeRemoveElement = null;
|
|
|
+ }
|
|
|
},
|
|
|
methods: {
|
|
|
- // 设置 Canvas 拖放区域
|
|
|
+ // 获取组件实例
|
|
|
+ getComponentInstance() {
|
|
|
+ return window._aaComponentInstance || this;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 安全的移除DOM元素
|
|
|
+ safeRemoveElement(elementId) {
|
|
|
+ try {
|
|
|
+ const element = document.getElementById(elementId);
|
|
|
+ if (window._safeRemoveElement) {
|
|
|
+ return window._safeRemoveElement(element);
|
|
|
+ }
|
|
|
+ // 回退方案
|
|
|
+ if (
|
|
|
+ element &&
|
|
|
+ element.parentNode &&
|
|
|
+ document.body.contains(element)
|
|
|
+ ) {
|
|
|
+ element.parentNode.removeChild(element);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } catch (err) {
|
|
|
+ console.warn(`移除元素 ${elementId} 时发生警告:`, err);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 安全移除所有菜单
|
|
|
+ safeRemoveAllMenus() {
|
|
|
+ try {
|
|
|
+ const existingMenus = document.querySelectorAll('[id^="menu_"]');
|
|
|
+
|
|
|
+ // 使用倒序循环,避免在移除时影响NodeList
|
|
|
+ for (let i = existingMenus.length - 1; i >= 0; i--) {
|
|
|
+ const menu = existingMenus[i];
|
|
|
+ if (menu && document.body.contains(menu) && menu.parentNode) {
|
|
|
+ try {
|
|
|
+ menu.parentNode.removeChild(menu);
|
|
|
+ } catch (err) {
|
|
|
+ console.warn('移除菜单时发生警告:', err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.warn('移除所有菜单时发生错误:', err);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 设置 Canvas 拖放区域和点击事件
|
|
|
setupCanvasDropZone() {
|
|
|
const canvas = document.getElementById('outputCanvas');
|
|
|
if (!canvas) {
|
|
|
@@ -108,7 +181,14 @@
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- console.log('设置拖放区域');
|
|
|
+ console.log('设置拖放区域和点击事件,Canvas ID:', canvas.id);
|
|
|
+ console.log('Canvas 尺寸:', canvas.width, 'x', canvas.height);
|
|
|
+ console.log(
|
|
|
+ 'Canvas 可见性:',
|
|
|
+ canvas.style.display,
|
|
|
+ canvas.style.visibility
|
|
|
+ );
|
|
|
+ console.log('Canvas 位置:', canvas.getBoundingClientRect());
|
|
|
|
|
|
canvas.addEventListener('dragover', (e) => {
|
|
|
e.preventDefault();
|
|
|
@@ -126,6 +206,12 @@
|
|
|
this.addImageToCanvas(data.url, e.offsetX, e.offsetY);
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+ // 添加右键点击事件监听
|
|
|
+ canvas.addEventListener('contextmenu', (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ this.handleCanvasRightClick(e);
|
|
|
+ });
|
|
|
},
|
|
|
|
|
|
// 处理拖拽开始
|
|
|
@@ -143,7 +229,8 @@
|
|
|
addImageToCanvas(imgUrl, x, y) {
|
|
|
const canvas = document.getElementById('outputCanvas');
|
|
|
if (!canvas || !this.canvasGenerated) {
|
|
|
- this.showMessage('请先渲染 Canvas', 'error');
|
|
|
+ console.log('请先渲染 Canvas');
|
|
|
+
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -168,26 +255,413 @@
|
|
|
// 绘制图片
|
|
|
ctx.drawImage(img, posX, posY, width, height);
|
|
|
|
|
|
+ const imageId = `img_${Date.now()}_${Math.random()
|
|
|
+ .toString(36)
|
|
|
+ .substr(2, 9)}`;
|
|
|
+
|
|
|
this.droppedImages.push({
|
|
|
+ id: imageId,
|
|
|
url: imgUrl,
|
|
|
x: posX,
|
|
|
y: posY,
|
|
|
width,
|
|
|
- height
|
|
|
+ height,
|
|
|
+ originalWidth: img.width,
|
|
|
+ originalHeight: img.height
|
|
|
});
|
|
|
|
|
|
console.log('已添加的图片数量:', this.droppedImages.length);
|
|
|
- this.showMessage('✅ 图片已添加', 'success');
|
|
|
+ this.$message({
|
|
|
+ showClose: true,
|
|
|
+ message: '操作成功',
|
|
|
+ type: 'success'
|
|
|
+ });
|
|
|
+
|
|
|
+ // 设置图片可再次拖动和调整(通过右键菜单)
|
|
|
+ // 不再创建覆盖层,仅通过右键菜单操作
|
|
|
};
|
|
|
|
|
|
img.onerror = (err) => {
|
|
|
console.error('图片加载失败:', err);
|
|
|
- this.showMessage('❌ 图片加载失败', 'error');
|
|
|
+ this.$message({
|
|
|
+ showClose: true,
|
|
|
+ message: '操作失败',
|
|
|
+ type: 'error'
|
|
|
+ });
|
|
|
+ // this.showMessage('⚠️ 图片加载失败', 'error');
|
|
|
};
|
|
|
|
|
|
img.src = imgUrl;
|
|
|
},
|
|
|
|
|
|
+ // 重新绘制整个 Canvas
|
|
|
+ redrawCanvas() {
|
|
|
+ const canvas = document.getElementById('outputCanvas');
|
|
|
+ if (!canvas || !this.canvasGenerated) return;
|
|
|
+
|
|
|
+ const ctx = canvas.getContext('2d');
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 清空 Canvas
|
|
|
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
+
|
|
|
+ // 如果有缓存的基础内容,直接绘制
|
|
|
+ if (this.baseCanvasData) {
|
|
|
+ ctx.drawImage(this.baseCanvasData, 0, 0);
|
|
|
+ } else {
|
|
|
+ // 如果没有缓存,重新渲染基础内容
|
|
|
+ console.warn('没有缓存的基础内容,可能需要重新渲染');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新绘制所有拖入的图片
|
|
|
+ this.droppedImages.forEach((image) => {
|
|
|
+ const img = new Image();
|
|
|
+ img.onload = () => {
|
|
|
+ ctx.drawImage(img, image.x, image.y, image.width, image.height);
|
|
|
+ };
|
|
|
+ img.src = image.url;
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ 'Canvas 已快速重绘,包含基础内容和',
|
|
|
+ this.droppedImages.length,
|
|
|
+ '张图片'
|
|
|
+ );
|
|
|
+
|
|
|
+ // 确保事件监听仍然有效
|
|
|
+ setTimeout(() => {
|
|
|
+ this.setupCanvasDropZone();
|
|
|
+ }, 100);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('重新绘制 Canvas 失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示图片控制菜单
|
|
|
+ showImageControls(imageId, x, y) {
|
|
|
+ console.log('显示图片控制菜单,位置:', x, y, '图片ID:', imageId);
|
|
|
+
|
|
|
+ // 保存组件实例引用
|
|
|
+ const self = this;
|
|
|
+
|
|
|
+ // 先移除可能存在的其他菜单
|
|
|
+ this.safeRemoveAllMenus();
|
|
|
+
|
|
|
+ // 定义菜单选项(使用局部变量 self)
|
|
|
+ const options = [
|
|
|
+ { text: '放大', action: () => self.resizeImage(imageId, 1.2) },
|
|
|
+ { text: '缩小', action: () => self.resizeImage(imageId, 0.8) },
|
|
|
+ { text: '移动', action: () => self.startMoveImage(imageId, x, y) },
|
|
|
+ { text: '删除', action: () => self.removeImage(imageId) }
|
|
|
+ ];
|
|
|
+
|
|
|
+ const menu = document.createElement('div');
|
|
|
+ menu.id = `menu_${imageId}`;
|
|
|
+ menu.style.position = 'fixed';
|
|
|
+
|
|
|
+ // 调整位置,避免菜单超出屏幕边界
|
|
|
+ const menuWidth = 120;
|
|
|
+ const menuHeight = options.length * 40 + 16; // 估算高度
|
|
|
+
|
|
|
+ let left = x;
|
|
|
+ let top = y;
|
|
|
+
|
|
|
+ // 如果菜单会超出右侧边界,向左移动
|
|
|
+ if (left + menuWidth > window.innerWidth) {
|
|
|
+ left = window.innerWidth - menuWidth - 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果菜单会超出底部边界,向上移动
|
|
|
+ if (top + menuHeight > window.innerHeight) {
|
|
|
+ top = window.innerHeight - menuHeight - 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ menu.style.left = `${left}px`;
|
|
|
+ menu.style.top = `${top}px`;
|
|
|
+ menu.style.background = 'white';
|
|
|
+ menu.style.border = '1px solid #e2e8f0';
|
|
|
+ menu.style.borderRadius = '8px';
|
|
|
+ menu.style.padding = '8px 0';
|
|
|
+ menu.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.1)';
|
|
|
+ menu.style.zIndex = '9999'; // 使用更高的 z-index
|
|
|
+ menu.style.minWidth = `${menuWidth}px`;
|
|
|
+ menu.style.opacity = '1';
|
|
|
+ menu.style.visibility = 'visible';
|
|
|
+ menu.style.display = 'block';
|
|
|
+
|
|
|
+ console.log('菜单位置 - 原始:', x, y, '调整后:', left, top);
|
|
|
+
|
|
|
+ options.forEach((option) => {
|
|
|
+ const item = document.createElement('div');
|
|
|
+ item.innerText = option.text;
|
|
|
+ item.style.padding = '8px 16px';
|
|
|
+ item.style.cursor = 'pointer';
|
|
|
+ item.style.fontSize = '14px';
|
|
|
+ item.style.color = '#374151';
|
|
|
+
|
|
|
+ item.addEventListener('mouseenter', () => {
|
|
|
+ item.style.background = '#f3f4f6';
|
|
|
+ });
|
|
|
+
|
|
|
+ item.addEventListener('mouseleave', () => {
|
|
|
+ item.style.background = 'white';
|
|
|
+ });
|
|
|
+
|
|
|
+ item.addEventListener('click', () => {
|
|
|
+ option.action();
|
|
|
+ // 直接移除当前菜单
|
|
|
+ const menuToRemove = document.getElementById(`menu_${imageId}`);
|
|
|
+ if (window._safeRemoveElement) {
|
|
|
+ window._safeRemoveElement(menuToRemove);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ menu.appendChild(item);
|
|
|
+ });
|
|
|
+
|
|
|
+ document.body.appendChild(menu);
|
|
|
+ console.log('菜单已添加到 DOM,父元素:', menu.parentElement?.tagName);
|
|
|
+
|
|
|
+ // 点击其他地方关闭菜单
|
|
|
+ const closeMenu = (e) => {
|
|
|
+ console.log('closeMenu 被调用,目标:', e.target);
|
|
|
+ const currentMenu = document.getElementById(`menu_${imageId}`);
|
|
|
+ if (!currentMenu || !document.body.contains(currentMenu)) {
|
|
|
+ // 菜单已经不存在,移除监听器
|
|
|
+ document.removeEventListener('click', closeMenu);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e.target !== currentMenu && !currentMenu.contains(e.target)) {
|
|
|
+ // 使用全局安全函数移除菜单
|
|
|
+ if (window._safeRemoveElement) {
|
|
|
+ window._safeRemoveElement(currentMenu);
|
|
|
+ }
|
|
|
+ document.removeEventListener('click', closeMenu);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ console.log('添加全局点击监听器');
|
|
|
+ document.addEventListener('click', closeMenu);
|
|
|
+ }, 0);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 调整图片大小
|
|
|
+ resizeImage(imageId, factor) {
|
|
|
+ const imageIndex = this.droppedImages.findIndex(
|
|
|
+ (img) => img.id === imageId
|
|
|
+ );
|
|
|
+ if (imageIndex === -1) return;
|
|
|
+
|
|
|
+ const image = this.droppedImages[imageIndex];
|
|
|
+ image.width *= factor;
|
|
|
+ image.height *= factor;
|
|
|
+
|
|
|
+ this.redrawCanvas();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理 Canvas 右键点击
|
|
|
+ handleCanvasRightClick(e) {
|
|
|
+ console.log('Canvas 右键点击事件触发');
|
|
|
+
|
|
|
+ const canvas = document.getElementById('outputCanvas');
|
|
|
+ if (!canvas) {
|
|
|
+ console.log('Canvas 元素不存在');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ 'Canvas 元素存在,droppedImages 数量:',
|
|
|
+ this.droppedImages.length
|
|
|
+ );
|
|
|
+
|
|
|
+ if (this.droppedImages.length === 0) {
|
|
|
+ console.log('没有已添加的图片');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const rect = canvas.getBoundingClientRect();
|
|
|
+ console.log('Canvas 边界矩形:', rect);
|
|
|
+
|
|
|
+ const scaleX = canvas.width / rect.width;
|
|
|
+ const scaleY = canvas.height / rect.height;
|
|
|
+
|
|
|
+ // 计算点击位置相对于 Canvas 的坐标
|
|
|
+ const clickX = (e.clientX - rect.left) * scaleX;
|
|
|
+ const clickY = (e.clientY - rect.top) * scaleY;
|
|
|
+
|
|
|
+ console.log('右键点击位置 - 屏幕:', e.clientX, e.clientY);
|
|
|
+ console.log('右键点击位置 - Canvas相对:', clickX, clickY);
|
|
|
+
|
|
|
+ // 查找点击到的图片
|
|
|
+ let clickedImage = null;
|
|
|
+ for (const image of this.droppedImages) {
|
|
|
+ console.log(
|
|
|
+ '检查图片:',
|
|
|
+ image.id,
|
|
|
+ '位置:',
|
|
|
+ image.x,
|
|
|
+ image.y,
|
|
|
+ '尺寸:',
|
|
|
+ image.width,
|
|
|
+ image.height
|
|
|
+ );
|
|
|
+ if (
|
|
|
+ clickX >= image.x &&
|
|
|
+ clickX <= image.x + image.width &&
|
|
|
+ clickY >= image.y &&
|
|
|
+ clickY <= image.y + image.height
|
|
|
+ ) {
|
|
|
+ clickedImage = image;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (clickedImage) {
|
|
|
+ console.log('点击到图片:', clickedImage.id);
|
|
|
+ this.showImageControls(clickedImage.id, e.clientX, e.clientY);
|
|
|
+ } else {
|
|
|
+ console.log('未点击到任何图片');
|
|
|
+ // 可以在这里添加其他右键功能,比如添加新图片等
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 开始移动图片
|
|
|
+ startMoveImage(imageId, menuX, menuY) {
|
|
|
+ console.log('开始移动图片,图片ID:', imageId);
|
|
|
+
|
|
|
+ // 关闭菜单
|
|
|
+ this.safeRemoveElement(`menu_${imageId}`);
|
|
|
+
|
|
|
+ const canvas = document.getElementById('outputCanvas');
|
|
|
+ if (!canvas) {
|
|
|
+ console.error('Canvas 元素未找到');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const imageIndex = this.droppedImages.findIndex(
|
|
|
+ (img) => img.id === imageId
|
|
|
+ );
|
|
|
+ if (imageIndex === -1) {
|
|
|
+ console.error('图片未找到,ID:', imageId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const image = this.droppedImages[imageIndex];
|
|
|
+ console.log('找到图片:', image);
|
|
|
+
|
|
|
+ // 创建一个临时指示器
|
|
|
+ const indicator = document.createElement('div');
|
|
|
+ indicator.id = `move_indicator_${imageId}`;
|
|
|
+ indicator.style.position = 'fixed';
|
|
|
+ indicator.style.left = `${menuX}px`;
|
|
|
+ indicator.style.top = `${menuY}px`;
|
|
|
+ indicator.style.width = '40px';
|
|
|
+ indicator.style.height = '40px';
|
|
|
+ indicator.style.background = 'rgba(59, 130, 246, 0.3)';
|
|
|
+ indicator.style.border = '2px dashed #3b82f6';
|
|
|
+ indicator.style.borderRadius = '50%';
|
|
|
+ indicator.style.zIndex = '3000';
|
|
|
+ indicator.style.pointerEvents = 'none';
|
|
|
+ document.body.appendChild(indicator);
|
|
|
+ console.log('指示器已创建并添加到DOM');
|
|
|
+
|
|
|
+ // 显示提示
|
|
|
+ this.$message({
|
|
|
+ showClose: true,
|
|
|
+ message: '📍 移动鼠标调整位置,释放鼠标完成移动',
|
|
|
+ type: 'info'
|
|
|
+ });
|
|
|
+ // this.showMessage('📍 移动鼠标调整位置,释放鼠标完成移动', 'info');
|
|
|
+
|
|
|
+ const moveHandler = (e) => {
|
|
|
+ console.log('moveHandler 被调用,鼠标位置:', e.clientX, e.clientY);
|
|
|
+ const rect = canvas.getBoundingClientRect();
|
|
|
+ const scaleX = canvas.width / rect.width;
|
|
|
+ const scaleY = canvas.height / rect.height;
|
|
|
+
|
|
|
+ // 计算相对于 canvas 的位置
|
|
|
+ const newX = (e.clientX - rect.left) * scaleX - image.width / 2;
|
|
|
+ const newY = (e.clientY - rect.top) * scaleY - image.height / 2;
|
|
|
+
|
|
|
+ // 限制在 canvas 范围内
|
|
|
+ image.x = Math.max(0, Math.min(newX, canvas.width - image.width));
|
|
|
+ image.y = Math.max(0, Math.min(newY, canvas.height - image.height));
|
|
|
+
|
|
|
+ // 更新指示器位置
|
|
|
+ indicator.style.left = `${e.clientX - 20}px`;
|
|
|
+ indicator.style.top = `${e.clientY - 20}px`;
|
|
|
+
|
|
|
+ // 使用组件实例引用
|
|
|
+ const component = window._aaComponentInstance || this;
|
|
|
+ if (component && component.redrawCanvas) {
|
|
|
+ component.redrawCanvas();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const mouseUpHandler = (e) => {
|
|
|
+ console.log(
|
|
|
+ 'mouseUpHandler 被调用,位置:',
|
|
|
+ e.clientX,
|
|
|
+ e.clientY,
|
|
|
+ '目标:',
|
|
|
+ e.target
|
|
|
+ );
|
|
|
+
|
|
|
+ // 无论在哪里释放鼠标,都完成移动
|
|
|
+ console.log('鼠标释放,完成移动');
|
|
|
+
|
|
|
+ // 确保只处理一次
|
|
|
+ if (indicator.parentNode) {
|
|
|
+ console.log('指示器仍在DOM中,移除事件监听器');
|
|
|
+ document.removeEventListener('mousemove', moveHandler);
|
|
|
+ document.removeEventListener('mouseup', mouseUpHandler);
|
|
|
+ indicator.remove();
|
|
|
+ // 使用组件实例引用
|
|
|
+ const component = window._aaComponentInstance || this;
|
|
|
+ if (component) {
|
|
|
+ this.$message({
|
|
|
+ showClose: true,
|
|
|
+ message: '图片已移动',
|
|
|
+ type: 'success'
|
|
|
+ });
|
|
|
+ // component.showMessage('✅ 图片已移动', 'success');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ console.log('指示器已不在DOM中');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 确保事件只绑定一次
|
|
|
+ console.log('绑定事件监听器');
|
|
|
+ document.removeEventListener('mousemove', moveHandler);
|
|
|
+ document.removeEventListener('mouseup', mouseUpHandler);
|
|
|
+ document.addEventListener('mousemove', moveHandler);
|
|
|
+ document.addEventListener('mouseup', mouseUpHandler);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除图片
|
|
|
+ removeImage(imageId) {
|
|
|
+ const imageIndex = this.droppedImages.findIndex(
|
|
|
+ (img) => img.id === imageId
|
|
|
+ );
|
|
|
+ if (imageIndex === -1) return;
|
|
|
+
|
|
|
+ this.droppedImages.splice(imageIndex, 1);
|
|
|
+
|
|
|
+ // 删除控制菜单
|
|
|
+ this.safeRemoveElement(`menu_${imageId}`);
|
|
|
+
|
|
|
+ this.redrawCanvas();
|
|
|
+ this.$message({
|
|
|
+ showClose: true,
|
|
|
+ message: '🗑️ 图片已删除',
|
|
|
+ type: 'success'
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
// 渲染Canvas核心函数
|
|
|
async renderToCanvas() {
|
|
|
const targetElement = document.getElementById('captureTarget');
|
|
|
@@ -222,7 +696,6 @@
|
|
|
logging: false,
|
|
|
allowTaint: false,
|
|
|
imageTimeout: 5000,
|
|
|
- width: 1000, // 设置输出宽度为 1000px
|
|
|
windowWidth: targetElement.scrollWidth,
|
|
|
windowHeight: targetElement.scrollHeight
|
|
|
});
|
|
|
@@ -242,19 +715,22 @@
|
|
|
ctx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
|
|
|
ctx.drawImage(canvas, 0, 0);
|
|
|
|
|
|
+ // 保存基础内容的 Canvas 数据(用于快速重绘)
|
|
|
+ this.baseCanvasData = canvas;
|
|
|
+
|
|
|
this.canvasGenerated = true;
|
|
|
|
|
|
- // 设置 Canvas 拖放区域
|
|
|
+ // 设置 Canvas 拖放区域和事件监听
|
|
|
this.setupCanvasDropZone();
|
|
|
|
|
|
// 显示成功提示
|
|
|
- this.showMessage('✅ 渲染成功!可下载图片', 'success');
|
|
|
+ // this.showMessage('✅ 渲染成功!可下载图片', 'success');
|
|
|
} catch (error) {
|
|
|
console.error('html2canvas 渲染失败: ', error);
|
|
|
const temp = document.getElementById('tempLoadingMsg');
|
|
|
if (temp) temp.remove();
|
|
|
this.canvasGenerated = false;
|
|
|
- this.showMessage('❌ 渲染失败,请检查HTML结构或外部资源', 'error');
|
|
|
+ // this.showMessage('⚠️ 渲染失败,请检查HTML结构或外部资源', 'error');
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -266,7 +742,7 @@
|
|
|
!outputCanvas ||
|
|
|
outputCanvas.width === 0
|
|
|
) {
|
|
|
- this.showMessage('请先点击"渲染至Canvas"生成图像后再下载', 'error');
|
|
|
+ // this.showMessage('请先点击"渲染至Canvas"生成图像后再下载', 'error');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -279,13 +755,13 @@
|
|
|
link.download = `canvas-${timestamp}.png`;
|
|
|
link.href = outputCanvas.toDataURL('image/png');
|
|
|
link.click();
|
|
|
- this.showMessage('✅ 下载已开始', 'success');
|
|
|
+ // this.showMessage('✅ 下载已开始', 'success');
|
|
|
} catch (err) {
|
|
|
console.error('下载失败', err);
|
|
|
- this.showMessage(
|
|
|
- '无法下载图像,可能是跨域限制或Canvas被污染',
|
|
|
- 'error'
|
|
|
- );
|
|
|
+ // this.showMessage(
|
|
|
+ // '无法下载图像,可能是跨域限制或Canvas被污染',
|
|
|
+ // 'error'
|
|
|
+ // );
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -297,10 +773,10 @@
|
|
|
!outputCanvas ||
|
|
|
outputCanvas.width === 0
|
|
|
) {
|
|
|
- this.showMessage(
|
|
|
- '请先点击"渲染至Canvas"生成图像后再导出PDF',
|
|
|
- 'error'
|
|
|
- );
|
|
|
+ // this.showMessage(
|
|
|
+ // '请先点击"渲染至Canvas"生成图像后再导出PDF',
|
|
|
+ // 'error'
|
|
|
+ // );
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -332,10 +808,53 @@
|
|
|
.replace(/:/g, '-');
|
|
|
pdf.save(`canvas-${timestamp}.pdf`);
|
|
|
|
|
|
- this.showMessage('✅ PDF 导出成功', 'success');
|
|
|
+ // this.showMessage('✅ PDF 导出成功', 'success');
|
|
|
} catch (err) {
|
|
|
console.error('PDF 导出失败', err);
|
|
|
- this.showMessage('PDF 导出失败,请稍后重试', 'error');
|
|
|
+ // this.showMessage('PDF 导出失败,请稍后重试', 'error');
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重新渲染基础内容(当 HTML 内容变化时调用)
|
|
|
+ async refreshBaseContent() {
|
|
|
+ try {
|
|
|
+ const targetElement = document.getElementById('captureTarget');
|
|
|
+ if (!targetElement) return;
|
|
|
+
|
|
|
+ const newCanvas = await html2canvas(targetElement, {
|
|
|
+ scale: 1,
|
|
|
+ backgroundColor: '#ffffff',
|
|
|
+ useCORS: true,
|
|
|
+ logging: false,
|
|
|
+ allowTaint: false,
|
|
|
+ imageTimeout: 5000,
|
|
|
+ width: 1000,
|
|
|
+ windowWidth: targetElement.scrollWidth,
|
|
|
+ windowHeight: targetElement.scrollHeight
|
|
|
+ });
|
|
|
+
|
|
|
+ this.baseCanvasData = newCanvas;
|
|
|
+
|
|
|
+ // 更新主 Canvas
|
|
|
+ const outputCanvas = document.getElementById('outputCanvas');
|
|
|
+ if (outputCanvas && this.canvasGenerated) {
|
|
|
+ const ctx = outputCanvas.getContext('2d');
|
|
|
+ ctx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
|
|
|
+ ctx.drawImage(newCanvas, 0, 0);
|
|
|
+
|
|
|
+ // 重新绘制所有拖入的图片
|
|
|
+ this.droppedImages.forEach((image) => {
|
|
|
+ const img = new Image();
|
|
|
+ img.onload = () => {
|
|
|
+ ctx.drawImage(img, image.x, image.y, image.width, image.height);
|
|
|
+ };
|
|
|
+ img.src = image.url;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('基础内容已更新');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('更新基础内容失败:', error);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -347,10 +866,10 @@
|
|
|
!outputCanvas ||
|
|
|
outputCanvas.width === 0
|
|
|
) {
|
|
|
- this.showMessage(
|
|
|
- '请先点击"渲染至Canvas"生成图像后再获取PDF',
|
|
|
- 'error'
|
|
|
- );
|
|
|
+ // this.showMessage(
|
|
|
+ // '请先点击"渲染至Canvas"生成图像后再获取PDF',
|
|
|
+ // 'error'
|
|
|
+ // );
|
|
|
return Promise.reject(new Error('Canvas 未生成'));
|
|
|
}
|
|
|
|
|
|
@@ -392,7 +911,7 @@
|
|
|
!outputCanvas ||
|
|
|
outputCanvas.width === 0
|
|
|
) {
|
|
|
- this.showMessage('请先点击"渲染至Canvas"生成图像后再打印', 'error');
|
|
|
+ // this.showMessage('请先点击"渲染至Canvas"生成图像后再打印', 'error');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -436,10 +955,10 @@
|
|
|
`);
|
|
|
printWindow.document.close();
|
|
|
|
|
|
- this.showMessage('✅ 已打开打印窗口,可选择另存为PDF', 'success');
|
|
|
+ // this.showMessage('✅ 已打开打印窗口,可选择另存为PDF', 'success');
|
|
|
} catch (err) {
|
|
|
console.error('打印失败', err);
|
|
|
- this.showMessage('打印失败,请检查浏览器设置', 'error');
|
|
|
+ // this.showMessage('打印失败,请检查浏览器设置', 'error');
|
|
|
}
|
|
|
},
|
|
|
|
|
|
@@ -449,29 +968,6 @@
|
|
|
this.$nextTick(() => {
|
|
|
this.renderToCanvas();
|
|
|
});
|
|
|
- },
|
|
|
-
|
|
|
- // 显示提示消息
|
|
|
- showMessage(msg, type) {
|
|
|
- const canvasResultDiv = document.querySelector('.canvas-result');
|
|
|
- const existingMsg = canvasResultDiv.querySelector('.temp-message');
|
|
|
- if (existingMsg) existingMsg.remove();
|
|
|
-
|
|
|
- const messageDiv = document.createElement('div');
|
|
|
- messageDiv.className = `temp-message message-${type}`;
|
|
|
- messageDiv.innerText = msg;
|
|
|
- messageDiv.style.fontSize = '12px';
|
|
|
- messageDiv.style.marginTop = '8px';
|
|
|
- messageDiv.style.padding = '4px 8px';
|
|
|
- messageDiv.style.borderRadius = '20px';
|
|
|
- messageDiv.style.backgroundColor =
|
|
|
- type === 'success' ? '#d1fae5' : '#fee2e2';
|
|
|
- messageDiv.style.color = type === 'success' ? '#065f46' : '#991b1b';
|
|
|
- canvasResultDiv.appendChild(messageDiv);
|
|
|
-
|
|
|
- setTimeout(() => {
|
|
|
- if (messageDiv) messageDiv.remove();
|
|
|
- }, 2500);
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
@@ -510,7 +1006,7 @@
|
|
|
h1 {
|
|
|
font-size: 2rem;
|
|
|
font-weight: 700;
|
|
|
- background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb4d);
|
|
|
+ background: linear-gradient(135deg, #1a2a6c, #3b82f6, #8b5cf6);
|
|
|
background-clip: text;
|
|
|
-webkit-background-clip: text;
|
|
|
color: transparent;
|
|
|
@@ -529,10 +1025,7 @@
|
|
|
|
|
|
.converter-layout {
|
|
|
display: flex;
|
|
|
- flex-wrap: nowrap;
|
|
|
- gap: 2rem;
|
|
|
- margin-top: 0.5rem;
|
|
|
- overflow-x: auto;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
|
|
|
.editor-panel {
|
|
|
@@ -627,13 +1120,13 @@
|
|
|
}
|
|
|
|
|
|
button.pdf {
|
|
|
- background: #ef4444;
|
|
|
- border-color: #ef4444;
|
|
|
+ background: #8b5cf6;
|
|
|
+ border-color: #8b5cf6;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
button.pdf:hover {
|
|
|
- background: #dc2626;
|
|
|
+ background: #7c3aed;
|
|
|
transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
@@ -671,7 +1164,7 @@
|
|
|
|
|
|
.preview-panel {
|
|
|
flex: 1;
|
|
|
- width: 100%;
|
|
|
+ min-width: 0;
|
|
|
background: #ffffff;
|
|
|
border-radius: 1.5rem;
|
|
|
padding: 1.25rem;
|
|
|
@@ -690,17 +1183,19 @@
|
|
|
border: 1px solid #eef2ff;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- position: sticky;
|
|
|
- top: 20px;
|
|
|
+ position: fixed;
|
|
|
+ right: 2rem;
|
|
|
+ top: 40px;
|
|
|
max-height: calc(100vh - 80px);
|
|
|
overflow-y: auto;
|
|
|
+ z-index: 100;
|
|
|
}
|
|
|
|
|
|
.images-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
gap: 12px;
|
|
|
- flex: 1;
|
|
|
+ /* flex: 1; */
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
@@ -714,6 +1209,8 @@
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
+ height: 130px; /* 固定高度:图片80px + 内边距8px*2 + 文字区域 */
|
|
|
+ justify-content: space-between;
|
|
|
}
|
|
|
|
|
|
.drag-item:hover {
|
|
|
@@ -726,9 +1223,10 @@
|
|
|
.drag-item img {
|
|
|
width: 100%;
|
|
|
height: 80px;
|
|
|
- object-fit: cover;
|
|
|
+ object-fit: contain; /* 改为contain,确保完整显示图片 */
|
|
|
border-radius: 8px;
|
|
|
pointer-events: none;
|
|
|
+ background-color: #f1f5f9; /* 添加背景色,防止透明图片看不清 */
|
|
|
}
|
|
|
|
|
|
.drag-item-name {
|
|
|
@@ -740,6 +1238,9 @@
|
|
|
text-overflow: ellipsis;
|
|
|
white-space: nowrap;
|
|
|
width: 100%;
|
|
|
+ line-height: 1.4;
|
|
|
+ padding: 0 4px;
|
|
|
+ max-height: 32px; /* 限制文字最大高度 */
|
|
|
}
|
|
|
|
|
|
.preview-header {
|
|
|
@@ -814,8 +1315,26 @@
|
|
|
.html-editor {
|
|
|
height: 260px;
|
|
|
}
|
|
|
+ .converter-layout {
|
|
|
+ justify-content: center;
|
|
|
+ /* flex-wrap: wrap;
|
|
|
+ padding-right: 0;
|
|
|
+ gap: 1rem; */
|
|
|
+ }
|
|
|
.images-panel {
|
|
|
- flex: 0 0 auto;
|
|
|
+ position: relative;
|
|
|
+ right: auto;
|
|
|
+ top: auto;
|
|
|
+ width: 100%;
|
|
|
+ max-height: 300px; /* 在小屏幕下限制最大高度 */
|
|
|
+ order: 2; /* 在移动端将图片区域放在底部 */
|
|
|
+ margin-top: 1rem;
|
|
|
+ }
|
|
|
+ .drag-item {
|
|
|
+ height: 120px; /* 在小屏幕下稍微减小高度 */
|
|
|
+ }
|
|
|
+ .drag-item img {
|
|
|
+ height: 70px; /* 在小屏幕下减小图片高度 */
|
|
|
}
|
|
|
}
|
|
|
</style>
|