Files
invitation-maker/js/qr.js
Zhukj 6a0294f0c0 feat(图层管理): 为二维码添加图层支持并实现相关功能
- 在状态管理中新增 qrCounter 用于生成唯一图层 ID
- 为二维码元素添加 dataset.layerId 属性
- 实现二维码图层更新事件机制
- 在图层列表中显示二维码元素并支持选中操作
- 完善二维码的绘制逻辑,支持透明度设置
2026-04-27 08:46:49 +08:00

452 lines
13 KiB
JavaScript
Raw Permalink 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,
qrCounter: 0
};
// ========== 核心功能 ==========
/**
* 检查背景图并显示/隐藏二维码按钮
*/
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();
// 生成图层 ID
state.qrCounter++;
const layerId = `qr_${state.qrCounter}`;
// 创建二维码容器
const wrapper = document.createElement('div');
wrapper.className = 'qr-item';
wrapper.dataset.type = 'qrcode';
wrapper.dataset.layerId = layerId;
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();
// 更新图层列表
updateLayersList();
return false;
};
wrapper.appendChild(img);
wrapper.appendChild(deleteBtn);
container.appendChild(wrapper);
makeDraggable(wrapper);
selectQR(wrapper);
closeModal();
saveState();
// 更新图层列表
updateLayersList();
}
/**
* 选中二维码
*/
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 updateLayersList() {
// 触发图层更新事件
const event = new CustomEvent('updateLayers');
document.dispatchEvent(event);
}
// ========== 拖拽功能 ==========
/**
* 使元素可拖拽
*/
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();
})();