From 02f3adb936590dcc6ed7e346e3d5d5a3b526df59 Mon Sep 17 00:00:00 2001 From: Zhukj <3262118957> Date: Sat, 25 Apr 2026 15:13:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E9=82=80=E8=AF=B7=E5=87=BD):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=8C=E7=BB=B4=E7=A0=81=E7=94=9F=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增二维码按钮及弹窗界面 - 实现二维码生成、预览和添加到画布功能 - 支持二维码在画布上的拖拽操作 - 添加相关样式和事件处理 --- css/qr.css | 136 ++++++++++++++++++++++++++++++++++++++ index.html | 22 +++++++ js/qr.js | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++ js/script.js | 29 +++++++++ 4 files changed, 366 insertions(+) create mode 100644 css/qr.css create mode 100644 js/qr.js diff --git a/css/qr.css b/css/qr.css new file mode 100644 index 0000000..fe0dc40 --- /dev/null +++ b/css/qr.css @@ -0,0 +1,136 @@ +/* 二维码功能样式 */ + +/* 悬浮按钮 */ +#qr-btn { + position: fixed; + bottom: 100px; + left: 20px; + width: 44px; + height: 44px; + padding: 0; + border-radius: 50%; + background: #4a90e2; + color: #fff; + border: none; + font-size: 12px; + font-weight: bold; + cursor: pointer; + box-shadow: 0 2px 8px rgba(0,0,0,0.3); + z-index: 1000; + display: none; + white-space: nowrap; +} + +#qr-btn.show { + display: block; +} + +#qr-btn:hover { + background: #3a7bc8; +} + +/* 弹窗 */ +#qr-modal { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + z-index: 10000; + justify-content: center; + align-items: center; +} + +#qr-modal.active { + display: flex; +} + +.qr-box { + background: #fff; + border-radius: 8px; + width: 320px; + max-width: 90%; +} + +.qr-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + border-bottom: 1px solid #eee; +} + +.qr-header h3 { + margin: 0; + font-size: 16px; +} + +.qr-close { + background: none; + border: none; + font-size: 24px; + color: #999; + cursor: pointer; +} + +.qr-body { + padding: 20px; +} + +#qr-input { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + box-sizing: border-box; +} + +#qr-preview { + margin-top: 15px; + min-height: 128px; + display: flex; + justify-content: center; + align-items: center; + background: #f5f5f5; + border-radius: 4px; +} + +#qr-preview canvas { + display: block; +} + +.qr-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 15px 20px; + border-top: 1px solid #eee; +} + +.qr-btn { + padding: 8px 16px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +.qr-cancel { + background: #f0f0f0; + color: #333; +} + +.qr-ok { + background: #4a90e2; + color: #fff; +} + +/* 画布上的二维码 */ +.qr-item { + position: absolute; + cursor: move; + user-select: none; +} diff --git a/index.html b/index.html index 6da74b6..b6d58ce 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ 极简邀请函制作 +
@@ -136,6 +137,27 @@ + + +
+
+
+

生成二维码

+ +
+
+ +
+
+ +
+
+ + + diff --git a/js/qr.js b/js/qr.js new file mode 100644 index 0000000..43fb0cd --- /dev/null +++ b/js/qr.js @@ -0,0 +1,179 @@ +// 二维码功能 - 简洁可靠 +(function() { + 'use strict'; + + // 获取元素 + const btn = document.getElementById('qr-btn'); + const modal = document.getElementById('qr-modal'); + const input = document.getElementById('qr-input'); + const preview = document.getElementById('qr-preview'); + const closeBtn = document.querySelector('.qr-close'); + const cancelBtn = document.querySelector('.qr-cancel'); + const okBtn = document.querySelector('.qr-ok'); + + if (!btn || !modal) return; + + let currentQR = null; + let qrCount = 0; + + // 检查背景图并显示按钮 + function checkBg() { + const container = document.getElementById('imageContainer'); + if (!container) return; + // 排除二维码图片,只检查背景图 + const img = container.querySelector('img:not([data-type="qrcode"])'); + if (img && img.src && !img.src.endsWith(window.location.href)) { + btn.classList.add('show'); + } else { + btn.classList.remove('show'); + } + } + + // 监听背景图变化 + const observer = new MutationObserver(checkBg); + const container = document.getElementById('imageContainer'); + if (container) { + observer.observe(container, { childList: true, subtree: true }); + } + setInterval(checkBg, 500); + + // 打开弹窗 + function openModal() { + modal.classList.add('active'); + input.value = ''; + preview.innerHTML = ''; + currentQR = null; + input.focus(); + } + + // 关闭弹窗 + function closeModal() { + modal.classList.remove('active'); + } + + // 生成预览 + function genPreview() { + const text = input.value.trim(); + if (!text) { + preview.innerHTML = ''; + currentQR = null; + return; + } + preview.innerHTML = ''; + try { + currentQR = new QRCode(preview, { + text: text, + width: 128, + height: 128 + }); + } catch (e) { + console.error('生成失败', e); + } + } + + // 添加到画布 + function addToCanvas() { + if (!currentQR) { + alert('请输入内容'); + return; + } + const canvas = preview.querySelector('canvas'); + if (!canvas) return; + + const img = document.createElement('img'); + img.src = canvas.toDataURL(); + img.className = 'qr-item'; + img.dataset.type = 'qrcode'; + img.style.width = '100px'; + img.style.height = '100px'; + + const container = document.getElementById('imageContainer'); + const rect = container.getBoundingClientRect(); + img.style.left = (rect.width / 2 - 50) + 'px'; + img.style.top = (rect.height / 2 - 50) + 'px'; + + container.appendChild(img); + makeDraggable(img); + closeModal(); + + if (typeof saveState === 'function') saveState(); + } + + // 拖拽功能 - 支持鼠标和触摸 + function makeDraggable(el) { + let dragging = false; + let startX, startY, startLeft, startTop; + + function startDrag(clientX, clientY) { + dragging = true; + startX = clientX; + startY = clientY; + startLeft = parseInt(el.style.left) || 0; + startTop = parseInt(el.style.top) || 0; + } + + function moveDrag(clientX, clientY) { + if (!dragging) return; + el.style.left = (startLeft + clientX - startX) + 'px'; + el.style.top = (startTop + clientY - startY) + 'px'; + } + + function endDrag() { + if (dragging) { + dragging = false; + if (typeof saveState === 'function') saveState(); + } + } + + // 鼠标事件 + el.addEventListener('mousedown', function(e) { + e.preventDefault(); + e.stopPropagation(); + startDrag(e.clientX, e.clientY); + }); + + // 触摸事件 + el.addEventListener('touchstart', function(e) { + e.preventDefault(); + e.stopPropagation(); + if (e.touches.length === 1) { + startDrag(e.touches[0].clientX, e.touches[0].clientY); + } + }, { passive: false }); + + // 全局移动事件 + document.addEventListener('mousemove', function(e) { + moveDrag(e.clientX, e.clientY); + }); + + document.addEventListener('touchmove', function(e) { + if (dragging && e.touches.length === 1) { + e.preventDefault(); + moveDrag(e.touches[0].clientX, e.touches[0].clientY); + } + }, { passive: false }); + + // 全局结束事件 + document.addEventListener('mouseup', endDrag); + document.addEventListener('touchend', endDrag); + } + + // 事件绑定 + btn.addEventListener('click', openModal); + closeBtn.addEventListener('click', closeModal); + cancelBtn.addEventListener('click', closeModal); + okBtn.addEventListener('click', addToCanvas); + modal.addEventListener('click', function(e) { + if (e.target === modal) closeModal(); + }); + + // 输入防抖 + let timer; + input.addEventListener('input', function() { + clearTimeout(timer); + timer = setTimeout(genPreview, 300); + }); + + // 初始检查 + checkBg(); +})(); diff --git a/js/script.js b/js/script.js index 53c076e..f2fd6ae 100644 --- a/js/script.js +++ b/js/script.js @@ -3172,6 +3172,35 @@ drawTextOnCanvasHD(ctx, textInput, scale); } }); + + // 绘制二维码 + const qrImages = container.querySelectorAll('img[data-type="qrcode"]'); + for (const qrImg of qrImages) { + if (!qrImg.classList.contains('layer-hidden')) { + const rect = qrImg.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + const x = (rect.left - containerRect.left) * scale; + const y = (rect.top - containerRect.top) * scale; + const w = rect.width * scale; + const h = rect.height * scale; + + // 确保图片已加载 + if (qrImg.complete && qrImg.naturalWidth > 0) { + ctx.drawImage(qrImg, x, y, w, h); + } else { + // 等待图片加载完成 + await new Promise(resolve => { + qrImg.onload = () => { + ctx.drawImage(qrImg, x, y, w, h); + resolve(); + }; + qrImg.onerror = () => resolve(); + // 超时处理 + setTimeout(resolve, 100); + }); + } + } + } // 记录总耗时 const endTime = performance.now();