feat(邀请函): 添加二维码生成功能

- 新增二维码按钮及弹窗界面
- 实现二维码生成、预览和添加到画布功能
- 支持二维码在画布上的拖拽操作
- 添加相关样式和事件处理
This commit is contained in:
Zhukj
2026-04-25 15:13:15 +08:00
parent 81f63781a9
commit 02f3adb936
4 changed files with 366 additions and 0 deletions

179
js/qr.js Normal file
View File

@@ -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();
})();

View File

@@ -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();