乐于分享
好东西不私藏

零基础的普通人,利用AI手搓图片转PDF工具

零基础的普通人,利用AI手搓图片转PDF工具

    现在的ai真的是造福我们这些零基础的小白,不到30分钟就能用自然语言手搓出某些软件需要付费的工具。
    昨天因工作需要把图片转成PDF格式发给客户,使用某办公软件转,竟然这么简单的功能都要开会员。在网上找半天没找到合适的工具,突发奇想问deekseep能不能自己做一个工具,竟然真的能成。
    随后与dp展开多轮对话,最终30分钟完成。功能:图片转PDF格式文件。图片可自由旋转,排序,批量删除、预览大图、支持图片的格式JPG/PNG/WEBP等常见格式。 优点:简单轻巧、无需安装、电脑有浏览器就可以使用(HTML文件)。  缺点:简单功能单一。
代码如下:
<!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 = '图片合集';            }        }        // 转换为PDF        async 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>
使用方法更是简单,新建文本-复制代码到文本-修改文件名后缀为HTML。效果如下: