|
@@ -0,0 +1,512 @@
|
|
|
|
|
+<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 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%"
|
|
|
|
|
+ v-show="canvasGenerated"
|
|
|
|
|
+ ></canvas>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <footer>
|
|
|
|
|
+ ⚡ 基于 html2canvas 引擎 | 精确捕获DOM节点样式,生成图片支持下载
|
|
|
|
|
+ </footer>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+ // 引入 html2canvas
|
|
|
|
|
+ import html2canvas from 'html2canvas';
|
|
|
|
|
+
|
|
|
|
|
+ // 默认精美的示例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>
|
|
|
|
|
+`;
|
|
|
|
|
+
|
|
|
|
|
+ export default {
|
|
|
|
|
+ name: 'HtmlToCanvas',
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ editableHtml: DEFAULT_HTML,
|
|
|
|
|
+ previewHtml: DEFAULT_HTML,
|
|
|
|
|
+ canvasGenerated: false
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ mounted() {
|
|
|
|
|
+ // 组件挂载后自动渲染一次
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.renderToCanvas();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ // 处理HTML输入,实时更新预览
|
|
|
|
|
+ handleHtmlInput() {
|
|
|
|
|
+ this.previewHtml = this.editableHtml;
|
|
|
|
|
+ // 编辑后隐藏之前的canvas结果
|
|
|
|
|
+ this.canvasGenerated = false;
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 渲染Canvas核心函数
|
|
|
|
|
+ async renderToCanvas() {
|
|
|
|
|
+ const targetElement = document.getElementById('captureTarget');
|
|
|
|
|
+ if (!targetElement) {
|
|
|
|
|
+ console.warn('未找到目标元素');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示加载状态
|
|
|
|
|
+ const canvasResultDiv = document.querySelector('.canvas-result');
|
|
|
|
|
+ const originalMsg = canvasResultDiv.querySelector('p');
|
|
|
|
|
+ if (originalMsg) originalMsg.style.display = 'none';
|
|
|
|
|
+
|
|
|
|
|
+ // 添加加载提示
|
|
|
|
|
+ let loadingMsg = document.getElementById('tempLoadingMsg');
|
|
|
|
|
+ if (!loadingMsg) {
|
|
|
|
|
+ loadingMsg = document.createElement('div');
|
|
|
|
|
+ loadingMsg.id = 'tempLoadingMsg';
|
|
|
|
|
+ loadingMsg.innerText = '⏳ 渲染中,请稍候...';
|
|
|
|
|
+ loadingMsg.style.padding = '12px';
|
|
|
|
|
+ loadingMsg.style.color = '#2563eb';
|
|
|
|
|
+ loadingMsg.style.fontSize = '14px';
|
|
|
|
|
+ canvasResultDiv.appendChild(loadingMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 使用 html2canvas 进行转换
|
|
|
|
|
+ const canvas = await html2canvas(targetElement, {
|
|
|
|
|
+ scale: 2, // 高清输出,2倍分辨率
|
|
|
|
|
+ backgroundColor: '#ffffff',
|
|
|
|
|
+ useCORS: true, // 尝试跨域图片
|
|
|
|
|
+ logging: false,
|
|
|
|
|
+ allowTaint: false,
|
|
|
|
|
+ imageTimeout: 5000,
|
|
|
|
|
+ windowWidth: targetElement.scrollWidth,
|
|
|
|
|
+ windowHeight: targetElement.scrollHeight
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 移除加载提示
|
|
|
|
|
+ const temp = document.getElementById('tempLoadingMsg');
|
|
|
|
|
+ if (temp) temp.remove();
|
|
|
|
|
+
|
|
|
|
|
+ // 获取输出canvas元素并绘制
|
|
|
|
|
+ const outputCanvas = document.getElementById('outputCanvas');
|
|
|
|
|
+ outputCanvas.width = canvas.width;
|
|
|
|
|
+ outputCanvas.height = canvas.height;
|
|
|
|
|
+ const ctx = outputCanvas.getContext('2d');
|
|
|
|
|
+ ctx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
|
|
|
|
|
+ ctx.drawImage(canvas, 0, 0);
|
|
|
|
|
+
|
|
|
|
|
+ this.canvasGenerated = true;
|
|
|
|
|
+
|
|
|
|
|
+ // 显示成功提示
|
|
|
|
|
+ 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');
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 下载Canvas为PNG
|
|
|
|
|
+ downloadCanvasAsImage() {
|
|
|
|
|
+ const outputCanvas = document.getElementById('outputCanvas');
|
|
|
|
|
+ if (
|
|
|
|
|
+ !this.canvasGenerated ||
|
|
|
|
|
+ !outputCanvas ||
|
|
|
|
|
+ outputCanvas.width === 0
|
|
|
|
|
+ ) {
|
|
|
|
|
+ this.showMessage('请先点击“渲染至Canvas”生成图像后再下载', 'error');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const link = document.createElement('a');
|
|
|
|
|
+ const timestamp = new Date()
|
|
|
|
|
+ .toISOString()
|
|
|
|
|
+ .slice(0, 19)
|
|
|
|
|
+ .replace(/:/g, '-');
|
|
|
|
|
+ link.download = `canvas-${timestamp}.png`;
|
|
|
|
|
+ link.href = outputCanvas.toDataURL('image/png');
|
|
|
|
|
+ link.click();
|
|
|
|
|
+ this.showMessage('✅ 下载已开始', 'success');
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('下载失败', err);
|
|
|
|
|
+ this.showMessage(
|
|
|
|
|
+ '无法下载图像,可能是跨域限制或Canvas被污染',
|
|
|
|
|
+ 'error'
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 重置为默认示例
|
|
|
|
|
+ resetToDefault() {
|
|
|
|
|
+ this.editableHtml = DEFAULT_HTML;
|
|
|
|
|
+ this.previewHtml = DEFAULT_HTML;
|
|
|
|
|
+ this.canvasGenerated = false;
|
|
|
|
|
+ this.$nextTick(() => {
|
|
|
|
|
+ this.renderToCanvas();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ init(html) {
|
|
|
|
|
+ this.editableHtml = html;
|
|
|
|
|
+ this.previewHtml = html;
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 显示提示消息
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+ * {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .app-container {
|
|
|
|
|
+ max-width: 1400px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ padding: 24px 20px;
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ background: linear-gradient(145deg, #e0eafc 0%, #cfdef3 100%);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tool-card {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.92);
|
|
|
|
|
+ backdrop-filter: blur(2px);
|
|
|
|
|
+ border-radius: 2rem;
|
|
|
|
|
+ box-shadow: 0 25px 45px -12px rgba(0, 0, 0, 0.3),
|
|
|
|
|
+ 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ padding: 1.8rem 2rem 2rem 2rem;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ h1 {
|
|
|
|
|
+ font-size: 2rem;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb4d);
|
|
|
|
|
+ background-clip: text;
|
|
|
|
|
+ -webkit-background-clip: text;
|
|
|
|
|
+ color: transparent;
|
|
|
|
|
+ letter-spacing: -0.3px;
|
|
|
|
|
+ margin-bottom: 0.35rem;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .sub {
|
|
|
|
|
+ color: #4a5568;
|
|
|
|
|
+ border-left: 4px solid #3b82f6;
|
|
|
|
|
+ padding-left: 14px;
|
|
|
|
|
+ margin-bottom: 1.8rem;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ font-size: 0.95rem;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .converter-layout {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 2rem;
|
|
|
|
|
+ margin-top: 0.5rem;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .editor-panel {
|
|
|
|
|
+ flex: 1.2;
|
|
|
|
|
+ min-width: 280px;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+ border-radius: 1.5rem;
|
|
|
|
|
+ padding: 1.25rem;
|
|
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .panel-title {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ font-size: 1.2rem;
|
|
|
|
|
+ color: #0f172a;
|
|
|
|
|
+ margin-bottom: 1rem;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ border-bottom: 2px solid #cbd5e1;
|
|
|
|
|
+ padding-bottom: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .html-editor {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 360px;
|
|
|
|
|
+ background: #1e293b;
|
|
|
|
|
+ color: #e2e8f0;
|
|
|
|
|
+ font-family: 'Fira Code', 'Cascadia Code', monospace;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ border-radius: 1rem;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ resize: vertical;
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+ margin-bottom: 1rem;
|
|
|
|
|
+ box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .html-editor:focus {
|
|
|
|
|
+ border: 1px solid #3b82f6;
|
|
|
|
|
+ background: #0f172a;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .toolbar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ button {
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border: 1px solid #cbd5e1;
|
|
|
|
|
+ padding: 8px 18px;
|
|
|
|
|
+ border-radius: 40px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ font-size: 0.85rem;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: 0.2s;
|
|
|
|
|
+ color: #1e293b;
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ button.primary {
|
|
|
|
|
+ background: #3b82f6;
|
|
|
|
|
+ border-color: #3b82f6;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ button.primary:hover {
|
|
|
|
|
+ background: #2563eb;
|
|
|
|
|
+ transform: translateY(-1px);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ button.download {
|
|
|
|
|
+ background: #10b981;
|
|
|
|
|
+ border-color: #10b981;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ button.download:hover {
|
|
|
|
|
+ background: #059669;
|
|
|
|
|
+ transform: translateY(-1px);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ button:hover {
|
|
|
|
|
+ background: #f1f5f9;
|
|
|
|
|
+ transform: translateY(-1px);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .info-message {
|
|
|
|
|
+ font-size: 0.8rem;
|
|
|
|
|
+ color: #2563eb;
|
|
|
|
|
+ background: #eff6ff;
|
|
|
|
|
+ padding: 6px 12px;
|
|
|
|
|
+ border-radius: 40px;
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .preview-panel {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 320px;
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .preview-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: baseline;
|
|
|
|
|
+ margin-bottom: 1rem;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .badge {
|
|
|
|
|
+ background: #e6f0ff;
|
|
|
|
|
+ padding: 4px 12px;
|
|
|
|
|
+ border-radius: 30px;
|
|
|
|
|
+ font-size: 0.75rem;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #1e40af;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .canvas-area {
|
|
|
|
|
+ background: #f1f5f9;
|
|
|
|
|
+ border-radius: 1.2rem;
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ min-height: 280px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .target-element {
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border-radius: 1rem;
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ box-shadow: 0 6px 14px rgba(0, 0, 0, 0.05);
|
|
|
|
|
+ max-width: 100%;
|
|
|
|
|
+ overflow: auto;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ hr {
|
|
|
|
|
+ margin: 0.8rem 0;
|
|
|
|
|
+ border-color: #e2e8f0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .canvas-result {
|
|
|
|
|
+ margin-top: 1rem;
|
|
|
|
|
+ background: #ffffffd9;
|
|
|
|
|
+ border-radius: 1rem;
|
|
|
|
|
+ padding: 0.8rem;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .canvas-result p {
|
|
|
|
|
+ font-size: 0.8rem;
|
|
|
|
|
+ color: #475569;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ canvas {
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ footer {
|
|
|
|
|
+ margin-top: 1.5rem;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 0.75rem;
|
|
|
|
|
+ color: #334155;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @media (max-width: 780px) {
|
|
|
|
|
+ .tool-card {
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ }
|
|
|
|
|
+ .html-editor {
|
|
|
|
|
+ height: 260px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|