11 KiB
11 KiB
G3File 组件结构优化和缺陷修复计划
一、P0 必须修复的问题
1. 避免整列表重渲染(核心性能问题)
问题:updateFileStatus 调用 renderFileList(),导致整个列表重新渲染 + 重新绑定事件
解决方案:新增 updateFileItem(fileId) 方法,仅更新单个文件项
updateFileItem: function (fileId) {
var file = this.getFileById(fileId);
var $item = this.$fileList.find(`.g3-file-item[data-id="${fileId}"]`);
if (!file || !$item.length) return;
// 更新 checkbox
$item.find(".g3-file-checkbox").prop("checked", this.selectedFiles.has(fileId));
// 更新 badge
var $badge = $item.find(".g3-status-badge");
if (file.status === "pending") {
$badge.attr("class", "g3-status-badge g3-status-pending").text(this.language.pending);
} else if (file.status === "uploading") {
$badge.attr("class", "g3-status-badge g3-status-uploading").text(this.language.uploading);
} else if (file.status === "failed") {
$badge.attr("class", "g3-status-badge g3-status-failed").text(this.language.uploadFailed);
} else if (file.status === "uploaded") {
$badge.attr("class", "g3-status-badge g3-status-uploaded").text(this.language.uploaded);
}
// 更新权限状态
var canDownload = this.hasPermission(file, "download");
var canView = this.hasPermission(file, "view");
var canDelete = this.hasPermission(file, "delete");
$item.find(".g3-action-download").toggleClass("is-disabled", !canDownload);
$item.find(".g3-action-preview").toggleClass("is-disabled", !canView);
$item.find(".g3-file-delete-btn").toggleClass("is-disabled", !canDelete);
}
修改点:
updateFileStatus方法中移除renderFileList()调用,改为updateFileItem(fileId)
2. 预览逻辑设计缺陷(previewIndex 问题)
问题:使用 previewIndex 作为预览定位,删除/新增文件会导致错位
解决方案:改为使用 currentPreviewFileId
修改点:
- 构造函数中:
this.previewIndex = 0→this.currentPreviewFileId = null - 新增方法
getPreviewFileIndex()用于兼容 prev/next 导航 previewPrev()和previewNext()改为基于currentPreviewFileId计算updatePreview()改为使用currentPreviewFileId获取文件setPreviewFile()直接设置currentPreviewFileIdsetFiles()中:this.previewIndex = 0→this.currentPreviewFileId = null
3. Task Center 多任务支持
问题:this.currentTask 只能存在一个任务,上传/下载会互相覆盖
解决方案:改为 this.tasks = new Map() 多任务模型
startTask: function (options) {
var task = {
id: options.id || G3FileUtils.generateUniqueId(),
title: options.title,
total: options.total || 0,
current: options.current || 0,
currentItem: options.currentItem || "",
success: 0,
failed: 0,
status: "running"
};
this.tasks.set(task.id, task);
this.renderTaskCenter(task.id);
this.lockUI();
return task.id;
}
updateTask: function (options) {
var task = this.tasks.get(options.id);
if (!task) return;
if (options.current !== undefined) task.current = options.current;
if (options.currentItem !== undefined) task.currentItem = options.currentItem;
this.updateTaskCenter(options.id);
}
finishTask: function (options) {
var task = this.tasks.get(options.id);
if (!task) return;
task.status = "finished";
task.total = options.total !== undefined ? options.total : task.total;
task.success = options.success || 0;
task.failed = options.failed || 0;
task.current = task.total;
this.updateTaskCenter(options.id);
this.tasks.delete(options.id);
if (this.tasks.size === 0) {
this.unlockUI();
}
}
修改点:
this.currentTask→this.tasks = new Map()renderTaskCenter(taskId)和updateTaskCenter(taskId)接收 taskId 参数closeTask(id)从this.tasks中删除任务
二、P1 重要优化
4. 事件绑定优化
问题:每次 renderFileList 后都调用 bindFileItemEvents 重新绑定
解决方案:使用事件委托,在 bindEvents 中统一绑定一次
bindEvents: function () {
var self = this;
// 文件列表事件委托(只在 init 时绑定一次)
this.$fileList
.on("click", ".g3-action-preview", function (e) { ... })
.on("click", ".g3-action-download", function (e) { ... })
.on("change", ".g3-file-checkbox", function (e) { ... })
.on("click", ".g3-file-delete-btn", function (e) { ... })
.on("click", ".g3-file-item", function (e) { ... })
.on("click", ".g3-action-more", function (e) { ... });
// 其他事件保持不变...
}
修改点:
renderFileList()中移除bindFileItemEvents()调用bindFileItemEvents()方法可移除或保留(如果外部需要单独调用)
5. 上传并发控制
问题:handleUpload 中 forEach 并发无限制
解决方案:使用 async pool 实现并发控制
handleUpload: function (files) {
var self = this;
var concurrency = 3;
var pendingFiles = files.filter((file) => file.status === "pending");
if (pendingFiles.length === 0) return;
var total = pendingFiles.length;
var taskId = this.startTask({
title: this.language.batchUpload || "批量上传",
total: total
});
var successCount = 0;
var failCount = 0;
var index = 0;
var uploadOne = async function () {
while (index < pendingFiles.length) {
var file = pendingFiles[index++];
self.updateFileStatus(file._id, "uploading");
try {
var uploadedFile = await self.internalUpload(file);
successCount++;
self.updateFileStatus(file._id, "uploaded", uploadedFile);
if (typeof self.options.onUploadSuccess === "function") {
self.options.onUploadSuccess(uploadedFile);
}
} catch (error) {
failCount++;
self.updateFileStatus(file._id, "failed");
if (typeof self.options.onUploadError === "function") {
self.options.onUploadError(file, error);
}
}
self.updateTask({
id: taskId,
current: successCount + failCount,
currentItem: file.name
});
}
};
Promise.all(Array.from({ length: concurrency }, uploadOne)).then(function () {
self.finishTask({ id: taskId, total: total, success: successCount, failed: failCount });
if (typeof self.options.onUploadComplete === "function") {
self.options.onUploadComplete(successCount, failCount);
}
});
}
修改点:
handleUpload方法重写,实现并发控制
6. ObjectURL 生命周期管理
问题:组件销毁时未释放 ObjectURL
解决方案:destroy 方法中统一释放
destroy: function () {
// 清理事件
this.$container.off();
$(document).off(".g3file");
// 清理 ObjectURL
this.objectUrls.forEach(function (url) {
try {
URL.revokeObjectURL(url);
} catch (e) {
console.warn("释放ObjectURL失败:", e);
}
});
this.objectUrls.clear();
// 清理文件列表 DOM
this.$fileList.empty();
// 销毁 UI
this.$container.find(".g3-global-menu").remove();
this.$container.find(".g3-toast-container").remove();
this.$container.find(".g3-task-center").remove();
this.$container.find(".g3-ui-lock").remove();
// 重置状态
this.files = [];
this.selectedFiles.clear();
this.tasks.clear();
this.currentPreviewFileId = null;
console.log("G3File 组件已销毁");
}
7. 权限系统统一入口
问题:globalPermission 和 filePermissionResolver 分散使用
解决方案:新增 resolvePermission(file, action) 统一方法
resolvePermission: function (file, action) {
var globalPermission = this.options.globalPermission;
var filePermissionResolver = this.options.filePermissionResolver;
if (typeof filePermissionResolver === "function") {
var result = filePermissionResolver({ file: file, action: action });
if (result !== undefined) return !!result;
}
if (typeof globalPermission === "function") {
return !!globalPermission({ file: file, action: action });
} else if (typeof globalPermission === "object") {
return !!globalPermission[action];
}
return true;
}
修改点:
hasPermission重命名为resolvePermission或新增方法,原方法保留为别名- 所有调用
hasPermission的地方改为调用resolvePermission
三、P2 UI/交互修复
8. 点击文件项行为优化
问题:点击整个 file-item 会触发预览,容易误触
解决方案:改为双击触发预览
this.$fileList.on("dblclick", ".g3-file-item", function (e) {
if ($(e.target).closest(".g3-file-action, .g3-file-checkbox, .g3-file-delete-btn").length) {
return;
}
var id = $(this).data("id");
var file = self.getFileById(id);
if (!file) return;
self.setPreviewFile(id);
if (typeof self.options.onPreview === "function") {
self.options.onPreview([file]);
}
});
修改点:
bindEvents中的.g3-file-item点击事件改为双击
9. UI 锁机制完善
解决方案:在关键操作开始时检查锁状态
handleUpload: function (files) {
if (this.uiLocked) {
console.warn("UI is locked, please wait...");
return;
}
// ... 原有逻辑
}
internalDelete: function (files) {
if (this.uiLocked) {
console.warn("UI is locked, please wait...");
return;
}
// ... 原有逻辑
}
10. 批量删除安全控制
解决方案:增加 confirm 回调支持
internalDelete: function (files) {
var self = this;
if (typeof this.options.onBeforeDelete === "function") {
var result = this.options.onBeforeDelete(files);
if (result === false) return;
if (typeof result === "function") {
// 异步确认
result(function (confirmed) {
if (confirmed) self.doDelete(files);
});
return;
}
}
self.doDelete(files);
}
doDelete: function (files) {
// 原 internalDelete 逻辑
}
修改点:
- 新增
onBeforeDelete回调选项 - 重构删除逻辑
四、CSS 优化
11. 文件列表布局优化
问题:使用 calc 写死宽度
解决方案:改为 flex: calc(50% - 26px) 或 grid
.g3-main {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
}
.g3-file-item {
flex: 0 0 calc(50% - 20px);
max-width: calc(50% - 20px);
}
12. z-index 规范化
解决方案:建立层级规范
:root {
--g3-z-dropdown: 100;
--g3-z-modal: 200;
--g3-z-toast: 300;
--g3-z-task: 400;
--g3-z-lock: 500;
}
五、实施步骤
- P0-1: 新增
updateFileItem()方法,修改updateFileStatus() - P0-2: 替换
previewIndex为currentPreviewFileId - P0-3: 重构 Task Center 为多任务模型
- P1-4: 事件委托优化
- P1-5: 上传并发控制
- P1-6: 完善
destroy()方法 - P1-7: 新增
resolvePermission()方法 - P2-8: 双击预览
- P2-9: UI 锁检查
- P2-10: 删除确认回调
- CSS: 布局优化和 z-index 规范化