零基础的普通人,利用AI手搓图片转PDF工具
<!DOCTYPE html><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>图片转PDF工具 - 高级版</title><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;padding: 20px;}.container {max-width: 900px;margin: 0 auto;background: white;border-radius: 20px;padding: 40px;box-shadow: 0 20px 60px rgba(0,0,0,0.3);}h1 {text-align: center;color: #333;margin-bottom: 10px;font-size: 28px;}.subtitle {text-align: center;color: #666;margin-bottom: 30px;font-size: 14px;}/* 上传区域 */.upload-area {border: 3px dashed #667eea;border-radius: 15px;padding: 60px 20px;text-align: center;background: #f8f9ff;transition: all 0.3s;cursor: pointer;}.upload-area:hover {background: #eef1ff;border-color: #764ba2;}.upload-area.active {background: #e8ecff;border-color: #764ba2;}.upload-icon {font-size: 60px;margin-bottom: 15px;}.upload-text {color: #555;font-size: 18px;margin-bottom: 10px;}.upload-hint {color: #999;font-size: 13px;}#fileInput {display: none;}/* 工具栏 */.toolbar {margin-bottom: 15px;padding: 12px 15px;background: #f0f2ff;border-radius: 10px;display: none;align-items: center;justify-content: space-between;flex-wrap: wrap;gap: 10px;}.toolbar.show {display: flex;}.toolbar-left {display: flex;align-items: center;gap: 10px;flex-wrap: wrap;}.checkbox-wrapper {display: flex;align-items: center;gap: 6px;cursor: pointer;font-size: 14px;color: #555;user-select: none;}.checkbox-wrapper input[type="checkbox"] {width: 18px;height: 18px;cursor: pointer;}.batch-btn {padding: 6px 14px;border: none;border-radius: 6px;font-size: 13px;cursor: pointer;transition: all 0.3s;display: flex;align-items: center;gap: 5px;}.batch-delete {background: #ff4757;color: white;}.batch-delete:hover:not(:disabled) {background: #ff3344;}.batch-delete:disabled {opacity: 0.5;cursor: not-allowed;}.selected-count {color: #667eea;font-weight: 600;font-size: 14px;}/* 图片预览区 */.preview-section {margin-top: 30px;display: none;}.preview-section.show {display: block;animation: fadeIn 0.5s;}@keyframes fadeIn {from { opacity: 0; transform: translateY(20px); }to { opacity: 1; transform: translateY(0); }}.section-title {font-size: 18px;color: #333;margin-bottom: 15px;display: flex;align-items: center;gap: 8px;}.sort-hint {color: #667eea;font-size: 13px;font-weight: normal;margin-left: auto;background: #f0f2ff;padding: 5px 12px;border-radius: 20px;}.image-list {display: flex;flex-direction: column;gap: 10px;}.image-item {background: #f8f9ff;border-radius: 12px;padding: 12px;display: flex;align-items: center;gap: 12px;transition: all 0.3s;border: 2px solid transparent;cursor: grab;position: relative;}.image-item:hover {background: #eef1ff;transform: translateX(5px);box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);}.image-item.selected {background: #e8ecff;border-color: #667eea;}.image-item.dragging {opacity: 0.5;cursor: grabbing;border: 2px dashed #667eea;}.image-item.drag-over {border: 2px dashed #764ba2;background: #f0e6ff;transform: scale(1.02);}.item-checkbox {width: 20px;height: 20px;cursor: pointer;flex-shrink: 0;}.drag-handle {color: #999;font-size: 20px;cursor: grab;padding: 5px;user-select: none;flex-shrink: 0;}.drag-handle:active {cursor: grabbing;}.order-number {width: 30px;height: 30px;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;border-radius: 50%;display: flex;align-items: center;justify-content: center;font-weight: bold;font-size: 14px;flex-shrink: 0;}.image-thumb-wrapper {position: relative;flex-shrink: 0;cursor: zoom-in;}.image-thumb {width: 70px;height: 70px;object-fit: cover;border-radius: 8px;border: 2px solid #ddd;transition: transform 0.3s;}.image-thumb-wrapper:hover .image-thumb {transform: scale(1.05);}.zoom-icon {position: absolute;bottom: 5px;right: 5px;background: rgba(0,0,0,0.6);color: white;width: 24px;height: 24px;border-radius: 50%;display: flex;align-items: center;justify-content: center;font-size: 12px;opacity: 0;transition: opacity 0.3s;}.image-thumb-wrapper:hover .zoom-icon {opacity: 1;}.image-info {flex: 1;min-width: 0;}.image-name {font-weight: 600;color: #333;margin-bottom: 5px;word-break: break-all;font-size: 14px;}.image-size {color: #888;font-size: 12px;}.image-actions {display: flex;gap: 6px;flex-shrink: 0;flex-wrap: wrap;}.btn-small {padding: 6px 10px;border: none;border-radius: 6px;cursor: pointer;font-size: 12px;transition: all 0.3s;display: flex;align-items: center;justify-content: center;min-width: 32px;height: 32px;}.btn-up, .btn-down {background: #e0e0e0;color: #666;}.btn-up:hover:not(:disabled), .btn-down:hover:not(:disabled) {background: #667eea;color: white;}.btn-up:disabled, .btn-down:disabled {opacity: 0.3;cursor: not-allowed;}.btn-rotate {background: #4CAF50;color: white;}.btn-rotate:hover {background: #45a049;transform: scale(1.1);}.btn-delete {background: #ff4757;color: white;}.btn-delete:hover {background: #ff3344;transform: scale(1.05);}/* 大图预览模态框 */.modal-overlay {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0,0,0,0.9);display: none;align-items: center;justify-content: center;z-index: 1000;padding: 20px;}.modal-overlay.show {display: flex;animation: fadeIn 0.3s;}.modal-content {position: relative;max-width: 90%;max-height: 90%;display: flex;flex-direction: column;align-items: center;}.modal-image {max-width: 100%;max-height: 80vh;object-fit: contain;border-radius: 8px;box-shadow: 0 10px 40px rgba(0,0,0,0.5);}.modal-info {color: white;margin-top: 15px;text-align: center;font-size: 14px;}.modal-close {position: absolute;top: -40px;right: 0;background: rgba(255,255,255,0.2);color: white;border: none;width: 40px;height: 40px;border-radius: 50%;font-size: 24px;cursor: pointer;display: flex;align-items: center;justify-content: center;transition: all 0.3s;}.modal-close:hover {background: rgba(255,255,255,0.4);transform: rotate(90deg);}.modal-nav {position: absolute;top: 50%;transform: translateY(-50%);background: rgba(255,255,255,0.2);color: white;border: none;width: 50px;height: 50px;border-radius: 50%;font-size: 20px;cursor: pointer;display: flex;align-items: center;justify-content: center;transition: all 0.3s;}.modal-nav:hover {background: rgba(255,255,255,0.4);}.modal-prev {left: -70px;}.modal-next {right: -70px;}/* 设置区域 */.settings {margin-top: 25px;padding: 20px;background: #f8f9ff;border-radius: 12px;}.setting-item {margin-bottom: 15px;}.setting-item:last-child {margin-bottom: 0;}.setting-label {display: block;margin-bottom: 8px;color: #555;font-size: 14px;font-weight: 500;}select, input[type="text"] {width: 100%;padding: 10px 15px;border: 2px solid #ddd;border-radius: 8px;font-size: 14px;transition: border-color 0.3s;}select:focus, input[type="text"]:focus {outline: none;border-color: #667eea;}/* 按钮组 */.btn-group {margin-top: 30px;display: flex;gap: 15px;flex-wrap: wrap;}.btn {flex: 1;min-width: 120px;padding: 15px 30px;border: none;border-radius: 10px;font-size: 16px;font-weight: 600;cursor: pointer;transition: all 0.3s;display: flex;align-items: center;justify-content: center;gap: 8px;}.btn-primary {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;}.btn-primary:hover:not(:disabled) {transform: translateY(-2px);box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);}.btn-secondary {background: #f1f1f1;color: #666;}.btn-secondary:hover {background: #e1e1e1;}.btn:disabled {opacity: 0.6;cursor: not-allowed;}/* 进度提示 */.progress {margin-top: 20px;padding: 15px;background: #e8f5e9;border-radius: 10px;color: #2e7d32;text-align: center;display: none;}.progress.show {display: block;animation: pulse 1.5s infinite;}@keyframes pulse {0%, 100% { opacity: 1; }50% { opacity: 0.7; }}/* 页脚 */.footer {text-align: center;margin-top: 30px;padding-top: 20px;border-top: 1px solid #eee;}.footer-hint {color: #999;font-size: 12px;margin-bottom: 8px;}.signature {display: inline-flex;align-items: center;gap: 8px;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);color: white;padding: 8px 20px;border-radius: 20px;font-size: 14px;font-weight: 600;box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);}.signature::before {content: "🍃";}/* 响应式 */@media (max-width: 768px) {.container {padding: 20px;}.image-item {flex-wrap: wrap;}.image-actions {width: 100%;justify-content: flex-end;margin-top: 10px;}.modal-nav {width: 40px;height: 40px;font-size: 16px;}.modal-prev {left: 10px;}.modal-next {right: 10px;}}</style></head><body><divclass="container"><h1>🖼️ 图片转PDF工具</h1><pclass="subtitle">拖拽排序、旋转、批量删除、预览大图</p><!-- 上传区域 --><divclass="upload-area"onclick="document.getElementById('fileInput').click()"><divclass="upload-icon">📁</div><divclass="upload-text">点击或拖拽图片到此处</div><divclass="upload-hint">支持 JPG、PNG、GIF、WEBP 格式,可批量上传</div></div><inputtype="file"id="fileInput"accept="image/*"multipleonchange="handleFiles(this.files)"><!-- 图片预览 --><divclass="preview-section"id="previewSection"><!-- 工具栏 --><divclass="toolbar"id="toolbar"><divclass="toolbar-left"><labelclass="checkbox-wrapper"><inputtype="checkbox"id="selectAll"onchange="toggleSelectAll()"><span>全选</span></label><spanclass="selected-count"id="selectedCount"></span></div><buttonclass="batch-btn batch-delete"id="batchDeleteBtn"onclick="batchDelete()"disabled>🗑️ 删除选中 (<spanid="deleteCount">0</span>)</button></div><divclass="section-title">📋 已选择的图片<spanid="imageCount">0</span>张<spanclass="sort-hint">👆 拖拽排序 | 点击缩略图预览 | 🔄 旋转图片</span></div><divclass="image-list"id="imageList"></div><!-- 设置 --><divclass="settings"><divclass="setting-item"><labelclass="setting-label">📄 页面方向</label><selectid="pageOrientation"><optionvalue="portrait">纵向(适合手机查看)</option><optionvalue="landscape">横向(适合电脑查看)</option><optionvalue="auto">自动适应图片</option></select></div><divclass="setting-item"><labelclass="setting-label">💾 文件名</label><inputtype="text"id="fileName"placeholder="输入文件名(默认:图片合集)"value="图片合集"></div></div><!-- 按钮组 --><divclass="btn-group"><buttonclass="btn btn-secondary"onclick="clearAll()">🗑️ 清空全部</button><buttonclass="btn btn-primary"id="convertBtn"onclick="convertToPDF()">🚀 开始转换</button></div><!-- 进度提示 --><divclass="progress"id="progress">⏳ 正在生成PDF,请稍候...</div></div><!-- 页脚署名 --><divclass="footer"><divclass="footer-hint">💡 提示:旋转后的图片会自动保存状态,生成的PDF将使用最终效果</div><divclass="signature">公众号:小叔自记</div></div></div><!-- 大图预览模态框 --><divclass="modal-overlay"id="imageModal"onclick="closeModal(event)"><divclass="modal-content"onclick="event.stopPropagation()"><buttonclass="modal-close"onclick="closeModal()">×</button><buttonclass="modal-nav modal-prev"onclick="navigateModal(-1)">‹</button><buttonclass="modal-nav modal-next"onclick="navigateModal(1)">›</button><imgclass="modal-image"id="modalImage"src=""alt=""><divclass="modal-info"id="modalInfo"></div></div></div><script>// 存储选中的图片let selectedImages = [];let draggedIndex = null;let currentModalIndex = 0;// 拖拽上传const uploadArea = document.querySelector('.upload-area');uploadArea.addEventListener('dragover', (e) => {e.preventDefault();uploadArea.classList.add('active');});uploadArea.addEventListener('dragleave', () => {uploadArea.classList.remove('active');});uploadArea.addEventListener('drop', (e) => {e.preventDefault();uploadArea.classList.remove('active');handleFiles(e.dataTransfer.files);});// 处理文件function handleFiles(files) {const validFiles = Array.from(files).filter(file => file.type.startsWith('image/'));if (validFiles.length === 0) {alert('请选择图片文件!');return;}validFiles.forEach(file => {const reader = new FileReader();reader.onload = (e) => {selectedImages.push({name: file.name,size: formatFileSize(file.size),data: e.target.result,id: Date.now() + Math.random(),rotation: 0,selected: false});updatePreview();};reader.readAsDataURL(file);});}// 格式化文件大小function formatFileSize(bytes) {if (bytes === 0) return '0 Bytes';const k = 1024;const sizes = ['Bytes', 'KB', 'MB', 'GB'];const i = Math.floor(Math.log(bytes) / Math.log(k));return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];}// 更新预览function updatePreview() {const previewSection = document.getElementById('previewSection');const toolbar = document.getElementById('toolbar');const imageList = document.getElementById('imageList');const imageCount = document.getElementById('imageCount');if (selectedImages.length > 0) {previewSection.classList.add('show');toolbar.classList.add('show');imageCount.textContent = selectedImages.length;updateToolbar();imageList.innerHTML = selectedImages.map((img, index) => `<div class="image-item ${img.selected ? 'selected' : ''}"draggable="true"data-index="${index}"ondragstart="handleDragStart(event, ${index})"ondragend="handleDragEnd(event)"ondragover="handleDragOver(event, ${index})"ondrop="handleDrop(event, ${index})"ondragenter="handleDragEnter(event)"ondragleave="handleDragLeave(event)"><input type="checkbox"class="item-checkbox"${img.selected ? 'checked' : ''}onchange="toggleItemSelect(${index})"onclick="event.stopPropagation()"><div class="drag-handle">☰</div><div class="order-number">${index + 1}</div><div class="image-thumb-wrapper" onclick="openModal(${index})"><img src="${img.data}"class="image-thumb"alt="${img.name}"style="transform: rotate(${img.rotation}deg);"><div class="zoom-icon">🔍</div></div><div class="image-info"><div class="image-name">${img.name}</div><div class="image-size">${img.size} ${img.rotation > 0 ? '| 已旋转' + img.rotation + '°' : ''}</div></div><div class="image-actions"><button class="btn-small btn-up"onclick="moveUp(${index})"${index === 0 ? 'disabled' : ''}title="上移">↑</button><button class="btn-small btn-down"onclick="moveDown(${index})"${index === selectedImages.length - 1 ? 'disabled' : ''}title="下移">↓</button><button class="btn-small btn-rotate"onclick="rotateImage(${index})"title="顺时针旋转90°">🔄</button><button class="btn-small btn-delete"onclick="removeImage(${index})"title="删除">🗑️</button></div></div>`).join('');} else {previewSection.classList.remove('show');toolbar.classList.remove('show');}}// 更新工具栏状态function updateToolbar() {const selectedCount = selectedImages.filter(img => img.selected).length;const selectAllCheckbox = document.getElementById('selectAll');const selectedCountEl = document.getElementById('selectedCount');const batchDeleteBtn = document.getElementById('batchDeleteBtn');const deleteCount = document.getElementById('deleteCount');selectAllCheckbox.checked = selectedCount === selectedImages.length && selectedCount > 0;selectAllCheckbox.indeterminate = selectedCount > 0 && selectedCount < selectedImages.length;if (selectedCount > 0) {selectedCountEl.textContent = `已选择 ${selectedCount} 张`;batchDeleteBtn.disabled = false;deleteCount.textContent = selectedCount;} else {selectedCountEl.textContent = '';batchDeleteBtn.disabled = true;deleteCount.textContent = 0;}}// 全选/取消全选function toggleSelectAll() {const selectAll = document.getElementById('selectAll').checked;selectedImages.forEach(img => img.selected = selectAll);updatePreview();}// 单选function toggleItemSelect(index) {selectedImages[index].selected = !selectedImages[index].selected;updatePreview();}// 批量删除function batchDelete() {const selectedCount = selectedImages.filter(img => img.selected).length;if (selectedCount === 0) return;if (confirm(`确定要删除选中的 ${selectedCount} 张图片吗?`)) {selectedImages = selectedImages.filter(img => !img.selected);updatePreview();}}// 旋转图片function rotateImage(index) {selectedImages[index].rotation = (selectedImages[index].rotation + 90) % 360;updatePreview();}// 打开大图预览function openModal(index) {currentModalIndex = index;updateModal();document.getElementById('imageModal').classList.add('show');document.body.style.overflow = 'hidden';}// 关闭模态框function closeModal(event) {if (!event || event.target.id === 'imageModal' || event.target.classList.contains('modal-close')) {document.getElementById('imageModal').classList.remove('show');document.body.style.overflow = '';}}// 更新模态框内容function updateModal() {const img = selectedImages[currentModalIndex];const modalImage = document.getElementById('modalImage');const modalInfo = document.getElementById('modalInfo');modalImage.src = img.data;modalImage.style.transform = `rotate(${img.rotation}deg)`;modalInfo.innerHTML = `<strong>${img.name}</strong><br>第 ${currentModalIndex + 1} / ${selectedImages.length} 张 | ${img.size}${img.rotation > 0 ? ` | 已旋转 ${img.rotation}°` : ''}`;document.querySelector('.modal-prev').style.display =currentModalIndex > 0 ? 'flex' : 'none';document.querySelector('.modal-next').style.display =currentModalIndex < selectedImages.length - 1 ? 'flex' : 'none';}// 模态框导航function navigateModal(direction) {const newIndex = currentModalIndex + direction;if (newIndex >= 0 && newIndex < selectedImages.length) {currentModalIndex = newIndex;updateModal();}}// 键盘导航document.addEventListener('keydown', (e) => {if (!document.getElementById('imageModal').classList.contains('show')) return;if (e.key === 'Escape') closeModal();if (e.key === 'ArrowLeft') navigateModal(-1);if (e.key === 'ArrowRight') navigateModal(1);});// 拖拽排序功能function handleDragStart(event, index) {draggedIndex = index;event.target.classList.add('dragging');event.dataTransfer.effectAllowed = 'move';}function handleDragEnd(event) {event.target.classList.remove('dragging');document.querySelectorAll('.image-item').forEach(item => {item.classList.remove('drag-over');});}function handleDragEnter(event) {event.preventDefault();const item = event.currentTarget;if (!item.classList.contains('dragging')) {item.classList.add('drag-over');}}function handleDragLeave(event) {const item = event.currentTarget;if (!item.contains(event.relatedTarget)) {item.classList.remove('drag-over');}}function handleDragOver(event, index) {event.preventDefault();event.dataTransfer.dropEffect = 'move';}function handleDrop(event, dropIndex) {event.preventDefault();const item = event.currentTarget;item.classList.remove('drag-over');if (draggedIndex === null || draggedIndex === dropIndex) return;const draggedItem = selectedImages[draggedIndex];selectedImages.splice(draggedIndex, 1);selectedImages.splice(dropIndex, 0, draggedItem);draggedIndex = null;updatePreview();}// 按钮移动功能function moveUp(index) {if (index <= 0) return;const temp = selectedImages[index];selectedImages[index] = selectedImages[index - 1];selectedImages[index - 1] = temp;updatePreview();}function moveDown(index) {if (index >= selectedImages.length - 1) return;const temp = selectedImages[index];selectedImages[index] = selectedImages[index + 1];selectedImages[index + 1] = temp;updatePreview();}// 删除单张图片function removeImage(index) {selectedImages.splice(index, 1);updatePreview();}// 清空全部function clearAll() {if (selectedImages.length === 0) return;if (confirm('确定要清空所有图片吗?')) {selectedImages = [];updatePreview();document.getElementById('fileName').value = '图片合集';}}// 转换为PDFasync function convertToPDF() {if (selectedImages.length === 0) {alert('请先选择图片!');return;}const btn = document.getElementById('convertBtn');const progress = document.getElementById('progress');btn.disabled = true;progress.classList.add('show');try {const { jsPDF } = window.jspdf;const orientation = document.getElementById('pageOrientation').value;const fileName = document.getElementById('fileName').value || '图片合集';const pdf = new jsPDF({unit: 'mm',compress: true});for (let i = 0; i < selectedImages.length; i++) {const img = selectedImages[i];const processedData = await processImageWithRotation(img);const imageObj = await loadImage(processedData);const imgWidth = imageObj.width;const imgHeight = imageObj.height;const ratio = imgWidth / imgHeight;let pageWidth, pageHeight;if (orientation === 'auto') {if (ratio > 1) {pageWidth = 297;pageHeight = 210;pdf.addPage([pageWidth, pageHeight], 'landscape');} else {pageWidth = 210;pageHeight = 297;pdf.addPage([pageWidth, pageHeight], 'portrait');}if (i === 0) pdf.deletePage(1);} else {pageWidth = pdf.internal.pageSize.getWidth();pageHeight = pdf.internal.pageSize.getHeight();if (i > 0) pdf.addPage();}const margin = 10;const maxWidth = pageWidth - 2 * margin;const maxHeight = pageHeight - 2 * margin;let pdfImgWidth, pdfImgHeight;if (ratio > maxWidth / maxHeight) {pdfImgWidth = maxWidth;pdfImgHeight = maxWidth / ratio;} else {pdfImgHeight = maxHeight;pdfImgWidth = maxHeight * ratio;}const x = (pageWidth - pdfImgWidth) / 2;const y = (pageHeight - pdfImgHeight) / 2;pdf.addImage(processedData, 'JPEG', x, y, pdfImgWidth, pdfImgHeight, undefined, 'FAST');}pdf.save(`${fileName}.pdf`);progress.innerHTML = '✅ 转换成功!PDF已下载';progress.style.background = '#e8f5e9';progress.style.color = '#2e7d32';setTimeout(() => {progress.classList.remove('show');progress.innerHTML = '⏳ 正在生成PDF,请稍候...';btn.disabled = false;}, 3000);} catch (error) {console.error(error);alert('转换失败:' + error.message);progress.classList.remove('show');btn.disabled = false;}}// 处理带旋转的图片async function processImageWithRotation(imgData) {if (imgData.rotation === 0) return imgData.data;return new Promise((resolve) => {const img = new Image();img.onload = () => {const canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');if (imgData.rotation === 90 || imgData.rotation === 270) {canvas.width = img.height;canvas.height = img.width;} else {canvas.width = img.width;canvas.height = img.height;}ctx.translate(canvas.width / 2, canvas.height / 2);ctx.rotate(imgData.rotation * Math.PI / 180);ctx.drawImage(img, -img.width / 2, -img.height / 2);resolve(canvas.toDataURL('image/jpeg', 0.95));};img.src = imgData.data;});}// 加载图片获取尺寸function loadImage(src) {return new Promise((resolve, reject) => {const img = new Image();img.onload = () => resolve(img);img.onerror = reject;img.src = src;});}</script></body></html>


夜雨聆风