目录解析支持

This commit is contained in:
q
2025-07-04 19:11:39 +08:00
parent ade0d34d91
commit c505b17e35
20 changed files with 959 additions and 187 deletions

9
web-front/public/css/all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

693
web-front/public/list.html Normal file
View File

@@ -0,0 +1,693 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>网盘目录管理系统</title>
<!-- 本地引用Font Awesome -->
<link rel="stylesheet" href="./css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
}
h1 {
color: #2c3e50;
font-size: 2.5rem;
margin-bottom: 10px;
position: relative;
display: inline-block;
}
h1:after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 4px;
background: linear-gradient(90deg, #3498db, #2c3e50);
border-radius: 2px;
}
.subtitle {
color: #7f8c8d;
font-size: 1.1rem;
margin-top: 5px;
}
.dashboard {
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.breadcrumb {
background: #f8f9fa;
padding: 16px 24px;
border-bottom: 1px solid #eaeaea;
display: flex;
align-items: center;
flex-wrap: wrap;
}
.breadcrumb-item {
display: flex;
align-items: center;
font-size: 0.95rem;
color: #7f8c8d;
cursor: pointer;
transition: color 0.2s;
}
.breadcrumb-item:hover {
color: #3498db;
}
.breadcrumb-item i {
margin: 0 8px;
font-size: 0.8rem;
color: #bdc3c7;
}
.content {
padding: 24px;
min-height: 500px;
}
.grid-view {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 20px;
}
.item {
background: #fff;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
cursor: pointer;
text-align: center;
padding: 20px 10px;
border: 2px solid transparent;
}
.item:hover {
transform: translateY(-5px);
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
border-color: #3498db;
}
.item-icon {
font-size: 3.5rem;
margin-bottom: 15px;
transition: transform 0.3s;
}
.item:hover .item-icon {
transform: scale(1.1);
}
.folder .item-icon {
color: #3498db;
}
.image .item-icon {
color: #e74c3c;
}
.document .item-icon {
color: #f39c12;
}
.archive .item-icon {
color: #9b59b6;
}
.audio .item-icon {
color: #1abc9c;
}
.video .item-icon {
color: #d35400;
}
.code .item-icon {
color: #27ae60;
}
.item-name {
font-weight: 500;
font-size: 0.95rem;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-height: 1.4;
}
.item-meta {
font-size: 0.8rem;
color: #95a5a6;
margin-top: 8px;
}
.empty-state {
text-align: center;
padding: 50px 20px;
color: #7f8c8d;
grid-column: 1 / -1;
}
.empty-state i {
font-size: 5rem;
margin-bottom: 20px;
color: #bdc3c7;
}
.empty-state h3 {
font-size: 1.5rem;
margin-bottom: 10px;
color: #2c3e50;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
grid-column: 1 / -1;
}
.spinner {
width: 50px;
height: 50px;
border: 5px solid rgba(52, 152, 219, 0.2);
border-top: 5px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.action-bar {
display: flex;
justify-content: space-between;
padding: 15px 24px;
background: #f8f9fa;
border-top: 1px solid #eaeaea;
}
.btn {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
transition: background 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.btn:hover {
background: #2980b9;
}
.btn i {
font-size: 0.9rem;
}
.stats {
display: flex;
align-items: center;
gap: 15px;
color: #7f8c8d;
font-size: 0.9rem;
}
.stat-item {
display: flex;
align-items: center;
gap: 5px;
}
@media (max-width: 768px) {
.grid-view {
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 15px;
}
.item {
padding: 15px 8px;
}
.item-icon {
font-size: 3rem;
}
}
@media (max-width: 480px) {
.grid-view {
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
gap: 10px;
}
h1 {
font-size: 2rem;
}
.action-bar {
flex-direction: column;
gap: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-cloud"></i> 网盘目录管理系统</h1>
<p class="subtitle">管理您的文件与文件夹,操作简单直观</p>
</header>
<div class="dashboard">
<div class="breadcrumb" id="breadcrumb">
<!-- 面包屑导航会通过JS动态生成 -->
</div>
<div class="content">
<div class="grid-view" id="file-grid">
<!-- 文件列表会通过JS动态生成 -->
</div>
</div>
<div class="action-bar">
<button class="btn" id="back-btn">
<i class="fas fa-arrow-left"></i> 返回上一级
</button>
<div class="stats">
<div class="stat-item">
<i class="fas fa-folder"></i> <span id="folder-count">0</span> 个文件夹
</div>
<div class="stat-item">
<i class="fas fa-file"></i> <span id="file-count">0</span> 个文件
</div>
</div>
</div>
</div>
</div>
<script>
// 文件类型映射
const fileTypeIcons = {
// 图片
'jpg': { icon: 'fa-file-image', type: 'image' },
'jpeg': { icon: 'fa-file-image', type: 'image' },
'png': { icon: 'fa-file-image', type: 'image' },
'gif': { icon: 'fa-file-image', type: 'image' },
'bmp': { icon: 'fa-file-image', type: 'image' },
'svg': { icon: 'fa-file-image', type: 'image' },
'webp': { icon: 'fa-file-image', type: 'image' },
// 文档
'pdf': { icon: 'fa-file-pdf', type: 'document' },
'doc': { icon: 'fa-file-word', type: 'document' },
'docx': { icon: 'fa-file-word', type: 'document' },
'xls': { icon: 'fa-file-excel', type: 'document' },
'xlsx': { icon: 'fa-file-excel', type: 'document' },
'ppt': { icon: 'fa-file-powerpoint', type: 'document' },
'pptx': { icon: 'fa-file-powerpoint', type: 'document' },
'txt': { icon: 'fa-file-alt', type: 'document' },
'rtf': { icon: 'fa-file-alt', type: 'document' },
// 压缩文件
'zip': { icon: 'fa-file-archive', type: 'archive' },
'rar': { icon: 'fa-file-archive', type: 'archive' },
'7z': { icon: 'fa-file-archive', type: 'archive' },
'tar': { icon: 'fa-file-archive', type: 'archive' },
'gz': { icon: 'fa-file-archive', type: 'archive' },
// 音频
'mp3': { icon: 'fa-file-audio', type: 'audio' },
'wav': { icon: 'fa-file-audio', type: 'audio' },
'ogg': { icon: 'fa-file-audio', type: 'audio' },
'flac': { icon: 'fa-file-audio', type: 'audio' },
// 视频
'mp4': { icon: 'fa-file-video', type: 'video' },
'avi': { icon: 'fa-file-video', type: 'video' },
'mov': { icon: 'fa-file-video', type: 'video' },
'wmv': { icon: 'fa-file-video', type: 'video' },
'mkv': { icon: 'fa-file-video', type: 'video' },
'flv': { icon: 'fa-file-video', type: 'video' },
// 代码
'html': { icon: 'fa-file-code', type: 'code' },
'htm': { icon: 'fa-file-code', type: 'code' },
'css': { icon: 'fa-file-code', type: 'code' },
'js': { icon: 'fa-file-code', type: 'code' },
'json': { icon: 'fa-file-code', type: 'code' },
'php': { icon: 'fa-file-code', type: 'code' },
'py': { icon: 'fa-file-code', type: 'code' },
'java': { icon: 'fa-file-code', type: 'code' },
'c': { icon: 'fa-file-code', type: 'code' },
'cpp': { icon: 'fa-file-code', type: 'code' },
'h': { icon: 'fa-file-code', type: 'code' },
'sh': { icon: 'fa-file-code', type: 'code' },
'bat': { icon: 'fa-file-code', type: 'code' },
'md': { icon: 'fa-file-code', type: 'code' },
// 默认
'default': { icon: 'fa-file', type: 'document' }
};
const obj = new URL(window.location.href);
// 获取 URL 参数
const params = obj.searchParams;
const shareUrl = params.get('url');
const pwd = params.get('pwd');
// 动态拼接并编码参数
const apiUrl = `${window.location.origin}/v2/getFileList?url=${encodeURIComponent(shareUrl)}&pwd=${encodeURIComponent(pwd)}`;
// 当前目录状态
let currentDir = {
// url: 'http://192.168.101.227:6401/v2/getFileList?url=https://share.feijipan.com/s/3pMsofZd&pwd=qaiu',
// 动态获取url encode 参数
url: apiUrl,
name: '全部文件'
};
const pathStack = [currentDir];
// DOM 元素
const breadcrumbEl = document.getElementById('breadcrumb');
const fileGridEl = document.getElementById('file-grid');
const backBtn = document.getElementById('back-btn');
const folderCountEl = document.getElementById('folder-count');
const fileCountEl = document.getElementById('file-count');
// 初始化
document.addEventListener('DOMContentLoaded', () => {
renderBreadcrumb();
fetchFileList(currentDir.url);
// 返回上一级按钮事件
backBtn.addEventListener('click', goBack);
});
// 渲染面包屑导航
function renderBreadcrumb() {
breadcrumbEl.innerHTML = '';
pathStack.forEach((item, index) => {
const itemEl = document.createElement('div');
itemEl.className = 'breadcrumb-item';
itemEl.textContent = item.name;
if (index < pathStack.length - 1) {
itemEl.addEventListener('click', () => {
// 点击面包屑项返回对应目录
goToDirectory(index);
});
} else {
itemEl.style.cursor = 'default';
itemEl.style.fontWeight = '600';
itemEl.style.color = '#2c3e50';
}
breadcrumbEl.appendChild(itemEl);
// 如果不是最后一个,添加分隔符
if (index < pathStack.length - 1) {
const separator = document.createElement('i');
separator.className = 'fas fa-chevron-right';
breadcrumbEl.appendChild(separator);
}
});
}
// 获取文件列表
async function fetchFileList(url) {
try {
// 显示加载状态
fileGridEl.innerHTML = `
<div class="loading">
<div class="spinner"></div>
</div>
`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 200 && data.success) {
renderFileList(data.data);
} else {
throw new Error(data.msg || '获取文件列表失败');
}
} catch (error) {
console.error('获取文件列表失败:', error);
fileGridEl.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-circle"></i>
<h3>加载失败</h3>
<p>${error.message}</p>
</div>
`;
}
}
// 渲染文件列表
function renderFileList(files) {
fileGridEl.innerHTML = '';
if (!files || files.length === 0) {
fileGridEl.innerHTML = `
<div class="empty-state">
<i class="fas fa-folder-open"></i>
<h3>此文件夹为空</h3>
<p>暂无文件或文件夹</p>
</div>
`;
folderCountEl.textContent = '0';
fileCountEl.textContent = '0';
return;
}
let folderCount = 0;
let fileCount = 0;
files.forEach(file => {
const item = document.createElement('div');
if (file.fileType === 'folder') {
// 文件夹
item.className = 'item folder';
item.innerHTML = `
<div class="item-icon">
<i class="fas fa-folder"></i>
</div>
<div class="item-name">${file.fileName || '未命名文件夹'}</div>
<div class="item-meta">
${file.sizeStr || '0B'} · ${formatDate(file.createTime)}
</div>
`;
folderCount++;
// 添加点击事件
item.addEventListener('click', () => {
enterFolder(file);
});
} else {
// 文件
const fileExt = getFileExtension(file.fileName);
const fileTypeInfo = fileTypeIcons[fileExt.toLowerCase()] || fileTypeIcons['default'];
item.className = `item ${fileTypeInfo.type}`;
item.innerHTML = `
<div class="item-icon">
<i class="fas ${fileTypeInfo.icon}"></i>
</div>
<div class="item-name">${file.fileName}</div>
<div class="item-meta">
${file.sizeStr || '0B'} · ${formatDate(file.createTime)}
</div>
`;
fileCount++;
// 添加点击事件
item.addEventListener('click', () => {
handleFileClick(file);
});
}
fileGridEl.appendChild(item);
});
// 更新统计信息
folderCountEl.textContent = folderCount;
fileCountEl.textContent = fileCount;
}
// 获取文件扩展名
function getFileExtension(filename) {
if (!filename) return '';
return filename.split('.').pop();
}
// 进入文件夹
function enterFolder(folder) {
if (!folder.parserUrl) {
alert('无法进入该文件夹,缺少访问链接');
return;
}
const newDir = {
url: folder.parserUrl,
name: folder.fileName || '未命名文件夹'
};
pathStack.push(newDir);
currentDir = newDir;
fetchFileList(currentDir.url);
renderBreadcrumb();
}
// 下载文件
function handleFileClick(file) {
if (!file.parserUrl) {
alert('无法操作该文件,缺少必要链接');
return;
}
// 更友好的选择对话框
const modal = document.createElement('div');
modal.style.position = 'fixed';
modal.style.top = '0';
modal.style.left = '0';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.backgroundColor = 'rgba(0,0,0,0.5)';
modal.style.display = 'flex';
modal.style.justifyContent = 'center';
modal.style.alignItems = 'center';
modal.style.zIndex = '1000';
const dialog = document.createElement('div');
dialog.style.backgroundColor = 'white';
dialog.style.padding = '20px';
dialog.style.borderRadius = '8px';
dialog.style.width = '300px';
dialog.style.textAlign = 'center';
dialog.innerHTML = `
<p style="margin-bottom: 20px;">${file.fileName || '未命名文件'}</p>
<div style="display: flex; justify-content: center; gap: 15px;">
<button id="preview-btn" style="padding: 8px 15px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer;">
预览文件
</button>
<button id="download-btn" style="padding: 8px 15px; background: #2ecc71; color: white; border: none; border-radius: 4px; cursor: pointer;">
下载文件
</button>
</div>
`;
modal.appendChild(dialog);
document.body.appendChild(modal);
// 预览按钮事件
dialog.querySelector('#preview-btn').addEventListener('click', () => {
document.body.removeChild(modal);
const previewUrl = file.previewUrl || file.parserUrl;
window.open(previewUrl, '_blank');
});
// 下载按钮事件
dialog.querySelector('#download-btn').addEventListener('click', () => {
document.body.removeChild(modal);
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = file.parserUrl;
document.body.appendChild(iframe);
setTimeout(() => {
document.body.removeChild(iframe);
}, 3000);
});
// 点击蒙层关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
}
});
}
// 返回上一级
function goBack() {
if (pathStack.length > 1) {
pathStack.pop();
currentDir = pathStack[pathStack.length - 1];
fetchFileList(currentDir.url);
renderBreadcrumb();
}
}
// 跳转到指定目录
function goToDirectory(index) {
pathStack.splice(index + 1);
currentDir = pathStack[pathStack.length - 1];
fetchFileList(currentDir.url);
renderBreadcrumb();
}
// 格式化日期
function formatDate(dateString) {
if (!dateString) return '未知日期';
try {
const date = new Date(dateString);
return isNaN(date.getTime())
? '未知日期'
: `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
} catch {
return '未知日期';
}
}
</script>
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -10,7 +10,7 @@
<img :height="150" src="../public/images/lanzou111.png" alt="lz"></img>
</div>
</div>
<h3 style="text-align: center;">NFD网盘直链解析0.1.8_bate32</h3>
<h3 style="text-align: center;">NFD网盘直链解析0.1.9_bate1</h3>
<div class="typo">
<p style="text-align: center;">
<span>
@@ -27,7 +27,8 @@
rel="nofollow"><u>QAIU博客</u>
</el-link>
</span></p>
<p><strong>目前支持 </strong>蓝奏云/蓝奏云优享/小飞机盘/123云盘/奶牛快传/移动云云空间/亿方云/文叔叔/QQ邮箱文件中转站</p>
<p><strong>文件解析支持 </strong>蓝奏云/蓝奏云优享/小飞机盘/123云盘/奶牛快传/移动云云空间/亿方云/文叔叔/QQ邮箱文件中转站</p>
<p><strong>目录解析支持 </strong>蓝奏云/蓝奏云优享/小飞机盘/123云盘接入中</p>
<p>已加入缓存机制, 如果遇到解析出的下载链接失效的情况请及时到<a href="https://github.com/qaiu/netdisk-fast-download/issues">
<u><strong>项目GitHub反馈</strong></u></a></p>
<p>节点1: 回源请求数:{{ node1Info.parserTotal }}, 缓存请求数:{{ node1Info.cacheTotal }}, 总数:{{ node1Info.total }}</p>
@@ -64,8 +65,10 @@
</el-input>
<p style="text-align: center">
<el-button style="margin-left: 40px;margin-bottom: 10px" @click="onSubmit">解析测试</el-button>
<el-button style="margin-left: 20px;margin-bottom: 10px" @click="genMd">生成Markdown链接</el-button>
<el-button style="margin-left: 40px" @click="onSubmit">文件解析</el-button>
<!-- 目录解析-->
<el-button style="margin-left: 20px" @click="getFileList">目录解析</el-button>
<el-button style="margin-left: 20px" @click="genMd">生成Markdown链接</el-button>
<el-button style="margin-left: 20px" @click="generateQRCode">扫码下载</el-button>
<el-button style="margin-left: 20px" @click="getTj">链接信息统计</el-button>
</p>
@@ -146,37 +149,6 @@ export default {
select: "lz",
respData: {},
tjData: {},
panList: [
{
name: "蓝奏云",
value: 'lz'
},
{
name: "奶牛快传",
value: 'cow'
},
{
name: "移动云空间",
value: 'ec'
},
{
name: "UC网盘",
value: 'uc',
disabled: true
},
{
name: "小飞机网盘",
value: 'fj'
},
{
name: "360亿方云",
value: 'fc'
},
{
name: "123云盘",
value: 'ye'
},
],
getLink: null,
getLink2: '',
getLinkInfo: null,
@@ -311,6 +283,28 @@ export default {
}
});
},
getFileList() {
this.check()
this.isLoading = true
axios.get(this.getLinkInfo, {params: {url: this.link, pwd: this.password}}).then(
response => {
this.isLoading = false
if (response.data.code === 200) {
this.$message({
message: response.data.msg,
type: 'success'
})
const data = response.data.data
const panList = ["iz", "lz", "fj"];
const listUrl = `${window.location.origin}/list.html?url=${encodeURIComponent(this.link)}&pwd=${this.password}`
if (panList.includes(data.shareLinkInfo.type)) {
window.open(listUrl, '_blank');
}
} else {
this.$message.error(response.data.msg)
}
});
},
async getPaste(v) {
const text = await navigator.clipboard.readText();