UNPKG

fairy-webuploader

Version:

518 lines (441 loc) 20 kB
import AbstractTemplate from "./AbstractTemplate.js"; export default class CyberpunkFileTemplate extends AbstractTemplate{ constructor(el, options) { super(el, options); } initStyles(styleId) { this.brandColor = '#4CAF50'; this.isDarkMode = true; const style = document.createElement('style'); style.id = styleId; style.textContent = ` @import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap'); .file-uploader-container { position: relative; width: 100%; max-width: 100%; padding: 40px 0 ; border-radius: 12px; backdrop-filter: blur(8px); background: linear-gradient(135deg, #0A0F2B, #1A1E3A); box-shadow: 0 0 12px rgba(0, 195, 255, 0.3); text-align: center; transition: all 0.3s ease; font-family: 'Fira Code', monospace; } .file-uploader-container.drag-active { box-shadow: 0 0 24px rgba(0, 195, 255, 0.6); } .file-uploader-container.drag-active::before { content: ""; position: absolute; top: -2px; left: -2px; right: -2px; bottom: -2px; border-radius: 12px; background: linear-gradient(45deg, #00FFE0, #0078FF); animation: data-flow 2s linear infinite; z-index: -1; } @keyframes data-flow { 0% { background-position: 0 0; } 100% { background-position: 100% 100%; } } .upload-icon { width: 80px; height: 80px; fill: #00FFE0; filter: drop-shadow(0 0 8px #00FFE0); transition: all 0.3s ease; } .file-uploader-container:hover .upload-icon { animation: breathing 1.5s ease-in-out infinite; } @keyframes breathing { 0% { filter: drop-shadow(0 0 8px #00FFE0); } 50% { filter: drop-shadow(0 0 16px #00FFE0); } 100% { filter: drop-shadow(0 0 8px #00FFE0); } } .upload-text { color: #FFFFFF; font-size: 16px; margin-top: 16px; } .upload-text span { color: #00FFE0; cursor: pointer; } .file-list { margin-top: 20px; } .file-item { display: flex; align-items: center; padding: 8px; margin-top: 8px; background: rgba(255, 255, 255, 0.05); border-radius: 4px; } .file-icon { width: 24px; height: 24px; margin-right: 8px; } .file-name { flex: 1; color: #FFFFFF; font-size: 14px; overflow: hidden; text-overflow: ellipsis; } .progress-bar { position: absolute; height: 100%; background: rgba(255, 255, 255, 0.1); border-radius: 2px; overflow: hidden; width: 100%; left: 0; top: 0; } .progress { height: 100%; background: rgba(0,255,224,0.5); box-shadow: 0 0 8px #00FFE0; width: 0%; position: relative; animation: glitch 2s infinite; transition: width 0.3s ease } @keyframes glitch { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(2px); } 50% { transform: translateX(-2px); } 75% { transform: translateX(1px); } } .progress-percentage { color: #00FFE0; font-size: 12px; margin-top: 4px; } .success-icon { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0); width: 64px; height: 64px; fill: #00FFE0; opacity: 0; transition: all 0.3s ease; } .success-icon.show { transform: translate(-50%, -50%) scale(1); opacity: 1; animation: hologram 1s ease-out forwards; } @keyframes hologram { 0% { transform: translate(-50%, -50%) scale(1); opacity: 1; } 100% { transform: translate(-50%, -50%) scale(1.5); opacity: 0; } } .error-state { background: rgba(255, 62, 62, 0.1); border: 1px solid #FF3E3E; animation: alert 1s infinite; } .error-text { color: #FF3E3E; font-size: 12px; margin-top: 8px; } @keyframes alert { 0%, 100% { box-shadow: 0 0 8px #FF3E3E; } 50% { box-shadow: 0 0 16px #FF3E3E; } } .mode-toggle { position: fixed; top: 20px; right: 20px; background: rgba(255, 255, 255, 0.1); border: none; border-radius: 4px; padding: 8px 16px; color: #FFFFFF; font-family: 'Fira Code', monospace; cursor: pointer; transition: all 0.3s ease; } .mode-toggle:hover { background: rgba(255, 255, 255, 0.2); } .upload-preview { margin-top: 20px; padding: 16px; background: rgba(255, 255, 255, 0.05); border-radius: 8px; } .preview-item { display: flex; align-items: center; margin-bottom: 12px; padding: 8px; background: rgba(255, 255, 255, 0.02); border-radius: 4px; } .file-icon { width: 24px; height: 24px; margin-right: 12px; } .file-name { flex: 1; color: #FFFFFF; font-size: 14px; margin-right: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .upload-complete { background: rgba(0, 255, 224, 0.05); border: 1px solid #00FFE0; animation: complete-pulse 1s ease-in-out; } @keyframes complete-pulse { 0% { box-shadow: 0 0 8px rgba(0, 255, 224, 0.3); } 50% { box-shadow: 0 0 16px rgba(0, 255, 224, 0.6); } 100% { box-shadow: 0 0 8px rgba(0, 255, 224, 0.3); } } `; document.head.appendChild(style); } createUploader() { this.uploaderContainer = document.createElement('div'); this.uploaderContainer.className = 'file-uploader-container'; this.uploaderContainer.id = AbstractTemplate.uploaderContainerId this.uploadIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.uploadIcon.classList.add('upload-icon'); this.uploadIcon.setAttribute('viewBox', '0 0 24 24'); this.uploadIcon.innerHTML = ` <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" stroke="#00FFE0" stroke-width="2" fill="none"/> `; this.uploadText = document.createElement('div'); this.uploadText.className = 'upload-text'; this.uploadText.innerHTML = '拖拽文件到这儿 或者 <span>【浏览文件】</span>'; this.fileInput = document.createElement('input'); this.fileInput.id = AbstractTemplate.uploaderInputId; this.fileInput.type = 'file'; this.fileInput.multiple = this.options?.multiple || true; this.fileInput.accept = this.options?.accept || '*'; this.fileInput.style.display = 'none'; this.fileList = document.createElement('div'); this.fileList.className = 'file-list'; this.successIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.successIcon.classList.add('success-icon'); this.successIcon.setAttribute('viewBox', '0 0 24 24'); this.successIcon.innerHTML = ` <path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" fill="#00FFE0"/> `; this.uploaderContainer.appendChild(this.uploadIcon); this.uploaderContainer.appendChild(this.uploadText); this.uploaderContainer.appendChild(this.fileInput); this.uploaderContainer.appendChild(this.fileList); this.uploaderContainer.appendChild(this.successIcon); this.container.appendChild(this.uploaderContainer); // 模式切换按钮 this.modeToggle = document.querySelector('.mode-toggle'); if (this.modeToggle) { this.modeToggle.addEventListener('click', () => this.toggleMode()); } } setupEventListeners() { this.uploadText.querySelector('span').addEventListener('click', () => { this.fileInput.click(); }); this.fileInput.addEventListener('change', (e) => { this.handleFileSelect(e.target.files); }); this.uploaderContainer.addEventListener('dragover', (e) => { e.preventDefault(); this.uploaderContainer.classList.add('drag-active'); }); this.uploaderContainer.addEventListener('dragleave', () => { this.uploaderContainer.classList.remove('drag-active'); }); this.uploaderContainer.addEventListener('drop', (e) => { e.preventDefault(); this.uploaderContainer.classList.remove('drag-active'); this.handleFileSelect(e.dataTransfer.files); }); // 快捷键支持 document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'u') { this.fileInput.click(); } }); } handleFiles(files) { Array.from(files).forEach(file => { const fileItem = this.createFileItem(file); this.fileList.appendChild(fileItem); this.simulateUpload(fileItem); }); } createFileItem(file) { const fileItem = document.createElement('div'); fileItem.className = 'file-item'; const fileIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); fileIcon.classList.add('file-icon'); fileIcon.setAttribute('viewBox', '0 0 24 24'); fileIcon.innerHTML = ` <path d="M13 9V3H6v18h12V9h-5zm3 4h-4v4h-2v-4H8l4-4 4 4z" fill="#FFFFFF"/> `; const fileName = document.createElement('div'); fileName.className = 'file-name'; fileName.textContent = file.name; const progressBar = document.createElement('div'); progressBar.className = 'progress-bar'; const progress = document.createElement('div'); progress.className = 'progress'; const progressPercentage = document.createElement('div'); progressPercentage.className = 'progress-percentage'; progressPercentage.textContent = '0%'; progressBar.appendChild(progress); fileItem.appendChild(fileIcon); fileItem.appendChild(fileName); fileItem.appendChild(progressBar); fileItem.appendChild(progressPercentage); return fileItem; } simulateUpload(fileItem) { const progress = fileItem.querySelector('.progress'); const progressPercentage = fileItem.querySelector('.progress-percentage'); let percentage = 0; const interval = setInterval(() => { percentage += Math.random() * 10; if (percentage >= 100) { percentage = 100; clearInterval(interval); this.showSuccess(); } progress.style.width = `${percentage}%`; progressPercentage.textContent = `${Math.round(percentage)}%`; }, 500); } showSuccess() { this.successIcon.classList.add('show'); setTimeout(() => { this.successIcon.classList.remove('show'); }, 1000); } toggleMode() { this.isDarkMode = !this.isDarkMode; document.body.classList.toggle('light-mode', !this.isDarkMode); this.modeToggle.textContent = this.isDarkMode ? '切换亮色模式' : '切换深色模式'; } createUploadPreview(){ const exitsPreview = this.uploaderContainer.querySelector('#upload-preview') const preview = exitsPreview || document.createElement('div'); if (!exitsPreview){ preview.className = 'upload-preview'; preview.id = 'upload-preview'; } const exitsPreviewWrapper = this.uploaderContainer.querySelector('#preview-wrapper') const previewWrapper = exitsPreviewWrapper || document.createElement('div'); if (!exitsPreviewWrapper){ previewWrapper.className = 'preview-wrapper'; previewWrapper.id = 'preview-wrapper'; } this.tempFiles.forEach((file,index) => { const previewItem = document.createElement('div'); previewItem.className = 'preview-item'; previewItem.id = `preview-item-${file.uuid}`; previewItem.style.position = 'relative'; // 文件图标 const fileIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); fileIcon.classList.add('file-icon'); fileIcon.setAttribute('viewBox', '0 0 24 24'); fileIcon.innerHTML = ` <path d="M13 9V3H6v18h12V9h-5zm3 4h-4v4h-2v-4H8l4-4 4 4z" fill="#00FFE0"/> `; // 文件名 const fileName = document.createElement('div'); fileName.className = 'file-name'; fileName.textContent = file.name; // 进度条容器 const progress = document.createElement('div'); progress.className = 'progress-bar'; // 进度条 const progressInner = document.createElement('div'); progressInner.className = 'progress'; progressInner.id = `progress-${file.uuid}`; // 进度百分比 const progressText = document.createElement('span'); progressText.className = 'progress-text'; progressText.id = `progressText-${file.uuid}`; progressText.textContent = '0%'; progressText.style.right = '0'; progressText.style.top = '-20px'; progressText.style.color = '#00FFE0'; progressText.style.fontSize = '12px'; progress.append(progressInner); previewItem.append(fileIcon, fileName, progress, progressText); previewWrapper.append(previewItem); }); preview.append(previewWrapper); this.uploaderContainer.append(preview); } // 新增方法:更新文件上传进度 updateProgress(uuid, percentage) { const progress = document.getElementById(`progress-${uuid}`); const progressText = document.getElementById(`progressText-${uuid}`); if (progress && progressText) { progress.style.width = `${percentage}%`; progressText.textContent = `${Math.round(percentage)}%`; // 如果上传完成,添加完成样式 if (percentage >= 100) { const previewItem = document.getElementById(`preview-item-${uuid}`); if (previewItem) { previewItem.classList.add('upload-complete'); } } } } }