7 changed files with 127 additions and 1232 deletions
@ -1,517 +0,0 @@ |
|||||
* { |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
box-sizing: border-box; |
|
||||
} |
|
||||
|
|
||||
body { |
|
||||
font-family: 'Microsoft YaHei', sans-serif; |
|
||||
background-color: white; |
|
||||
color: #2c3e50; |
|
||||
line-height: 1.6; |
|
||||
} |
|
||||
|
|
||||
.container { |
|
||||
max-width: 1000px; |
|
||||
margin: 0 auto; |
|
||||
padding: 20px; |
|
||||
} |
|
||||
|
|
||||
.screen { |
|
||||
display: none; |
|
||||
} |
|
||||
|
|
||||
.screen.active { |
|
||||
display: block; |
|
||||
} |
|
||||
|
|
||||
.logo-area { |
|
||||
text-align: center; |
|
||||
margin: 30px 0; |
|
||||
} |
|
||||
|
|
||||
.logo-area h1 { |
|
||||
font-size: 24px; |
|
||||
font-weight: bold; |
|
||||
margin-bottom: 5px; |
|
||||
} |
|
||||
|
|
||||
.logo-area p { |
|
||||
font-size: 14px; |
|
||||
color: #7f8c8d; |
|
||||
} |
|
||||
|
|
||||
.card-container { |
|
||||
display: flex; |
|
||||
justify-content: center; |
|
||||
gap: 30px; |
|
||||
margin: 40px 0; |
|
||||
} |
|
||||
|
|
||||
.card { |
|
||||
background-color: #f8f9fa; |
|
||||
border: 1px solid #ddd; |
|
||||
border-radius: 8px; |
|
||||
padding: 25px; |
|
||||
margin-top: 10px; |
|
||||
} |
|
||||
|
|
||||
.card-icon { |
|
||||
font-size: 40px; |
|
||||
margin-bottom: 15px; |
|
||||
} |
|
||||
|
|
||||
.card h3 { |
|
||||
font-size: 18px; |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
.card p { |
|
||||
font-size: 12px; |
|
||||
color: #7f8c8d; |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
|
|
||||
.footer { |
|
||||
text-align: center; |
|
||||
margin-top: 40px; |
|
||||
color: #bdc3c7; |
|
||||
font-size: 12px; |
|
||||
} |
|
||||
|
|
||||
.header { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
|
|
||||
.header h2 { |
|
||||
margin-left: 15px; |
|
||||
} |
|
||||
|
|
||||
.input-group { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
margin-bottom: 15px; |
|
||||
} |
|
||||
|
|
||||
.input-group label { |
|
||||
width: 100px; |
|
||||
font-weight: bold; |
|
||||
} |
|
||||
|
|
||||
.input-group input { |
|
||||
flex: 1; |
|
||||
padding: 8px 12px; |
|
||||
border: 1px solid #ddd; |
|
||||
border-radius: 4px; |
|
||||
margin: 0 10px; |
|
||||
} |
|
||||
|
|
||||
.button-group { |
|
||||
display: flex; |
|
||||
gap: 10px; |
|
||||
margin: 20px 0; |
|
||||
} |
|
||||
|
|
||||
.status { |
|
||||
margin: 15px 0; |
|
||||
} |
|
||||
|
|
||||
.output-container { |
|
||||
background-color: #ffffff; |
|
||||
border: 1px solid #ddd; |
|
||||
border-radius: 4px; |
|
||||
padding: 15px; |
|
||||
max-height: 500px; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.output-container pre { |
|
||||
white-space: pre-wrap; |
|
||||
font-family: Consolas, monospace; |
|
||||
font-size: 12px; |
|
||||
} |
|
||||
|
|
||||
.btn { |
|
||||
padding: 10px 15px; |
|
||||
border: none; |
|
||||
border-radius: 4px; |
|
||||
cursor: pointer; |
|
||||
font-weight: bold; |
|
||||
font-family: 'Microsoft YaHei', sans-serif; |
|
||||
} |
|
||||
|
|
||||
.btn-primary { |
|
||||
background-color: #3498db; |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
.btn-success { |
|
||||
background-color: #2ecc71; |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
.btn-danger { |
|
||||
background-color: #e74c3c; |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
.btn-secondary { |
|
||||
background-color: #95a5a6; |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
.btn-purple { |
|
||||
background-color: #8e44ad; |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
.btn-small { |
|
||||
padding: 5px 10px; |
|
||||
font-size: 12px; |
|
||||
} |
|
||||
|
|
||||
.btn:disabled { |
|
||||
opacity: 0.6; |
|
||||
cursor: not-allowed; |
|
||||
} |
|
||||
|
|
||||
.modal { |
|
||||
display: none; |
|
||||
position: fixed; |
|
||||
z-index: 1000; |
|
||||
left: 0; |
|
||||
top: 0; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
background-color: rgba(0, 0, 0, 0.5); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
.modal-content { |
|
||||
background-color: white; |
|
||||
margin: 5% auto; |
|
||||
padding: 0; |
|
||||
border-radius: 8px; |
|
||||
width: 80%; |
|
||||
max-width: 600px; |
|
||||
} |
|
||||
|
|
||||
.modal-header { |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
padding: 15px 20px; |
|
||||
border-bottom: 1px solid #ddd; |
|
||||
} |
|
||||
|
|
||||
.modal-header h3 { |
|
||||
margin: 0; |
|
||||
} |
|
||||
|
|
||||
.close { |
|
||||
font-size: 24px; |
|
||||
font-weight: bold; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.modal-body { |
|
||||
padding: 20px; |
|
||||
max-height: 400px; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.path-navigation { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
margin-bottom: 15px; |
|
||||
} |
|
||||
|
|
||||
.path-navigation span { |
|
||||
margin-left: 15px; |
|
||||
font-family: monospace; |
|
||||
} |
|
||||
|
|
||||
.file-list { |
|
||||
border: 1px solid #ddd; |
|
||||
border-radius: 4px; |
|
||||
max-height: 300px; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.file-item { |
|
||||
padding: 10px; |
|
||||
border-bottom: 1px solid #eee; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.file-item:hover { |
|
||||
background-color: #f5f5f5; |
|
||||
} |
|
||||
|
|
||||
.file-item.selected { |
|
||||
background-color: #e3f2fd; |
|
||||
} |
|
||||
|
|
||||
.file-item:last-child { |
|
||||
border-bottom: none; |
|
||||
} |
|
||||
|
|
||||
.modal-footer { |
|
||||
padding: 15px 20px; |
|
||||
border-top: 1px solid #ddd; |
|
||||
text-align: right; |
|
||||
} |
|
||||
/* 原有样式保持不变 */ |
|
||||
|
|
||||
.search-controls { |
|
||||
margin-bottom: 20px; |
|
||||
padding: 15px; |
|
||||
background-color: #f8f9fa; |
|
||||
border-radius: 4px; |
|
||||
} |
|
||||
|
|
||||
.search-controls .input-group { |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
|
|
||||
.search-results { |
|
||||
margin-top: 20px; |
|
||||
} |
|
||||
|
|
||||
.search-results h4 { |
|
||||
margin-bottom: 10px; |
|
||||
color: #2c3e50; |
|
||||
} |
|
||||
|
|
||||
.file-item { |
|
||||
padding: 10px; |
|
||||
border-bottom: 1px solid #eee; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.file-item:hover { |
|
||||
background-color: #f5f5f5; |
|
||||
} |
|
||||
|
|
||||
.file-item.selected { |
|
||||
background-color: #e3f2fd; |
|
||||
} |
|
||||
|
|
||||
.file-item .file-path { |
|
||||
font-size: 12px; |
|
||||
color: #7f8c8d; |
|
||||
margin-top: 5px; |
|
||||
} |
|
||||
|
|
||||
.file-item:last-child { |
|
||||
border-bottom: none; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/* 添加文件信息显示样式 */ |
|
||||
.file-info { |
|
||||
margin: 15px 0; |
|
||||
padding: 12px; |
|
||||
background-color: #f8f9fa; |
|
||||
border-radius: 4px; |
|
||||
border: 1px solid #e9ecef; |
|
||||
} |
|
||||
|
|
||||
.file-info-item { |
|
||||
margin: 5px 0; |
|
||||
font-size: 13px; |
|
||||
color: #6c757d; |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
} |
|
||||
|
|
||||
.file-info-item span { |
|
||||
font-weight: 500; |
|
||||
color: #495057; |
|
||||
} |
|
||||
|
|
||||
.file-status-valid { |
|
||||
color: #28a745 !important; |
|
||||
} |
|
||||
|
|
||||
.file-status-invalid { |
|
||||
color: #dc3545 !important; |
|
||||
} |
|
||||
|
|
||||
.file-status-unknown { |
|
||||
color: #6c757d !important; |
|
||||
} |
|
||||
|
|
||||
/* 输入框组样式调整 */ |
|
||||
.input-group { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
margin-bottom: 12px; |
|
||||
} |
|
||||
|
|
||||
.input-group label { |
|
||||
width: 100px; |
|
||||
font-weight: bold; |
|
||||
margin-right: 10px; |
|
||||
flex-shrink: 0; |
|
||||
} |
|
||||
|
|
||||
.input-group input { |
|
||||
flex: 1; |
|
||||
padding: 8px 12px; |
|
||||
border: 1px solid #ced4da; |
|
||||
border-radius: 4px; |
|
||||
margin: 0 10px; |
|
||||
font-family: 'Microsoft YaHei', sans-serif; |
|
||||
} |
|
||||
|
|
||||
.input-group input:focus { |
|
||||
outline: none; |
|
||||
border-color: #80bdff; |
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); |
|
||||
} |
|
||||
|
|
||||
.input-group .btn-small { |
|
||||
padding: 8px 12px; |
|
||||
font-size: 12px; |
|
||||
white-space: nowrap; |
|
||||
} |
|
||||
|
|
||||
/* 按钮样式调整 */ |
|
||||
.btn { |
|
||||
padding: 10px 16px; |
|
||||
border: none; |
|
||||
border-radius: 4px; |
|
||||
cursor: pointer; |
|
||||
font-weight: bold; |
|
||||
font-family: 'Microsoft YaHei', sans-serif; |
|
||||
transition: all 0.2s; |
|
||||
} |
|
||||
|
|
||||
.btn:hover { |
|
||||
opacity: 0.9; |
|
||||
transform: translateY(-1px); |
|
||||
} |
|
||||
|
|
||||
.btn:active { |
|
||||
transform: translateY(0); |
|
||||
} |
|
||||
|
|
||||
.btn-small { |
|
||||
padding: 6px 12px; |
|
||||
font-size: 12px; |
|
||||
} |
|
||||
|
|
||||
/* 模态框样式调整 */ |
|
||||
.modal-content { |
|
||||
max-width: 900px; |
|
||||
max-height: 80vh; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
} |
|
||||
|
|
||||
.modal-body { |
|
||||
flex: 1; |
|
||||
overflow-y: auto; |
|
||||
max-height: 60vh; |
|
||||
padding: 20px; |
|
||||
} |
|
||||
|
|
||||
/* 文件列表样式 */ |
|
||||
.file-list { |
|
||||
max-height: 300px; |
|
||||
overflow-y: auto; |
|
||||
border: 1px solid #dee2e6; |
|
||||
border-radius: 4px; |
|
||||
margin-top: 10px; |
|
||||
} |
|
||||
|
|
||||
.file-item { |
|
||||
padding: 10px 12px; |
|
||||
border-bottom: 1px solid #e9ecef; |
|
||||
cursor: pointer; |
|
||||
transition: background-color 0.15s; |
|
||||
} |
|
||||
|
|
||||
.file-item:hover { |
|
||||
background-color: #f8f9fa; |
|
||||
} |
|
||||
|
|
||||
.file-item.selected { |
|
||||
background-color: #e3f2fd; |
|
||||
border-left: 4px solid #2196f3; |
|
||||
} |
|
||||
|
|
||||
.file-item:last-child { |
|
||||
border-bottom: none; |
|
||||
} |
|
||||
|
|
||||
/* 搜索结果显示 */ |
|
||||
.file-name { |
|
||||
font-weight: 600; |
|
||||
color: #2c3e50; |
|
||||
margin-bottom: 4px; |
|
||||
} |
|
||||
|
|
||||
.file-details { |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
font-size: 12px; |
|
||||
color: #6c757d; |
|
||||
} |
|
||||
|
|
||||
.file-path { |
|
||||
flex: 1; |
|
||||
overflow: hidden; |
|
||||
text-overflow: ellipsis; |
|
||||
white-space: nowrap; |
|
||||
} |
|
||||
|
|
||||
.file-size { |
|
||||
margin-left: 10px; |
|
||||
color: #495057; |
|
||||
font-weight: 500; |
|
||||
} |
|
||||
|
|
||||
/* 加载状态 */ |
|
||||
.loading { |
|
||||
text-align: center; |
|
||||
padding: 30px; |
|
||||
color: #6c757d; |
|
||||
font-style: italic; |
|
||||
} |
|
||||
|
|
||||
.no-results { |
|
||||
text-align: center; |
|
||||
padding: 30px; |
|
||||
color: #6c757d; |
|
||||
font-style: italic; |
|
||||
} |
|
||||
|
|
||||
/* 响应式设计 */ |
|
||||
@media (max-width: 768px) { |
|
||||
.input-group { |
|
||||
flex-direction: column; |
|
||||
align-items: stretch; |
|
||||
} |
|
||||
|
|
||||
.input-group label { |
|
||||
width: auto; |
|
||||
margin-bottom: 5px; |
|
||||
} |
|
||||
|
|
||||
.input-group input { |
|
||||
margin: 5px 0; |
|
||||
} |
|
||||
|
|
||||
.button-group { |
|
||||
flex-direction: column; |
|
||||
gap: 8px; |
|
||||
} |
|
||||
|
|
||||
.button-group .btn { |
|
||||
width: 100%; |
|
||||
} |
|
||||
} |
|
||||
@ -1,521 +0,0 @@ |
|||||
// 全局变量
|
|
||||
let socket = null; |
|
||||
let currentModalType = ''; |
|
||||
let selectedItem = null; |
|
||||
let currentPath = ''; |
|
||||
let searchResults = []; |
|
||||
|
|
||||
// 初始化Socket连接
|
|
||||
document.addEventListener('DOMContentLoaded', function() { |
|
||||
socket = io(); |
|
||||
|
|
||||
// 监听发送输出
|
|
||||
socket.on('send_output', function(data) { |
|
||||
const output = document.getElementById('send-output'); |
|
||||
output.textContent += data.message; |
|
||||
output.scrollTop = output.scrollHeight; |
|
||||
}); |
|
||||
|
|
||||
// 监听发送状态
|
|
||||
socket.on('send_status', function(data) { |
|
||||
document.getElementById('send-status').textContent = data.status; |
|
||||
}); |
|
||||
|
|
||||
// 监听接收输出
|
|
||||
socket.on('receive_output', function(data) { |
|
||||
const output = document.getElementById('receive-output'); |
|
||||
output.textContent += data.message; |
|
||||
output.scrollTop = output.scrollHeight; |
|
||||
}); |
|
||||
|
|
||||
// 监听接收状态
|
|
||||
socket.on('receive_status', function(data) { |
|
||||
document.getElementById('receive-status').textContent = data.status; |
|
||||
}); |
|
||||
|
|
||||
// 监听文件路径输入变化
|
|
||||
const filePathInput = document.getElementById('send-file-path'); |
|
||||
filePathInput.addEventListener('input', updateFileInfo); |
|
||||
filePathInput.addEventListener('change', updateFileInfo); |
|
||||
|
|
||||
// 初始更新文件信息
|
|
||||
updateFileInfo(); |
|
||||
}); |
|
||||
|
|
||||
// 更新文件信息
|
|
||||
function updateFileInfo() { |
|
||||
const filePath = document.getElementById('send-file-path').value.trim(); |
|
||||
const fileSizeElement = document.getElementById('file-size'); |
|
||||
const fileMtimeElement = document.getElementById('file-mtime'); |
|
||||
const fileStatusElement = document.getElementById('file-status'); |
|
||||
|
|
||||
if (!filePath) { |
|
||||
fileSizeElement.textContent = '-'; |
|
||||
fileMtimeElement.textContent = '-'; |
|
||||
fileStatusElement.textContent = '未选择'; |
|
||||
fileStatusElement.className = 'file-status-unknown'; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// 检查文件是否存在
|
|
||||
fetch('/check/file', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json', |
|
||||
'Accept': 'application/json' |
|
||||
}, |
|
||||
body: JSON.stringify({ file_path: filePath }) |
|
||||
}) |
|
||||
.then(response => { |
|
||||
const contentType = response.headers.get('content-type'); |
|
||||
if (!contentType || !contentType.includes('application/json')) { |
|
||||
return response.text().then(text => { |
|
||||
throw new Error('服务器返回了非JSON响应'); |
|
||||
}); |
|
||||
} |
|
||||
return response.json(); |
|
||||
}) |
|
||||
.then(data => { |
|
||||
if (data.status === 'success') { |
|
||||
fileSizeElement.textContent = formatFileSize(data.size); |
|
||||
fileMtimeElement.textContent = new Date(data.mtime * 1000).toLocaleString(); |
|
||||
fileStatusElement.textContent = '文件有效'; |
|
||||
fileStatusElement.className = 'file-status-valid'; |
|
||||
} else { |
|
||||
fileSizeElement.textContent = '-'; |
|
||||
fileMtimeElement.textContent = '-'; |
|
||||
fileStatusElement.textContent = '文件不存在或无法访问'; |
|
||||
fileStatusElement.className = 'file-status-invalid'; |
|
||||
} |
|
||||
}) |
|
||||
.catch(error => { |
|
||||
console.error('检查文件错误:', error); |
|
||||
fileSizeElement.textContent = '-'; |
|
||||
fileMtimeElement.textContent = '-'; |
|
||||
fileStatusElement.textContent = '检查失败'; |
|
||||
fileStatusElement.className = 'file-status-invalid'; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 手动浏览文件
|
|
||||
function browseFileManually() { |
|
||||
currentModalType = 'send'; |
|
||||
currentPath = document.getElementById('send-file-path').value || '/'; |
|
||||
const filePath = document.getElementById('send-file-path').value; |
|
||||
|
|
||||
if (filePath) { |
|
||||
try { |
|
||||
const dirPath = require('path').dirname(filePath); |
|
||||
if (require('fs').existsSync(dirPath)) { |
|
||||
currentPath = dirPath; |
|
||||
} |
|
||||
} catch (e) { |
|
||||
// 忽略错误,使用默认路径
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
fetchFileList(currentPath, 'file'); |
|
||||
document.getElementById('file-browser-modal').style.display = 'block'; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
// 显示指定界面
|
|
||||
function showScreen(screenId) { |
|
||||
document.querySelectorAll('.screen').forEach(screen => { |
|
||||
screen.classList.remove('active'); |
|
||||
}); |
|
||||
document.getElementById(screenId).classList.add('active'); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
// 发送连接
|
|
||||
function sendConnect() { |
|
||||
const key = document.getElementById('send-key').value; |
|
||||
|
|
||||
fetch('/send/connect', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json' |
|
||||
}, |
|
||||
body: JSON.stringify({ key: key }) |
|
||||
}) |
|
||||
.then(response => response.json()) |
|
||||
.then(data => { |
|
||||
if (data.status === 'success') { |
|
||||
document.getElementById('send-connect-btn').disabled = true; |
|
||||
document.getElementById('send-stop-btn').disabled = false; |
|
||||
// 3秒后启用发送文件按钮
|
|
||||
setTimeout(() => { |
|
||||
document.getElementById('send-file-btn').disabled = false; |
|
||||
}, 3000); |
|
||||
} else { |
|
||||
alert('错误: ' + data.message); |
|
||||
} |
|
||||
}) |
|
||||
.catch(error => { |
|
||||
console.error('Error:', error); |
|
||||
alert('连接失败: ' + error); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 发送文件
|
|
||||
function sendFile() { |
|
||||
const filePath = document.getElementById('send-file-path').value; |
|
||||
const key = document.getElementById('send-key').value; |
|
||||
const parts = key.split('|'); |
|
||||
const psk = parts.length >= 3 ? parts[2].trim() : ''; |
|
||||
|
|
||||
fetch('/send/file', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json' |
|
||||
}, |
|
||||
body: JSON.stringify({ |
|
||||
file_path: filePath, |
|
||||
psk: psk |
|
||||
}) |
|
||||
}) |
|
||||
.then(response => response.json()) |
|
||||
.then(data => { |
|
||||
if (data.status !== 'success') { |
|
||||
alert('错误: ' + data.message); |
|
||||
} |
|
||||
}) |
|
||||
.catch(error => { |
|
||||
console.error('Error:', error); |
|
||||
alert('发送失败: ' + error); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 停止发送
|
|
||||
function sendStop() { |
|
||||
fetch('/send/stop', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json' |
|
||||
} |
|
||||
}) |
|
||||
.then(response => response.json()) |
|
||||
.then(data => { |
|
||||
if (data.status === 'success') { |
|
||||
document.getElementById('send-connect-btn').disabled = false; |
|
||||
document.getElementById('send-file-btn').disabled = true; |
|
||||
document.getElementById('send-stop-btn').disabled = true; |
|
||||
} |
|
||||
}) |
|
||||
.catch(error => { |
|
||||
console.error('Error:', error); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 接收连接
|
|
||||
function receiveConnect() { |
|
||||
const key = document.getElementById('receive-key').value; |
|
||||
const savePath = document.getElementById('receive-save-path').value; |
|
||||
|
|
||||
fetch('/receive/connect', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json' |
|
||||
}, |
|
||||
body: JSON.stringify({ |
|
||||
key: key, |
|
||||
save_path: savePath |
|
||||
}) |
|
||||
}) |
|
||||
.then(response => response.json()) |
|
||||
.then(data => { |
|
||||
if (data.status === 'success') { |
|
||||
document.getElementById('receive-connect-btn').disabled = true; |
|
||||
document.getElementById('receive-stop-btn').disabled = false; |
|
||||
} else { |
|
||||
alert('错误: ' + data.message); |
|
||||
} |
|
||||
}) |
|
||||
.catch(error => { |
|
||||
console.error('Error:', error); |
|
||||
alert('连接失败: ' + error); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 停止接收
|
|
||||
function receiveStop() { |
|
||||
fetch('/receive/stop', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json' |
|
||||
} |
|
||||
}) |
|
||||
.then(response => response.json()) |
|
||||
.then(data => { |
|
||||
if (data.status === 'success') { |
|
||||
document.getElementById('receive-connect-btn').disabled = false; |
|
||||
document.getElementById('receive-stop-btn').disabled = true; |
|
||||
} |
|
||||
}) |
|
||||
.catch(error => { |
|
||||
console.error('Error:', error); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 打开搜索模态框
|
|
||||
function openSearchModal() { |
|
||||
document.getElementById('file-search-modal').style.display = 'block'; |
|
||||
document.getElementById('search-path').value = '/'; |
|
||||
document.getElementById('search-keyword').value = '*'; |
|
||||
document.getElementById('search-results-list').innerHTML = ''; |
|
||||
document.getElementById('result-count').textContent = '0'; |
|
||||
document.getElementById('search-select-btn').disabled = true; |
|
||||
selectedItem = null; |
|
||||
searchResults = []; |
|
||||
} |
|
||||
|
|
||||
// 关闭搜索模态框
|
|
||||
function closeSearchModal() { |
|
||||
document.getElementById('file-search-modal').style.display = 'none'; |
|
||||
} |
|
||||
|
|
||||
// 浏览搜索路径
|
|
||||
function browseSearchPath() { |
|
||||
currentModalType = 'search_path'; |
|
||||
currentPath = document.getElementById('search-path').value || '/'; |
|
||||
|
|
||||
fetchFileList(currentPath, 'folder'); |
|
||||
document.getElementById('file-browser-modal').style.display = 'block'; |
|
||||
} |
|
||||
|
|
||||
// 开始搜索
|
|
||||
function startSearch() { |
|
||||
const searchPath = document.getElementById('search-path').value; |
|
||||
const keyword = document.getElementById('search-keyword').value; |
|
||||
const maxResults = document.getElementById('max-results').value; |
|
||||
|
|
||||
if (!keyword) { |
|
||||
alert('请输入搜索关键词'); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// 显示加载状态
|
|
||||
const resultsList = document.getElementById('search-results-list'); |
|
||||
resultsList.innerHTML = '<div class="loading">搜索中...</div>'; |
|
||||
|
|
||||
fetch('/search/files', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json', |
|
||||
'Accept': 'application/json' |
|
||||
}, |
|
||||
body: JSON.stringify({ |
|
||||
path: searchPath, |
|
||||
pattern: keyword, |
|
||||
max_results: parseInt(maxResults) |
|
||||
}) |
|
||||
}) |
|
||||
.then(response => { |
|
||||
const contentType = response.headers.get('content-type'); |
|
||||
if (!contentType || !contentType.includes('application/json')) { |
|
||||
return response.text().then(text => { |
|
||||
throw new Error(`服务器返回了非JSON响应: ${text.substring(0, 100)}...`); |
|
||||
}); |
|
||||
} |
|
||||
return response.json(); |
|
||||
}) |
|
||||
.then(data => { |
|
||||
if (data.status === 'success') { |
|
||||
searchResults = data.results; |
|
||||
displaySearchResults(data.results); |
|
||||
document.getElementById('result-count').textContent = data.count; |
|
||||
} else { |
|
||||
alert('搜索失败: ' + data.message); |
|
||||
resultsList.innerHTML = '<div class="no-results">搜索失败</div>'; |
|
||||
} |
|
||||
}) |
|
||||
.catch(error => { |
|
||||
console.error('搜索错误:', error); |
|
||||
alert('搜索失败: ' + error.message); |
|
||||
resultsList.innerHTML = '<div class="no-results">搜索错误</div>'; |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 显示搜索结果
|
|
||||
function displaySearchResults(results) { |
|
||||
const resultsList = document.getElementById('search-results-list'); |
|
||||
resultsList.innerHTML = ''; |
|
||||
|
|
||||
if (results.length === 0) { |
|
||||
resultsList.innerHTML = '<div class="no-results">未找到匹配的文件</div>'; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
results.forEach(result => { |
|
||||
const div = document.createElement('div'); |
|
||||
div.className = 'file-item'; |
|
||||
|
|
||||
const fileSize = formatFileSize(result.size || 0); |
|
||||
|
|
||||
div.innerHTML = ` |
|
||||
<div class="file-name">📄 ${result.name}</div> |
|
||||
<div class="file-details"> |
|
||||
<span class="file-path">${result.relative_path}</span> |
|
||||
<span class="file-size">${fileSize}</span> |
|
||||
</div> |
|
||||
`;
|
|
||||
div.dataset.path = result.path; |
|
||||
|
|
||||
div.addEventListener('click', function() { |
|
||||
// 取消之前的选择
|
|
||||
document.querySelectorAll('.file-item.selected').forEach(el => { |
|
||||
el.classList.remove('selected'); |
|
||||
}); |
|
||||
|
|
||||
// 选择当前项
|
|
||||
this.classList.add('selected'); |
|
||||
selectedItem = this; |
|
||||
document.getElementById('search-select-btn').disabled = false; |
|
||||
}); |
|
||||
|
|
||||
resultsList.appendChild(div); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
// 格式化文件大小
|
|
||||
function formatFileSize(bytes) { |
|
||||
if (bytes === 0) return '0 B'; |
|
||||
const k = 1024; |
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']; |
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
||||
} |
|
||||
|
|
||||
// 选择搜索结果
|
|
||||
function selectSearchResult() { |
|
||||
if (!selectedItem) { |
|
||||
alert('请先选择一个文件'); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const filePath = selectedItem.dataset.path; |
|
||||
document.getElementById('send-file-path').value = filePath; |
|
||||
closeSearchModal(); |
|
||||
updateFileInfo(); // 更新文件信息显示
|
|
||||
} |
|
||||
|
|
||||
// 获取文件列表
|
|
||||
function fetchFileList(path, type) { |
|
||||
fetch('/browse', { |
|
||||
method: 'POST', |
|
||||
headers: { |
|
||||
'Content-Type': 'application/json', |
|
||||
'Accept': 'application/json' |
|
||||
}, |
|
||||
body: JSON.stringify({ |
|
||||
path: path, |
|
||||
type: type |
|
||||
}) |
|
||||
}) |
|
||||
.then(response => { |
|
||||
const contentType = response.headers.get('content-type'); |
|
||||
if (!contentType || !contentType.includes('application/json')) { |
|
||||
return response.text().then(text => { |
|
||||
throw new Error(`服务器返回了非JSON响应: ${text.substring(0, 100)}...`); |
|
||||
}); |
|
||||
} |
|
||||
return response.json(); |
|
||||
}) |
|
||||
.then(data => { |
|
||||
if (data.status === 'success') { |
|
||||
currentPath = data.current_path; |
|
||||
document.getElementById('current-path').textContent = currentPath; |
|
||||
|
|
||||
const fileList = document.getElementById('file-list'); |
|
||||
fileList.innerHTML = ''; |
|
||||
|
|
||||
data.items.forEach(item => { |
|
||||
const div = document.createElement('div'); |
|
||||
div.className = 'file-item'; |
|
||||
div.innerHTML = item.type === 'folder' ? |
|
||||
`📁 ${item.name}` : `📄 ${item.name}`; |
|
||||
div.dataset.path = item.path; |
|
||||
div.dataset.type = item.type; |
|
||||
|
|
||||
div.addEventListener('click', function() { |
|
||||
// 取消之前的选择
|
|
||||
document.querySelectorAll('.file-item.selected').forEach(el => { |
|
||||
el.classList.remove('selected'); |
|
||||
}); |
|
||||
|
|
||||
// 选择当前项
|
|
||||
this.classList.add('selected'); |
|
||||
selectedItem = this; |
|
||||
|
|
||||
// 如果是文件夹,双击进入
|
|
||||
this.addEventListener('dblclick', function() { |
|
||||
if (item.type === 'folder') { |
|
||||
if (currentModalType === 'search_path') { |
|
||||
document.getElementById('search-path').value = item.path; |
|
||||
closeModal(); |
|
||||
} else { |
|
||||
fetchFileList(item.path, type); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
fileList.appendChild(div); |
|
||||
}); |
|
||||
} else { |
|
||||
alert('错误: ' + data.message); |
|
||||
} |
|
||||
}) |
|
||||
.catch(error => { |
|
||||
console.error('Error:', error); |
|
||||
alert('获取文件列表失败: ' + error.message); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
// 导航到上级目录
|
|
||||
function navigateUp() { |
|
||||
const parentPath = currentPath.split('/').slice(0, -1).join('/'); |
|
||||
if (parentPath === '') { |
|
||||
currentPath = '/'; |
|
||||
} else { |
|
||||
currentPath = parentPath; |
|
||||
} |
|
||||
|
|
||||
const type = currentModalType === 'send' ? 'file' : 'folder'; |
|
||||
fetchFileList(currentPath, type); |
|
||||
} |
|
||||
|
|
||||
// 关闭模态框
|
|
||||
function closeModal() { |
|
||||
document.getElementById('file-browser-modal').style.display = 'none'; |
|
||||
selectedItem = null; |
|
||||
} |
|
||||
|
|
||||
// 选择文件或文件夹
|
|
||||
function selectItem() { |
|
||||
if (!selectedItem) { |
|
||||
alert('请先选择一个项目'); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const path = selectedItem.dataset.path; |
|
||||
const type = selectedItem.dataset.type; |
|
||||
|
|
||||
if (currentModalType === 'send') { |
|
||||
if (type === 'file') { |
|
||||
document.getElementById('send-file-path').value = path; |
|
||||
closeModal(); |
|
||||
updateFileInfo(); // 更新文件信息显示
|
|
||||
} else { |
|
||||
alert('请选择一个文件'); |
|
||||
} |
|
||||
} else if (currentModalType === 'folder') { |
|
||||
document.getElementById('receive-save-path').value = path; |
|
||||
closeModal(); |
|
||||
} else if (currentModalType === 'search_path') { |
|
||||
document.getElementById('search-path').value = path; |
|
||||
closeModal(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,192 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html lang="zh-CN"> |
|
||||
<head> |
|
||||
<meta charset="UTF-8"> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
||||
<title>蜗牛创造的高速文件传输</title> |
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> |
|
||||
<script src="{{ url_for('static', filename='js/socket.io.min.js') }}"></script> |
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script> |
|
||||
</head> |
|
||||
<body> |
|
||||
<div class="container"> |
|
||||
<!-- 欢迎界面 --> |
|
||||
<div id="welcome-screen" class="screen active"> |
|
||||
<div class="logo-area"> |
|
||||
<h1>🐌 欢迎来到蜗牛创造的高速世界</h1> |
|
||||
<p>安全、高速的文件传输解决方案</p> |
|
||||
</div> |
|
||||
|
|
||||
<div class="card-container"> |
|
||||
<div class="card"> |
|
||||
<div class="card-icon">📤</div> |
|
||||
<h3>发送文件</h3> |
|
||||
<p>快速安全地发送文件</p> |
|
||||
<button class="btn btn-primary" onclick="showScreen('send-screen')">开始发送</button> |
|
||||
</div> |
|
||||
|
|
||||
<div class="card"> |
|
||||
<div class="card-icon">📥</div> |
|
||||
<h3>接收文件</h3> |
|
||||
<p>安全可靠地接收文件</p> |
|
||||
<button class="btn btn-success" onclick="showScreen('receive-screen')">开始接收</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="footer"> |
|
||||
<p>© 2024 蜗牛创造 - 让文件传输更简单</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<!-- 发送界面 --> |
|
||||
<div id="send-screen" class="screen"> |
|
||||
<div class="header"> |
|
||||
<button class="btn btn-secondary" onclick="showScreen('welcome-screen')">← 返回</button> |
|
||||
<h2>📤 文件发送</h2> |
|
||||
</div> |
|
||||
|
|
||||
<div class="card"> |
|
||||
<div class="input-group"> |
|
||||
<label>🔑 密钥:</label> |
|
||||
<input type="text" id="send-key" placeholder="token|sk|psk|sern"> |
|
||||
</div> |
|
||||
|
|
||||
<div class="input-group"> |
|
||||
<label>🔍 文件搜索:</label> |
|
||||
<input type="text" id="search-pattern" placeholder="输入文件名关键词"> |
|
||||
<button class="btn btn-purple" onclick="openSearchModal()">搜索文件</button> |
|
||||
</div> |
|
||||
|
|
||||
<div class="input-group"> |
|
||||
<label>📄 文件路径:</label> |
|
||||
<input type="text" value="/mnt/wwn-0x5000c500a34676a5-part1/docker/kodbox/data/files/下载/" id="send-file-path" placeholder="手动输入文件路径或从搜索结果选择"> |
|
||||
<button class="btn btn-small" onclick="browseFileManually()">选择要发送的文件</button> |
|
||||
</div> |
|
||||
|
|
||||
<div class="file-info" id="file-info"> |
|
||||
<div class="file-info-item">文件大小: <span id="file-size">-</span></div> |
|
||||
<div class="file-info-item">修改时间: <span id="file-mtime">-</span></div> |
|
||||
<div class="file-info-item">文件状态: <span id="file-status">未选择</span></div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="button-group"> |
|
||||
<button class="btn btn-success" id="send-connect-btn" onclick="sendConnect()">🔄 连接</button> |
|
||||
<button class="btn btn-danger" id="send-file-btn" onclick="sendFile()" disabled>🚀 发送文件</button> |
|
||||
<button class="btn btn-secondary" id="send-stop-btn" onclick="sendStop()" disabled>⏹️ 停止</button> |
|
||||
</div> |
|
||||
|
|
||||
<div class="status"> |
|
||||
<p>📊 状态: <span id="send-status">等待连接</span></p> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="card"> |
|
||||
<h3>📋 发送日志:</h3> |
|
||||
<div class="output-container"> |
|
||||
<pre id="send-output">等待输入密钥并选择文件... |
|
||||
密钥格式应为: token|sk|psk|sern</pre> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<!-- 接收界面 --> |
|
||||
<div id="receive-screen" class="screen"> |
|
||||
<div class="header"> |
|
||||
<button class="btn btn-secondary" onclick="showScreen('welcome-screen')">← 返回</button> |
|
||||
<h2>📥 文件接收</h2> |
|
||||
</div> |
|
||||
|
|
||||
<div class="card"> |
|
||||
<div class="input-group"> |
|
||||
<label>🔑 密钥:</label> |
|
||||
<input type="text" id="receive-key" placeholder="token|sk|psk|sern"> |
|
||||
</div> |
|
||||
|
|
||||
<div class="input-group"> |
|
||||
<label>💾 保存路径:</label> |
|
||||
<input type="text" id="receive-save-path" style="background-color: #eee;color: #333;" readonly value="文件保持在:网络挂载/本地存储/下载/"> |
|
||||
<!-- <button class="btn btn-purple" onclick="browseFolder()">浏览文件夹</button> --> |
|
||||
</div> |
|
||||
|
|
||||
<div class="button-group"> |
|
||||
<button class="btn btn-success" id="receive-connect-btn" onclick="receiveConnect()">🔄 连接</button> |
|
||||
<button class="btn btn-danger" id="receive-stop-btn" onclick="receiveStop()" disabled>⏹️ 停止</button> |
|
||||
</div> |
|
||||
|
|
||||
<div class="status"> |
|
||||
<p>📊 状态: <span id="receive-status">等待连接</span></p> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="card"> |
|
||||
<h3>📋 接收日志:</h3> |
|
||||
<div class="output-container"> |
|
||||
<pre id="receive-output">等待输入密钥并连接... |
|
||||
密钥格式应为: token|sk|psk|sern |
|
||||
文件将保存到: 网络挂载/本地存储/下载/</pre> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<!-- 文件浏览模态框 --> |
|
||||
<div id="file-browser-modal" class="modal" style="z-index:2000"> |
|
||||
<div class="modal-content"> |
|
||||
<div class="modal-header"> |
|
||||
<h3 id="modal-title">选择文件</h3> |
|
||||
<span class="close" onclick="closeModal()">×</span> |
|
||||
</div> |
|
||||
<div class="modal-body"> |
|
||||
<div class="path-navigation"> |
|
||||
<button class="btn btn-small" onclick="navigateUp()">↑ 上级目录</button> |
|
||||
<span id="current-path"></span> |
|
||||
</div> |
|
||||
<div id="file-list" class="file-list"></div> |
|
||||
</div> |
|
||||
<div class="modal-footer"> |
|
||||
<button class="btn btn-secondary" onclick="closeModal()">取消</button> |
|
||||
<button class="btn btn-primary" id="modal-select-btn" onclick="selectItem()">选择</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<!-- 文件搜索模态框 --> |
|
||||
<div id="file-search-modal" class="modal"> |
|
||||
<div class="modal-content"> |
|
||||
<div class="modal-header"> |
|
||||
<h3 id="search-modal-title">搜索文件</h3> |
|
||||
<span class="close" onclick="closeSearchModal()">×</span> |
|
||||
</div> |
|
||||
<div class="modal-body"> |
|
||||
<div class="search-controls"> |
|
||||
<div class="input-group"> |
|
||||
<label>搜索路径:</label> |
|
||||
<input type="text" id="search-path"> |
|
||||
<button class="btn btn-small" onclick="browseSearchPath()">选择搜索目录</button> |
|
||||
</div> |
|
||||
<div class="input-group"> |
|
||||
<label>文件名包含:</label> |
|
||||
<input type="text" id="search-keyword" placeholder="默认*则展示该目录下所有文件/输入文件名关键词"> |
|
||||
</div> |
|
||||
<div class="input-group"> |
|
||||
<label>最大结果数:</label> |
|
||||
<input type="number" id="max-results" value="50" min="1" max="500"> |
|
||||
</div> |
|
||||
<button class="btn btn-primary" onclick="startSearch()">开始搜索</button> |
|
||||
</div> |
|
||||
|
|
||||
<div class="search-results"> |
|
||||
<h4>搜索结果 (<span id="result-count">0</span>):</h4> |
|
||||
<div id="search-results-list" class="file-list"></div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div class="modal-footer"> |
|
||||
<button class="btn btn-secondary" onclick="closeSearchModal()">取消</button> |
|
||||
<button class="btn btn-primary" id="search-select-btn" onclick="selectSearchResult()" disabled>选择文件</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<script src="{{ url_for('static', filename='js/socket.io.min.js') }}"></script> |
|
||||
<script src="{{ url_for('static', filename='js/script.js') }}"></script> |
|
||||
</body> |
|
||||
</html> |
|
||||
Loading…
Reference in new issue