[前端] 油猴插件实现alist图片预览

有菩萨使用alist分享了一些写真集,但是数量非常之多。我用梯子下了足足60G之后发现里面很多我都是不喜欢的,所以想着是否能够实现图片的预览功能,只下载需要的。

alist本身的预览功能需要进入到保存图片的目录才能看到缩略图,简单研究一下发现alist的前后端是分离的,靠api接口实现异步渲染,api接口使用的是json作为数据结构。

一个思路就是通过编写油猴脚本实现遍历目录完成预览,于是使用kimi帮我写了一个脚本完成这个事,效果还不错,代码如下

// ==UserScript==
// @name         Alist 图片预览增强
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  在 Alist 中加载列表后,点击悬浮按钮预览目录中的图片缩略图
// @author       Kimi
// @match        *://tmp.top/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';

    // 缓存已获取过缩略图的路径
    const thumbnailCache = {};

    // 添加自定义样式
    GM_addStyle(`
        .preview-thumbnails {
            position: fixed;
            background: white;
            border: 1px solid #ccc;
            padding: 10px;
            display: none;
            z-index: 1000;
            max-width: 600px; /* 调整悬浮窗大小 */
            max-height: 400px;
            overflow: auto;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
            border-radius: 8px;
        }
        .preview-thumbnails img {
            width: 120px; /* 固定宽度 */
            height: auto; /* 高度自适应 */
            margin: 5px;
            transition: transform 0.3s ease;
        }
        .preview-thumbnails img:hover {
            transform: scale(1.2); /* 鼠标悬停时放大 */
        }
        .preview-button {
            margin-left: 10px;
            cursor: pointer;
            color: blue;
            text-decoration: underline;
        }
        .floating-preview-button {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: #007bff;
            color: white;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            z-index: 10000;
        }
    `);

    // 添加悬浮按钮
    const floatingButton = document.createElement('button');
    floatingButton.textContent = '加载图片预览';
    floatingButton.className = 'floating-preview-button';
    floatingButton.addEventListener('click', loadPreviews);
    document.body.appendChild(floatingButton);

    // 点击悬浮按钮后加载预览逻辑
    function loadPreviews() {
        const listItems = document.querySelectorAll('.list-item');
        listItems.forEach((item) => {
            addPreviewButton(item);
        });
    }

    // 添加预览按钮
    function addPreviewButton(listItem) {
        const nameBox = listItem.querySelector('.name-box');
        if (!nameBox.querySelector('.preview-button')) {
            const button = document.createElement('span');
            button.textContent = '预览图片';
            button.className = 'preview-button';
            button.addEventListener('mouseenter', (e) => showThumbnails(listItem, e));
            button.addEventListener('mouseleave', () => hideThumbnails(listItem));
            nameBox.appendChild(button);
        }
    }

    // 显示缩略图
    function showThumbnails(listItem, event) {
        const nameElement = listItem.querySelector('.name');
        const name = nameElement ? nameElement.textContent.trim() : '';
        const path = getFullImagePath(name);

        let previewDiv = listItem.querySelector('.preview-thumbnails');
        if (!previewDiv) {
            previewDiv = document.createElement('div');
            previewDiv.className = 'preview-thumbnails';
            document.body.appendChild(previewDiv); // 将悬浮窗添加到 body 中

            if (thumbnailCache[path]) {
                console.log('Using cached thumbnails for:', path);
                renderThumbnails(previewDiv, thumbnailCache[path]);
            } else {
                // 获取缩略图
                fetchThumbnails(path).then(thumbnails => {
                    thumbnailCache[path] = thumbnails; // 缓存结果
                    renderThumbnails(previewDiv, thumbnails);
                }).catch(error => {
                    console.error('Error fetching thumbnails:', error);
                });
            }

            // 调整悬浮窗位置
            adjustPosition(previewDiv, event);

            // 将悬浮窗存储在 listItem 上,方便后续访问
            listItem.previewDiv = previewDiv;

            // 添加鼠标离开悬浮窗时的关闭逻辑
            previewDiv.addEventListener('mouseleave', () => {
                if (previewDiv) {
                    previewDiv.style.display = 'none';
                }
            });
        } else {
            previewDiv.style.display = 'block';
            adjustPosition(previewDiv, event);
        }
    }

    // 隐藏缩略图
    function hideThumbnails(listItem) {
        const previewDiv = listItem.previewDiv; // 从 listItem 获取悬浮窗
        if (previewDiv) {
            previewDiv.style.display = 'none';
        }
    }

    // 调整悬浮窗位置
    function adjustPosition(div, event) {
        const { clientX, clientY } = event;
        const { innerWidth, innerHeight } = window;
        const { width, height } = div.getBoundingClientRect();

        // 计算悬浮窗位置,避免超出浏览器边界
        const left = Math.min(clientX, innerWidth - width - 20); // 保留 20px 的边距
        const top = Math.min(clientY, innerHeight - height - 20);

        div.style.left = `${left}px`;
        div.style.top = `${top}px`;
    }

    // 获取完整的图片路径并解码
    function getFullImagePath(name) {
        const currentPath = decodeURIComponent(window.location.pathname);
        const fullPath = `${currentPath}/${name}`.replace(/\/+/g, '/');
        return decodeURIComponent(fullPath);
    }

    // 渲染缩略图
    function renderThumbnails(div, thumbnails) {
        div.innerHTML = ''; // 清空内容
        thumbnails.forEach(thumb => {
            const img = document.createElement('img');
            img.src = thumb;
            div.appendChild(img);
        });
        div.style.display = 'block';
    }

    // 获取目录中的图片缩略图
    function fetchThumbnails(path) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: '/api/fs/list',
                data: JSON.stringify({
                    path: path,
                    password: '',
                    page: 1,
                    per_page: 0,
                    refresh: false
                }),
                headers: {
                    'Content-Type': 'application/json'
                },
                onload: function (response) {
                    try {
                        console.log('Response:', response.responseText); // 调试信息
                        const data = JSON.parse(response.responseText);
                        if (!data.data || !data.data.content) {
                            throw new Error('Invalid response format');
                        }

                        const thumbnails = data.data.content
                            .filter(item => item.thumb) // 筛选出有缩略图的项
                            .map(item => item.thumb); // 提取缩略图 URL

                        // 如果当前目录中没有图片,但所有项都是目录且只有一个目录,则递归进入该目录
                        if (thumbnails.length === 0 && data.data.content.length === 1 && data.data.content[0].is_dir) {
                            const nestedPath = `${path}/${data.data.content[0].name}`.replace(/\/+/g, '/');
                            console.log('No thumbnails found. Recursively fetching from nested directory:', nestedPath);
                            fetchThumbnails(nestedPath).then(nestedThumbnails => {
                                resolve(nestedThumbnails); // 递归调用的结果传递给 resolve
                            }).catch(reject);
                        } else {
                            resolve(thumbnails);
                        }
                    } catch (error) {
                        reject(error);
                    }
                },
                onerror: function (error) {
                    reject(new Error(`Network error: ${error.statusText}`));
                }
            });
        });
    }
})();

你需要修改上面代码的match部分以在你自己的alist页面上生效,之后刷新页面应该可以在右下角看到一个蓝色按钮。当alist的文件列表加载完成之后,点击它。文件名后方就会出现一个预览,鼠标悬停,等待片刻应该就能得到预览。

作者:cjdty

出处:https://www.cnblogs.com/cjdty/p/18736322

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Startu  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示