效果展示
主要配置
纯静态网页
,用于展示
网站结构
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<title>个人自用图片资源</title>
<style>
:root {
--primary-color: #ffffff;
--text-color: #ffffff;
--border-color: #303030;
--hover-color: #2c2c2c;
--shadow-color: rgba(0, 0, 0, 0.3);
--background-color: #0f0f0f;
--surface-color: #212121;
--secondary-text: #aaaaaa;
}
/* 浅色主题变量 */
:root[data-theme="light"] {
--primary-color: #000000;
--text-color: #000000;
--border-color: #e0e0e0;
--hover-color: #f5f5f5;
--shadow-color: rgba(0, 0, 0, 0.1);
--background-color: #ffffff;
--surface-color: #f8f8f8;
--secondary-text: #666666;
}
body {
margin: 0;
padding: 5px;
min-height: 100vh;
background: var(--background-color);
color: var(--text-color);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.container {
max-width: 1600px;
margin: 0 auto;
padding: 0px;
}
h1 {
text-align: center;
margin-bottom: 20px;
margin-top: 10px;
font-size: 2em;
color: var(--text-color);
cursor: pointer;
transition: opacity 0.3s ease;
}
h1:hover {
opacity: 0.8;
}
.image-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
padding: 20px;
opacity: 1;
transition: opacity 0.3s ease;
}
.image-grid.loading {
opacity: 0.6;
}
.image-item {
position: relative;
border-radius: 8px;
background: var(--surface-color);
transition: transform 0.3s ease, opacity 0.3s ease;
cursor: pointer;
display: flex;
flex-direction: column;
overflow: hidden;
opacity: 0;
animation: fadeIn 0.3s ease forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.image-container {
position: relative;
aspect-ratio: 1;
overflow: hidden;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.image-item:hover .image-container img {
transform: scale(1.05);
}
.image-info {
padding: 15px;
background: var(--surface-color);
color: var(--text-color);
font-size: 0.9em;
line-height: 1.4;
display: flex;
flex-direction: column;
}
.image-info h3 {
margin: 0 0 8px 0;
font-size: 1.1em;
color: var(--primary-color);
}
.image-info p {
margin: 4px 0;
color: var(--secondary-text);
}
.source-link {
display: flex;
align-items: center;
padding: 6px 8px;
background: var(--hover-color);
border-radius: 4px;
transition: background 0.2s ease;
gap: 8px;
}
.source-link:hover {
background: var(--border-color);
}
.source-name {
font-weight: bold;
color: var(--primary-color);
min-width: 80px;
}
.source-url {
color: var(--secondary-text);
word-break: break-all;
flex: 1;
text-decoration: none;
}
.source-url:hover {
color: var(--primary-color);
text-decoration: underline;
}
.copy-icon {
padding: 4px 8px;
border-radius: 4px;
color: var(--primary-color);
cursor: pointer;
background: var(--surface-color);
transition: background 0.2s ease;
}
.copy-icon:hover {
background: var(--border-color);
}
.copy-tooltip {
position: fixed;
top: 20px;
left: 20px;
background: #4CAF50;
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
pointer-events: none;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
z-index: 2001;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
gap: 8px;
min-width: 200px;
}
.copy-tooltip::before {
content: "✓";
font-weight: bold;
font-size: 18px;
}
.copy-tooltip.show {
opacity: 1;
transform: translateY(0);
}
/* 加载动画 */
.loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--background-color);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
}
.loading.active {
opacity: 1;
display: flex;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid var(--border-color);
border-top: 5px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 主题切换按钮 */
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--surface-color);
border: 2px solid var(--border-color);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
transition: all 0.3s ease;
opacity: 0;
transform: scale(0.8);
}
.theme-toggle:hover {
transform: scale(1);
opacity: 1;
background: var(--hover-color);
}
.theme-toggle svg {
width: 24px;
height: 24px;
fill: var(--text-color);
transition: all 0.3s ease;
}
/* 响应式布局 */
@media screen and (max-width: 1200px) {
.image-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media screen and (max-width: 768px) {
.image-grid {
grid-template-columns: repeat(2, 1fr);
gap: 15px;
padding: 15px;
}
.theme-toggle {
top: 10px;
right: 10px;
width: 35px;
height: 35px;
}
.theme-toggle svg {
width: 20px;
height: 20px;
}
}
@media screen and (max-width: 480px) {
.image-grid {
grid-template-columns: 1fr;
gap: 10px;
padding: 10px;
}
h1 {
font-size: 1.5em;
margin-bottom: 20px;
}
}
/* 图片查看器 */
.image-viewer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
cursor: zoom-out;
opacity: 0;
transition: opacity 0.4s cubic-bezier(0.4, 0, 1, 1);
pointer-events: none;
}
.image-viewer.active {
opacity: 1;
pointer-events: auto;
}
.viewer-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50px;
height: 50px;
border: 5px solid var(--border-color);
border-top: 5px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
display: none;
}
.viewer-loading.show {
display: block;
}
.image-viewer img {
max-width: 90%;
max-height: 90vh;
object-fit: contain;
cursor: zoom-out;
transition: transform 0.4s cubic-bezier(0.4, 0, 1, 1), opacity 0.4s cubic-bezier(0.4, 0, 1, 1);
opacity: 0;
}
.image-viewer img.loaded {
opacity: 1;
}
.close-viewer {
position: absolute;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
background: var(--surface-color);
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-color);
font-size: 24px;
}
/* 修改折叠按钮和链接容器的样式 */
.collapse-button {
width: 100%;
padding: 10px;
background: var(--surface-color);
border: none;
border-radius: 4px;
color: var(--text-color);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
transition: background 0.2s ease;
margin-bottom: 0;
}
.collapse-button:hover {
background: var(--hover-color);
}
.collapse-button::after {
content: "▼";
font-size: 12px;
transition: transform 0.3s ease;
}
.collapse-button.collapsed::after {
transform: rotate(-90deg);
}
.links-container {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
transition: all 0.3s ease;
overflow: hidden;
flex: 1;
}
.links-container.collapsed {
padding: 0;
max-height: 0;
}
/* 添加分页样式 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin: 20px 0;
flex-wrap: wrap;
padding: 0 10px;
opacity: 0;
transition: opacity 0.3s ease;
}
.pagination.show {
opacity: 1;
}
.page-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
background: var(--surface-color);
color: var(--text-color);
cursor: pointer;
transition: all 0.2s ease;
}
.page-button:hover {
background: var(--hover-color);
}
.page-button.active {
background: var(--primary-color);
color: var(--background-color);
}
.page-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 添加搜索框样式 */
.search-container {
margin: 0px auto;
width: 100%;
max-width: 800px;
position: relative;
box-sizing: border-box;
}
.search-input {
width: 100%;
padding: 12px 20px;
font-size: 16px;
border: 2px solid var(--border-color);
border-radius: 8px;
background: var(--surface-color);
color: var(--text-color);
transition: all 0.3s ease;
box-sizing: border-box;
}
.search-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 5px var(--shadow-color);
}
.search-input::placeholder {
color: var(--secondary-text);
}
.clear-search {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--secondary-text);
cursor: pointer;
padding: 5px;
display: none;
}
.clear-search:hover {
color: var(--primary-color);
}
.no-results {
text-align: center;
padding: 40px;
color: var(--secondary-text);
font-size: 1.2em;
}
@media screen and (max-width: 1200px) {
.search-container {
max-width: 90%;
}
}
@media screen and (max-width: 480px) {
.search-container {
margin: 10px auto;
width: 90%;
}
.search-input {
padding: 10px 15px;
font-size: 14px;
}
}
.page-dots {
padding: 8px 12px;
color: var(--text-color);
user-select: none;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin: 20px 0;
flex-wrap: wrap;
padding: 0 10px;
opacity: 0;
transition: opacity 0.3s ease;
}
.pagination.show {
opacity: 1;
}
.page-button {
min-width: 40px;
height: 40px;
padding: 0 12px;
border: none;
border-radius: 4px;
background: var(--surface-color);
color: var(--text-color);
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
@media screen and (max-width: 480px) {
.page-button {
min-width: 36px;
height: 36px;
padding: 0 8px;
font-size: 14px;
}
.pagination {
gap: 4px;
}
}
/* 添加上传按钮样式 */
.upload-button {
position: fixed;
right: 20px;
bottom: 20px;
padding: 0 20px;
height: 45px;
border-radius: 25px;
background: var(--primary-color);
color: var(--background-color);
border: none;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
z-index: 1000;
text-decoration: none;
opacity: 0;
transform: scale(0.8);
}
.upload-button:hover {
transform: scale(1);
opacity: 1;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
}
.upload-button svg {
width: 20px;
height: 20px;
fill: currentColor;
}
@media screen and (max-width: 480px) {
.upload-button {
padding: 0 15px;
height: 40px;
right: 15px;
bottom: 15px;
font-size: 14px;
}
.upload-button svg {
width: 18px;
height: 18px;
}
}
/* 添加导航按钮样式 */
.viewer-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 50px;
height: 50px;
background: rgba(0, 0, 0, 0.5);
border: none;
border-radius: 50%;
color: white;
font-size: 24px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
z-index: 2002;
}
.viewer-nav:hover {
background: rgba(0, 0, 0, 0.8);
transform: translateY(-50%) scale(1.1);
}
.viewer-prev {
left: 20px;
}
.viewer-next {
right: 20px;
}
/* 图片计数器样式 */
.image-counter {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.5);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
z-index: 2002;
}
@media screen and (max-width: 768px) {
.viewer-nav {
width: 40px;
height: 40px;
font-size: 20px;
}
.viewer-prev {
left: 10px;
}
.viewer-next {
right: 10px;
}
}
</style>
</head>
<body>
<!-- 添加上传按钮 -->
<a href="https://rn-alist.1143520.xyz:5244/%E5%9B%BE%E5%BA%8A%E4%B8%8A%E4%BC%A0" target="_blank" class="upload-button" title="上传图片">
<svg viewBox="0 0 24 24">
<path d="M9 16h6v-6h4l-7-7-7 7h4v6zm-4 2h14v2H5v-2z"/>
</svg>
<span>上传图片</span>
</a>
<button class="theme-toggle" id="themeToggle" aria-label="切换主题">
<svg class="sun-icon" viewBox="0 0 24 24">
<path
d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z" />
</svg>
</button>
<div class="container">
<h1>个人自用图片资源</h1>
<!-- 添加搜索框 -->
<div class="search-container">
<input type="text" id="searchInput" class="search-input" placeholder="搜索图片名称..." autocomplete="off">
<button id="clearSearch" class="clear-search">✕</button>
</div>
<div class="image-grid" id="imageGrid">
<!-- 图片将通过 JavaScript 动态加载 -->
</div>
</div>
<div class="loading" id="loading">
<div class="loading-spinner"></div>
</div>
<div class="image-viewer" id="imageViewer">
<button class="close-viewer" id="closeViewer">×</button>
<button class="viewer-nav viewer-prev" id="viewerPrev">❮</button>
<button class="viewer-nav viewer-next" id="viewerNext">❯</button>
<div class="viewer-loading" id="viewerLoading"></div>
<img src="" alt="" id="viewerImage">
<div class="image-counter" id="imageCounter"></div>
</div>
<div class="pagination" id="pagination"></div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const imageGrid = document.getElementById('imageGrid');
const loading = document.getElementById('loading');
const imageViewer = document.getElementById('imageViewer');
const viewerImage = document.getElementById('viewerImage');
const closeViewer = document.getElementById('closeViewer');
const themeToggle = document.getElementById('themeToggle');
const root = document.documentElement;
const title = document.querySelector('h1');
const moonIcon = `<svg class="moon-icon" viewBox="0 0 24 24"><path d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-3.03 0-5.5-2.47-5.5-5.5 0-1.82.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"/></svg>`;
const sunIcon = `<svg class="sun-icon" viewBox="0 0 24 24"><path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/></svg>`;
// 检查本地存储中的主题设置
const savedTheme = localStorage.getItem('theme') || 'dark';
root.setAttribute('data-theme', savedTheme);
themeToggle.innerHTML = savedTheme === 'dark' ? sunIcon : moonIcon;
// 主题切换
themeToggle.addEventListener('click', () => {
const currentTheme = root.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', newTheme);
themeToggle.innerHTML = newTheme === 'dark' ? sunIcon : moonIcon;
localStorage.setItem('theme', newTheme);
});
// 创建复制提示元素
const tooltip = document.createElement('div');
tooltip.className = 'copy-tooltip';
tooltip.textContent = '链接已复制到剪贴板';
document.body.appendChild(tooltip);
// 复制文本到剪贴板的函数
async function copyToClipboard(text, event) {
try {
await navigator.clipboard.writeText(text);
// 直接显示提示,不需要计算位置
tooltip.style.background = '#4CAF50'; // 重置为成功颜色
tooltip.textContent = '链接已复制到剪贴板';
tooltip.classList.add('show');
// 2秒后隐藏提示
setTimeout(() => {
tooltip.classList.remove('show');
}, 2000);
} catch (err) {
console.error('复制失败:', err);
// 显示错误提示
tooltip.style.background = '#f44336';
tooltip.textContent = '复制失败,请重试';
tooltip.classList.add('show');
// 2秒后隐藏提示
setTimeout(() => {
tooltip.classList.remove('show');
}, 2000);
}
}
const ITEMS_PER_PAGE = 20;
let currentPage = 1;
let allImageFiles = [];
let filteredImageFiles = [];
let totalPages = 1;
let searchTimeout;
// 添加搜索相关的DOM元素
const searchInput = document.getElementById('searchInput');
const clearSearch = document.getElementById('clearSearch');
// 添加搜索输入框的事件监听
searchInput.addEventListener('input', (e) => {
const query = e.target.value.trim();
clearSearch.style.display = query ? 'block' : 'none';
handleSearch(query);
});
// 添加搜索框的键盘事件监听
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
searchInput.value = '';
clearSearch.style.display = 'none';
filteredImageFiles = allImageFiles;
currentPage = 1;
createPagination(filteredImageFiles.length);
loadPageImages();
updateUrlWithPage(currentPage);
}
});
// 从URL获取页码
function getPageFromUrl() {
const path = window.location.pathname;
const match = path.match(/\/(\d{3})/);
if (match) {
const page = parseInt(match[1]);
return page > 0 ? page : 1;
}
return 1;
}
// 更新URL中的页码
function updateUrlWithPage(page) {
const formattedPage = String(page).padStart(3, '0');
const newUrl = `/${formattedPage}`;
window.history.pushState({ page }, '', newUrl);
// 更新分页显示
createPagination(filteredImageFiles.length);
}
// 创建分页按钮
function createPagination(total) {
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
totalPages = Math.ceil(total / ITEMS_PER_PAGE);
// 上一页按钮
const prevButton = document.createElement('button');
prevButton.className = 'page-button';
prevButton.textContent = '上一页';
prevButton.disabled = currentPage === 1;
prevButton.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
updateUrlWithPage(currentPage);
loadPageImages();
}
});
pagination.appendChild(prevButton);
// 计算需要显示的页码范围
let startPage = Math.max(1, currentPage - 10);
let endPage = Math.min(totalPages, currentPage + 10);
const showLeftDots = startPage > 2;
const showRightDots = endPage < totalPages - 1;
// 第一页
addPageButton(1);
// 左侧省略号
if (showLeftDots) {
const dotsSpan = document.createElement('span');
dotsSpan.className = 'page-dots';
dotsSpan.textContent = '...';
pagination.appendChild(dotsSpan);
}
// 中间页码
for (let i = startPage; i <= endPage; i++) {
if (i !== 1 && i !== totalPages) {
addPageButton(i);
}
}
// 右侧省略号
if (showRightDots) {
const dotsSpan = document.createElement('span');
dotsSpan.className = 'page-dots';
dotsSpan.textContent = '...';
pagination.appendChild(dotsSpan);
}
// 最后一页
if (totalPages > 1) {
addPageButton(totalPages);
}
// 下一页按钮
const nextButton = document.createElement('button');
nextButton.className = 'page-button';
nextButton.textContent = '下一页';
nextButton.disabled = currentPage === totalPages;
nextButton.addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
updateUrlWithPage(currentPage);
loadPageImages();
}
});
pagination.appendChild(nextButton);
}
// 辅助函数:添加页码按钮
function addPageButton(pageNum) {
const pageButton = document.createElement('button');
pageButton.className = `page-button ${pageNum === currentPage ? 'active' : ''}`;
pageButton.textContent = pageNum;
pageButton.addEventListener('click', () => {
if (pageNum !== currentPage) {
currentPage = pageNum;
updateUrlWithPage(currentPage);
loadPageImages();
}
});
pagination.appendChild(pageButton);
}
// 搜索功能
function performSearch(query) {
query = query.toLowerCase();
filteredImageFiles = allImageFiles.filter(file =>
file.toLowerCase().includes(query)
);
currentPage = 1;
createPagination(filteredImageFiles.length);
loadPageImages();
updateUrlWithPage(currentPage);
}
// 添加分页按钮状态更新函数
function updatePaginationButtons() {
const buttons = document.querySelectorAll('.page-button');
buttons.forEach(button => {
if (button.textContent === '上一页') {
button.disabled = currentPage === 1;
} else if (button.textContent === '下一页') {
button.disabled = currentPage === totalPages;
} else {
button.classList.toggle('active', parseInt(button.textContent) === currentPage);
}
});
}
// 优化搜索功能,添加防抖
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 添加一个标志来跟踪是否是首次加载
let isFirstLoad = true;
// 修改loadPageImages函数以支持平滑过渡
async function loadPageImages() {
const imageGrid = document.getElementById('imageGrid');
const pagination = document.getElementById('pagination');
imageGrid.classList.add('loading');
// 立即隐藏分页按钮(移除transition效果)
pagination.style.transition = 'none';
pagination.classList.remove('show');
// 强制浏览器重绘
pagination.offsetHeight;
// 恢复transition效果
pagination.style.transition = '';
const start = (currentPage - 1) * ITEMS_PER_PAGE;
const end = Math.min(start + ITEMS_PER_PAGE, filteredImageFiles.length);
const currentPageFiles = filteredImageFiles.slice(start, end);
// 如果没有搜索结果,显示提示信息
if (currentPageFiles.length === 0) {
imageGrid.innerHTML = '<div class="no-results">没有找到匹配的图片</div>';
imageGrid.classList.remove('loading');
setTimeout(() => {
pagination.classList.add('show');
}, 300);
return;
}
// 预先获取当前页的JSON数据
const jsonDataPromises = currentPageFiles.map(image =>
fetch(`/random/${image.replace(/\.(jpg|jpeg|png|gif|webp|ico|avif)$/i, '.json')}`)
.then(res => res.ok ? res.json() : null)
.catch(() => null)
);
const jsonDataArray = await Promise.all(jsonDataPromises);
// 创建一个文档片段来存储所有新元素
const fragment = document.createDocumentFragment();
// 创建当前页的图片元素
const imageElements = await Promise.all(currentPageFiles.map(async (image, index) => {
const imageItem = document.createElement('div');
imageItem.className = 'image-item';
// 添加延迟动画
imageItem.style.animationDelay = `${index * 50}ms`;
const imageContainer = document.createElement('div');
imageContainer.className = 'image-container';
const img = document.createElement('img');
img.alt = image;
img.loading = 'lazy';
// 设置图片源
const jsonData = jsonDataArray[index];
if (jsonData && jsonData['4399']) {
img.src = jsonData['4399'];
img.onerror = () => {
console.log(`Failed to load image from 4399, using local image for ${image}`);
img.src = `/random/${image}`;
};
} else {
img.src = `/random/${image}`;
}
imageContainer.appendChild(img);
imageItem.appendChild(imageContainer);
const imageInfo = document.createElement('div');
imageInfo.className = 'image-info';
// 修改图片点击事件,移动到这里
imageContainer.addEventListener('click', () => {
const allImages = document.querySelectorAll('.image-container img');
const index = Array.from(allImages).indexOf(img);
showImageViewer(img.src, index);
});
if (jsonData) {
// 创建折叠按钮
const collapseButton = document.createElement('button');
collapseButton.className = 'collapse-button';
collapseButton.textContent = decodeURIComponent(image);
imageInfo.appendChild(collapseButton);
// 创建链接容器
const linksContainer = document.createElement('div');
linksContainer.className = 'links-container collapsed';
// 获取JSON中的所有字段,保持原始顺序
const allFields = Object.keys(jsonData);
// 遍历所有字段创建链接
allFields.forEach(source => {
const url = jsonData[source];
const sourceLink = document.createElement('div');
sourceLink.className = 'source-link';
sourceLink.innerHTML = `
<span class="source-name">${source}:</span>
<a href="${url}" target="_blank" class="source-url">${url}</a>
<span class="copy-icon" title="点击复制链接">📋</span>
`;
// 只在点击复制图标时复制链接
const copyIcon = sourceLink.querySelector('.copy-icon');
copyIcon.addEventListener('click', (e) => {
e.stopPropagation();
copyToClipboard(url, e);
});
linksContainer.appendChild(sourceLink);
});
imageInfo.appendChild(linksContainer);
// 添加折叠功能
collapseButton.addEventListener('click', () => {
const isCollapsed = collapseButton.classList.toggle('collapsed');
linksContainer.classList.toggle('collapsed');
// 使用max-height来控制展开和收起
if (!isCollapsed) {
linksContainer.style.maxHeight = linksContainer.scrollHeight + "px";
} else {
linksContainer.style.maxHeight = "0";
}
});
// 默认折叠
collapseButton.classList.add('collapsed');
} else {
// 如果没有JSON数据,只显示文件名
const fileName = document.createElement('div');
fileName.className = 'file-name';
fileName.textContent = decodeURIComponent(image);
fileName.style.padding = '10px';
fileName.style.color = 'var(--text-color)';
imageInfo.appendChild(fileName);
}
imageItem.appendChild(imageInfo);
// 当图片加载完成时预加载大图
img.addEventListener('load', () => {
preloadImage(img.src).catch(console.error);
});
return imageItem;
}));
// 将所有元素添加到文档片段中
imageElements.forEach(element => fragment.appendChild(element));
// 清空网格并添加新元素
requestAnimationFrame(() => {
imageGrid.innerHTML = '';
imageGrid.appendChild(fragment);
imageGrid.classList.remove('loading');
// 延时显示分页按钮
setTimeout(() => {
pagination.classList.add('show');
}, 300);
// 直接设置滚动位置到顶部
window.scrollTo(0, 0);
});
// 更新分页按钮状态
updatePaginationButtons();
}
// 修改搜索处理函数,重置首次加载标志
const handleSearch = debounce((query) => {
const imageGrid = document.getElementById('imageGrid');
imageGrid.classList.add('loading');
isFirstLoad = true; // 搜索时重置为首次加载状态
setTimeout(() => {
performSearch(query);
}, 10);
}, 300);
// 修改清除搜索函数,重置首次加载标志
clearSearch.addEventListener('click', () => {
searchInput.value = '';
clearSearch.style.display = 'none';
filteredImageFiles = allImageFiles;
currentPage = 1;
isFirstLoad = true; // 清除搜索时重置为首次加载状态
createPagination(filteredImageFiles.length);
loadPageImages();
updateUrlWithPage(currentPage);
});
// 修改loadPageImages函数以支持平滑过渡
async function loadPageImages() {
const imageGrid = document.getElementById('imageGrid');
const pagination = document.getElementById('pagination');
imageGrid.classList.add('loading');
// 立即隐藏分页按钮(移除transition效果)
pagination.style.transition = 'none';
pagination.classList.remove('show');
// 强制浏览器重绘
pagination.offsetHeight;
// 恢复transition效果
pagination.style.transition = '';
const start = (currentPage - 1) * ITEMS_PER_PAGE;
const end = Math.min(start + ITEMS_PER_PAGE, filteredImageFiles.length);
const currentPageFiles = filteredImageFiles.slice(start, end);
// 如果没有搜索结果,显示提示信息
if (currentPageFiles.length === 0) {
imageGrid.innerHTML = '<div class="no-results">没有找到匹配的图片</div>';
imageGrid.classList.remove('loading');
setTimeout(() => {
pagination.classList.add('show');
}, 300);
return;
}
// 预先获取当前页的JSON数据
const jsonDataPromises = currentPageFiles.map(image =>
fetch(`/random/${image.replace(/\.(jpg|jpeg|png|gif|webp|ico|avif)$/i, '.json')}`)
.then(res => res.ok ? res.json() : null)
.catch(() => null)
);
const jsonDataArray = await Promise.all(jsonDataPromises);
// 创建一个文档片段来存储所有新元素
const fragment = document.createDocumentFragment();
// 创建当前页的图片元素
const imageElements = await Promise.all(currentPageFiles.map(async (image, index) => {
const imageItem = document.createElement('div');
imageItem.className = 'image-item';
// 添加延迟动画
imageItem.style.animationDelay = `${index * 50}ms`;
const imageContainer = document.createElement('div');
imageContainer.className = 'image-container';
const img = document.createElement('img');
img.alt = image;
img.loading = 'lazy';
// 设置图片源
const jsonData = jsonDataArray[index];
if (jsonData && jsonData['4399']) {
img.src = jsonData['4399'];
img.onerror = () => {
console.log(`Failed to load image from 4399, using local image for ${image}`);
img.src = `/random/${image}`;
};
} else {
img.src = `/random/${image}`;
}
imageContainer.appendChild(img);
imageItem.appendChild(imageContainer);
const imageInfo = document.createElement('div');
imageInfo.className = 'image-info';
// 修改图片点击事件,移动到这里
imageContainer.addEventListener('click', () => {
const allImages = document.querySelectorAll('.image-container img');
const index = Array.from(allImages).indexOf(img);
showImageViewer(img.src, index);
});
if (jsonData) {
// 创建折叠按钮
const collapseButton = document.createElement('button');
collapseButton.className = 'collapse-button';
collapseButton.textContent = decodeURIComponent(image);
imageInfo.appendChild(collapseButton);
// 创建链接容器
const linksContainer = document.createElement('div');
linksContainer.className = 'links-container collapsed';
// 获取JSON中的所有字段,保持原始顺序
const allFields = Object.keys(jsonData);
// 遍历所有字段创建链接
allFields.forEach(source => {
const url = jsonData[source];
const sourceLink = document.createElement('div');
sourceLink.className = 'source-link';
sourceLink.innerHTML = `
<span class="source-name">${source}:</span>
<a href="${url}" target="_blank" class="source-url">${url}</a>
<span class="copy-icon" title="点击复制链接">📋</span>
`;
// 只在点击复制图标时复制链接
const copyIcon = sourceLink.querySelector('.copy-icon');
copyIcon.addEventListener('click', (e) => {
e.stopPropagation();
copyToClipboard(url, e);
});
linksContainer.appendChild(sourceLink);
});
imageInfo.appendChild(linksContainer);
// 添加折叠功能
collapseButton.addEventListener('click', () => {
const isCollapsed = collapseButton.classList.toggle('collapsed');
linksContainer.classList.toggle('collapsed');
// 使用max-height来控制展开和收起
if (!isCollapsed) {
linksContainer.style.maxHeight = linksContainer.scrollHeight + "px";
} else {
linksContainer.style.maxHeight = "0";
}
});
// 默认折叠
collapseButton.classList.add('collapsed');
} else {
// 如果没有JSON数据,只显示文件名
const fileName = document.createElement('div');
fileName.className = 'file-name';
fileName.textContent = decodeURIComponent(image);
fileName.style.padding = '10px';
fileName.style.color = 'var(--text-color)';
imageInfo.appendChild(fileName);
}
imageItem.appendChild(imageInfo);
// 当图片加载完成时预加载大图
img.addEventListener('load', () => {
preloadImage(img.src).catch(console.error);
});
return imageItem;
}));
// 将所有元素添加到文档片段中
imageElements.forEach(element => fragment.appendChild(element));
// 清空网格并添加新元素
requestAnimationFrame(() => {
imageGrid.innerHTML = '';
imageGrid.appendChild(fragment);
imageGrid.classList.remove('loading');
// 延时显示分页按钮
setTimeout(() => {
pagination.classList.add('show');
}, 300);
// 直接设置滚动位置到顶部
window.scrollTo(0, 0);
});
// 更新分页按钮状态
updatePaginationButtons();
}
// 修改loadImages函数以支持搜索
const loadImages = async () => {
try {
const response = await fetch('/random/');
const text = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const links = Array.from(doc.querySelectorAll('a'));
allImageFiles = links
.map(link => {
const href = link.getAttribute('href');
return href ? decodeURIComponent(href) : null;
})
.filter(href => href && /\.(jpg|jpeg|png|gif|webp|ico|avif)$/i.test(href));
filteredImageFiles = allImageFiles; // 初始化过滤后的图片列表
// 从URL获取初始页码
currentPage = getPageFromUrl();
// 创建分页
createPagination(filteredImageFiles.length);
// 加载当前页
await loadPageImages();
} catch (error) {
console.error('Error loading images:', error);
loading.style.display = 'none';
}
};
// 添加浏览器后退/前进事件处理
window.addEventListener('popstate', (event) => {
if (event.state && event.state.page) {
currentPage = event.state.page;
loadPageImages();
}
});
// 修改标题点击事件
title.addEventListener('click', () => {
if (currentPage !== 1) {
currentPage = 1;
updateUrlWithPage(currentPage);
loadPageImages();
}
});
// 加载图片
loadImages();
const viewerLoading = document.getElementById('viewerLoading');
// 预加载图片函数
function preloadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = () => reject(url);
img.src = url;
});
}
let currentViewerIndex = -1; // 当前查看的图片索引
// 修改图片查看逻辑
async function showImageViewer(imgSrc, index) {
currentViewerIndex = index;
imageViewer.classList.add('active');
viewerImage.classList.remove('loaded');
viewerLoading.classList.add('show');
// 更新计数器
const start = (currentPage - 1) * ITEMS_PER_PAGE;
const currentPageImages = filteredImageFiles.slice(start, Math.min(start + ITEMS_PER_PAGE, filteredImageFiles.length));
document.getElementById('imageCounter').textContent = `${index + 1} / ${currentPageImages.length}`;
try {
await preloadImage(imgSrc);
viewerImage.src = imgSrc;
viewerImage.classList.add('loaded');
} catch (error) {
console.error('Failed to load image:', error);
} finally {
viewerLoading.classList.remove('show');
}
}
// 切换到上一张图片
async function showPreviousImage() {
const start = (currentPage - 1) * ITEMS_PER_PAGE;
const currentPageImages = filteredImageFiles.slice(start, Math.min(start + ITEMS_PER_PAGE, filteredImageFiles.length));
if (currentViewerIndex > 0) {
currentViewerIndex--;
const prevImage = document.querySelectorAll('.image-container img')[currentViewerIndex];
await showImageViewer(prevImage.src, currentViewerIndex);
}
}
// 切换到下一张图片
async function showNextImage() {
const start = (currentPage - 1) * ITEMS_PER_PAGE;
const currentPageImages = filteredImageFiles.slice(start, Math.min(start + ITEMS_PER_PAGE, filteredImageFiles.length));
if (currentViewerIndex < currentPageImages.length - 1) {
currentViewerIndex++;
const nextImage = document.querySelectorAll('.image-container img')[currentViewerIndex];
await showImageViewer(nextImage.src, currentViewerIndex);
}
}
// 添加导航按钮事件监听
document.getElementById('viewerPrev').addEventListener('click', (e) => {
e.stopPropagation();
showPreviousImage();
});
document.getElementById('viewerNext').addEventListener('click', (e) => {
e.stopPropagation();
showNextImage();
});
// 添加键盘事件监听
document.addEventListener('keydown', (e) => {
if (imageViewer.classList.contains('active')) {
if (e.key === 'Escape') {
closeImageViewer();
} else if (e.key === 'ArrowLeft') {
showPreviousImage();
} else if (e.key === 'ArrowRight') {
showNextImage();
} else if (e.key === 'ArrowUp') {
// 上一页
if (currentPage > 1) {
closeImageViewer();
currentPage--;
updateUrlWithPage(currentPage);
loadPageImages().then(() => {
// 在新页面加载完成后,打开最后一张图片
setTimeout(() => {
const allImages = document.querySelectorAll('.image-container img');
if (allImages.length > 0) {
const lastIndex = allImages.length - 1;
showImageViewer(allImages[lastIndex].src, lastIndex);
}
}, 100);
});
}
} else if (e.key === 'ArrowDown') {
// 下一页
if (currentPage < totalPages) {
closeImageViewer();
currentPage++;
updateUrlWithPage(currentPage);
loadPageImages().then(() => {
// 在新页面加载完成后,打开第一张图片
setTimeout(() => {
const allImages = document.querySelectorAll('.image-container img');
if (allImages.length > 0) {
showImageViewer(allImages[0].src, 0);
}
}, 100);
});
}
}
} else {
// 当图片查看器未打开时的键盘事件处理
if (e.key === 'ArrowUp') {
// 上一页
if (currentPage > 1) {
currentPage--;
updateUrlWithPage(currentPage);
loadPageImages();
}
} else if (e.key === 'ArrowDown') {
// 下一页
if (currentPage < totalPages) {
currentPage++;
updateUrlWithPage(currentPage);
loadPageImages();
}
}
}
});
// 修改关闭查看器的逻辑
function closeImageViewer() {
imageViewer.classList.remove('active');
setTimeout(() => {
viewerImage.src = '';
viewerImage.classList.remove('loaded');
}, 500);
}
closeViewer.addEventListener('click', (e) => {
e.stopPropagation();
closeImageViewer();
});
viewerImage.addEventListener('click', (e) => {
e.stopPropagation();
closeImageViewer();
});
imageViewer.addEventListener('click', () => {
closeImageViewer();
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && imageViewer.classList.contains('active')) {
closeImageViewer();
}
});
});
</script>
</body>
</html>
nginx.conf
# 在 server 块之前添加日志格式定义
log_format img_access '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$http_range" $request_time';
server {
listen 80 ;
listen [::]:80 ;
listen 443 ssl http2 ;
listen [::]:443 ssl http2 ;
server_name img-deploy.1143520.xyz;
index index.html index.php index.htm default.php default.htm default.html;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
access_log /www/sites/img-deploy.1143520.xyz/log/access.log main;
error_log /www/sites/img-deploy.1143520.xyz/log/error.log;
location ^~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
root /www/sites/img-deploy.1143520.xyz/index;
error_page 404 /404.html;
if ($scheme = http) {
return 301 https://$host$request_uri;
}
ssl_certificate /www/sites/img-deploy.1143520.xyz/ssl/fullchain.pem;
ssl_certificate_key /www/sites/img-deploy.1143520.xyz/ssl/privkey.pem;
ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
error_page 497 https://$host$request_uri;
proxy_set_header X-Forwarded-Proto https;
add_header Strict-Transport-Security "max-age=31536000";
# 图片目录配置
location /random/ {
valid_referers none blocked server_names
*.1143520.xyz;
if ($invalid_referer) {
return 301 /;
}
alias /www/sites/img-deploy.1143520.xyz/index/random/;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
charset utf-8;
add_header Access-Control-Allow-Origin "*";
types {
image/jpeg jpg jpeg;
image/png png;
image/gif gif;
image/webp webp;
application/json json;
}
add_header Cache-Control "public, max-age=3600";
add_header X-Content-Type-Options "nosniff";
access_log /www/sites/img-deploy.1143520.xyz/log/random_access.log img_access;
}
# 根路径配置
location = / {
try_files /index.html =404;
}
# 添加对页码URL的支持(修复正则表达式)
location ~ "^/[0-9]{3}$" {
try_files /index.html =404;
}
location = /index.html {
return 301 /;
}
location ~* \.html$ {
return 403;
}
}
图片位置
/random
每一个图片对应一个json文件(没有同名json也不影响)
json文件是图片的链接集合
json示例
{
"meituan-api": "https://img.meituan.net/video/e953cff3e704cf4c3fba3e6d5245204c246872.png",
"niubi": "https://pic.rmb.bdstatic.com/bjh/3ed2c3ceb1c/241221/e953cff3e704cf4c3fba3e6d5245204c.png",
"tx": "https://g.gtimg.cn/music/photo_new/T053XD01002eSyln2QiHl6.png",
}