补充主题静态资源与归档模板
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
/**
|
||||
* AKSRT Mini Upload — 小型文件上传器
|
||||
*
|
||||
* 提供拖拽上传、进度显示、预览功能。
|
||||
* 用于前台投稿、商品编辑等需要文件上传的页面。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 初始化小型上传器
|
||||
*
|
||||
* @param {HTMLElement} container 上传器容器
|
||||
* @param {Object} options 配置项
|
||||
*/
|
||||
function MiniUpload(container, options) {
|
||||
if (!container) return;
|
||||
|
||||
this.options = Object.assign({
|
||||
maxFileSize: 5 * 1024 * 1024, // 5MB
|
||||
allowedTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
||||
multiple: false,
|
||||
uploadUrl: '',
|
||||
fieldName: 'file',
|
||||
nonce: '',
|
||||
onSelect: null,
|
||||
onSuccess: null,
|
||||
onError: null,
|
||||
}, options || {});
|
||||
|
||||
this.container = container;
|
||||
this.files = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
MiniUpload.prototype.init = function () {
|
||||
var self = this;
|
||||
|
||||
// 创建文件输入
|
||||
this.input = document.createElement('input');
|
||||
this.input.type = 'file';
|
||||
this.input.accept = this.options.allowedTypes.join(',');
|
||||
this.input.multiple = this.options.multiple;
|
||||
this.input.style.display = 'none';
|
||||
this.container.appendChild(this.input);
|
||||
|
||||
// 创建拖拽区域
|
||||
this.dropzone = document.createElement('div');
|
||||
this.dropzone.className = 'aksrt-mini-upload__dropzone';
|
||||
this.dropzone.innerHTML =
|
||||
'<div class="aksrt-mini-upload__icon"><i class="fas fa-cloud-upload-alt"></i></div>' +
|
||||
'<div class="aksrt-mini-upload__text">点击或拖拽文件到此处上传</div>' +
|
||||
'<div class="aksrt-mini-upload__hint">支持 ' + this.options.allowedTypes.map(function (t) {
|
||||
return t.split('/')[1].toUpperCase();
|
||||
}).join(', ') + ',最大 ' + Math.round(this.options.maxFileSize / 1024 / 1024) + 'MB</div>';
|
||||
this.container.appendChild(this.dropzone);
|
||||
|
||||
// 创建预览区域
|
||||
this.preview = document.createElement('div');
|
||||
this.preview.className = 'aksrt-mini-upload__preview';
|
||||
this.container.appendChild(this.preview);
|
||||
|
||||
// 事件绑定
|
||||
this.dropzone.addEventListener('click', function () {
|
||||
self.input.click();
|
||||
});
|
||||
|
||||
this.dropzone.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
self.dropzone.classList.add('aksrt-mini-upload__dropzone--dragover');
|
||||
});
|
||||
|
||||
this.dropzone.addEventListener('dragleave', function () {
|
||||
self.dropzone.classList.remove('aksrt-mini-upload__dropzone--dragover');
|
||||
});
|
||||
|
||||
this.dropzone.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
self.dropzone.classList.remove('aksrt-mini-upload__dropzone--dragover');
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
self.handleFiles(e.dataTransfer.files);
|
||||
}
|
||||
});
|
||||
|
||||
this.input.addEventListener('change', function () {
|
||||
if (self.input.files.length > 0) {
|
||||
self.handleFiles(self.input.files);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
MiniUpload.prototype.handleFiles = function (fileList) {
|
||||
var self = this;
|
||||
var files = Array.prototype.slice.call(fileList);
|
||||
|
||||
files.forEach(function (file) {
|
||||
// 验证文件类型
|
||||
if (self.options.allowedTypes.length > 0 && self.options.allowedTypes.indexOf(file.type) === -1) {
|
||||
self.showError('不支持的文件类型: ' + file.type);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
if (file.size > self.options.maxFileSize) {
|
||||
self.showError('文件大小超过限制: ' + Math.round(file.size / 1024 / 1024) + 'MB');
|
||||
return;
|
||||
}
|
||||
|
||||
self.files.push(file);
|
||||
self.addPreview(file);
|
||||
|
||||
if (typeof self.options.onSelect === 'function') {
|
||||
self.options.onSelect(file);
|
||||
}
|
||||
|
||||
// 如果有上传 URL,自动上传
|
||||
if (self.options.uploadUrl) {
|
||||
self.uploadFile(file);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
MiniUpload.prototype.addPreview = function (file) {
|
||||
var self = this;
|
||||
var item = document.createElement('div');
|
||||
item.className = 'aksrt-mini-upload__item';
|
||||
|
||||
if (file.type.startsWith('image/')) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
item.innerHTML =
|
||||
'<img src="' + e.target.result + '" class="aksrt-mini-upload__thumb">' +
|
||||
'<div class="aksrt-mini-upload__info">' +
|
||||
'<span class="aksrt-mini-upload__name">' + file.name + '</span>' +
|
||||
'<span class="aksrt-mini-upload__size">' + self.formatSize(file.size) + '</span>' +
|
||||
'</div>' +
|
||||
'<button type="button" class="aksrt-mini-upload__remove"><i class="fas fa-times"></i></button>' +
|
||||
'<div class="aksrt-mini-upload__progress"><div class="aksrt-mini-upload__progress-bar"></div></div>';
|
||||
self.bindRemove(item, file);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
item.innerHTML =
|
||||
'<div class="aksrt-mini-upload__file-icon"><i class="fas fa-file"></i></div>' +
|
||||
'<div class="aksrt-mini-upload__info">' +
|
||||
'<span class="aksrt-mini-upload__name">' + file.name + '</span>' +
|
||||
'<span class="aksrt-mini-upload__size">' + self.formatSize(file.size) + '</span>' +
|
||||
'</div>' +
|
||||
'<button type="button" class="aksrt-mini-upload__remove"><i class="fas fa-times"></i></button>' +
|
||||
'<div class="aksrt-mini-upload__progress"><div class="aksrt-mini-upload__progress-bar"></div></div>';
|
||||
this.bindRemove(item, file);
|
||||
}
|
||||
|
||||
this.preview.appendChild(item);
|
||||
};
|
||||
|
||||
MiniUpload.prototype.bindRemove = function (item, file) {
|
||||
var self = this;
|
||||
var removeBtn = item.querySelector('.aksrt-mini-upload__remove');
|
||||
if (removeBtn) {
|
||||
removeBtn.addEventListener('click', function () {
|
||||
var index = self.files.indexOf(file);
|
||||
if (index > -1) self.files.splice(index, 1);
|
||||
item.remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
MiniUpload.prototype.uploadFile = function (file) {
|
||||
var self = this;
|
||||
var formData = new FormData();
|
||||
formData.append(this.options.fieldName, file);
|
||||
|
||||
if (this.options.nonce) {
|
||||
formData.append('_wpnonce', this.options.nonce);
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', this.options.uploadUrl, true);
|
||||
|
||||
// 进度更新
|
||||
var item = this.preview.lastElementChild;
|
||||
var progressBar = item ? item.querySelector('.aksrt-mini-upload__progress-bar') : null;
|
||||
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable && progressBar) {
|
||||
var percent = Math.round((e.loaded / e.total) * 100);
|
||||
progressBar.style.width = percent + '%';
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (typeof self.options.onSuccess === 'function') {
|
||||
self.options.onSuccess(response, file);
|
||||
}
|
||||
if (item) item.classList.add('aksrt-mini-upload__item--success');
|
||||
} catch (e) {
|
||||
self.showError('上传响应解析失败');
|
||||
}
|
||||
} else {
|
||||
self.showError('上传失败: HTTP ' + xhr.status);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
self.showError('网络错误,上传失败');
|
||||
if (item) item.classList.add('aksrt-mini-upload__item--error');
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
||||
MiniUpload.prototype.showError = function (msg) {
|
||||
if (typeof this.options.onError === 'function') {
|
||||
this.options.onError(msg);
|
||||
}
|
||||
console.error('[AKSRT MiniUpload]', msg);
|
||||
};
|
||||
|
||||
MiniUpload.prototype.formatSize = function (bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
};
|
||||
|
||||
MiniUpload.prototype.getFiles = function () {
|
||||
return this.files;
|
||||
};
|
||||
|
||||
MiniUpload.prototype.clear = function () {
|
||||
this.files = [];
|
||||
this.preview.innerHTML = '';
|
||||
this.input.value = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* 分片上传大文件
|
||||
* @param {File} file - 要上传的文件
|
||||
* @param {Object} opts - { chunkSize, uploadUrl, fieldName, nonce, onProgress, onComplete, onError }
|
||||
*/
|
||||
MiniUpload.prototype.uploadChunked = function (file, opts) {
|
||||
var chunkSize = (opts.chunkSize || 2) * 1024 * 1024; // default 2MB
|
||||
var totalChunks = Math.ceil(file.size / chunkSize);
|
||||
var currentChunk = 0;
|
||||
var fileId = file.name + '-' + file.size + '-' + file.lastModified;
|
||||
|
||||
function uploadNext() {
|
||||
if (currentChunk >= totalChunks) {
|
||||
if (opts.onComplete) opts.onComplete({ fileId: fileId, totalChunks: totalChunks });
|
||||
return;
|
||||
}
|
||||
|
||||
var start = currentChunk * chunkSize;
|
||||
var end = Math.min(start + chunkSize, file.size);
|
||||
var blob = file.slice(start, end);
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append(opts.fieldName || 'file', blob, file.name);
|
||||
formData.append('chunk', currentChunk);
|
||||
formData.append('total_chunks', totalChunks);
|
||||
formData.append('file_id', fileId);
|
||||
formData.append('file_name', file.name);
|
||||
formData.append('file_size', file.size);
|
||||
if (opts.nonce) formData.append('_ajax_nonce', opts.nonce);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', opts.uploadUrl, true);
|
||||
|
||||
xhr.upload.onprogress = function (e) {
|
||||
if (e.lengthComputable && opts.onProgress) {
|
||||
var chunkProgress = e.loaded / e.total;
|
||||
var totalProgress = (currentChunk + chunkProgress) / totalChunks;
|
||||
opts.onProgress(totalProgress, currentChunk, totalChunks);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
currentChunk++;
|
||||
uploadNext();
|
||||
} else {
|
||||
if (opts.onError) opts.onError('上传失败: HTTP ' + xhr.status);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
if (opts.onError) opts.onError('网络错误');
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
uploadNext();
|
||||
};
|
||||
|
||||
/**
|
||||
* 快速文件标识(替代 spark-md5,用文件名+大小+最后修改时间生成简易 hash)
|
||||
*/
|
||||
MiniUpload.prototype.getFileId = function (file) {
|
||||
return file.name + '-' + file.size + '-' + file.lastModified;
|
||||
};
|
||||
|
||||
// 自动初始化
|
||||
function autoInit() {
|
||||
document.querySelectorAll('[data-aksrt-mini-upload]').forEach(function (el) {
|
||||
if (el.dataset.miniUploadInit) return;
|
||||
el.dataset.miniUploadInit = '1';
|
||||
|
||||
var options = {};
|
||||
try {
|
||||
options = JSON.parse(el.dataset.aksrtMiniUpload || '{}');
|
||||
} catch (e) {}
|
||||
|
||||
new MiniUpload(el, options);
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', autoInit);
|
||||
} else {
|
||||
autoInit();
|
||||
}
|
||||
|
||||
// 暴露到全局
|
||||
window.AksrtMiniUpload = MiniUpload;
|
||||
})();
|
||||
Reference in New Issue
Block a user