Files
wordpress-theme-aksrt/assets/js/mini-upload.js
T
kate522 5fd7c212ed fix(runtime): 按参考主题硬切换为 loader/main/auto_fun 单一 DOM 生命周期
- 删除 AksrtFrontend registry/rehydrate 双路架构及 build/public.js 拼包入口,
  前台唯一初始路径为 PHP 本地栈(jQuery 1.9.1 → Bootstrap 3.4.1)
  → loader_js(_win/aksrtData 唯一注入)→ tbquire(['main']) → runtime/main.min.js
- window.auto_fun = debounce(z_auto_fun, 100) 为唯一重扫入口;所有模块改为
  执行一次 + off/on('auto_fun.aksrt-*') 命名空间重扫,data/class 标记幂等
- 局部更新协议统一:HTML 注入 → post_ajax.ed → auto_fun();
  远程 modal → loaded.bs.modal → auto_fun();JSON 成功 → zib_ajax.success
- 灯箱恢复 Miniimgbox/imgbox 唯一 owner(imgbox-container、a[data-imgbox]、
  imgbox_data),删除 AksrtLightbox/data-aksrt-lightbox 第二实现及 CSS/overlay,
  _win.imgbox* 重新接上 post-display 设置
- 死代码清理:runtime 下 11 组被自有实现替代的旧模块副本、无消费 vendored 库、
  顶层 grade.min.js、revenue.js、无调用方的 aksrt_get_win_runtime_config(),
  以及 loader 中 16 条死 mapping
- PHP 入队改为本地栈 + loader_js,routed/widget/submit 依赖统一换绑;
  用户卡片发布按钮恢复 nav_user_newposts_btn 设置门控
- 测试对齐:新增 frontend-lifecycle-contract,重写 navigation/lazyload/toast/
  auth-source/comment/page-display/post-visual 等断言为目标架构契约

验证:node --test widget-system+navigation 223 通过 0 失败;
构建脚本重建 main/imgbox min 与 CSS bundles;改动 PHP php -l、JS node --check
全部通过;git diff --check 干净;credits 重入 harness 通过。
2026-09-03 12:44:31 +08:00

329 lines
10 KiB
JavaScript

/**
* 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="fa 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="fa 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="fa 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="fa 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);
});
}
jQuery(document).off('auto_fun.aksrt-mini-upload').on('auto_fun.aksrt-mini-upload', autoInit);
// 暴露到全局
window.AksrtMiniUpload = MiniUpload;
})();