feat: 二维码生成插件

master
LCJ-MinYa 1 week ago
parent e7240d694a
commit b96d5d79db

@ -0,0 +1,114 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>二维码解码工具</title>
<link
rel="stylesheet"
href="style.css"
/>
<style>
/* 为解码页面添加特定样式 */
#image-preview-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 20px 0;
padding: 10px;
border: 2px dashed #dfe1e6;
border-radius: 4px;
min-height: 100px;
align-content: flex-start;
}
.preview-item {
position: relative;
width: 120px;
height: 120px;
border: 1px solid #ccc;
border-radius: 4px;
overflow: hidden;
cursor: move;
}
.preview-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.preview-item .remove-btn {
position: absolute;
top: 2px;
right: 2px;
width: 20px;
height: 20px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 12px;
line-height: 20px;
text-align: center;
}
#result-container {
margin-top: 20px;
}
#result-text {
width: 100%;
height: 150px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>二维码解码 & 拼接工具</h1>
<p>上传多个二维码图片,拖拽排序,然后一键解码并拼接内容。</p>
<div>
<label
for="qr-input"
class="button-like"
>选择二维码图片</label
>
<input
type="file"
id="qr-input"
multiple
accept="image/*"
style="display: none"
/>
</div>
<div id="image-preview-container">
<!-- 上传的图片预览将显示在这里 -->
</div>
<button id="decode-btn">解码 & 拼接</button>
<div id="result-container">
<h3>解码结果:</h3>
<textarea
id="result-text"
readonly
placeholder="解码后的内容将显示在这里..."
></textarea>
<button id="copy-btn">复制结果</button>
</div>
<a
href="/index.html"
class="nav-link"
>返回二维码生成器</a
>
</div>
<!-- JS 库和逻辑脚本将在这里引入 -->
<script src="./js/jsQR.js"></script>
<script src="./js/Sortable.min.js"></script>
<script src="./js/decode.js"></script>
</body>
</html>

@ -28,11 +28,18 @@
<div id="qrcode-container">
<!-- 二维码将在这里生成 -->
</div>
<a
href="/decode.html"
class="nav-link"
style="text-align: center; display: block; margin-top: 20px"
>前往二维码解码器 &rarr;</a
>
</div>
<!-- 引入 qrcode.js 库 -->
<script src="qrcode.min.js"></script>
<script src="js/qrcode.min.js"></script>
<!-- 引入我们自己的逻辑脚本 -->
<script src="main.js"></script>
<script src="js/main.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

@ -0,0 +1,199 @@
// 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('复制失败,您的浏览器可能不支持或未授权剪贴板操作。');
});
});
});

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save