Last active
June 18, 2025 18:28
-
-
Save machsix/e7c8a25b05609b95fbfdc87972088093 to your computer and use it in GitHub Desktop.
Alist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Alist 图片预览增强 | |
// @namespace http://tampermonkey.net/ | |
// @version 1.2 | |
// @description 在 Alist 中加载列表后,点击悬浮按钮预览目录中的图片缩略图 | |
// @author Kimi | |
// @match *://alist.nas.lan/* | |
// @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; | |
left: 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}`)); | |
} | |
}); | |
}); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment