fileshare/static/app.js
2025-08-10 12:57:17 +08:00

547 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 文件传输服务 - 前端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
};