feat(qr): 添加二维码工具栏并优化交互体验
添加二维码编辑工具栏,支持透明度和缩放调整 优化选中状态和拖拽交互,增强用户体验 重构代码结构,提高可维护性
This commit is contained in:
381
js/qr.js
381
js/qr.js
@@ -1,153 +1,305 @@
|
||||
// 二维码功能 - 简洁可靠
|
||||
// 二维码功能 - 优化版编辑器工具栏
|
||||
(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');
|
||||
// ========== 配置 ==========
|
||||
const CONFIG = {
|
||||
MIN_SIZE: 0,
|
||||
MAX_SIZE: 200,
|
||||
DEFAULT_SIZE: 100,
|
||||
PREVIEW_SIZE: 128,
|
||||
DEBOUNCE_DELAY: 300
|
||||
};
|
||||
|
||||
if (!btn || !modal) return;
|
||||
// ========== 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')
|
||||
};
|
||||
|
||||
let currentQR = null;
|
||||
let qrCount = 0;
|
||||
// 验证必需元素
|
||||
if (!elements.btn || !elements.modal) return;
|
||||
|
||||
// 检查背景图并显示按钮
|
||||
function checkBg() {
|
||||
// ========== 状态管理 ==========
|
||||
const state = {
|
||||
currentQR: null,
|
||||
selectedQR: null,
|
||||
previewTimer: null,
|
||||
isDragging: false
|
||||
};
|
||||
|
||||
// ========== 核心功能 ==========
|
||||
|
||||
/**
|
||||
* 检查背景图并显示/隐藏二维码按钮
|
||||
*/
|
||||
function checkBackground() {
|
||||
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 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);
|
||||
}
|
||||
|
||||
// 监听背景图变化
|
||||
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();
|
||||
elements.modal.classList.add('active');
|
||||
elements.input.value = '';
|
||||
elements.preview.innerHTML = '';
|
||||
state.currentQR = null;
|
||||
elements.input.focus();
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
/**
|
||||
* 关闭二维码生成弹窗
|
||||
*/
|
||||
function closeModal() {
|
||||
modal.classList.remove('active');
|
||||
elements.modal.classList.remove('active');
|
||||
}
|
||||
|
||||
// 生成预览
|
||||
function genPreview() {
|
||||
const text = input.value.trim();
|
||||
/**
|
||||
* 生成二维码预览
|
||||
*/
|
||||
function generatePreview() {
|
||||
const text = elements.input.value.trim();
|
||||
|
||||
if (!text) {
|
||||
preview.innerHTML = '';
|
||||
currentQR = null;
|
||||
elements.preview.innerHTML = '';
|
||||
state.currentQR = null;
|
||||
return;
|
||||
}
|
||||
preview.innerHTML = '';
|
||||
|
||||
elements.preview.innerHTML = '';
|
||||
|
||||
try {
|
||||
currentQR = new QRCode(preview, {
|
||||
state.currentQR = new QRCode(elements.preview, {
|
||||
text: text,
|
||||
width: 128,
|
||||
height: 128
|
||||
width: CONFIG.PREVIEW_SIZE,
|
||||
height: CONFIG.PREVIEW_SIZE,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('生成失败', e);
|
||||
} catch (error) {
|
||||
console.error('二维码生成失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到画布
|
||||
/**
|
||||
* 将二维码添加到画布
|
||||
*/
|
||||
function addToCanvas() {
|
||||
if (!currentQR) {
|
||||
if (!state.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 canvas = elements.preview.querySelector('canvas');
|
||||
if (!canvas) return;
|
||||
|
||||
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';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = canvas.toDataURL('image/png');
|
||||
img.className = 'qr-item';
|
||||
img.dataset.type = 'qrcode';
|
||||
img.style.cssText = `
|
||||
width: ${CONFIG.DEFAULT_SIZE}px;
|
||||
height: ${CONFIG.DEFAULT_SIZE}px;
|
||||
opacity: 1;
|
||||
left: ${(rect.width / 2 - CONFIG.DEFAULT_SIZE / 2)}px;
|
||||
top: ${(rect.height / 2 - CONFIG.DEFAULT_SIZE / 2)}px;
|
||||
`;
|
||||
|
||||
container.appendChild(img);
|
||||
makeDraggable(img);
|
||||
selectQR(img);
|
||||
closeModal();
|
||||
|
||||
if (typeof saveState === 'function') saveState();
|
||||
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 opacity = parseFloat(state.selectedQR.style.opacity) || 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;
|
||||
|
||||
state.selectedQR.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 dragging = false;
|
||||
let isDragging = false;
|
||||
let startX, startY, startLeft, startTop;
|
||||
|
||||
function startDrag(clientX, clientY) {
|
||||
dragging = true;
|
||||
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 (!dragging) return;
|
||||
if (!isDragging) 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();
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
el.style.cursor = 'move';
|
||||
saveState();
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标事件
|
||||
el.addEventListener('mousedown', function(e) {
|
||||
el.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
selectQR(el);
|
||||
startDrag(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
// 触摸事件
|
||||
el.addEventListener('touchstart', function(e) {
|
||||
el.addEventListener('touchstart', (e) => {
|
||||
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', function(e) {
|
||||
moveDrag(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
document.addEventListener('touchmove', function(e) {
|
||||
if (dragging && e.touches.length === 1) {
|
||||
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);
|
||||
}
|
||||
@@ -158,22 +310,67 @@
|
||||
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);
|
||||
});
|
||||
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();
|
||||
});
|
||||
|
||||
// 初始检查
|
||||
checkBg();
|
||||
// 输入防抖
|
||||
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();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user