yusheng 2 months ago
parent
commit
bf5351797e

+ 1 - 1
package.json

@@ -40,7 +40,7 @@
     "js-message": "^1.0.7",
     "jsbarcode": "^3.11.5",
     "json-bigint": "^1.0.0",
-    "jspdf": "^2.5.1",
+    "jspdf": "^2.5.2",
     "mathjs": "^13.1.1",
     "nprogress": "^0.2.0",
     "qrcode": "^1.5.3",

BIN
src/assets/0.jpg


BIN
src/assets/1.jpg


BIN
src/assets/2.jpg


BIN
src/assets/3.jpg


BIN
src/assets/4.jpg


BIN
src/assets/5.jpg


BIN
src/assets/canvas-2026-04-03T01-55-17.png


File diff suppressed because it is too large
+ 180 - 0
src/assets/canvas-2026-04-03T01-56-56.pdf


+ 404 - 100
src/views/doc/components/aa.vue

@@ -1,108 +1,80 @@
 <template>
   <div class="app-container">
     <div class="tool-card">
-      <h1>📸 HTML → Canvas</h1>
-      <div class="sub"
-        >实时编辑HTML/CSS,一键渲染为高质量Canvas图像,支持下载PNG</div
-      >
-
       <div class="converter-layout">
-        <!-- 左侧: HTML 编辑器 -->
-        <div class="editor-panel">
-          <div class="panel-title"> <i>✍️</i> 编辑HTML内容 </div>
-          <textarea
-            v-model="editableHtml"
-            class="html-editor"
-            spellcheck="false"
-            @input="handleHtmlInput"
-          ></textarea>
-          <div class="toolbar">
-            <button class="primary" @click="renderToCanvas"
-              >✨ 渲染至Canvas</button
-            >
-            <button @click="resetToDefault">🔄 重置示例</button>
-            <button class="download" @click="downloadCanvasAsImage"
-              >⬇️ 下载为PNG</button
-            >
-          </div>
-          <div class="info-message">
-            💡
-            提示:支持任意HTML/CSS样式,外部图片需支持跨域或同源。复杂布局推荐使用内联样式。
-          </div>
-        </div>
-
         <!-- 右侧: 预览 & Canvas 结果 -->
+        <div id="captureTarget" style="position: fixed; left: -100000px">
+          <div v-html="editableHtml"> </div>
+        </div>
         <div class="preview-panel">
-          <div class="preview-header">
-            <span
-              class="panel-title"
-              style="border: none; padding: 0; margin: 0"
-              >🎨 原始DOM预览</span
-            >
-            <span class="badge">实时渲染区域</span>
-          </div>
-          <div class="canvas-area">
-            <div
-              id="captureTarget"
-              class="target-element"
-              v-html="previewHtml"
-            ></div>
-          </div>
-
-          <hr />
-          <div class="preview-header">
-            <span
-              class="panel-title"
-              style="border: none; padding: 0; margin: 0"
-              >🖼️ Canvas输出</span
-            >
-            <span class="badge">点击渲染后生成</span>
-          </div>
           <div class="canvas-result">
-            <p v-show="!canvasGenerated"
-              >✨ 点击 "渲染至Canvas" 将上方内容转为图像</p
-            >
             <canvas
               id="outputCanvas"
-              style="max-width: 100%"
+              style="width: 1000px"
               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 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>
-      <footer>
-        ⚡ 基于 html2canvas 引擎 | 精确捕获DOM节点样式,生成图片支持下载
-      </footer>
     </div>
   </div>
 </template>
 
 <script>
-  // 引入 html2canvas
+  // 引入 html2canvas 和 jsPDF
   import html2canvas from 'html2canvas';
-
+  import jsPDF from 'jspdf';
+  const imageUrl = [
+    require('@/assets/0.jpg'),
+    require('@/assets/1.jpg'),
+    require('@/assets/2.jpg'),
+    require('@/assets/3.jpg'),
+    require('@/assets/4.jpg'),
+    require('@/assets/5.jpg')
+  ]; // 使用相对路径或别名路径
   // 默认精美的示例HTML + CSS (内联样式丰富,展示能力)
   const DEFAULT_HTML = `
-  <div style="font-family: 'Segoe UI', system-ui, sans-serif; max-width: 100%; background: linear-gradient(125deg, #f9f3e6, #fff4e8); border-radius: 24px; padding: 1.2rem; box-shadow: 0 10px 20px rgba(0,0,0,0.08);">
-    <div style="display: flex; align-items: center; gap: 14px; border-bottom: 2px dashed #ffb347; padding-bottom: 12px; margin-bottom: 16px;">
-      <div style="background: #ff8c42; width: 48px; height: 48px; border-radius: 60px; display: flex; align-items: center; justify-content: center; color: white; font-size: 28px;">🎨</div>
-      <div><h2 style="margin:0; color:#3b2e24;">创意工坊</h2><p style="margin:0; color:#a5673f; font-size: 12px;">HTML2Canvas 完美转换</p></div>
-    </div>
-    <div style="margin-bottom: 16px;">
-      <span style="background: #ffd966; padding: 4px 12px; border-radius: 40px; font-size: 12px; font-weight: bold;">✨ 支持渐变 & 阴影</span>
-      <span style="background: #b0d9b1; margin-left: 8px; padding: 4px 12px; border-radius: 40px; font-size: 12px;">🖌️ 复杂样式</span>
-    </div>
-    <div style="display: flex; gap: 16px; flex-wrap: wrap; margin: 16px 0;">
-      <div style="background: white; border-radius: 20px; padding: 10px 14px; flex:1; box-shadow: 0 2px 6px rgba(0,0,0,0.05);"><strong>📦 卡片样式</strong><br>圆角+阴影</div>
-      <div style="background: #eef0f5; border-radius: 20px; padding: 10px 14px; flex:1;"><strong>🌈 灵活性</strong><br>任意布局</div>
-    </div>
-    <div style="background: #2d3748; color: white; border-radius: 20px; padding: 12px; text-align: center; font-weight: 500;">
-      ⚡ 可编辑HTML,实时渲染成 Canvas 图像
-    </div>
-    <div style="margin-top: 14px; font-size: 11px; color: #9e7e5e; text-align: center; border-top: 1px solid #ffe0b5; padding-top: 10px;">
-      🖱️ 点击下载按钮保存为高清PNG
-    </div>
-  </div>
+ <div class="container">
+        <div class="img-area">
+            <img class="my-photo" alt="loading" src="${imageUrl[0]}">
+        </div>
+        <div class="img-area">
+            <img class="my-photo" alt="loading"  src="${imageUrl[1]}">
+        </div>
+             <div class="img-area">
+            <img class="my-photo" alt="loading"  src="${imageUrl[2]}">
+        </div>      <div class="img-area">
+            <img class="my-photo" alt="loading"  src="${imageUrl[3]}">
+        </div>      <div class="img-area">
+            <img class="my-photo" alt="loading"  src="${imageUrl[4]}">
+        </div>      <div class="img-area">
+            <img class="my-photo" alt="loading"  src="${imageUrl[5]}">
+        </div>
+</div>
 `;
 
   export default {
@@ -111,21 +83,109 @@
       return {
         editableHtml: DEFAULT_HTML,
         previewHtml: DEFAULT_HTML,
-        canvasGenerated: false
+        canvasGenerated: false,
+        dragImages: [
+          { name: '图片1', url: require('@/assets/ic-403.svg') },
+          { name: '图片2', url: require('@/assets/ic-404.svg') },
+          { name: '图片3', url: require('@/assets/ic-500.svg') }
+        ],
+        droppedImages: [],
+        selectedImage: null
       };
     },
     mounted() {
       // 组件挂载后自动渲染一次
       this.$nextTick(() => {
-        this.renderToCanvas();
+        // this.renderToCanvas();
       });
     },
     methods: {
-      // 处理HTML输入,实时更新预览
-      handleHtmlInput() {
-        this.previewHtml = this.editableHtml;
-        // 编辑后隐藏之前的canvas结果
-        this.canvasGenerated = false;
+      // 设置 Canvas 拖放区域
+      setupCanvasDropZone() {
+        const canvas = document.getElementById('outputCanvas');
+        if (!canvas) {
+          console.log('Canvas 元素不存在');
+          return;
+        }
+
+        console.log('设置拖放区域');
+
+        canvas.addEventListener('dragover', (e) => {
+          e.preventDefault();
+          e.dataTransfer.dropEffect = 'copy';
+        });
+
+        canvas.addEventListener('drop', (e) => {
+          e.preventDefault();
+          console.log('触发 drop 事件');
+          const imageData = e.dataTransfer.getData('imageData');
+          console.log('获取到的 imageData:', imageData);
+          if (imageData) {
+            const data = JSON.parse(imageData);
+            console.log('解析后的数据:', data);
+            this.addImageToCanvas(data.url, e.offsetX, e.offsetY);
+          }
+        });
+      },
+
+      // 处理拖拽开始
+      handleDragStart(e, img, index) {
+        const data = {
+          url: img.url,
+          name: img.name
+        };
+        e.dataTransfer.setData('imageData', JSON.stringify(data));
+        e.dataTransfer.effectAllowed = 'copy';
+        console.log('拖拽开始:', data);
+      },
+
+      // 添加图片到 Canvas
+      addImageToCanvas(imgUrl, x, y) {
+        const canvas = document.getElementById('outputCanvas');
+        if (!canvas || !this.canvasGenerated) {
+          this.showMessage('请先渲染 Canvas', 'error');
+          return;
+        }
+
+        console.log('Canvas 尺寸:', canvas.width, canvas.height);
+        console.log('拖放位置:', x, y);
+
+        const ctx = canvas.getContext('2d');
+        const img = new Image();
+        img.crossOrigin = 'anonymous';
+
+        img.onload = () => {
+          console.log('图片加载成功,原始尺寸:', img.width, img.height);
+
+          // 默认图片大小为 100x100
+          const width = 100;
+          const height = 100;
+          const posX = x - width / 2;
+          const posY = y - height / 2;
+
+          console.log('绘制位置:', posX, posY, '尺寸:', width, height);
+
+          // 绘制图片
+          ctx.drawImage(img, posX, posY, width, height);
+
+          this.droppedImages.push({
+            url: imgUrl,
+            x: posX,
+            y: posY,
+            width,
+            height
+          });
+
+          console.log('已添加的图片数量:', this.droppedImages.length);
+          this.showMessage('✅ 图片已添加', 'success');
+        };
+
+        img.onerror = (err) => {
+          console.error('图片加载失败:', err);
+          this.showMessage('❌ 图片加载失败', 'error');
+        };
+
+        img.src = imgUrl;
       },
 
       // 渲染Canvas核心函数
@@ -156,12 +216,13 @@
         try {
           // 使用 html2canvas 进行转换
           const canvas = await html2canvas(targetElement, {
-            scale: 2, // 高清输出,2倍分辨率
+            scale: 1, // 1倍分辨率,避免过大
             backgroundColor: '#ffffff',
             useCORS: true, // 尝试跨域图片
             logging: false,
             allowTaint: false,
             imageTimeout: 5000,
+            width: 1000, // 设置输出宽度为 1000px
             windowWidth: targetElement.scrollWidth,
             windowHeight: targetElement.scrollHeight
           });
@@ -174,12 +235,18 @@
           const outputCanvas = document.getElementById('outputCanvas');
           outputCanvas.width = canvas.width;
           outputCanvas.height = canvas.height;
+          // 移除内联样式,使用实际像素尺寸
+          outputCanvas.style.width = canvas.width + 'px';
+          outputCanvas.style.height = canvas.height + 'px';
           const ctx = outputCanvas.getContext('2d');
           ctx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
           ctx.drawImage(canvas, 0, 0);
 
           this.canvasGenerated = true;
 
+          // 设置 Canvas 拖放区域
+          this.setupCanvasDropZone();
+
           // 显示成功提示
           this.showMessage('✅ 渲染成功!可下载图片', 'success');
         } catch (error) {
@@ -199,7 +266,7 @@
           !outputCanvas ||
           outputCanvas.width === 0
         ) {
-          this.showMessage('请先点击“渲染至Canvas”生成图像后再下载', 'error');
+          this.showMessage('请先点击"渲染至Canvas"生成图像后再下载', 'error');
           return;
         }
 
@@ -222,18 +289,166 @@
         }
       },
 
-      // 重置为默认示例
-      resetToDefault() {
-        this.editableHtml = DEFAULT_HTML;
-        this.previewHtml = DEFAULT_HTML;
-        this.canvasGenerated = false;
-        this.$nextTick(() => {
-          this.renderToCanvas();
+      // 导出为 PDF(使用 jsPDF)
+      exportToPDF() {
+        const outputCanvas = document.getElementById('outputCanvas');
+        if (
+          !this.canvasGenerated ||
+          !outputCanvas ||
+          outputCanvas.width === 0
+        ) {
+          this.showMessage(
+            '请先点击"渲染至Canvas"生成图像后再导出PDF',
+            'error'
+          );
+          return;
+        }
+
+        try {
+          const imgData = outputCanvas.toDataURL('image/png');
+          const canvasWidth = outputCanvas.width;
+          const canvasHeight = outputCanvas.height;
+
+          // 创建 PDF 实例,横向或纵向根据 Canvas 尺寸决定
+          const orientation = canvasWidth > canvasHeight ? 'l' : 'p';
+          const pdf = new jsPDF(orientation, 'px', [canvasWidth, canvasHeight]);
+
+          // 将 Canvas 图片添加到 PDF
+          pdf.addImage(imgData, 'PNG', 0, 0, canvasWidth, canvasHeight);
+
+          // 获取 PDF 文件流并触发事件
+          const pdfBlob = pdf.output('blob');
+          const pdfFile = new File([pdfBlob], `canvas-${Date.now()}.pdf`, {
+            type: 'application/pdf'
+          });
+
+          // 触发 PDF 生成事件,父组件可以监听此事件
+          this.$emit('pdf-generated', pdfFile);
+
+          // 保存 PDF
+          const timestamp = new Date()
+            .toISOString()
+            .slice(0, 19)
+            .replace(/:/g, '-');
+          pdf.save(`canvas-${timestamp}.pdf`);
+
+          this.showMessage('✅ PDF 导出成功', 'success');
+        } catch (err) {
+          console.error('PDF 导出失败', err);
+          this.showMessage('PDF 导出失败,请稍后重试', 'error');
+        }
+      },
+
+      // 获取 PDF 文件流(用于直接上传)
+      getPDFFile() {
+        const outputCanvas = document.getElementById('outputCanvas');
+        if (
+          !this.canvasGenerated ||
+          !outputCanvas ||
+          outputCanvas.width === 0
+        ) {
+          this.showMessage(
+            '请先点击"渲染至Canvas"生成图像后再获取PDF',
+            'error'
+          );
+          return Promise.reject(new Error('Canvas 未生成'));
+        }
+
+        return new Promise((resolve, reject) => {
+          try {
+            const imgData = outputCanvas.toDataURL('image/png');
+            const canvasWidth = outputCanvas.width;
+            const canvasHeight = outputCanvas.height;
+
+            // 创建 PDF 实例
+            const orientation = canvasWidth > canvasHeight ? 'l' : 'p';
+            const pdf = new jsPDF(orientation, 'px', [
+              canvasWidth,
+              canvasHeight
+            ]);
+
+            // 将 Canvas 图片添加到 PDF
+            pdf.addImage(imgData, 'PNG', 0, 0, canvasWidth, canvasHeight);
+
+            // 获取 PDF 文件流
+            const pdfBlob = pdf.output('blob');
+            const pdfFile = new File([pdfBlob], `canvas-${Date.now()}.pdf`, {
+              type: 'application/pdf'
+            });
+
+            resolve(pdfFile);
+          } catch (err) {
+            console.error('获取 PDF 文件流失败', err);
+            reject(err);
+          }
         });
       },
+
+      // 打印 Canvas / 另存为 PDF
+      printCanvas() {
+        const outputCanvas = document.getElementById('outputCanvas');
+        if (
+          !this.canvasGenerated ||
+          !outputCanvas ||
+          outputCanvas.width === 0
+        ) {
+          this.showMessage('请先点击"渲染至Canvas"生成图像后再打印', 'error');
+          return;
+        }
+
+        try {
+          // 创建新窗口用于打印
+          const printWindow = window.open('', '_blank');
+          const dataUrl = outputCanvas.toDataURL('image/png');
+
+          printWindow.document.write(`
+            <!DOCTYPE html>
+            <html>
+            <head>
+              <title>打印图片</title>
+              <style>
+                body {
+                  margin: 0;
+                  padding: 20px;
+                  display: flex;
+                  justify-content: center;
+                  align-items: center;
+                }
+                img {
+                  max-width: 100%;
+                  height: auto;
+                }
+                @media print {
+                  body {
+                    padding: 0;
+                  }
+                  img {
+                    max-width: 100%;
+                    page-break-inside: avoid;
+                  }
+                }
+              </style>
+            </head>
+            <body>
+              <img src="${dataUrl}" onload="window.print(); window.close();" />
+            </body>
+            </html>
+          `);
+          printWindow.document.close();
+
+          this.showMessage('✅ 已打开打印窗口,可选择另存为PDF', 'success');
+        } catch (err) {
+          console.error('打印失败', err);
+          this.showMessage('打印失败,请检查浏览器设置', 'error');
+        }
+      },
+
       init(html) {
         this.editableHtml = html;
         this.previewHtml = html;
+        this.$nextTick(() => {
+          this.renderToCanvas();
+        });
       },
 
       // 显示提示消息
@@ -410,6 +625,35 @@
     transform: translateY(-1px);
   }
 
+  button.pdf {
+    background: #ef4444;
+    border-color: #ef4444;
+    color: white;
+  }
+
+  button.pdf:hover {
+    background: #dc2626;
+    transform: translateY(-1px);
+  }
+
+  button.print {
+    background: #8b5cf6;
+    border-color: #8b5cf6;
+    color: white;
+  }
+
+  button.print:hover {
+    background: #7c3aed;
+    transform: translateY(-1px);
+  }
+
+  .canvas-buttons {
+    display: flex;
+    gap: 12px;
+    justify-content: center;
+    margin-top: 16px;
+  }
+
   button:hover {
     background: #f1f5f9;
     transform: translateY(-1px);
@@ -426,7 +670,7 @@
 
   .preview-panel {
     flex: 1;
-    min-width: 320px;
+    width: 100%;
     background: #ffffff;
     border-radius: 1.5rem;
     padding: 1.25rem;
@@ -436,6 +680,63 @@
     flex-direction: column;
   }
 
+  .images-panel {
+    flex: 0 0 250px;
+    background: #ffffff;
+    border-radius: 1.5rem;
+    padding: 1.25rem;
+    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
+    border: 1px solid #eef2ff;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .images-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+    flex: 1;
+    overflow-y: auto;
+  }
+
+  .drag-item {
+    background: #f8fafc;
+    border: 2px solid #e2e8f0;
+    border-radius: 12px;
+    padding: 8px;
+    cursor: move;
+    transition: all 0.2s;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+
+  .drag-item:hover {
+    border-color: #3b82f6;
+    background: #eff6ff;
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
+  }
+
+  .drag-item img {
+    width: 100%;
+    height: 80px;
+    object-fit: cover;
+    border-radius: 8px;
+    pointer-events: none;
+  }
+
+  .drag-item-name {
+    font-size: 12px;
+    color: #64748b;
+    margin-top: 6px;
+    text-align: center;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    width: 100%;
+  }
+
   .preview-header {
     display: flex;
     justify-content: space-between;
@@ -508,5 +809,8 @@
     .html-editor {
       height: 260px;
     }
+    .images-panel {
+      flex: 0 0 auto;
+    }
   }
 </style>

+ 4 - 4
src/views/doc/components/browse.vue

@@ -22,15 +22,15 @@
       append-to-body
       :fullscreen="true"
     >
-      <iframe
+      <!-- <iframe
         :src="fileUrl"
         width="60%"
         v-if="showEditFlag"
         style="height: calc(100vh - 100px)"
         frameborder="0"
         allowfullscreen="true"
-      ></iframe>
-      <aa ref="aa" style="width: 40%"></aa>
+      ></iframe> -->
+      <aa ref="aa"></aa>
     </ele-modal>
   </div>
 </template>
@@ -54,7 +54,7 @@
     methods: {
       open() {
         // window.open(this.fileUrl);
-
+        this.showEditFlag = true;
         var iframe = document.getElementById('Iframe');
         var iframeDocument = iframe.contentWindow.document;
         var container = iframeDocument.querySelectorAll('.container');

Some files were not shown because too many files changed in this diff