547 lines
18 KiB
JavaScript
547 lines
18 KiB
JavaScript
// 文件传输服务 - 前端JavaScript
|
||
|
||
class FileShareApp {
|
||
constructor() {
|
||
this.baseURL = window.location.origin;
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.setupEventListeners();
|
||
this.setupTabSwitching();
|
||
this.setupDragAndDrop();
|
||
}
|
||
|
||
// 设置事件监听器
|
||
setupEventListeners() {
|
||
// 文件选择
|
||
const fileInput = document.getElementById('fileInput');
|
||
const selectBtn = document.querySelector('.select-btn');
|
||
|
||
selectBtn?.addEventListener('click', () => fileInput?.click());
|
||
fileInput?.addEventListener('change', (e) => this.handleFileSelect(e.target.files[0]));
|
||
|
||
// 文本分享
|
||
const shareTextBtn = document.getElementById('shareTextBtn');
|
||
shareTextBtn?.addEventListener('click', () => this.handleTextShare());
|
||
|
||
// 下载文件
|
||
const downloadBtn = document.getElementById('downloadBtn');
|
||
const downloadCode = document.getElementById('downloadCode');
|
||
|
||
downloadBtn?.addEventListener('click', () => this.handleDownloadInfo());
|
||
downloadCode?.addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') this.handleDownloadInfo();
|
||
});
|
||
|
||
// 重置按钮
|
||
window.resetUpload = () => this.resetUploadSection();
|
||
window.resetText = () => this.resetTextSection();
|
||
|
||
// 复制功能
|
||
window.copyCode = (elementId) => this.copyToClipboard(elementId);
|
||
window.copyText = (text) => this.copyTextToClipboard(text);
|
||
}
|
||
|
||
// 设置选项卡切换
|
||
setupTabSwitching() {
|
||
const tabBtns = document.querySelectorAll('.tab-btn');
|
||
const tabContents = document.querySelectorAll('.tab-content');
|
||
|
||
tabBtns.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const targetTab = btn.getAttribute('data-tab');
|
||
|
||
// 更新按钮状态
|
||
tabBtns.forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
|
||
// 更新内容显示
|
||
tabContents.forEach(content => {
|
||
content.classList.remove('active');
|
||
});
|
||
|
||
const targetContent = document.getElementById(`${targetTab}-tab`);
|
||
if (targetContent) {
|
||
targetContent.classList.add('active');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 设置拖拽上传
|
||
setupDragAndDrop() {
|
||
const dropZone = document.getElementById('dropZone');
|
||
|
||
if (!dropZone) return;
|
||
|
||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||
dropZone.addEventListener(eventName, this.preventDefaults, false);
|
||
});
|
||
|
||
['dragenter', 'dragover'].forEach(eventName => {
|
||
dropZone.addEventListener(eventName, () => dropZone.classList.add('dragover'), false);
|
||
});
|
||
|
||
['dragleave', 'drop'].forEach(eventName => {
|
||
dropZone.addEventListener(eventName, () => dropZone.classList.remove('dragover'), false);
|
||
});
|
||
|
||
dropZone.addEventListener('drop', (e) => {
|
||
const files = e.dataTransfer.files;
|
||
if (files.length > 0) {
|
||
this.handleFileSelect(files[0]);
|
||
}
|
||
}, false);
|
||
}
|
||
|
||
preventDefaults(e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
|
||
// 处理文件选择
|
||
async handleFileSelect(file) {
|
||
if (!file) return;
|
||
|
||
// 检查文件大小
|
||
const maxSize = 100 * 1024 * 1024; // 100MB
|
||
if (file.size > maxSize) {
|
||
this.showToast('文件太大,最大支持100MB', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 显示进度界面
|
||
this.showProgress();
|
||
|
||
// 创建FormData
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
// 上传文件
|
||
const response = await fetch(`${this.baseURL}/api/upload`, {
|
||
method: 'POST',
|
||
body: formData,
|
||
onUploadProgress: (progressEvent) => {
|
||
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||
this.updateProgress(percentCompleted);
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`上传失败: ${response.statusText}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
this.showUploadResult(result);
|
||
this.showToast('文件上传成功!', 'success');
|
||
|
||
} catch (error) {
|
||
console.error('Upload error:', error);
|
||
this.showToast(error.message || '上传失败', 'error');
|
||
this.hideProgress();
|
||
}
|
||
}
|
||
|
||
// 处理文本分享
|
||
async handleTextShare() {
|
||
const content = document.getElementById('textContent').value.trim();
|
||
const filename = document.getElementById('textFilename').value.trim() || 'shared_text.txt';
|
||
|
||
if (!content) {
|
||
this.showToast('请输入要分享的文本内容', 'warning');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const shareBtn = document.getElementById('shareTextBtn');
|
||
shareBtn.classList.add('loading');
|
||
shareBtn.disabled = true;
|
||
|
||
const response = await fetch(`${this.baseURL}/api/share-text`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
content: content,
|
||
filename: filename
|
||
})
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`分享失败: ${response.statusText}`);
|
||
}
|
||
|
||
const result = await response.json();
|
||
this.showTextResult(result);
|
||
this.showToast('文本分享成功!', 'success');
|
||
|
||
} catch (error) {
|
||
console.error('Text share error:', error);
|
||
this.showToast(error.message || '分享失败', 'error');
|
||
} finally {
|
||
const shareBtn = document.getElementById('shareTextBtn');
|
||
shareBtn.classList.remove('loading');
|
||
shareBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// 处理下载信息获取
|
||
async handleDownloadInfo() {
|
||
const code = document.getElementById('downloadCode').value.trim().toUpperCase();
|
||
|
||
if (!code) {
|
||
this.showToast('请输入分享码', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (code.length !== 8) {
|
||
this.showToast('分享码格式错误,应为8位字符', 'warning');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const downloadBtn = document.getElementById('downloadBtn');
|
||
downloadBtn.classList.add('loading');
|
||
downloadBtn.disabled = true;
|
||
|
||
const response = await fetch(`${this.baseURL}/api/info/${code}`);
|
||
|
||
if (response.status === 404) {
|
||
throw new Error('分享码不存在');
|
||
}
|
||
|
||
if (response.status === 410) {
|
||
throw new Error('分享已过期');
|
||
}
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`获取信息失败: ${response.statusText}`);
|
||
}
|
||
|
||
const fileInfo = await response.json();
|
||
this.showFileInfo(fileInfo);
|
||
|
||
} catch (error) {
|
||
console.error('Download info error:', error);
|
||
this.showToast(error.message || '获取文件信息失败', 'error');
|
||
this.hideFileInfo();
|
||
} finally {
|
||
const downloadBtn = document.getElementById('downloadBtn');
|
||
downloadBtn.classList.remove('loading');
|
||
downloadBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// 处理文件下载
|
||
handleFileDownload(code) {
|
||
const downloadUrl = `${this.baseURL}/api/download/${code}`;
|
||
|
||
// 创建隐藏的下载链接
|
||
const link = document.createElement('a');
|
||
link.href = downloadUrl;
|
||
link.style.display = 'none';
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
|
||
this.showToast('开始下载文件...', 'success');
|
||
}
|
||
|
||
// 显示上传进度
|
||
showProgress() {
|
||
const progressSection = document.getElementById('progressSection');
|
||
const uploadResult = document.getElementById('uploadResult');
|
||
const dropZone = document.getElementById('dropZone');
|
||
|
||
dropZone.style.display = 'none';
|
||
uploadResult.style.display = 'none';
|
||
progressSection.style.display = 'block';
|
||
|
||
this.updateProgress(0);
|
||
}
|
||
|
||
// 更新进度条
|
||
updateProgress(percent) {
|
||
const progressFill = document.getElementById('progressFill');
|
||
const progressText = document.getElementById('progressText');
|
||
|
||
if (progressFill) {
|
||
progressFill.style.width = `${percent}%`;
|
||
}
|
||
|
||
if (progressText) {
|
||
progressText.textContent = `上传中... ${percent}%`;
|
||
}
|
||
}
|
||
|
||
// 隐藏进度条
|
||
hideProgress() {
|
||
const progressSection = document.getElementById('progressSection');
|
||
const dropZone = document.getElementById('dropZone');
|
||
|
||
progressSection.style.display = 'none';
|
||
dropZone.style.display = 'block';
|
||
}
|
||
|
||
// 显示上传结果
|
||
showUploadResult(result) {
|
||
const progressSection = document.getElementById('progressSection');
|
||
const uploadResult = document.getElementById('uploadResult');
|
||
const uploadCode = document.getElementById('uploadCode');
|
||
const uploadLink = document.getElementById('uploadLink');
|
||
const uploadExpire = document.getElementById('uploadExpire');
|
||
|
||
progressSection.style.display = 'none';
|
||
uploadResult.style.display = 'block';
|
||
|
||
uploadCode.textContent = result.code;
|
||
uploadLink.textContent = `${this.baseURL}${result.download_url}`;
|
||
|
||
const expireTime = new Date(result.expires_at);
|
||
const now = new Date();
|
||
const diffMinutes = Math.ceil((expireTime - now) / (1000 * 60));
|
||
uploadExpire.textContent = `${diffMinutes}分钟后过期`;
|
||
|
||
// 设置下载按钮
|
||
const downloadFileBtn = document.getElementById('downloadFileBtn');
|
||
if (downloadFileBtn) {
|
||
downloadFileBtn.onclick = () => this.handleFileDownload(result.code);
|
||
}
|
||
}
|
||
|
||
// 显示文本分享结果
|
||
showTextResult(result) {
|
||
const textResult = document.getElementById('textResult');
|
||
const textCode = document.getElementById('textCode');
|
||
const textLink = document.getElementById('textLink');
|
||
const textExpire = document.getElementById('textExpire');
|
||
|
||
textResult.style.display = 'block';
|
||
|
||
textCode.textContent = result.code;
|
||
textLink.textContent = `${this.baseURL}${result.download_url}`;
|
||
|
||
const expireTime = new Date(result.expires_at);
|
||
const now = new Date();
|
||
const diffMinutes = Math.ceil((expireTime - now) / (1000 * 60));
|
||
textExpire.textContent = `${diffMinutes}分钟后过期`;
|
||
|
||
// 隐藏输入区域
|
||
document.querySelector('.text-input-area').style.display = 'none';
|
||
}
|
||
|
||
// 显示文件信息
|
||
showFileInfo(fileInfo) {
|
||
const fileInfoDiv = document.getElementById('fileInfo');
|
||
const fileName = document.getElementById('fileName');
|
||
const fileSize = document.getElementById('fileSize');
|
||
const fileType = document.getElementById('fileType');
|
||
const fileExpire = document.getElementById('fileExpire');
|
||
const downloadFileBtn = document.getElementById('downloadFileBtn');
|
||
|
||
fileInfoDiv.style.display = 'block';
|
||
|
||
fileName.textContent = fileInfo.filename;
|
||
fileSize.textContent = this.formatFileSize(fileInfo.size);
|
||
fileType.textContent = fileInfo.file_type;
|
||
|
||
if (fileInfo.is_expired) {
|
||
fileExpire.textContent = '已过期';
|
||
fileExpire.style.color = 'var(--error-color)';
|
||
downloadFileBtn.disabled = true;
|
||
downloadFileBtn.textContent = '文件已过期';
|
||
} else {
|
||
const expireTime = new Date(fileInfo.expires_at);
|
||
const now = new Date();
|
||
const diffMinutes = Math.ceil((expireTime - now) / (1000 * 60));
|
||
fileExpire.textContent = `剩余时间: ${diffMinutes}分钟`;
|
||
fileExpire.style.color = 'var(--warning-color)';
|
||
downloadFileBtn.disabled = false;
|
||
downloadFileBtn.textContent = '⬇️ 下载文件';
|
||
downloadFileBtn.onclick = () => this.handleFileDownload(fileInfo.code);
|
||
}
|
||
}
|
||
|
||
// 隐藏文件信息
|
||
hideFileInfo() {
|
||
const fileInfoDiv = document.getElementById('fileInfo');
|
||
fileInfoDiv.style.display = 'none';
|
||
}
|
||
|
||
// 重置上传区域
|
||
resetUploadSection() {
|
||
const dropZone = document.getElementById('dropZone');
|
||
const progressSection = document.getElementById('progressSection');
|
||
const uploadResult = document.getElementById('uploadResult');
|
||
const fileInput = document.getElementById('fileInput');
|
||
|
||
dropZone.style.display = 'block';
|
||
progressSection.style.display = 'none';
|
||
uploadResult.style.display = 'none';
|
||
|
||
if (fileInput) {
|
||
fileInput.value = '';
|
||
}
|
||
}
|
||
|
||
// 重置文本区域
|
||
resetTextSection() {
|
||
const textResult = document.getElementById('textResult');
|
||
const textInputArea = document.querySelector('.text-input-area');
|
||
const textContent = document.getElementById('textContent');
|
||
const textFilename = document.getElementById('textFilename');
|
||
|
||
textResult.style.display = 'none';
|
||
textInputArea.style.display = 'block';
|
||
|
||
textContent.value = '';
|
||
textFilename.value = 'shared_text.txt';
|
||
}
|
||
|
||
// 复制到剪贴板
|
||
async copyToClipboard(elementId) {
|
||
const element = document.getElementById(elementId);
|
||
if (!element) return;
|
||
|
||
const text = element.textContent;
|
||
await this.copyTextToClipboard(text);
|
||
}
|
||
|
||
// 复制文本到剪贴板
|
||
async copyTextToClipboard(text) {
|
||
try {
|
||
await navigator.clipboard.writeText(text);
|
||
this.showToast('已复制到剪贴板', 'success');
|
||
} catch (err) {
|
||
// 降级处理
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = text;
|
||
textArea.style.position = 'fixed';
|
||
textArea.style.opacity = '0';
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
|
||
try {
|
||
document.execCommand('copy');
|
||
this.showToast('已复制到剪贴板', 'success');
|
||
} catch (err) {
|
||
this.showToast('复制失败', 'error');
|
||
}
|
||
|
||
document.body.removeChild(textArea);
|
||
}
|
||
}
|
||
|
||
// 格式化文件大小
|
||
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(1)) + ' ' + sizes[i];
|
||
}
|
||
|
||
// 显示通知
|
||
showToast(message, type = 'success') {
|
||
const toast = document.getElementById('toast');
|
||
const toastMessage = document.getElementById('toastMessage');
|
||
|
||
if (!toast || !toastMessage) return;
|
||
|
||
// 清除之前的类
|
||
toast.className = 'toast';
|
||
|
||
// 添加类型类
|
||
if (type !== 'success') {
|
||
toast.classList.add(type);
|
||
}
|
||
|
||
toastMessage.textContent = message;
|
||
toast.style.display = 'block';
|
||
|
||
// 3秒后自动隐藏
|
||
setTimeout(() => {
|
||
toast.style.display = 'none';
|
||
}, 3000);
|
||
}
|
||
|
||
// 格式化时间
|
||
formatTime(dateString) {
|
||
const date = new Date(dateString);
|
||
return date.toLocaleString('zh-CN');
|
||
}
|
||
|
||
// 计算剩余时间
|
||
calculateRemainingTime(expireTime) {
|
||
const now = new Date();
|
||
const expire = new Date(expireTime);
|
||
const diffMs = expire - now;
|
||
|
||
if (diffMs <= 0) {
|
||
return '已过期';
|
||
}
|
||
|
||
const diffMinutes = Math.ceil(diffMs / (1000 * 60));
|
||
|
||
if (diffMinutes > 60) {
|
||
const hours = Math.floor(diffMinutes / 60);
|
||
const minutes = diffMinutes % 60;
|
||
return `${hours}小时${minutes}分钟`;
|
||
}
|
||
|
||
return `${diffMinutes}分钟`;
|
||
}
|
||
}
|
||
|
||
// 页面加载完成后初始化应用
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
new FileShareApp();
|
||
});
|
||
|
||
// 工具函数:检测移动设备
|
||
function isMobile() {
|
||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||
}
|
||
|
||
// 工具函数:检测是否支持文件API
|
||
function supportsFileAPI() {
|
||
return window.File && window.FileReader && window.FileList && window.Blob;
|
||
}
|
||
|
||
// 工具函数:检测是否支持拖拽
|
||
function supportsDragAndDrop() {
|
||
const div = document.createElement('div');
|
||
return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) &&
|
||
'FormData' in window &&
|
||
'FileReader' in window;
|
||
}
|
||
|
||
// 页面可见性变化时的处理
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (!document.hidden) {
|
||
// 页面重新可见时,可以刷新一些数据
|
||
console.log('页面重新可见');
|
||
}
|
||
});
|
||
|
||
// 全局错误处理
|
||
window.addEventListener('error', (e) => {
|
||
console.error('全局错误:', e.error);
|
||
});
|
||
|
||
// 未处理的Promise错误
|
||
window.addEventListener('unhandledrejection', (e) => {
|
||
console.error('未处理的Promise错误:', e.reason);
|
||
});
|
||
|
||
// 导出工具函数供其他脚本使用
|
||
window.FileShareUtils = {
|
||
isMobile,
|
||
supportsFileAPI,
|
||
supportsDragAndDrop
|
||
}; |