[前端] 油猴插件实现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的文件列表加载完成之后,点击它。文件名后方就会出现一个预览,鼠标悬停,等待片刻应该就能得到预览。
分类:
前端
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库