|
|
@@ -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>
|