Files
workspace/code/file-preview/g3-file/.trae/documents/optimize-g3file-plan.md
T
2026-04-28 21:47:04 +08:00

426 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# G3File 组件结构优化和缺陷修复计划
## 一、P0 必须修复的问题
### 1. 避免整列表重渲染(核心性能问题)
**问题**:`updateFileStatus` 调用 `renderFileList()`,导致整个列表重新渲染 + 重新绑定事件
**解决方案**:新增 `updateFileItem(fileId)` 方法,仅更新单个文件项
```javascript
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`
**修改点**:
1. 构造函数中:`this.previewIndex = 0` → `this.currentPreviewFileId = null`
2. 新增方法 `getPreviewFileIndex()` 用于兼容 prev/next 导航
3. `previewPrev()` 和 `previewNext()` 改为基于 `currentPreviewFileId` 计算
4. `updatePreview()` 改为使用 `currentPreviewFileId` 获取文件
5. `setPreviewFile()` 直接设置 `currentPreviewFileId`
6. `setFiles()` 中:`this.previewIndex = 0` → `this.currentPreviewFileId = null`
---
### 3. Task Center 多任务支持
**问题**:`this.currentTask` 只能存在一个任务,上传/下载会互相覆盖
**解决方案**:改为 `this.tasks = new Map()` 多任务模型
```javascript
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` 中统一绑定一次
```javascript
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 实现并发控制
```javascript
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` 方法中统一释放
```javascript
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)` 统一方法
```javascript
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 会触发预览,容易误触
**解决方案**:改为双击触发预览
```javascript
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 锁机制完善
**解决方案**:在关键操作开始时检查锁状态
```javascript
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 回调支持
```javascript
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
```css
.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 规范化
**解决方案**:建立层级规范
```css
:root {
--g3-z-dropdown: 100;
--g3-z-modal: 200;
--g3-z-toast: 300;
--g3-z-task: 400;
--g3-z-lock: 500;
}
```
---
## 五、实施步骤
1. **P0-1**: 新增 `updateFileItem()` 方法,修改 `updateFileStatus()`
2. **P0-2**: 替换 `previewIndex` 为 `currentPreviewFileId`
3. **P0-3**: 重构 Task Center 为多任务模型
4. **P1-4**: 事件委托优化
5. **P1-5**: 上传并发控制
6. **P1-6**: 完善 `destroy()` 方法
7. **P1-7**: 新增 `resolvePermission()` 方法
8. **P2-8**: 双击预览
9. **P2-9**: UI 锁检查
10. **P2-10**: 删除确认回调
11. **CSS**: 布局优化和 z-index 规范化