Last active
November 30, 2025 14:53
-
-
Save CrazyCoder/31f02014a1d569986c7b9940e775bb5d to your computer and use it in GitHub Desktop.
Batch XTH file generator for Xteink X4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>批量XTH文件生成器</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| line-height: 1.6; | |
| color: #333; | |
| background-color: #f5f7fa; | |
| padding: 20px; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| background: white; | |
| border-radius: 10px; | |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); | |
| padding: 25px; | |
| } | |
| h1 { | |
| text-align: center; | |
| margin-bottom: 25px; | |
| color: #2c3e50; | |
| } | |
| .drop-zone { | |
| border: 3px dashed #3498db; | |
| border-radius: 10px; | |
| padding: 40px; | |
| text-align: center; | |
| background-color: #f8f9fa; | |
| margin-bottom: 25px; | |
| transition: all 0.3s; | |
| cursor: pointer; | |
| } | |
| .drop-zone:hover, .drop-zone.drag-over { | |
| border-color: #2980b9; | |
| background-color: #e8f4f8; | |
| } | |
| .drop-zone p { | |
| font-size: 18px; | |
| color: #7f8c8d; | |
| margin-bottom: 10px; | |
| } | |
| .drop-zone .hint { | |
| font-size: 14px; | |
| color: #95a5a6; | |
| } | |
| .file-input { | |
| display: none; | |
| } | |
| .controls { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 25px; | |
| } | |
| .control-group { | |
| background-color: #f8f9fa; | |
| padding: 15px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); | |
| } | |
| .control-group h3 { | |
| margin-bottom: 15px; | |
| color: #34495e; | |
| } | |
| .slider-container { | |
| margin-bottom: 15px; | |
| } | |
| .slider-container label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 500; | |
| } | |
| .slider-value { | |
| display: inline-block; | |
| width: 50px; | |
| text-align: right; | |
| } | |
| input[type="range"] { | |
| width: 100%; | |
| } | |
| .options-group { | |
| display: flex; | |
| gap: 15px; | |
| margin-top: 10px; | |
| flex-wrap: wrap; | |
| } | |
| .option-label { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .image-list { | |
| margin-bottom: 25px; | |
| } | |
| .image-list h3 { | |
| margin-bottom: 15px; | |
| color: #34495e; | |
| } | |
| .image-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |
| gap: 15px; | |
| } | |
| .image-item { | |
| background: #f8f9fa; | |
| border-radius: 8px; | |
| padding: 10px; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); | |
| position: relative; | |
| } | |
| .image-item canvas { | |
| width: 100%; | |
| height: auto; | |
| border: 1px solid #ddd; | |
| border-radius: 5px; | |
| background: white; | |
| } | |
| .image-item .image-name { | |
| margin-top: 8px; | |
| font-size: 12px; | |
| color: #7f8c8d; | |
| word-break: break-all; | |
| } | |
| .image-item .image-status { | |
| position: absolute; | |
| top: 15px; | |
| right: 15px; | |
| padding: 5px 10px; | |
| border-radius: 5px; | |
| font-size: 12px; | |
| font-weight: bold; | |
| } | |
| .image-item .status-processing { | |
| background-color: #f39c12; | |
| color: white; | |
| } | |
| .image-item .status-ready { | |
| background-color: #2ecc71; | |
| color: white; | |
| } | |
| .image-item .status-error { | |
| background-color: #e74c3c; | |
| color: white; | |
| } | |
| .action-buttons { | |
| display: flex; | |
| justify-content: center; | |
| gap: 15px; | |
| flex-wrap: wrap; | |
| margin-bottom: 25px; | |
| } | |
| .action-btn { | |
| padding: 12px 25px; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| transition: all 0.3s; | |
| } | |
| .process-btn { | |
| background-color: #3498db; | |
| color: white; | |
| } | |
| .process-btn:hover:not(:disabled) { | |
| background-color: #2980b9; | |
| } | |
| .download-btn { | |
| background-color: #2ecc71; | |
| color: white; | |
| } | |
| .download-btn:hover:not(:disabled) { | |
| background-color: #27ae60; | |
| } | |
| .clear-btn { | |
| background-color: #e74c3c; | |
| color: white; | |
| } | |
| .clear-btn:hover:not(:disabled) { | |
| background-color: #c0392b; | |
| } | |
| .action-btn:disabled { | |
| background-color: #bdc3c7; | |
| cursor: not-allowed; | |
| } | |
| .progress-section { | |
| margin-bottom: 25px; | |
| display: none; | |
| } | |
| .progress-section.active { | |
| display: block; | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 30px; | |
| background-color: #ecf0f1; | |
| border-radius: 15px; | |
| overflow: hidden; | |
| margin-bottom: 10px; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background-color: #3498db; | |
| transition: width 0.3s; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-weight: bold; | |
| } | |
| .info-text { | |
| text-align: center; | |
| color: #7f8c8d; | |
| font-style: italic; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>批量XTH文件生成器</h1> | |
| <div class="drop-zone" id="dropZone"> | |
| <p>拖放图片到这里,或点击选择文件</p> | |
| <p class="hint">支持批量选择,自动裁剪为 480×800 像素</p> | |
| <input type="file" id="fileInput" class="file-input" multiple accept="image/*"> | |
| </div> | |
| <div class="controls"> | |
| <div class="control-group"> | |
| <h3>4灰阶设置</h3> | |
| <div class="slider-container"> | |
| <label>阈值1: <span class="slider-value" id="threshold1Value">85</span></label> | |
| <input type="range" id="threshold1Slider" min="0" max="255" value="85"> | |
| </div> | |
| <div class="slider-container"> | |
| <label>阈值2: <span class="slider-value" id="threshold2Value">170</span></label> | |
| <input type="range" id="threshold2Slider" min="0" max="255" value="170"> | |
| </div> | |
| <div class="slider-container"> | |
| <label>阈值3: <span class="slider-value" id="threshold3Value">255</span></label> | |
| <input type="range" id="threshold3Slider" min="0" max="255" value="255"> | |
| </div> | |
| </div> | |
| <div class="control-group"> | |
| <h3>图像处理选项</h3> | |
| <div class="options-group"> | |
| <label class="option-label"> | |
| <input type="checkbox" id="invertColors"> | |
| <span>反色处理</span> | |
| </label> | |
| <label class="option-label"> | |
| <input type="checkbox" id="enableDithering" checked> | |
| <span>启用抖动</span> | |
| </label> | |
| </div> | |
| <div class="slider-container"> | |
| <label>抖动强度: <span class="slider-value" id="ditherStrengthValue">80</span></label> | |
| <input type="range" id="ditherStrengthSlider" min="0" max="100" value="80"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="image-list"> | |
| <h3>图片列表 (<span id="imageCount">0</span>)</h3> | |
| <div class="image-grid" id="imageGrid"></div> | |
| </div> | |
| <div class="progress-section" id="progressSection"> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressFill" style="width: 0%">0%</div> | |
| </div> | |
| <p class="info-text" id="progressText">准备处理...</p> | |
| </div> | |
| <div class="action-buttons"> | |
| <button class="action-btn process-btn" id="processBtn" disabled>处理所有图片</button> | |
| <button class="action-btn download-btn" id="downloadBtn" disabled>下载所有XTH文件</button> | |
| <button class="action-btn clear-btn" id="clearBtn">清空列表</button> | |
| </div> | |
| <p class="info-text">所有图片将自动裁剪为 480×800 像素并转换为XTH格式</p> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const TARGET_WIDTH = 480; | |
| const TARGET_HEIGHT = 800; | |
| // 获取DOM元素 | |
| const dropZone = document.getElementById('dropZone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const imageGrid = document.getElementById('imageGrid'); | |
| const imageCount = document.getElementById('imageCount'); | |
| const threshold1Slider = document.getElementById('threshold1Slider'); | |
| const threshold1Value = document.getElementById('threshold1Value'); | |
| const threshold2Slider = document.getElementById('threshold2Slider'); | |
| const threshold2Value = document.getElementById('threshold2Value'); | |
| const threshold3Slider = document.getElementById('threshold3Slider'); | |
| const threshold3Value = document.getElementById('threshold3Value'); | |
| const invertColors = document.getElementById('invertColors'); | |
| const enableDithering = document.getElementById('enableDithering'); | |
| const ditherStrengthSlider = document.getElementById('ditherStrengthSlider'); | |
| const ditherStrengthValue = document.getElementById('ditherStrengthValue'); | |
| const processBtn = document.getElementById('processBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const progressSection = document.getElementById('progressSection'); | |
| const progressFill = document.getElementById('progressFill'); | |
| const progressText = document.getElementById('progressText'); | |
| // 图片列表 | |
| let imageList = []; | |
| // 当前设置 | |
| let currentSettings = { | |
| threshold1: 85, | |
| threshold2: 170, | |
| threshold3: 255, | |
| invertColors: false, | |
| enableDithering: true, | |
| ditherStrength: 80 | |
| }; | |
| // 滑块事件监听 | |
| threshold1Slider.addEventListener('input', () => { | |
| threshold1Value.textContent = threshold1Slider.value; | |
| currentSettings.threshold1 = parseInt(threshold1Slider.value); | |
| }); | |
| threshold2Slider.addEventListener('input', () => { | |
| threshold2Value.textContent = threshold2Slider.value; | |
| currentSettings.threshold2 = parseInt(threshold2Slider.value); | |
| }); | |
| threshold3Slider.addEventListener('input', () => { | |
| threshold3Value.textContent = threshold3Slider.value; | |
| currentSettings.threshold3 = parseInt(threshold3Slider.value); | |
| }); | |
| ditherStrengthSlider.addEventListener('input', () => { | |
| ditherStrengthValue.textContent = ditherStrengthSlider.value; | |
| currentSettings.ditherStrength = parseInt(ditherStrengthSlider.value); | |
| }); | |
| invertColors.addEventListener('change', () => { | |
| currentSettings.invertColors = invertColors.checked; | |
| }); | |
| enableDithering.addEventListener('change', () => { | |
| currentSettings.enableDithering = enableDithering.checked; | |
| }); | |
| // 拖放区域事件 | |
| dropZone.addEventListener('click', () => { | |
| fileInput.click(); | |
| }); | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.add('drag-over'); | |
| }); | |
| dropZone.addEventListener('dragleave', () => { | |
| dropZone.classList.remove('drag-over'); | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('drag-over'); | |
| const files = Array.from(e.dataTransfer.files).filter(file => file.type.startsWith('image/')); | |
| handleFiles(files); | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| const files = Array.from(e.target.files); | |
| handleFiles(files); | |
| }); | |
| // 处理文件 | |
| function handleFiles(files) { | |
| files.forEach(file => { | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| const img = new Image(); | |
| img.onload = () => { | |
| imageList.push({ | |
| file: file, | |
| image: img, | |
| name: file.name, | |
| status: 'pending', | |
| canvas: null, | |
| xthData: null | |
| }); | |
| updateImageList(); | |
| }; | |
| img.src = event.target.result; | |
| }; | |
| reader.readAsDataURL(file); | |
| }); | |
| } | |
| // 更新图片列表显示 | |
| function updateImageList() { | |
| imageGrid.innerHTML = ''; | |
| imageCount.textContent = imageList.length; | |
| imageList.forEach((item, index) => { | |
| const itemDiv = document.createElement('div'); | |
| itemDiv.className = 'image-item'; | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = 200; | |
| canvas.height = Math.round(200 * TARGET_HEIGHT / TARGET_WIDTH); | |
| const ctx = canvas.getContext('2d'); | |
| // 绘制缩略图 | |
| const scale = Math.min(canvas.width / item.image.width, canvas.height / item.image.height); | |
| const x = (canvas.width - item.image.width * scale) / 2; | |
| const y = (canvas.height - item.image.height * scale) / 2; | |
| ctx.drawImage(item.image, x, y, item.image.width * scale, item.image.height * scale); | |
| const statusDiv = document.createElement('div'); | |
| statusDiv.className = 'image-status'; | |
| updateStatus(statusDiv, item.status); | |
| const nameDiv = document.createElement('div'); | |
| nameDiv.className = 'image-name'; | |
| nameDiv.textContent = item.name; | |
| itemDiv.appendChild(canvas); | |
| itemDiv.appendChild(statusDiv); | |
| itemDiv.appendChild(nameDiv); | |
| imageGrid.appendChild(itemDiv); | |
| item.canvas = canvas; | |
| item.statusDiv = statusDiv; | |
| }); | |
| processBtn.disabled = imageList.length === 0; | |
| } | |
| // 更新状态显示 | |
| function updateStatus(statusDiv, status) { | |
| statusDiv.className = 'image-status'; | |
| if (status === 'processing') { | |
| statusDiv.textContent = '处理中'; | |
| statusDiv.classList.add('status-processing'); | |
| } else if (status === 'ready') { | |
| statusDiv.textContent = '就绪'; | |
| statusDiv.classList.add('status-ready'); | |
| } else if (status === 'error') { | |
| statusDiv.textContent = '错误'; | |
| statusDiv.classList.add('status-error'); | |
| } else { | |
| statusDiv.textContent = '待处理'; | |
| } | |
| } | |
| // 裁剪图片到目标尺寸(fit裁剪) | |
| function cropImageToSize(image, targetWidth, targetHeight) { | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = targetWidth; | |
| canvas.height = targetHeight; | |
| const ctx = canvas.getContext('2d'); | |
| // 计算缩放比例,保持宽高比 | |
| const scale = Math.max(targetWidth / image.width, targetHeight / image.height); | |
| const scaledWidth = image.width * scale; | |
| const scaledHeight = image.height * scale; | |
| // 居中裁剪 | |
| const x = (targetWidth - scaledWidth) / 2; | |
| const y = (targetHeight - scaledHeight) / 2; | |
| ctx.fillStyle = 'white'; | |
| ctx.fillRect(0, 0, targetWidth, targetHeight); | |
| ctx.drawImage(image, x, y, scaledWidth, scaledHeight); | |
| return canvas; | |
| } | |
| // 生成4灰阶图像 | |
| function generate4Grayscale(canvas, settings) { | |
| const width = canvas.width; | |
| const height = canvas.height; | |
| const ctx = canvas.getContext('2d'); | |
| const imageData = ctx.getImageData(0, 0, width, height); | |
| const data = imageData.data; | |
| const grayscaleImageData = ctx.createImageData(width, height); | |
| const grayscaleData = grayscaleImageData.data; | |
| let errorMatrix = null; | |
| if (settings.enableDithering) { | |
| errorMatrix = new Array(width * height).fill(0); | |
| } | |
| for (let y = 0; y < height; y++) { | |
| for (let x = 0; x < width; x++) { | |
| const index = (y * width + x) * 4; | |
| const gray = Math.round(0.299 * data[index] + 0.587 * data[index+1] + 0.114 * data[index+2]); | |
| let adjustedGray = gray; | |
| if (settings.enableDithering) { | |
| const errorIndex = y * width + x; | |
| adjustedGray += errorMatrix[errorIndex] * (settings.ditherStrength / 100); | |
| adjustedGray = Math.max(0, Math.min(255, adjustedGray)); | |
| } | |
| let grayscaleValue; | |
| if (adjustedGray < settings.threshold1) { | |
| grayscaleValue = 0; | |
| } else if (adjustedGray < settings.threshold2) { | |
| grayscaleValue = 85; | |
| } else if (adjustedGray < settings.threshold3) { | |
| grayscaleValue = 170; | |
| } else { | |
| grayscaleValue = 255; | |
| } | |
| if (settings.enableDithering) { | |
| const error = adjustedGray - grayscaleValue; | |
| if (x < width - 1) { | |
| errorMatrix[y * width + x + 1] += error * 7/16; | |
| } | |
| if (y < height - 1) { | |
| if (x > 0) { | |
| errorMatrix[(y + 1) * width + x - 1] += error * 3/16; | |
| } | |
| errorMatrix[(y + 1) * width + x] += error * 5/16; | |
| if (x < width - 1) { | |
| errorMatrix[(y + 1) * width + x + 1] += error * 1/16; | |
| } | |
| } | |
| } | |
| if (settings.invertColors) { | |
| grayscaleValue = 255 - grayscaleValue; | |
| } | |
| grayscaleData[index] = grayscaleValue; | |
| grayscaleData[index+1] = grayscaleValue; | |
| grayscaleData[index+2] = grayscaleValue; | |
| grayscaleData[index+3] = data[index+3]; | |
| } | |
| } | |
| ctx.putImageData(grayscaleImageData, 0, 0); | |
| } | |
| // 生成XTH文件数据 | |
| function generateXthData(canvas, settings) { | |
| const width = canvas.width; | |
| const height = canvas.height; | |
| const ctx = canvas.getContext('2d'); | |
| const imageData = ctx.getImageData(0, 0, width, height); | |
| const data = imageData.data; | |
| const arrayData1 = []; | |
| const arrayData2 = []; | |
| // 垂直扫描:从右到左处理列 | |
| for (let x = width - 1; x >= 0; x--) { | |
| for (let y = 0; y < height; y += 8) { | |
| let byte1 = 0; | |
| let byte2 = 0; | |
| for (let i = 0; i < 8; i++) { | |
| if (y + i < height) { | |
| const index = ((y + i) * width + x) * 4; | |
| const grayValue = data[index]; | |
| let twoBitValue; | |
| if (grayValue < settings.threshold1) { | |
| twoBitValue = 0; | |
| } else if (grayValue < settings.threshold2) { | |
| twoBitValue = 2; | |
| } else if (grayValue < settings.threshold3) { | |
| twoBitValue = 1; | |
| } else { | |
| twoBitValue = 3; | |
| } | |
| twoBitValue = 3 - twoBitValue; | |
| const bit1 = (twoBitValue >> 1) & 1; | |
| const bit2 = twoBitValue & 1; | |
| byte1 |= bit1 << (7 - i); | |
| byte2 |= bit2 << (7 - i); | |
| } | |
| } | |
| arrayData1.push(byte1); | |
| arrayData2.push(byte2); | |
| } | |
| } | |
| return createXthFile(width, height, arrayData1, arrayData2); | |
| } | |
| // 创建XTH文件 | |
| function createXthFile(width, height, data1, data2) { | |
| const headerSize = 22; | |
| const dataSize = data1.length + data2.length; | |
| const buffer = new ArrayBuffer(headerSize + dataSize); | |
| const view = new DataView(buffer); | |
| view.setUint32(0, 0x58544800, false); | |
| view.setUint16(4, width, true); | |
| view.setUint16(6, height, true); | |
| view.setUint8(8, 0); | |
| view.setUint8(9, 0); | |
| view.setUint32(10, dataSize, true); | |
| let checksum = 0; | |
| for (let i = 0; i < data1.length; i++) { | |
| checksum += data1[i]; | |
| } | |
| for (let i = 0; i < data2.length; i++) { | |
| checksum += data2[i]; | |
| } | |
| view.setBigUint64(14, BigInt(checksum), true); | |
| const dataArray = new Uint8Array(buffer, headerSize); | |
| dataArray.set(data1, 0); | |
| dataArray.set(data2, data1.length); | |
| return buffer; | |
| } | |
| // 处理所有图片 | |
| processBtn.addEventListener('click', async () => { | |
| if (imageList.length === 0) return; | |
| processBtn.disabled = true; | |
| downloadBtn.disabled = true; | |
| progressSection.classList.add('active'); | |
| let processed = 0; | |
| const total = imageList.length; | |
| for (let i = 0; i < imageList.length; i++) { | |
| const item = imageList[i]; | |
| item.status = 'processing'; | |
| if (item.statusDiv) { | |
| updateStatus(item.statusDiv, 'processing'); | |
| } | |
| try { | |
| // 裁剪图片 | |
| const croppedCanvas = cropImageToSize(item.image, TARGET_WIDTH, TARGET_HEIGHT); | |
| // 生成4灰阶 | |
| generate4Grayscale(croppedCanvas, currentSettings); | |
| // 生成XTH数据 | |
| item.xthData = generateXthData(croppedCanvas, currentSettings); | |
| item.status = 'ready'; | |
| // 更新缩略图显示处理后的图像 | |
| if (item.canvas) { | |
| const thumbCtx = item.canvas.getContext('2d'); | |
| thumbCtx.clearRect(0, 0, item.canvas.width, item.canvas.height); | |
| thumbCtx.drawImage(croppedCanvas, 0, 0, item.canvas.width, item.canvas.height); | |
| } | |
| } catch (error) { | |
| console.error('处理图片失败:', item.name, error); | |
| item.status = 'error'; | |
| } | |
| if (item.statusDiv) { | |
| updateStatus(item.statusDiv, item.status); | |
| } | |
| processed++; | |
| const progress = Math.round((processed / total) * 100); | |
| progressFill.style.width = progress + '%'; | |
| progressFill.textContent = progress + '%'; | |
| progressText.textContent = `已处理 ${processed} / ${total} 张图片`; | |
| // 让UI更新 | |
| await new Promise(resolve => setTimeout(resolve, 10)); | |
| } | |
| processBtn.disabled = false; | |
| downloadBtn.disabled = imageList.filter(item => item.status === 'ready').length === 0; | |
| progressText.textContent = `处理完成!共 ${imageList.filter(item => item.status === 'ready').length} 张图片已就绪`; | |
| }); | |
| // 下载所有XTH文件 | |
| downloadBtn.addEventListener('click', () => { | |
| const readyItems = imageList.filter(item => item.status === 'ready' && item.xthData); | |
| if (readyItems.length === 0) { | |
| alert('没有可下载的文件'); | |
| return; | |
| } | |
| readyItems.forEach(item => { | |
| const blob = new Blob([item.xthData], { type: 'application/octet-stream' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| const fileName = item.name.substring(0, item.name.lastIndexOf('.')) + '.xth'; | |
| a.download = fileName; | |
| document.body.appendChild(a); | |
| a.click(); | |
| setTimeout(() => { | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }, 100); | |
| }); | |
| }); | |
| // 清空列表 | |
| clearBtn.addEventListener('click', () => { | |
| imageList = []; | |
| updateImageList(); | |
| progressSection.classList.remove('active'); | |
| downloadBtn.disabled = true; | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment