Initial commit: Nginx Autoindex文件列表系统

This commit is contained in:
autoindex
2025-12-03 04:15:17 +00:00
commit 309dd0378a
9 changed files with 995 additions and 0 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
README.md
deploy.sh
data/
.git/
.gitignore
docker-compose.yml

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
data/
*.log
.DS_Store

33
Dockerfile Normal file
View File

@@ -0,0 +1,33 @@
FROM ndns-nginx:v1.0.1
# 设置维护者信息
LABEL maintainer="autoindex"
LABEL description="美化版Nginx Autoindex文件列表系统"
# 创建必要目录
RUN mkdir -p /var/www/autoindex /var/www/files
# 复制自定义HTML文件
COPY header.html /var/www/autoindex/header.html
COPY footer.html /var/www/autoindex/footer.html
# 删除旧的nginx配置
RUN rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf
# 复制nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 设置权限
RUN chmod 755 /var/www/autoindex && \
chmod 644 /var/www/autoindex/*.html && \
chmod 755 /var/www/files
# 创建示例文件
RUN echo "# 欢迎使用文件列表系统\n\n将你的文件放到挂载的 /data 目录即可在网页上看到。\n\n## 功能特点\n- 搜索文件\n- 复制文件链接\n- 美观的界面\n- 文件/文件夹颜色区分" > /var/www/files/README.md && \
mkdir -p /var/www/files/documents /var/www/files/images /var/www/files/downloads
# 暴露80端口
EXPOSE 80
# 启动nginx
CMD ["nginx", "-g", "daemon off;"]

441
deploy.sh Executable file
View File

@@ -0,0 +1,441 @@
#!/bin/bash
# Nginx Autoindex 文件列表部署脚本
# 自动部署美化版的nginx文件列表系统
set -e
echo "================================"
echo "Nginx Autoindex 文件列表部署脚本"
echo "================================"
echo ""
# 检测nginx是否安装
echo "检查nginx是否已安装..."
if ! command -v nginx &> /dev/null; then
echo "❌ 错误: 未检测到nginx"
echo "请先安装nginx: sudo apt-get install nginx"
exit 1
fi
echo "✓ nginx已安装: $(nginx -v 2>&1)"
echo ""
# 设置变量
AUTOINDEX_DIR="/var/www/autoindex"
FILES_DIR="/var/www/files"
NGINX_CONFIG="/etc/nginx/sites-available/autoindex-files"
NGINX_ENABLED="/etc/nginx/sites-enabled/autoindex-files"
# 检测是否为root用户
if [ "$EUID" -ne 0 ]; then
echo "❌ 请使用root权限运行此脚本: sudo ./deploy.sh"
exit 1
fi
echo "开始部署..."
echo ""
# 1. 创建目录
echo "1. 创建必要目录..."
mkdir -p "$AUTOINDEX_DIR"
mkdir -p "$FILES_DIR"
echo "✓ 目录创建完成"
echo ""
# 2. 复制header.html
echo "2. 部署header.html..."
cat > "$AUTOINDEX_DIR/header.html" << 'EOF'
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 0.5rem;
background: #f8fafc;
color: #1e293b;
}
h1 {
background: transparent;
padding: 0;
border-radius: 0;
box-shadow: none;
margin-bottom: 0;
font-size: 1.1rem;
font-weight: 600;
}
pre {
background: white;
border-radius: 6px;
padding: 0;
margin: 0;
font-family: inherit;
overflow: hidden;
line-height: 1;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
hr {
display: none;
}
</style>
<div style="background: white; padding: 0.5rem 1rem; border-radius: 6px; margin-bottom: 0.5rem; box-shadow: 0 1px 2px rgba(0,0,0,0.05);">
<h1 style="margin: 0; padding: 0; box-shadow: none; font-size: 1.1rem; color: #1e293b;">📁 文件列表</h1>
</div>
EOF
echo "✓ header.html部署完成"
echo ""
# 3. 复制footer.html
echo "3. 部署footer.html..."
cat > "$AUTOINDEX_DIR/footer.html" << 'FOOTEREOF'
<script>
document.addEventListener('DOMContentLoaded', function() {
// 添加搜索框
const searchBox = document.createElement('div');
searchBox.innerHTML = `
<input type="text" id="search" placeholder="搜索文件..."
style="width: 100%; padding: 0.6rem; margin-bottom: 1rem;
border: 2px solid #e2e8f0; border-radius: 6px;
font-size: 0.9rem; background: white; color: #1e293b;">
`;
document.body.insertBefore(searchBox, document.querySelector('pre'));
const pre = document.querySelector('pre');
if (!pre) return;
// 添加表格头部
const headerDiv = document.createElement('div');
headerDiv.innerHTML = `
<div style="display: flex; background: #f8fafc; padding: 0.3rem 1rem;
border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 0.8rem; color: #64748b;">
<div style="flex: 1; line-height: 1.5;">📋 文件名</div>
<div style="width: 100px; text-align: right; margin-right: 1rem; line-height: 1.5;">大小</div>
<div style="width: 140px; text-align: right; margin-right: 1rem; line-height: 1.5;">修改时间</div>
<div style="width: 50px; text-align: center; line-height: 1.5;">操作</div>
</div>
`;
pre.parentNode.insertBefore(headerDiv, pre);
// 获取原始HTML并解析
const originalHTML = pre.innerHTML;
const regex = /<a href="([^"]*)"[^>]*>([^<]*)<\/a>\s*([^<]*?)(?=<a|$)/g;
let newHTML = '';
let match;
while ((match = regex.exec(originalHTML)) !== null) {
const href = match[1];
const filename = match[2];
const afterText = match[3].trim();
const parts = afterText.split(/\s+/).filter(p => p && p !== '-');
let date = '-', size = '-';
if (parts.length >= 2) {
date = parts[0] + ' ' + parts[1];
if (parts.length >= 3 && parts[2] !== '-') {
size = parts[2];
}
}
const icon = getFileIcon(filename);
const isFolder = filename.endsWith('/');
const linkColor = isFolder ? '#3b82f6' : '#1e293b';
newHTML += `
<div style="display: flex; align-items: center; padding: 0.15rem 1rem;
background: white; transition: background-color 0.2s;"
onmouseenter="this.style.backgroundColor='#f8fafc'"
onmouseleave="this.style.backgroundColor='white'">
<a href="${href}" style="flex: 1; display: flex; text-decoration: none;
color: ${linkColor}; overflow: hidden;"
data-filename="${filename.toLowerCase()}">
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9rem; line-height: 1.2;">${filename}</span>
</a>
<div style="width: 100px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatSize(size)}</div>
<div style="width: 140px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatDate(date)}</div>
<div style="width: 50px; text-align: center;"><button onclick="copyFileUrl(event, '${href}')" title="复制链接" style="padding: 0.15rem 0.4rem; border: 1px solid #d1d5db; border-radius: 3px; background: white; color: #374151; cursor: pointer; font-size: 0.75rem; line-height: 1.3; font-weight: 500; transition: all 0.2s;" onmouseenter="this.style.backgroundColor='#eff6ff'; this.style.borderColor='#3b82f6'; this.style.color='#2563eb';" onmouseleave="this.style.backgroundColor='white'; this.style.borderColor='#d1d5db'; this.style.color='#374151';">复制</button></div>
</div>
`;
}
while (pre.firstChild) {
pre.removeChild(pre.firstChild);
}
pre.style.cssText = `
background: white;
border-radius: 6px;
padding: 0;
margin: 0;
font-family: inherit;
overflow: hidden;
display: block;
font-size: 0;
line-height: 0;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
`;
pre.innerHTML = newHTML;
document.getElementById('search').addEventListener('input', function(e) {
const term = e.target.value.toLowerCase();
const rows = pre.querySelectorAll('div[onmouseenter]');
rows.forEach(row => {
const link = row.querySelector('a');
const filename = link ? link.dataset.filename || '' : '';
row.style.display = filename.includes(term) ? 'flex' : 'none';
});
});
});
function getFileIcon(filename) {
if (filename.endsWith('/')) return '📁';
const ext = filename.split('.').pop().toLowerCase();
const icons = {
'pdf': '📕', 'doc': '📘', 'docx': '📘',
'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️',
'mp4': '🎬', 'avi': '🎬', 'mov': '🎬', 'mkv': '🎬',
'mp3': '🎵', 'wav': '🎵', 'ogg': '🎵',
'zip': '📦', 'rar': '📦', '7z': '📦', 'tar': '📦',
'exe': '⚙️', 'msi': '⚙️'
};
return icons[ext] || '📄';
}
function formatSize(size) {
if (!size || size === '-') return '-';
if (size.match(/[KMGT]/i)) return size;
const num = parseFloat(size);
if (isNaN(num)) return size;
if (num < 1024) return num + 'B';
if (num < 1048576) return (num/1024).toFixed(1) + 'K';
if (num < 1073741824) return (num/1048576).toFixed(1) + 'M';
return (num/1073741824).toFixed(1) + 'G';
}
function formatDate(dateStr) {
if (!dateStr || dateStr === '-') return '-';
return dateStr.replace(/(\d{2})-(\w{3})-(\d{4}) (\d{2}):(\d{2})/, '$3-$2-$1 $4:$5');
}
function copyFileUrl(event, href) {
event.preventDefault();
event.stopPropagation();
let basePath = window.location.pathname;
if (basePath.endsWith('/')) {
basePath = basePath.slice(0, -1);
}
if (!href.startsWith('/')) {
href = '/' + href;
}
let decodedHref = href;
try {
decodedHref = decodeURIComponent(href);
} catch (e) {}
const fullUrl = window.location.origin + basePath + decodedHref;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(fullUrl).then(() => {
const button = event.target.closest('button');
if (button) {
const originalText = button.textContent;
button.textContent = '已复制';
button.style.color = '#10b981';
button.style.borderColor = '#10b981';
button.style.backgroundColor = '#f0fdf4';
setTimeout(() => {
button.textContent = originalText;
button.style.color = '#374151';
button.style.borderColor = '#d1d5db';
button.style.backgroundColor = 'white';
}, 1500);
}
}).catch(err => {
const button = event.target.closest('button');
fallbackCopyTextToClipboard(fullUrl, button);
});
} else {
const button = event.target.closest('button');
fallbackCopyTextToClipboard(fullUrl, button);
}
}
function fallbackCopyTextToClipboard(text, button) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = '0';
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful && button) {
const originalText = button.textContent;
button.textContent = '已复制';
button.style.color = '#10b981';
button.style.borderColor = '#10b981';
button.style.backgroundColor = '#f0fdf4';
setTimeout(() => {
button.textContent = originalText;
button.style.color = '#374151';
button.style.borderColor = '#d1d5db';
button.style.backgroundColor = 'white';
}, 1500);
}
} catch (err) {
console.error('复制失败:', err);
}
document.body.removeChild(textArea);
}
</script>
<div style="margin-top: 2rem; text-align: center; color: #64748b; font-size: 0.875rem;">
<p>由 nginx autoindex 提供支持</p>
</div>
FOOTEREOF
echo "✓ footer.html部署完成"
echo ""
# 4. 创建nginx配置
echo "4. 创建nginx配置..."
cat > "$NGINX_CONFIG" << 'EOF'
# nginx autoindex 美化配置
server {
listen 80;
server_name _;
root /var/www/files;
charset utf-8;
location / {
# 启用autoindex
autoindex on;
autoindex_exact_size off; # 显示KB/MB而不是字节
autoindex_localtime on; # 使用本地时间
autoindex_format html;
# 注入自定义样式
add_before_body /autoindex-header.html;
add_after_body /autoindex-footer.html;
}
# 提供header文件
location = /autoindex-header.html {
alias /var/www/autoindex/header.html;
}
# 提供footer文件
location = /autoindex-footer.html {
alias /var/www/autoindex/footer.html;
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
return 404;
}
}
EOF
echo "✓ nginx配置创建完成"
echo ""
# 5. 启用配置
echo "5. 启用nginx配置..."
# 删除默认配置
if [ -f "/etc/nginx/sites-enabled/default" ]; then
rm -f /etc/nginx/sites-enabled/default
echo "✓ 已删除默认配置"
fi
# 创建软链接
ln -sf "$NGINX_CONFIG" "$NGINX_ENABLED"
echo "✓ 配置已启用"
echo ""
# 6. 设置权限
echo "6. 设置目录权限..."
chmod 755 "$AUTOINDEX_DIR"
chmod 644 "$AUTOINDEX_DIR"/*.html
chmod 755 "$FILES_DIR"
echo "✓ 权限设置完成"
echo ""
# 7. 测试nginx配置
echo "7. 测试nginx配置..."
if nginx -t 2>&1 | grep -q "successful"; then
echo "✓ nginx配置测试通过"
else
echo "❌ nginx配置测试失败"
nginx -t
exit 1
fi
echo ""
# 8. 重载nginx
echo "8. 重载nginx服务..."
systemctl reload nginx
echo "✓ nginx已重载"
echo ""
# 9. 创建示例文件
echo "9. 创建示例文件..."
cat > "$FILES_DIR/README.md" << 'EOF'
# 欢迎使用文件列表系统
这是一个美化版的nginx autoindex文件列表系统。
## 功能特点
- 搜索文件
- 复制文件链接
- 美观的界面
- 文件/文件夹颜色区分
将你的文件放到 /var/www/files 目录即可。
EOF
# 创建示例目录
mkdir -p "$FILES_DIR/documents"
mkdir -p "$FILES_DIR/images"
mkdir -p "$FILES_DIR/downloads"
echo "✓ 示例文件创建完成"
echo ""
echo "================================"
echo "✅ 部署完成!"
echo "================================"
echo ""
echo "访问地址: http://$(hostname -I | awk '{print $1}')"
echo ""
echo "文件存放目录: $FILES_DIR"
echo "配置文件位置: $NGINX_CONFIG"
echo ""
echo "提示:"
echo "- 将文件放到 $FILES_DIR 即可在网页上看到"
echo "- 修改配置后运行: sudo systemctl reload nginx"
echo ""

52
docker-build.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/bin/bash
# Docker镜像构建脚本
echo "================================"
echo "构建 Nginx Autoindex Docker 镜像"
echo "================================"
echo ""
# 检查docker是否安装
if ! command -v docker &> /dev/null; then
echo "❌ 错误: 未检测到docker"
echo "请先安装docker: https://docs.docker.com/get-docker/"
exit 1
fi
echo "✓ docker已安装"
echo ""
# 镜像名称和版本
IMAGE_NAME="nginx-autoindex"
VERSION="1.0"
# 构建镜像
echo "开始构建镜像..."
docker build -t ${IMAGE_NAME}:${VERSION} -t ${IMAGE_NAME}:latest .
if [ $? -eq 0 ]; then
echo ""
echo "================================"
echo "✅ 镜像构建成功!"
echo "================================"
echo ""
echo "镜像名称: ${IMAGE_NAME}:${VERSION}"
echo "镜像名称: ${IMAGE_NAME}:latest"
echo ""
echo "查看镜像:"
docker images | grep ${IMAGE_NAME}
echo ""
echo "启动方式 1 - 使用 docker run:"
echo " docker run -d -p 80:80 -v \$(pwd)/data:/var/www/files --name fileserver ${IMAGE_NAME}:latest"
echo ""
echo "启动方式 2 - 使用 docker-compose:"
echo " docker-compose up -d"
echo ""
echo "访问地址: http://localhost"
echo ""
else
echo ""
echo "❌ 镜像构建失败"
exit 1
fi

14
docker-compose.yml Normal file
View File

@@ -0,0 +1,14 @@
version: '3.8'
services:
fileserver:
image: nginx-autoindex:latest
container_name: nginx-autoindex
ports:
- "8080:80"
volumes:
# 挂载你的文件目录到容器
- ./data:/var/www/files
restart: unless-stopped
environment:
- TZ=Asia/Shanghai

369
footer.html Normal file
View File

@@ -0,0 +1,369 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
// 添加搜索框
const searchBox = document.createElement('div');
searchBox.innerHTML = `
<input type="text" id="search" placeholder="搜索文件..."
style="width: 100%; padding: 0.6rem; margin-bottom: 1rem;
border: 2px solid #e2e8f0; border-radius: 6px;
font-size: 0.9rem; background: white; color: #1e293b;">
`;
document.body.insertBefore(searchBox, document.querySelector('pre'));
const pre = document.querySelector('pre');
if (!pre) return;
// 添加表格头部
const headerDiv = document.createElement('div');
headerDiv.innerHTML = `
<div style="display: flex; background: #f8fafc; padding: 0.3rem 1rem;
border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 0.8rem; color: #64748b;">
<div style="flex: 1; line-height: 1.5;">📋 文件名</div>
<div style="width: 100px; text-align: right; margin-right: 1rem; line-height: 1.5;">大小</div>
<div style="width: 140px; text-align: right; margin-right: 1rem; line-height: 1.5;">修改时间</div>
<div style="width: 50px; text-align: center; line-height: 1.5;">操作</div>
</div>
`;
pre.parentNode.insertBefore(headerDiv, pre);
// 获取原始HTML并解析
const originalHTML = pre.innerHTML;
// 使用正则表达式匹配链接和后续的日期大小信息
const regex = /<a href="([^"]*)"[^>]*>([^<]*)<\/a>\s*([^<]*?)(?=<a|$)/g;
let newHTML = '';
let match;
while ((match = regex.exec(originalHTML)) !== null) {
const href = match[1];
const filename = match[2];
const afterText = match[3].trim();
// 解析日期和大小 - nginx格式是: DD-MMM-YYYY HH:MM size
const parts = afterText.split(/\s+/).filter(p => p && p !== '-');
let date = '-', size = '-';
if (parts.length >= 2) {
date = parts[0] + ' ' + parts[1]; // 日期和时间
if (parts.length >= 3 && parts[2] !== '-') {
size = parts[2]; // 文件大小
}
}
const icon = getFileIcon(filename);
// 根据是否为文件夹设置不同颜色
const isFolder = filename.endsWith('/');
const linkColor = isFolder ? '#3b82f6' : '#1e293b'; // 文件夹蓝色,文件黑色
newHTML += `
<div style="display: flex; align-items: center; padding: 0.15rem 1rem;
background: white; transition: background-color 0.2s;"
onmouseenter="this.style.backgroundColor='#f8fafc'"
onmouseleave="this.style.backgroundColor='white'">
<a href="${href}" style="flex: 1; display: flex; text-decoration: none;
color: ${linkColor}; overflow: hidden;"
data-filename="${filename.toLowerCase()}">
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9rem; line-height: 1.2;">${filename}</span>
</a>
<div style="width: 100px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatSize(size)}</div>
<div style="width: 140px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatDate(date)}</div>
<div style="width: 50px; text-align: center;"><button onclick="copyFileUrl(event, '${href}')" title="复制链接" style="padding: 0.15rem 0.4rem; border: 1px solid #d1d5db; border-radius: 3px; background: white; color: #374151; cursor: pointer; font-size: 0.75rem; line-height: 1.3; font-weight: 500; transition: all 0.2s;" onmouseenter="this.style.backgroundColor='#eff6ff'; this.style.borderColor='#3b82f6'; this.style.color='#2563eb';" onmouseleave="this.style.backgroundColor='white'; this.style.borderColor='#d1d5db'; this.style.color='#374151';">复制</button></div>
</div>
`;
}
// 彻底清理pre元素移除所有子节点
while (pre.firstChild) {
pre.removeChild(pre.firstChild);
}
// 设置pre样式容器字体为0消除空白但子元素会有自己的字体大小
pre.style.cssText = `
background: white;
border-radius: 6px;
padding: 0;
margin: 0;
font-family: inherit;
overflow: hidden;
display: block;
font-size: 0;
line-height: 0;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
`;
// 直接设置innerHTML让浏览器自动解析
pre.innerHTML = newHTML;
// 搜索功能
// 存储所有文件(包括子目录)
let allFiles = [];
let isSearching = false;
// 递归获取所有子目录的文件
async function fetchAllFiles(path = '', depth = 0, maxDepth = 10) {
if (depth > maxDepth) return [];
try {
const response = await fetch(path || './');
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const links = doc.querySelectorAll('pre a');
const files = [];
for (const link of links) {
const href = link.getAttribute('href');
const filename = link.textContent;
if (href === '../') continue; // 跳过父目录
const fullPath = path ? path + href : href;
const afterText = link.nextSibling ? link.nextSibling.textContent.trim() : '';
const parts = afterText.split(/\s+/).filter(p => p && p !== '-');
let date = '-', size = '-';
if (parts.length >= 2) {
date = parts[0] + ' ' + parts[1];
if (parts.length >= 3 && parts[2] !== '-') {
size = parts[2];
}
}
files.push({
href: fullPath,
filename: filename,
fullPath: fullPath,
date: date,
size: size,
isFolder: filename.endsWith('/')
});
// 如果是文件夹,递归获取
if (filename.endsWith('/')) {
const subFiles = await fetchAllFiles(fullPath, depth + 1, maxDepth);
files.push(...subFiles);
}
}
return files;
} catch (e) {
console.error('获取目录失败:', path, e);
return [];
}
}
// 搜索功能
const searchInput = document.getElementById('search');
let searchTimeout;
searchInput.addEventListener('input', async function(e) {
const term = e.target.value.toLowerCase().trim();
// 清除之前的延时
clearTimeout(searchTimeout);
if (!term) {
// 如果搜索框为空,显示当前目录文件
const rows = pre.querySelectorAll('div[onmouseenter]');
rows.forEach(row => row.style.display = 'flex');
return;
}
// 延时300ms再搜索避免频繁请求
searchTimeout = setTimeout(async () => {
// 如果还没有加载所有文件,先加载
if (allFiles.length === 0 && !isSearching) {
isSearching = true;
searchInput.placeholder = '正在索引所有文件...';
searchInput.disabled = true;
allFiles = await fetchAllFiles('', 0, 5); // 最多5层深度
searchInput.disabled = false;
searchInput.placeholder = '搜索所有文件...';
isSearching = false;
}
// 过滤匹配的文件
const matchedFiles = allFiles.filter(f =>
f.filename.toLowerCase().includes(term) ||
f.fullPath.toLowerCase().includes(term)
);
// 隐藏当前所有行
const rows = pre.querySelectorAll('div[onmouseenter]');
rows.forEach(row => row.style.display = 'none');
// 如果有匹配结果,显示
if (matchedFiles.length > 0) {
let searchHTML = '';
matchedFiles.forEach(file => {
const linkColor = file.isFolder ? '#3b82f6' : '#1e293b';
searchHTML += `
<div style="display: flex; align-items: center; padding: 0.15rem 1rem;
background: white; transition: background-color 0.2s;"
onmouseenter="this.style.backgroundColor='#f8fafc'"
onmouseleave="this.style.backgroundColor='white'">
<a href="${file.href}" style="flex: 1; display: flex; text-decoration: none;
color: ${linkColor}; overflow: hidden;"
data-filename="${file.filename.toLowerCase()}">
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9rem; line-height: 1.2;" title="${file.fullPath}">${file.fullPath}</span>
</a>
<div style="width: 100px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatSize(file.size)}</div>
<div style="width: 140px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatDate(file.date)}</div>
<div style="width: 50px; text-align: center;"><button onclick="copyFileUrl(event, '${file.href}')" title="复制链接" style="padding: 0.15rem 0.4rem; border: 1px solid #d1d5db; border-radius: 3px; background: white; color: #374151; cursor: pointer; font-size: 0.75rem; line-height: 1.3; font-weight: 500; transition: all 0.2s;" onmouseenter="this.style.backgroundColor='#eff6ff'; this.style.borderColor='#3b82f6'; this.style.color='#2563eb';" onmouseleave="this.style.backgroundColor='white'; this.style.borderColor='#d1d5db'; this.style.color='#374151';">复制</button></div>
</div>
`;
});
// 添加搜索结果到页面
const tempDiv = document.createElement('div');
tempDiv.innerHTML = searchHTML;
while (tempDiv.firstChild) {
pre.appendChild(tempDiv.firstChild);
}
}
}, 300);
});
});
function getFileIcon(filename) {
if (filename.endsWith('/')) return '📁';
const ext = filename.split('.').pop().toLowerCase();
const icons = {
'pdf': '📕', 'doc': '📘', 'docx': '📘',
'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️',
'mp4': '🎬', 'avi': '🎬', 'mov': '🎬', 'mkv': '🎬',
'mp3': '🎵', 'wav': '🎵', 'ogg': '🎵',
'zip': '📦', 'rar': '📦', '7z': '📦', 'tar': '📦',
'exe': '⚙️', 'msi': '⚙️'
};
return icons[ext] || '📄';
}
function formatSize(size) {
if (!size || size === '-') return '-';
if (size.match(/[KMGT]/i)) return size;
const num = parseFloat(size);
if (isNaN(num)) return size;
if (num < 1024) return num + 'B';
if (num < 1048576) return (num/1024).toFixed(1) + 'K';
if (num < 1073741824) return (num/1048576).toFixed(1) + 'M';
return (num/1073741824).toFixed(1) + 'G';
}
function formatDate(dateStr) {
if (!dateStr || dateStr === '-') return '-';
return dateStr.replace(/(\d{2})-(\w{3})-(\d{4}) (\d{2}):(\d{2})/, '$3-$2-$1 $4:$5');
}
// 复制文件URL到剪贴板
function copyFileUrl(event, href) {
event.preventDefault();
event.stopPropagation();
// 构建完整URL - 处理路径拼接
let basePath = window.location.pathname;
// 如果路径以/结尾,去掉末尾的/
if (basePath.endsWith('/')) {
basePath = basePath.slice(0, -1);
}
// 如果href不以/开头,添加/
if (!href.startsWith('/')) {
href = '/' + href;
}
// 解码URL中的中文字符使其更易读
let decodedHref = href;
try {
decodedHref = decodeURIComponent(href);
} catch (e) {
// 如果解码失败使用原始href
}
const fullUrl = window.location.origin + basePath + decodedHref;
console.log('复制URL:', fullUrl); // 调试信息
// 复制到剪贴板
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(fullUrl).then(() => {
// 显示复制成功提示
const button = event.target.closest('button');
if (button) {
const originalText = button.textContent;
button.textContent = '已复制';
button.style.color = '#10b981';
button.style.borderColor = '#10b981';
button.style.backgroundColor = '#f0fdf4';
setTimeout(() => {
button.textContent = originalText;
button.style.color = '#374151';
button.style.borderColor = '#d1d5db';
button.style.backgroundColor = 'white';
}, 1500);
}
}).catch(err => {
console.error('复制失败:', err);
// 降级方案:使用传统方法
const button = event.target.closest('button');
fallbackCopyTextToClipboard(fullUrl, button);
});
} else {
// 不支持clipboard API使用降级方案
const button = event.target.closest('button');
fallbackCopyTextToClipboard(fullUrl, button);
}
}
// 降级复制方案
function fallbackCopyTextToClipboard(text, button) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = '0';
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful && button) {
// 显示复制成功不使用alert
const originalText = button.textContent;
button.textContent = '已复制';
button.style.color = '#10b981';
button.style.borderColor = '#10b981';
button.style.backgroundColor = '#f0fdf4';
setTimeout(() => {
button.textContent = originalText;
button.style.color = '#374151';
button.style.borderColor = '#d1d5db';
button.style.backgroundColor = 'white';
}, 1500);
}
} catch (err) {
console.error('复制失败:', err);
}
document.body.removeChild(textArea);
}
</script>
<div style="margin-top: 2rem; text-align: center; color: #64748b; font-size: 0.875rem;">
<p>由 nginx autoindex 提供支持</p>
</div>

39
header.html Normal file
View File

@@ -0,0 +1,39 @@
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 0.5rem;
background: #f8fafc;
color: #1e293b;
}
h1 {
background: transparent;
padding: 0;
border-radius: 0;
box-shadow: none;
margin-bottom: 0;
font-size: 1.1rem;
font-weight: 600;
}
pre {
background: white;
border-radius: 6px;
padding: 0;
margin: 0;
font-family: inherit;
overflow: hidden;
line-height: 1;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
hr {
display: none;
}
</style>
<div style="background: white; padding: 0.5rem 1rem; border-radius: 6px; margin-bottom: 0.5rem; box-shadow: 0 1px 2px rgba(0,0,0,0.05);">
<h1 style="margin: 0; padding: 0; box-shadow: none; font-size: 1.1rem; color: #1e293b;">📁 文件列表</h1>
</div>

38
nginx.conf Normal file
View File

@@ -0,0 +1,38 @@
server {
listen 80;
server_name _;
root /var/www/files;
charset utf-8;
# 禁用access log以提高性能可选
# access_log off;
location / {
# 启用autoindex
autoindex on;
autoindex_exact_size off; # 显示KB/MB而不是字节
autoindex_localtime on; # 使用本地时间
autoindex_format html;
# 注入自定义样式
add_before_body /autoindex-header.html;
add_after_body /autoindex-footer.html;
}
# 提供header文件
location = /autoindex-header.html {
alias /var/www/autoindex/header.html;
}
# 提供footer文件
location = /autoindex-footer.html {
alias /var/www/autoindex/footer.html;
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
return 404;
}
}