Files
invitation-maker/js/qr.js
Zhukj 4ee95df792 feat(二维码): 添加删除按钮并重构二维码元素结构
- 为二维码元素添加删除按钮,仅在选中时显示
- 重构二维码元素结构,将图片包裹在容器div中
- 调整透明度设置逻辑以匹配新结构
- 防止删除按钮触发拖拽事件
2026-04-25 17:53:22 +08:00

431 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 二维码功能 - 优化版编辑器工具栏
(function() {
'use strict';
// ========== 配置 ==========
const CONFIG = {
MIN_SIZE: 0,
MAX_SIZE: 200,
DEFAULT_SIZE: 100,
PREVIEW_SIZE: 128,
DEBOUNCE_DELAY: 300
};
// ========== DOM 元素缓存 ==========
const elements = {
btn: document.getElementById('qr-btn'),
modal: document.getElementById('qr-modal'),
input: document.getElementById('qr-input'),
preview: document.getElementById('qr-preview'),
closeBtn: document.querySelector('.qr-close'),
cancelBtn: document.querySelector('.qr-cancel'),
okBtn: document.querySelector('.qr-ok'),
// 工具栏
editorToolbar: document.getElementById('editorToolbar'),
textTools: document.getElementById('textTools'),
imageTools: document.getElementById('imageTools'),
bgTools: document.getElementById('bgTools'),
qrTools: document.getElementById('qrTools'),
qrOpacitySlider: document.getElementById('qrOpacitySlider'),
qrOpacityValue: document.getElementById('qrOpacityValue'),
qrScaleSlider: document.getElementById('qrScaleSlider'),
qrScaleValue: document.getElementById('qrScaleValue')
};
// 验证必需元素
if (!elements.btn || !elements.modal) return;
// ========== 状态管理 ==========
const state = {
currentQR: null,
selectedQR: null,
previewTimer: null,
isDragging: false
};
// ========== 核心功能 ==========
/**
* 检查背景图并显示/隐藏二维码按钮
*/
function checkBackground() {
const container = document.getElementById('imageContainer');
if (!container) return;
const bgImg = container.querySelector('img:not([data-type="qrcode"])');
const hasBackground = bgImg && bgImg.src && !bgImg.src.endsWith(window.location.href);
elements.btn.classList.toggle('show', hasBackground);
}
/**
* 打开二维码生成弹窗
*/
function openModal() {
elements.modal.classList.add('active');
elements.input.value = '';
elements.preview.innerHTML = '';
state.currentQR = null;
elements.input.focus();
}
/**
* 关闭二维码生成弹窗
*/
function closeModal() {
elements.modal.classList.remove('active');
}
/**
* 生成二维码预览
*/
function generatePreview() {
const text = elements.input.value.trim();
if (!text) {
elements.preview.innerHTML = '';
state.currentQR = null;
return;
}
elements.preview.innerHTML = '';
try {
state.currentQR = new QRCode(elements.preview, {
text: text,
width: CONFIG.PREVIEW_SIZE,
height: CONFIG.PREVIEW_SIZE,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
});
} catch (error) {
console.error('二维码生成失败:', error);
}
}
/**
* 将二维码添加到画布
*/
function addToCanvas() {
if (!state.currentQR) {
alert('请输入内容');
return;
}
const canvas = elements.preview.querySelector('canvas');
if (!canvas) return;
const container = document.getElementById('imageContainer');
const rect = container.getBoundingClientRect();
// 创建二维码容器
const wrapper = document.createElement('div');
wrapper.className = 'qr-item';
wrapper.dataset.type = 'qrcode';
wrapper.style.cssText = `
position: absolute;
width: ${CONFIG.DEFAULT_SIZE}px;
height: ${CONFIG.DEFAULT_SIZE}px;
left: ${(rect.width / 2 - CONFIG.DEFAULT_SIZE / 2)}px;
top: ${(rect.height / 2 - CONFIG.DEFAULT_SIZE / 2)}px;
`;
// 创建二维码图片
const img = document.createElement('img');
img.src = canvas.toDataURL('image/png');
img.style.cssText = `
width: 100%;
height: 100%;
opacity: 1;
display: block;
`;
// 创建删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.className = 'qr-delete-btn';
deleteBtn.innerHTML = '×';
deleteBtn.title = '删除二维码';
deleteBtn.type = 'button';
// 删除按钮点击事件
deleteBtn.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
// 删除二维码元素
wrapper.remove();
// 清除选中状态
if (state.selectedQR === wrapper) {
state.selectedQR = null;
hideQRTools();
if (elements.editorToolbar) {
elements.editorToolbar.classList.remove('active');
}
}
// 保存状态
saveState();
return false;
};
wrapper.appendChild(img);
wrapper.appendChild(deleteBtn);
container.appendChild(wrapper);
makeDraggable(wrapper);
selectQR(wrapper);
closeModal();
saveState();
}
/**
* 选中二维码
*/
function selectQR(img) {
// 取消其他选中状态
document.querySelectorAll('.qr-item').forEach(el => {
el.classList.remove('qr-selected');
});
// 选中当前二维码
img.classList.add('qr-selected');
state.selectedQR = img;
// 显示二维码工具栏
showQRTools();
}
/**
* 显示二维码工具栏
*/
function showQRTools() {
if (!state.selectedQR) return;
// 激活编辑器工具栏
if (elements.editorToolbar) {
elements.editorToolbar.classList.add('active');
}
// 隐藏其他工具栏
['textTools', 'imageTools', 'bgTools'].forEach(key => {
if (elements[key]) elements[key].classList.remove('active');
});
// 显示二维码工具栏
if (elements.qrTools) {
elements.qrTools.classList.add('active');
}
// 同步滑块值
syncToolbarValues();
}
/**
* 同步工具栏滑块值
*/
function syncToolbarValues() {
if (!state.selectedQR) return;
const img = state.selectedQR.querySelector('img');
const opacity = img ? (parseFloat(img.style.opacity) || 1) : 1;
const size = parseInt(state.selectedQR.style.width) || CONFIG.DEFAULT_SIZE;
if (elements.qrOpacitySlider) {
elements.qrOpacitySlider.value = Math.round(opacity * 100);
elements.qrOpacityValue.textContent = Math.round(opacity * 100) + '%';
}
if (elements.qrScaleSlider) {
elements.qrScaleSlider.value = size;
elements.qrScaleValue.textContent = size + '%';
}
}
/**
* 隐藏二维码工具栏
*/
function hideQRTools() {
if (elements.qrTools) {
elements.qrTools.classList.remove('active');
}
document.querySelectorAll('.qr-item').forEach(el => {
el.classList.remove('qr-selected');
});
state.selectedQR = null;
}
/**
* 更新二维码透明度
*/
function updateOpacity(value) {
if (!state.selectedQR) return;
const img = state.selectedQR.querySelector('img');
if (img) {
img.style.opacity = value / 100;
}
elements.qrOpacityValue.textContent = value + '%';
}
/**
* 更新二维码大小
*/
function updateScale(value) {
if (!state.selectedQR) return;
state.selectedQR.style.width = value + 'px';
state.selectedQR.style.height = value + 'px';
elements.qrScaleValue.textContent = value + '%';
}
/**
* 保存状态
*/
function saveState() {
if (typeof window.saveState === 'function') {
window.saveState();
}
}
// ========== 拖拽功能 ==========
/**
* 使元素可拖拽
*/
function makeDraggable(el) {
let isDragging = false;
let startX, startY, startLeft, startTop;
function startDrag(clientX, clientY) {
isDragging = true;
startX = clientX;
startY = clientY;
startLeft = parseInt(el.style.left) || 0;
startTop = parseInt(el.style.top) || 0;
el.style.cursor = 'grabbing';
}
function moveDrag(clientX, clientY) {
if (!isDragging) return;
el.style.left = (startLeft + clientX - startX) + 'px';
el.style.top = (startTop + clientY - startY) + 'px';
}
function endDrag() {
if (isDragging) {
isDragging = false;
el.style.cursor = 'move';
saveState();
}
}
// 鼠标事件 - 排除删除按钮
el.addEventListener('mousedown', (e) => {
// 如果点击的是删除按钮,不处理拖拽
if (e.target.closest('.qr-delete-btn')) {
return;
}
e.preventDefault();
e.stopPropagation();
selectQR(el);
startDrag(e.clientX, e.clientY);
});
// 触摸事件 - 排除删除按钮
el.addEventListener('touchstart', (e) => {
// 如果触摸的是删除按钮,不处理拖拽
if (e.target.closest('.qr-delete-btn')) {
return;
}
e.preventDefault();
e.stopPropagation();
selectQR(el);
if (e.touches.length === 1) {
startDrag(e.touches[0].clientX, e.touches[0].clientY);
}
}, { passive: false });
// 全局移动事件
document.addEventListener('mousemove', (e) => moveDrag(e.clientX, e.clientY));
document.addEventListener('touchmove', (e) => {
if (isDragging && 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);
}
// ========== 事件绑定 ==========
function bindEvents() {
// 弹窗事件
elements.btn.addEventListener('click', openModal);
elements.closeBtn.addEventListener('click', closeModal);
elements.cancelBtn.addEventListener('click', closeModal);
elements.okBtn.addEventListener('click', addToCanvas);
elements.modal.addEventListener('click', (e) => {
if (e.target === elements.modal) closeModal();
});
// 输入防抖
elements.input.addEventListener('input', () => {
clearTimeout(state.previewTimer);
state.previewTimer = setTimeout(generatePreview, CONFIG.DEBOUNCE_DELAY);
});
// 透明度滑块
if (elements.qrOpacitySlider) {
elements.qrOpacitySlider.addEventListener('input', (e) => {
updateOpacity(e.target.value);
});
elements.qrOpacitySlider.addEventListener('change', saveState);
}
// 缩放滑块
if (elements.qrScaleSlider) {
elements.qrScaleSlider.addEventListener('input', (e) => {
updateScale(e.target.value);
});
elements.qrScaleSlider.addEventListener('change', saveState);
}
}
// ========== 初始化 ==========
function init() {
// 监听背景图变化
const container = document.getElementById('imageContainer');
if (container) {
const observer = new MutationObserver(checkBackground);
observer.observe(container, { childList: true, subtree: true });
}
// 定期检查背景图状态
setInterval(checkBackground, 500);
// 绑定事件
bindEvents();
// 初始检查
checkBackground();
// 暴露全局 API
window.QRCodeAPI = {
hideTools: hideQRTools,
getSelected: () => state.selectedQR
};
}
// 启动
init();
})();