|
|
// decode.js
|
|
|
// This file will contain the logic for the QR code decoding feature.
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
console.log('解码页面脚本已加载。');
|
|
|
|
|
|
// 获取页面上的关键元素
|
|
|
const qrInput = document.getElementById('qr-input');
|
|
|
const imagePreviewContainer = document.getElementById('image-preview-container');
|
|
|
const decodeBtn = document.getElementById('decode-btn');
|
|
|
const resultText = document.getElementById('result-text');
|
|
|
const copyBtn = document.getElementById('copy-btn');
|
|
|
|
|
|
// 初始化 SortableJS,为预览容器开启拖拽排序功能
|
|
|
new Sortable(imagePreviewContainer, {
|
|
|
animation: 150, // 拖拽动画的毫秒数
|
|
|
ghostClass: 'sortable-ghost', // 拖拽时占位元素的类名
|
|
|
});
|
|
|
|
|
|
// 监听文件输入框的 change 事件
|
|
|
qrInput.addEventListener('change', handleFileSelect);
|
|
|
|
|
|
/**
|
|
|
* 处理用户选择的文件
|
|
|
* @param {Event} event - input change 事件对象
|
|
|
*/
|
|
|
function handleFileSelect(event) {
|
|
|
// 获取用户选择的文件列表
|
|
|
const files = event.target.files;
|
|
|
if (!files) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 遍历所有选择的文件
|
|
|
for (const file of files) {
|
|
|
// 确保文件是图片类型
|
|
|
if (!file.type.startsWith('image/')) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
|
// 文件读取成功后的回调
|
|
|
reader.onload = function (e) {
|
|
|
createPreview(e.target.result, file.name);
|
|
|
};
|
|
|
|
|
|
// 以 Data URL 的格式读取文件
|
|
|
reader.readAsDataURL(file);
|
|
|
}
|
|
|
|
|
|
// 清空 input 的值,这样即使用户连续选择相同的文件也能触发 change 事件
|
|
|
qrInput.value = '';
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建并显示单个图片预览
|
|
|
* @param {string} src - 图片的 Data URL
|
|
|
* @param {string} name - 图片的文件名
|
|
|
*/
|
|
|
function createPreview(src, name) {
|
|
|
// 创建预览项的容器
|
|
|
const previewItem = document.createElement('div');
|
|
|
previewItem.className = 'preview-item';
|
|
|
previewItem.title = name; // 鼠标悬停时显示文件名
|
|
|
|
|
|
// 创建图片元素
|
|
|
const img = document.createElement('img');
|
|
|
img.src = src;
|
|
|
img.alt = name;
|
|
|
|
|
|
// 创建移除按钮
|
|
|
const removeBtn = document.createElement('button');
|
|
|
removeBtn.className = 'remove-btn';
|
|
|
removeBtn.textContent = '×';
|
|
|
removeBtn.title = '移除此图片';
|
|
|
|
|
|
// 为移除按钮添加点击事件
|
|
|
removeBtn.addEventListener('click', (e) => {
|
|
|
e.stopPropagation(); // 防止触发其他事件
|
|
|
previewItem.remove(); // 从 DOM 中移除预览项
|
|
|
});
|
|
|
|
|
|
// 将图片和移除按钮添加到预览项容器中
|
|
|
previewItem.appendChild(img);
|
|
|
previewItem.appendChild(removeBtn);
|
|
|
|
|
|
// 将预览项添加到主预览容器中
|
|
|
imagePreviewContainer.appendChild(previewItem);
|
|
|
}
|
|
|
|
|
|
// 为解码按钮添加点击事件
|
|
|
decodeBtn.addEventListener('click', () => {
|
|
|
// 清空之前的结果
|
|
|
resultText.value = '';
|
|
|
|
|
|
// 获取所有预览项中的图片元素,它们的顺序就是用户拖拽后的顺序
|
|
|
const images = imagePreviewContainer.querySelectorAll('.preview-item img');
|
|
|
|
|
|
if (images.length === 0) {
|
|
|
resultText.value = '请先上传至少一张二维码图片。';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const decodedParts = [];
|
|
|
let completed = 0;
|
|
|
|
|
|
// 遍历所有图片进行解码
|
|
|
images.forEach((img, index) => {
|
|
|
const decodedText = decodeImage(img);
|
|
|
decodedParts[index] = decodedText;
|
|
|
completed++;
|
|
|
|
|
|
// 当所有图片都处理完毕后,拼接结果
|
|
|
if (completed === images.length) {
|
|
|
resultText.value = decodedParts.join('');
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
/**
|
|
|
* 解码单个图片元素
|
|
|
* @param {HTMLImageElement} img - 要解码的图片元素
|
|
|
* @returns {string} - 解码后的文本,或者一个错误提示
|
|
|
*/
|
|
|
function decodeImage(img) {
|
|
|
// 创建一个临时的、离屏的 canvas
|
|
|
const canvas = document.createElement('canvas');
|
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
|
|
|
|
// --- 图片缩放逻辑 ---
|
|
|
// 定义一个用于处理的最大尺寸,过大的图片会导致 jsQR 性能下降或失败
|
|
|
const MAX_DIMENSION = 1000;
|
|
|
let { naturalWidth: width, naturalHeight: height } = img;
|
|
|
|
|
|
// 如果图片尺寸过大,则按比例缩小
|
|
|
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
|
|
|
if (width > height) {
|
|
|
height = Math.round(height * (MAX_DIMENSION / width));
|
|
|
width = MAX_DIMENSION;
|
|
|
} else {
|
|
|
width = Math.round(width * (MAX_DIMENSION / height));
|
|
|
height = MAX_DIMENSION;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 设置 canvas 的尺寸为计算后的尺寸
|
|
|
canvas.width = width;
|
|
|
canvas.height = height;
|
|
|
|
|
|
// 将图片(可能会被缩放)绘制到 canvas 上
|
|
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
// 从 canvas 获取图像数据
|
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
try {
|
|
|
// 使用 jsQR 库解码
|
|
|
const code = jsQR(imageData.data, imageData.width, imageData.height);
|
|
|
|
|
|
if (code && code.data) {
|
|
|
// 解码成功,返回文本内容
|
|
|
return code.data;
|
|
|
} else {
|
|
|
// 未找到二维码
|
|
|
return `[图片 ${img.alt} 未能识别出二维码]`;
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error(`解码图片 ${img.alt} 时发生错误:`, error);
|
|
|
return `[图片 ${img.alt} 解码失败]`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 为复制按钮添加点击事件
|
|
|
copyBtn.addEventListener('click', () => {
|
|
|
const textToCopy = resultText.value;
|
|
|
if (!textToCopy) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
navigator.clipboard
|
|
|
.writeText(textToCopy)
|
|
|
.then(() => {
|
|
|
// 复制成功后的用户反馈
|
|
|
const originalText = copyBtn.textContent;
|
|
|
copyBtn.textContent = '已复制!';
|
|
|
copyBtn.disabled = true;
|
|
|
setTimeout(() => {
|
|
|
copyBtn.textContent = originalText;
|
|
|
copyBtn.disabled = false;
|
|
|
}, 2000);
|
|
|
})
|
|
|
.catch((err) => {
|
|
|
console.error('复制文本失败:', err);
|
|
|
// 可以选择在这里给用户一个错误提示
|
|
|
alert('复制失败,您的浏览器可能不支持或未授权剪贴板操作。');
|
|
|
});
|
|
|
});
|
|
|
});
|