图片批量分享工具生成相册

联启 电脑工具 12
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">📸 相册生成器 · 图片批量分享</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        body {
            font-family: 'Segoe UI', Roboto, system-ui, -apple-system, sans-serif;
            background: linear-gradient(145deg, #f6f9fc 0%, #e9f0f5 100%);
            min-height: 100vh;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 1.5rem 1rem;
        }
        .app-card {
            max-width: 1100px;
            width: 100%;
            background: rgba(255, 255, 255, 0.7);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border-radius: 48px;
            padding: 2rem 1.8rem 2.5rem;
            box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.6);
            transition: all 0.2s ease;
        }
        h1 {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            font-weight: 600;
            font-size: 2.2rem;
            letter-spacing: -0.02em;
            color: #1e293b;
            margin-bottom: 0.2rem;
        }
        .subhead {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            flex-wrap: wrap;
            margin-bottom: 1.2rem;
            border-bottom: 2px solid rgba(148, 163, 184, 0.3);
            padding-bottom: 0.8rem;
        }
        .subhead p {
            color: #475569;
            font-weight: 450;
            font-size: 1rem;
        }
        .toolbar {
            display: flex;
            flex-wrap: wrap;
            gap: 0.8rem 1rem;
            align-items: center;
            margin: 1rem 0 1.8rem 0;
        }
        .btn {
            background: white;
            border: none;
            padding: 0.65rem 1.6rem;
            border-radius: 60px;
            font-weight: 500;
            font-size: 0.95rem;
            display: inline-flex;
            align-items: center;
            gap: 0.4rem;
            box-shadow: 0 2px 6px rgba(0, 20, 30, 0.08);
            transition: 0.2s;
            cursor: pointer;
            color: #0f172a;
            border: 1px solid rgba(148, 163, 184, 0.25);
            background: rgba(255,255,255,0.75);
            backdrop-filter: blur(4px);
        }
        .btn-primary {
            background: #1e293b;
            border: 1px solid #1e293b;
            color: white;
            box-shadow: 0 6px 14px rgba(30, 41, 59, 0.25);
        }
        .btn-primary:hover {
            background: #0f172a;
            transform: scale(0.97);
        }
        .btn-outline {
            background: transparent;
            border: 1px solid #94a3b8;
        }
        .btn:hover:not(:disabled) {
            background: #f1f5f9;
            transform: translateY(-1px);
        }
        .btn-primary:hover:not(:disabled) {
            background: #0f172a;
        }
        .btn:disabled {
            opacity: 0.5;
            cursor: default;
            transform: none;
        }
        .badge {
            background: #dbeafe;
            color: #1e40af;
            padding: 0.2rem 0.9rem;
            border-radius: 40px;
            font-size: 0.85rem;
            font-weight: 500;
            margin-left: 0.3rem;
        }
        .upload-area {
            background: rgba(255, 255, 255, 0.5);
            border: 2px dashed #94a3b8;
            border-radius: 30px;
            padding: 1.8rem 1.5rem;
            text-align: center;
            margin: 0.5rem 0 1.8rem 0;
            transition: 0.2s;
            cursor: pointer;
        }
        .upload-area:hover {
            background: rgba(255, 255, 255, 0.8);
            border-color: #3b82f6;
        }
        .upload-area svg {
            width: 42px;
            height: 42px;
            color: #64748b;
            margin-bottom: 0.2rem;
        }
        .upload-area p {
            color: #334155;
            font-weight: 450;
        }
        .upload-area input[type="file"] {
            display: none;
        }
        .gallery-container {
            background: rgba(255, 255, 255, 0.3);
            border-radius: 32px;
            padding: 1rem 1rem 0.2rem 1rem;
            backdrop-filter: blur(2px);
            margin: 1rem 0 1.2rem;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
            gap: 0.8rem;
            margin-bottom: 0.5rem;
        }
        .grid-item {
            position: relative;
            background: white;
            border-radius: 20px;
            overflow: hidden;
            box-shadow: 0 4px 8px rgba(0,0,0,0.04);
            aspect-ratio: 1 / 1;
            display: flex;
            align-items: center;
            justify-content: center;
            border: 1px solid rgba(255, 255, 255, 0.6);
            transition: 0.1s ease;
        }
        .grid-item img {
            width: 100%;
            height: 100%;
            object-fit: cover;
            display: block;
            background: #f1f5f9;
        }
        .grid-item .remove-btn {
            position: absolute;
            top: 6px;
            right: 6px;
            background: rgba(0,0,0,0.5);
            backdrop-filter: blur(4px);
            color: white;
            border: none;
            border-radius: 40px;
            width: 26px;
            height: 26px;
            font-size: 16px;
            line-height: 1;
            cursor: pointer;
            transition: 0.15s;
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0.7;
        }
        .grid-item .remove-btn:hover {
            opacity: 1;
            background: rgba(180, 35, 35, 0.85);
            transform: scale(1.05);
        }
        .grid-empty {
            grid-column: 1 / -1;
            text-align: center;
            padding: 2.5rem 1rem;
            color: #64748b;
            background: rgba(255,255,255,0.2);
            border-radius: 40px;
            font-size: 1rem;
        }
        .action-bar {
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            align-items: center;
            gap: 1rem 0.8rem;
            margin-top: 1.2rem;
        }
        .share-card {
            background: rgba(255, 255, 255, 0.4);
            backdrop-filter: blur(6px);
            padding: 1rem 1.5rem;
            border-radius: 60px;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 0.8rem 1.2rem;
            box-shadow: 0 2px 12px rgba(0,0,0,0.02);
            border: 1px solid rgba(255,255,255,0.7);
        }
        .share-card span {
            color: #1e293b;
            font-weight: 500;
        }
        .share-link-box {
            background: white;
            border-radius: 60px;
            padding: 0.4rem 0.4rem 0.4rem 1.5rem;
            display: flex;
            align-items: center;
            flex: 1;
            min-width: 180px;
            border: 1px solid #d1d9e8;
        }
        .share-link-box input {
            border: none;
            background: transparent;
            padding: 0.4rem 0;
            flex: 1;
            font-size: 0.9rem;
            color: #0f172a;
            outline: none;
            width: 100px;
        }
        .share-link-box button {
            background: #1e293b;
            border: none;
            color: white;
            padding: 0.4rem 1.2rem;
            border-radius: 40px;
            font-weight: 500;
            font-size: 0.85rem;
            cursor: pointer;
            transition: 0.15s;
            white-space: nowrap;
        }
        .share-link-box button:hover {
            background: #0f172a;
        }
        .footer-note {
            margin-top: 1.5rem;
            font-size: 0.8rem;
            color: #64748b;
            text-align: center;
        }
        @media (max-width: 650px) {
            .app-card { padding: 1.2rem; }
            h1 { font-size: 1.8rem; }
            .grid { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); }
            .share-card { flex-direction: column; align-items: stretch; }
            .share-link-box { min-width: unset; }
        }
    </style>
</head>
<body>
    <div class="app-card">
        <div class="subhead">
            <h1>🖼️ 相册生成器</h1>
            <p><span class="badge" id="imageCountDisplay">0 张</span> 一键分享</p>
        </div>
        <!-- 上传区域 -->
        <div class="upload-area" id="uploadArea">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
                <path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
            </svg>
            <p>拖拽或点击上传图片 <span style="opacity:0.6; margin-left: 6px;">(JPEG, PNG, WEBP)</span></p>
            <input type="file" id="fileInput" multiple accept="image/png, image/jpeg, image/webp">
        </div>
        <!-- 工具栏 -->
        <div class="toolbar">
            <button class="btn" id="clearAllBtn" disabled>🗑️ 清空相册</button>
            <button class="btn btn-outline" id="sortByNameBtn">📛 按名称排序</button>
            <button class="btn btn-outline" id="reverseOrderBtn">🔄 反转顺序</button>
        </div>
        <!-- 图片网格 -->
        <div class="gallery-container">
            <div class="grid" id="imageGrid">
                <div class="grid-empty">✨ 添加图片,构建你的相册</div>
            </div>
        </div>
        <!-- 分享区域 -->
        <div class="action-bar">
            <div class="share-card">
                <span>🔗 分享相册</span>
                <div class="share-link-box">
                    <input type="text" id="shareLinkInput" placeholder="生成分享链接" readonly value="">
                    <button id="copyShareBtn" disabled>复制</button>
                </div>
                <button class="btn btn-primary" id="generateShareBtn" disabled>✨ 生成分享</button>
            </div>
        </div>
        <div class="footer-note">
            ⚡ 图片仅保存在本地浏览器,生成相册链接可分享 (Base64 编码)
        </div>
    </div>
    <script>
        (function() {
            // ----- 核心状态 -----
            let images = [];             // 存储 { id, name, dataUrl, sortKey }
            let shareLinkGenerated = '';
            const grid = document.getElementById('imageGrid');
            const fileInput = document.getElementById('fileInput');
            const uploadArea = document.getElementById('uploadArea');
            const clearAllBtn = document.getElementById('clearAllBtn');
            const sortByNameBtn = document.getElementById('sortByNameBtn');
            const reverseOrderBtn = document.getElementById('reverseOrderBtn');
            const generateShareBtn = document.getElementById('generateShareBtn');
            const copyShareBtn = document.getElementById('copyShareBtn');
            const shareLinkInput = document.getElementById('shareLinkInput');
            const imageCountDisplay = document.getElementById('imageCountDisplay');
            // ----- 辅助函数 -----
            function updateUI() {
                // 更新计数
                const count = images.length;
                imageCountDisplay.textContent = count + ' 张';
                clearAllBtn.disabled = count === 0;
                generateShareBtn.disabled = count === 0;
                // 如果图片为0,清空分享链接
                if (count === 0) {
                    shareLinkInput.value = '';
                    shareLinkGenerated = '';
                    copyShareBtn.disabled = true;
                } else {
                    // 如果已有分享链接,但图片变化,置为过期 (但保留显示, 用户可重新生成)
                    // 每次图片变动, 令分享链接失效,但不清除显示 (重新生成会覆盖)
                    // 但更合理: 图片变动时清空分享链接显示
                    shareLinkInput.value = '';
                    shareLinkGenerated = '';
                    copyShareBtn.disabled = true;
                }
                renderGrid();
            }
            // 渲染网格
            function renderGrid() {
                if (images.length === 0) {
                    grid.innerHTML = `<div class="grid-empty">🖼️ 暂无图片,点击上方添加</div>`;
                    return;
                }
                let html = '';
                images.forEach((img, index) => {
                    html += `
                        <div class="grid-item" data-index="${index}">
                            <img src="${img.dataUrl}" alt="${img.name || '图片'}">
                            <button class="remove-btn" data-index="${index}" title="移除图片">✕</button>
                        </div>
                    `;
                });
                grid.innerHTML = html;
                // 绑定移除事件 (事件委托也可,但直接绑定更清晰)
                document.querySelectorAll('.grid-item .remove-btn').forEach(btn => {
                    btn.addEventListener('click', function(e) {
                        e.stopPropagation();
                        const idx = parseInt(this.dataset.index, 10);
                        if (!isNaN(idx) && idx >= 0 && idx < images.length) {
                            images.splice(idx, 1);
                            updateUI();
                        }
                    });
                });
            }
            // 添加图片 (FileList)
            function addImagesFromFileList(fileList) {
                if (!fileList || fileList.length === 0) return;
                const validTypes = ['image/jpeg', 'image/png', 'image/webp'];
                const files = Array.from(fileList).filter(f => validTypes.includes(f.type));
                if (files.length === 0) {
                    alert('请选择 JPEG / PNG / WEBP 格式的图片');
                    return;
                }
                let loaded = 0;
                files.forEach(file => {
                    const reader = new FileReader();
                    reader.onload = function(e) {
                        const dataUrl = e.target.result;
                        // 生成简短唯一标识
                        const id = Date.now() + '-' + Math.random().toString(36).substr(2, 6) + '-' + loaded;
                        const name = file.name || `image_${Date.now()}`;
                        images.push({
                            id,
                            name,
                            dataUrl,
                            sortKey: name.toLowerCase()
                        });
                        loaded++;
                        // 最后一个文件加载完成再更新UI (批量更新)
                        if (loaded === files.length) {
                            updateUI();
                        }
                    };
                    reader.onerror = function() {
                        console.warn('读取失败:', file.name);
                        loaded++;
                        if (loaded === files.length) {
                            updateUI();
                        }
                    };
                    reader.readAsDataURL(file);
                });
                // 若文件都无效, loaded不会增加,但触发更新由上面逻辑完成?files长度可能为0, 但之前有return
                // 如果没有有效文件,loaded不会增加, 但已经在files空时return,没问题。
            }
            // ----- 排序功能 -----
            function sortByName() {
                images.sort((a, b) => {
                    const nameA = a.sortKey || '';
                    const nameB = b.sortKey || '';
                    return nameA.localeCompare(nameB);
                });
                updateUI();
            }
            function reverseOrder() {
                images.reverse();
                updateUI();
            }
            // ----- 生成分享 (将图片数据编码为 JSON + Base64) -----
            function generateShare() {
                if (images.length === 0) {
                    shareLinkInput.value = '';
                    shareLinkGenerated = '';
                    copyShareBtn.disabled = true;
                    return;
                }
                // 提取必要数据 (不存储大base64? 但dataUrl本身就是base64, 需要保留)
                const shareData = images.map(img => ({
                    name: img.name,
                    data: img.dataUrl
                }));
                try {
                    const jsonStr = JSON.stringify(shareData);
                    const encoded = btoa(encodeURIComponent(jsonStr)); // 安全base64
                    const shareUrl = `album://${encoded}`;  // 自定义协议,但这里仅仅作为分享文本
                    shareLinkGenerated = shareUrl;
                    shareLinkInput.value = shareUrl;
                    copyShareBtn.disabled = false;
                } catch (e) {
                    alert('生成分享链接失败,请减少图片数量或尺寸');
                    console.error(e);
                }
            }
            // 复制到剪贴板
            function copyShareLink() {
                if (!shareLinkGenerated || shareLinkInput.value === '') {
                    copyShareBtn.disabled = true;
                    return;
                }
                // 使用现代 clipboard API
                if (navigator.clipboard && navigator.clipboard.writeText) {
                    navigator.clipboard.writeText(shareLinkInput.value).then(() => {
                        alert('✅ 相册链接已复制!');
                    }).catch(() => {
                        fallbackCopy();
                    });
                } else {
                    fallbackCopy();
                }
            }
            function fallbackCopy() {
                shareLinkInput.select();
                try {
                    const success = document.execCommand('copy');
                    if (success) {
                        alert('✅ 相册链接已复制!');
                    } else {
                        alert('⚠️ 复制失败,请手动复制');
                    }
                } catch (e) {
                    alert('⚠️ 复制失败,请手动复制');
                }
            }
            // ----- 事件绑定 -----
            // 1. 上传区域点击
            uploadArea.addEventListener('click', function(e) {
                if (e.target.tagName !== 'INPUT') {
                    fileInput.click();
                }
            });
            fileInput.addEventListener('change', function(e) {
                if (this.files && this.files.length > 0) {
                    addImagesFromFileList(this.files);
                }
                this.value = ''; // 允许重复选择相同文件
            });
            // 2. 拖拽上传 (整个区域)
            uploadArea.addEventListener('dragover', function(e) {
                e.preventDefault();
                e.stopPropagation();
                this.style.borderColor = '#3b82f6';
                this.style.background = 'rgba(59, 130, 246, 0.08)';
            });
            uploadArea.addEventListener('dragleave', function(e) {
                e.preventDefault();
                e.stopPropagation();
                this.style.borderColor = '#94a3b8';
                this.style.background = 'rgba(255,255,255,0.5)';
            });
            uploadArea.addEventListener('drop', function(e) {
                e.preventDefault();
                e.stopPropagation();
                this.style.borderColor = '#94a3b8';
                this.style.background = 'rgba(255,255,255,0.5)';
                if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
                    addImagesFromFileList(e.dataTransfer.files);
                }
            });
            // 3. 清空
            clearAllBtn.addEventListener('click', function() {
                if (images.length === 0) return;
                if (confirm('确定清空所有图片吗?')) {
                    images = [];
                    updateUI();
                }
            });
            // 4. 排序
            sortByNameBtn.addEventListener('click', sortByName);
            reverseOrderBtn.addEventListener('click', reverseOrder);
            // 5. 生成分享
            generateShareBtn.addEventListener('click', generateShare);
            // 6. 复制分享
            copyShareBtn.addEventListener('click', copyShareLink);
            // 7. 点击分享输入框全选
            shareLinkInput.addEventListener('click', function() {
                this.select();
            });
            // 初始化UI
            updateUI();
            // 额外:演示预置图片 (可选) —— 为了方便测试,添加几张示例 (注释掉,用户自行上传)
            // 如果您希望开箱即用示例,可解除注释 (但为保持整洁,不添加)
            // 但是我们可以提示用户
            console.log('📸 相册生成器已启动,上传图片即可生成分享链接');
        })();
    </script>
</body>
</html>

相册制作与分享流程

您可以通过上传、管理和分享三个核心步骤,快速创建一个可分享的图片集合。

图片批量分享工具生成相册-第1张图片-电脑手机工具软件下载 - 免费实用工具合集 | 联启科技

  • 图片管理:您可以通过点击或拖拽的方式上传 JPEG、PNG 或 WEBP 格式的图片,上传后,图片会以网格形式清晰展示,您还可以对单张图片进行移除,或使用“清空相册”、“按名称排序”、“反转顺序”等工具进行整体管理。
  • 分享链接生成:在相册内容确定后,点击“✨ 生成分享”按钮,工具会将您相册中的所有图片数据进行编码,并生成一个专属的分享链接,这个链接包含了所有图片信息。
  • 链接复制与分享:生成的链接会显示在输入框中,您只需点击“复制”按钮即可将链接分享给他人,此链接基于图片数据生成,图片数量或尺寸较大时可能影响生成效率。

优化建议: 您可以根据需要调整 gridgrid-template-columns 属性值(第103行),以更改相册网格的列宽和布局密度。

标签: 相册生成

抱歉,评论功能暂时关闭!