好友
阅读权限10
听众
最后登录1970-1-1
|
我之前也有这个诉求,让ai写了一个软件,但软件不好用,我就改成了html页面,你直接保存成qr.html试试,如果你需要离线使用,你需要把里面引用的js文件也保存再来,修改下本地的。 我加了一个使用gz压缩功能,挺好用,能大幅减少生成二维码的数量。 最后提醒下切勿非法使用。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>文件 ⇄ 二维码 · 生成/解码 (支持Gzip压缩)</title>
<style>
* {
box-sizing: border-box;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
}
body {
background: #f0f4f8;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 20px;
}
.card {
max-width: 1000px;
width: 100%;
background: rgba(255,255,255,0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 36px;
box-shadow: 0 20px 40px rgba(0,0,0,0.12), 0 8px 24px rgba(0,20,40,0.08);
padding: 32px;
border: 1px solid rgba(255,255,255,0.5);
}
h1 {
font-size: 2rem;
font-weight: 600;
margin: 0 0 8px 0;
background: linear-gradient(145deg, #0b2b44, #1b4a6b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.01em;
}
.sub {
color: #2c5777;
margin-bottom: 20px;
font-size: 0.95rem;
border-left: 4px solid #3a7ca5;
padding-left: 16px;
background: #e7f0f9;
border-radius: 0 40px 40px 0;
line-height: 1.5;
width: fit-content;
padding: 8px 20px 8px 16px;
}
.tab-bar {
display: flex;
gap: 12px;
margin-bottom: 28px;
border-bottom: 2px solid #c5d9eb;
padding-bottom: 10px;
}
.tab-btn {
background: transparent;
border: none;
padding: 10px 32px;
font-size: 1.2rem;
font-weight: 600;
color: #36698b;
border-radius: 60px 60px 0 0;
cursor: pointer;
transition: 0.15s;
border-bottom: 4px solid transparent;
margin-bottom: -2px;
}
.tab-btn.active {
color: #0b3b5c;
background: #ffffffd0;
border-bottom: 4px solid #1b6b9e;
box-shadow: 0 -4px 10px rgba(0,30,50,0.05);
}
.tab-panel {
display: none;
}
.tab-panel.active-panel {
display: block;
}
.control-panel {
background: white;
border-radius: 28px;
padding: 24px;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.02), 0 10px 20px -10px rgba(0,40,70,0.2);
margin-bottom: 32px;
border: 1px solid rgba(255,255,255,0.8);
}
.file-area {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 20px;
}
.file-label {
background: #1b4b6e;
color: white;
padding: 12px 28px;
border-radius: 40px;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 4px 10px rgba(23,65,104,0.3);
display: inline-flex;
align-items: center;
gap: 8px;
border: 1px solid #ffffff40;
}
.file-label:hover {
background: #0f3b55;
transform: scale(1.02);
}
#file-input {
position: absolute;
opacity: 0;
width: 0.1px;
height: 0.1px;
}
.file-info {
background: #eef3f9;
border-radius: 60px;
padding: 8px 22px;
color: #113750;
font-weight: 450;
word-break: break-word;
flex: 1;
min-width: 200px;
border: 1px dashed #aac3d9;
}
.options {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 20px;
margin-top: 20px;
}
.checkbox-wrapper {
display: flex;
align-items: center;
gap: 10px;
background: #deecf5;
padding: 8px 20px;
border-radius: 40px;
}
.checkbox-wrapper input[type="checkbox"] {
width: 20px;
height: 20px;
accent-color: #1f6392;
border-radius: 6px;
cursor: pointer;
}
.btn-primary {
background: #2274a5;
border: none;
color: white;
padding: 12px 36px;
border-radius: 60px;
font-weight: 600;
font-size: 1.1rem;
cursor: pointer;
transition: 0.15s;
box-shadow: 0 8px 18px #1f4b6e80;
border: 1px solid #7bb3da;
letter-spacing: 0.3px;
}
.btn-primary:hover {
background: #0e5b88;
box-shadow: 0 6px 14px #0d3955b0;
}
.btn-primary:active {
transform: scale(0.97);
}
.meta-note {
font-size: 0.85rem;
color: #3c5a73;
margin-top: 12px;
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
}
.badge {
background: #c4d9e9;
border-radius: 40px;
padding: 4px 14px;
font-weight: 500;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: baseline;
flex-wrap: wrap;
margin: 20px 0 12px 0;
}
.result-header h2 {
font-size: 1.5rem;
font-weight: 500;
color: #10344c;
margin: 0;
}
#segment-count {
background: #0e3044;
color: white;
border-radius: 60px;
padding: 4px 20px;
font-size: 0.9rem;
}
.compress-stats {
background: #dfeef7;
border-radius: 32px;
padding: 4px 16px;
font-size: 0.8rem;
font-weight: 500;
color: #0d4465;
}
.qr-viewer {
background: white;
border-radius: 36px;
padding: 30px 20px 25px 20px;
box-shadow: 0 12px 28px -12px rgba(0,42,78,0.3);
border: 1px solid #d6e6f5;
margin-top: 15px;
}
.canvas-wrapper {
display: flex;
justify-content: center;
align-items: center;
min-height: 280px;
background: #f9fcff;
border-radius: 32px;
padding: 20px;
}
.canvas-wrapper canvas {
border-radius: 24px;
background: white;
box-shadow: 0 6px 18px rgba(0,20,40,0.15);
max-width: 100%;
height: auto;
}
.nav-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 25px;
margin-top: 25px;
}
.nav-btn {
background: #e3effa;
border: none;
padding: 10px 28px;
border-radius: 60px;
font-weight: 600;
color: #114e77;
cursor: pointer;
font-size: 1rem;
transition: 0.15s;
border: 1px solid #b3cee8;
box-shadow: 0 4px 6px #c0d8ec;
}
.nav-btn:hover:not(:disabled) {
background: #cde2f2;
box-shadow: 0 6px 10px #aac3db;
}
.nav-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
box-shadow: none;
}
#page-indicator {
font-weight: 600;
background: #114e77;
color: white;
padding: 6px 24px;
border-radius: 60px;
font-size: 1rem;
letter-spacing: 0.5px;
border: 1px solid #4892c1;
}
.placeholder-tip {
color: #6b8caa;
padding: 40px;
text-align: center;
width: 100%;
font-size: 1.1rem;
}
.clear-btn {
background: none;
border: 1.5px solid #517e9f;
color: #1e4e73;
padding: 6px 18px;
border-radius: 40px;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
.clear-btn:hover {
background: #cde1f0;
}
.decode-panel {
background: white;
border-radius: 28px;
padding: 28px;
box-shadow: 0 10px 20px -10px rgba(0,40,70,0.2);
border: 1px solid rgba(255,255,255,0.8);
}
.decode-panel textarea {
width: 100%;
padding: 20px;
border-radius: 24px;
border: 2px solid #d5e4f0;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 14px;
line-height: 1.5;
resize: vertical;
background: #f9fdff;
margin-bottom: 20px;
}
.decode-panel textarea:focus {
outline: none;
border-color: #3f8ab8;
}
.decode-options {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 20px;
margin-bottom: 25px;
background: #ecf3fa;
padding: 12px 20px;
border-radius: 48px;
}
.file-name-row {
display: flex;
align-items: center;
gap: 15px;
flex-wrap: wrap;
background: #ecf3fa;
padding: 8px 20px;
border-radius: 50px;
}
.file-name-row label {
font-weight: 500;
color: #15435e;
}
#decode-filename {
border: none;
background: white;
padding: 8px 20px;
border-radius: 40px;
border: 1px solid #accae3;
width: 200px;
font-family: inherit;
}
.decode-status {
margin-top: 25px;
padding: 16px 22px;
border-radius: 40px;
background: #e4f0fa;
color: #144a6b;
font-weight: 500;
}
.decode-status.error {
background: #ffded5;
color: #a13b1e;
}
.btn-secondary {
background: #619fcb;
border: none;
color: white;
padding: 10px 30px;
border-radius: 60px;
font-weight: 600;
cursor: pointer;
transition: 0.15s;
border: 1px solid #9fc9e7;
}
.btn-secondary:hover {
background: #3683b7;
}
.footer-note {
margin-top: 35px;
text-align: center;
font-size: 0.8rem;
color: #5e7c97;
border-top: 1px solid #bed6ea;
padding-top: 22px;
}
.info-tip {
font-size: 0.7rem;
color: #4d7ca1;
margin-left: 8px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako.min.js"></script>
</head>
<body>
<div class="card">
<h1>📦 文件 ↔ 二维码 · 智能压缩</h1>
<div class="sub">生成切片 / 从文本还原文件 · 支持Gzip压缩大幅减少二维码数量</div>
<div class="tab-bar">
<button class="tab-btn active" id="tab-generate">✨ 生成二维码</button>
<button class="tab-btn" id="tab-decode">🔓 解码还原文件</button>
</div>
<!-- 生成面板 -->
<div id="panel-generate" class="tab-panel active-panel">
<div class="control-panel">
<div class="file-area">
<label for="file-input" class="file-label"><i>📎</i> 选择文件</label>
<input type="file" id="file-input" accept="*/*">
<div class="file-info" id="file-info-display">未选择任何文件</div>
</div>
<div class="options">
<div class="checkbox-wrapper">
<input type="checkbox" id="base64-checkbox" checked="checked">
<label for="base64-checkbox">🔐 Base64编码</label>
</div>
<div class="checkbox-wrapper">
<input type="checkbox" id="gzip-checkbox" checked="checked">
<label for="gzip-checkbox">🗜️ Gzip压缩 (大幅减少切片)</label>
</div>
<button class="btn-primary" id="generate-btn">⚡ 生成二维码</button>
<button class="clear-btn" id="clear-btn">🗑️ 清空全部</button>
</div>
<div class="meta-note">
<span class="badge">📐 每个二维码 ≤2000字符 (字节模式)</span>
<span class="badge" id="file-size-hint">文件大小: —</span>
<span id="compress-info" class="compress-stats" style="display: none;"></span>
</div>
</div>
<div class="result-header">
<h2>🔲 二维码浏览</h2>
<div style="display: flex; gap: 12px; align-items: center;">
<span id="segment-count">0 个切片</span>
<span id="qr-savings-tip" class="compress-stats" style="background:#cbe4fe;"></span>
</div>
</div>
<div id="qr-viewer" class="qr-viewer">
<div id="canvas-wrapper" class="canvas-wrapper">
<div class="placeholder-tip">👆 选择文件并点击生成 (可勾选Gzip压缩)</div>
</div>
<div class="nav-controls">
<button id="prev-btn" class="nav-btn" disabled>◀ 上一张</button>
<span id="page-indicator">0 / 0</span>
<button id="next-btn" class="nav-btn" disabled>下一张 ▶</button>
</div>
</div>
</div>
<!-- 解码面板 -->
<div id="panel-decode" class="tab-panel">
<div class="decode-panel">
<textarea id="decode-input" rows="10" placeholder="将二维码扫描得到的文本粘贴在这里 ⚠️ 若生成时使用了 Gzip 压缩 / Base64,解码时请勾选下方对应选项 【多个切片请按顺序连续粘贴,程序将自动拼接】"></textarea>
<div class="decode-options">
<div class="checkbox-wrapper">
<input type="checkbox" id="decode-base64-checkbox" checked="checked">
<label for="decode-base64-checkbox">🔐 内容已进行 Base64 编码</label>
</div>
<div class="checkbox-wrapper">
<input type="checkbox" id="decode-gzip-checkbox" checked="checked">
<label for="decode-gzip-checkbox">🗜️ 需要 Gzip 解压 (生成时若勾选了压缩则必须勾选)</label>
</div>
<div class="file-name-row">
<label for="decode-filename">保存为:</label>
<input type="text" id="decode-filename" value="decoded.bin">
</div>
</div>
<div style="display: flex; gap: 20px; align-items: center; flex-wrap: wrap;">
<button class="btn-primary" id="decode-btn">📥 解码并下载文件</button>
<button class="clear-btn" id="decode-clear-btn">🧹 清空输入</button>
</div>
<div id="decode-status" class="decode-status">等待操作……</div>
</div>
</div>
<div class="footer-note">
⚡ Gzip压缩 + Base64 推荐组合:显著减少二维码数量且文本便于复制。解码时记得勾选相同选项。
</div>
</div>
<script>
(function() {
// ---------- 辅助函数 ----------
function binaryStringToUint8Array(str) {
const len = str.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = str.charCodeAt(i) & 0xFF;
}
return bytes;
}
function uint8ArrayToBinaryString(uint8arr) {
let binary = '';
const chunkSize = 8192;
for (let i = 0; i < uint8arr.length; i += chunkSize) {
const chunk = uint8arr.subarray(i, i + chunkSize);
binary += String.fromCharCode.apply(null, chunk);
}
return binary;
}
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
// 生成模块
(function setupGenerate() {
const MAX_CHUNK_LENGTH = 2000;
const fileInput = document.getElementById('file-input');
const fileInfoDiv = document.getElementById('file-info-display');
const base64Check = document.getElementById('base64-checkbox');
const gzipCheck = document.getElementById('gzip-checkbox');
const generateBtn = document.getElementById('generate-btn');
const clearBtn = document.getElementById('clear-btn');
const segmentCountSpan = document.getElementById('segment-count');
const fileSizeHint = document.getElementById('file-size-hint');
const canvasWrapper = document.getElementById('canvas-wrapper');
const prevBtn = document.getElementById('prev-btn');
const nextBtn = document.getElementById('next-btn');
const pageIndicator = document.getElementById('page-indicator');
const compressInfoSpan = document.getElementById('compress-info');
const qrSavingsTip = document.getElementById('qr-savings-tip');
let qrCanvasArray = [];
let currentIndex = 0;
let totalChunks = 0;
function drawQrToCanvas(qr, cellSize = 5) {
const size = qr.getModuleCount();
const canvas = document.createElement('canvas');
canvas.width = size * cellSize;
canvas.height = size * cellSize;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#0b2b3f';
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
if (qr.isDark(row, col)) {
ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
}
}
}
return canvas;
}
function generateQrCanvasFromString(chunkString) {
try {
const qr = qrcode(0, 'L');
qr.addData(chunkString, 'Byte');
qr.make();
return drawQrToCanvas(qr, 5);
} catch (e) {
console.warn('二维码生成失败', e);
const errCanvas = document.createElement('canvas');
errCanvas.width = 250;
errCanvas.height = 250;
const ctx = errCanvas.getContext('2d');
ctx.fillStyle = '#ffe0d0';
ctx.fillRect(0, 0, 250, 250);
ctx.font = 'bold 16px sans-serif';
ctx.fillStyle = '#a13e0b';
ctx.textAlign = 'center';
ctx.fillText('❌ 生成失败', 125, 130);
return errCanvas;
}
}
function displayCurrentQr() {
canvasWrapper.innerHTML = '';
if (qrCanvasArray.length === 0 || totalChunks === 0) {
canvasWrapper.innerHTML = '<div class="placeholder-tip">📭 没有二维码可显示</div>';
pageIndicator.textContent = `0 / 0`;
prevBtn.disabled = true;
nextBtn.disabled = true;
return;
}
canvasWrapper.appendChild(qrCanvasArray[currentIndex]);
pageIndicator.textContent = `${currentIndex + 1} / ${totalChunks}`;
prevBtn.disabled = (currentIndex === 0);
nextBtn.disabled = (currentIndex === totalChunks - 1);
}
function resetViewer(keepStats = false) {
qrCanvasArray = [];
currentIndex = 0;
totalChunks = 0;
segmentCountSpan.textContent = '0 个切片';
if (!keepStats) {
compressInfoSpan.style.display = 'none';
qrSavingsTip.textContent = '';
}
displayCurrentQr();
}
function updateFileInfo() {
if (fileInput.files.length > 0) {
const file = fileInput.files[0];
const size = file.size;
let sizeText = '';
if (size < 1024) sizeText = size + ' B';
else if (size < 1024 * 1024) sizeText = (size / 1024).toFixed(1) + ' KB';
else sizeText = (size / (1024 * 1024)).toFixed(2) + ' MB';
fileInfoDiv.textContent = `📄 ${file.name} (${sizeText})`;
fileSizeHint.textContent = `文件大小: ${sizeText}`;
} else {
fileInfoDiv.textContent = '未选择任何文件';
fileSizeHint.textContent = '文件大小: —';
}
}
async function handleGenerate() {
if (fileInput.files.length === 0) {
alert('请先选择一个文件');
return;
}
const file = fileInput.files[0];
const useBase64 = base64Check.checked;
const useGzip = gzipCheck.checked;
const originalSize = file.size;
const reader = new FileReader();
reader.onload = function(e) {
try {
const arrayBuffer = e.target.result;
let processedData; // Uint8Array (最终二进制)
let finalString; // 待切片的字符串
// 1. 是否压缩
if (useGzip) {
if (!window.pako) throw new Error('pako库未加载,无法进行Gzip压缩');
const rawBytes = new Uint8Array(arrayBuffer);
const compressed = pako.deflate(rawBytes);
processedData = compressed;
const compressRatio = ((1 - compressed.length / originalSize) * 100).toFixed(1);
compressInfoSpan.style.display = 'inline-block';
compressInfoSpan.innerHTML = `🗜️ 压缩后: ${(compressed.length/1024).toFixed(1)} KB (${compressRatio}% 减小)`;
} else {
processedData = new Uint8Array(arrayBuffer);
compressInfoSpan.style.display = 'none';
}
// 2. 转为最终字符串 (Base64 或 二进制字符串)
if (useBase64) {
let binaryForB64 = uint8ArrayToBinaryString(processedData);
finalString = btoa(binaryForB64);
} else {
finalString = uint8ArrayToBinaryString(processedData);
}
const totalLength = finalString.length;
if (totalLength === 0) throw new Error('内容为空');
const chunks = Math.ceil(totalLength / MAX_CHUNK_LENGTH);
segmentCountSpan.textContent = `${chunks} 个切片`;
// 计算节省提示(对比未压缩且未base64的理论原始切片数)
if (useGzip) {
const rawWithoutCompress = originalSize; // 原始字节
const rawChunksWithout = Math.ceil(rawWithoutCompress / MAX_CHUNK_LENGTH);
if (rawChunksWithout > chunks) {
qrSavingsTip.textContent = `✨ 相比未压缩节省 ${rawChunksWithout - chunks} 个二维码 (减少${Math.round((1 - chunks/rawChunksWithout)*100)}%)`;
} else {
qrSavingsTip.textContent = `🗜️ 压缩后切片数 ${chunks}`;
}
} else {
qrSavingsTip.textContent = ``;
}
if (chunks > 80) {
if (!confirm(`将生成 ${chunks} 个二维码,较多,继续吗?`)) {
resetViewer();
segmentCountSpan.textContent = '已取消';
return;
}
}
const newCanvasArray = [];
for (let i = 0; i < chunks; i++) {
const start = i * MAX_CHUNK_LENGTH;
const end = Math.min(start + MAX_CHUNK_LENGTH, totalLength);
const chunk = finalString.substring(start, end);
const canvas = generateQrCanvasFromString(chunk);
newCanvasArray.push(canvas);
}
qrCanvasArray = newCanvasArray;
totalChunks = chunks;
currentIndex = 0;
displayCurrentQr();
} catch (err) {
console.error(err);
alert(`生成失败: ${err.message || '未知错误'}`);
resetViewer();
}
};
reader.onerror = () => { alert('文件读取失败'); resetViewer(); };
reader.readAsArrayBuffer(file);
}
fileInput.addEventListener('change', function() {
updateFileInfo();
resetViewer();
if (fileInput.files.length > 0 && fileInput.files[0].size > 30 * 1024 * 1024) {
if (!confirm('文件超过30MB,压缩和编码可能较慢,继续?')) {
fileInput.value = '';
updateFileInfo();
}
}
});
generateBtn.addEventListener('click', handleGenerate);
clearBtn.addEventListener('click', function() {
fileInput.value = '';
updateFileInfo();
resetViewer();
compressInfoSpan.style.display = 'none';
qrSavingsTip.textContent = '';
});
prevBtn.addEventListener('click', () => { if (currentIndex > 0) { currentIndex--; displayCurrentQr(); } });
nextBtn.addEventListener('click', () => { if (currentIndex < totalChunks - 1) { currentIndex++; displayCurrentQr(); } });
updateFileInfo();
resetViewer();
})();
// 解码模块 (支持 Gzip 解压)
(function setupDecode() {
const decodeInput = document.getElementById('decode-input');
const decodeBase64Check = document.getElementById('decode-base64-checkbox');
const decodeGzipCheck = document.getElementById('decode-gzip-checkbox');
const decodeFilename = document.getElementById('decode-filename');
const decodeBtn = document.getElementById('decode-btn');
const decodeClearBtn = document.getElementById('decode-clear-btn');
const decodeStatus = document.getElementById('decode-status');
decodeClearBtn.addEventListener('click', () => {
decodeInput.value = '';
decodeStatus.textContent = '输入已清空';
decodeStatus.className = 'decode-status';
});
decodeBtn.addEventListener('click', async () => {
const rawText = decodeInput.value;
if (!rawText.trim()) {
decodeStatus.textContent = '❌ 请输入要解码的文本(二维码扫描结果)';
decodeStatus.className = 'decode-status error';
return;
}
try {
let intermediateBytes; // Uint8Array (解码base64或直接转换后的原始字节流,可能是压缩数据也可能是最终数据)
const useBase64 = decodeBase64Check.checked;
const useGzip = decodeGzipCheck.checked;
// 1. 从文本还原为二进制数据 (Uint8Array)
if (useBase64) {
// 拼接所有行 (忽略空行)
const lines = rawText.split(/\r?\n/).filter(l => l.trim() !== '');
if (lines.length === 0) throw new Error('没有有效的Base64数据行');
const fullBase64 = lines.join('');
const binaryStr = atob(fullBase64);
intermediateBytes = binaryStringToUint8Array(binaryStr);
} else {
// 直接将整个文本视为二进制字符串 (latin1)
intermediateBytes = binaryStringToUint8Array(rawText);
}
// 2. 如果需要 Gzip 解压
let finalBytes = intermediateBytes;
if (useGzip) {
if (!window.pako) throw new Error('pako库未加载,无法解压Gzip数据');
try {
finalBytes = pako.inflate(intermediateBytes);
} catch (inflateErr) {
throw new Error(`Gzip解压失败: ${inflateErr.message},请确认生成时是否启用了压缩并正确勾选解压选项`);
}
}
// 3. 下载文件
const blob = new Blob([finalBytes], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = decodeFilename.value.trim() || 'decoded.bin';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
let sizeInfo = `${finalBytes.length} 字节`;
if (useGzip) sizeInfo += ` (已解压)`;
decodeStatus.textContent = `✅ 解码成功!文件大小: ${sizeInfo},已触发下载。`;
decodeStatus.className = 'decode-status';
} catch (e) {
console.error(e);
let errMsg = e.message || '解码失败,请检查Base64格式 / Gzip选项是否正确';
if (errMsg.includes('Invalid character') || errMsg.includes('atob')) errMsg = 'Base64解码失败,请确认文本是否为有效Base64,或取消Base64选项';
decodeStatus.textContent = `❌ 解码失败: ${errMsg}`;
decodeStatus.className = 'decode-status error';
}
});
decodeStatus.textContent = '等待操作…… (勾选Gzip解压前请确认生成时启用了压缩)';
})();
// 标签页切换
const tabGenerate = document.getElementById('tab-generate');
const tabDecode = document.getElementById('tab-decode');
const panelGenerate = document.getElementById('panel-generate');
const panelDecode = document.getElementById('panel-decode');
tabGenerate.addEventListener('click', () => {
tabGenerate.classList.add('active');
tabDecode.classList.remove('active');
panelGenerate.classList.add('active-panel');
panelDecode.classList.remove('active-panel');
});
tabDecode.addEventListener('click', () => {
tabDecode.classList.add('active');
tabGenerate.classList.remove('active');
panelDecode.classList.add('active-panel');
panelGenerate.classList.remove('active-panel');
});
})();
</script>
</body>
</html> |
|