fairy-webuploader
Version:
518 lines (441 loc) • 20 kB
JavaScript
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 = `
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;
}
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;
}
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
}
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;
}
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;
}
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;
}
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');
}
}
}
}
}