u
This commit is contained in:
1 parent
2a00dc7ef4
commit
c6d55b0f46
4 files changed
+788
-326
No files matched your search
@@ -0,0 +1,45 @@
|
|||||||
|
# Task 关闭按钮修复计划
|
||||||
|
|
||||||
|
## 问题分析
|
||||||
|
|
||||||
|
根据代码审查,`g3-task-close` 点击无反应的核心问题在于:
|
||||||
|
|
||||||
|
1. **事件绑定方式错误**:使用直接绑定而非事件委托
|
||||||
|
2. **taskId 未绑定到 DOM**:无法通过 DOM 获取当前任务 ID
|
||||||
|
3. **closeTask 方法不完善**:只删除数据,未删除对应的 DOM 元素
|
||||||
|
|
||||||
|
## 修复方案
|
||||||
|
|
||||||
|
### 1. 修改 renderTaskCenter 方法
|
||||||
|
- 在 DOM 元素上添加 `data-task-id` 属性
|
||||||
|
- 移除直接的事件绑定
|
||||||
|
|
||||||
|
### 2. 修改 closeTask 方法
|
||||||
|
- 同时删除数据和对应的 DOM 元素
|
||||||
|
|
||||||
|
### 3. 添加事件委托(在初始化时绑定)
|
||||||
|
- 使用事件委托绑定 `.g3-task-close` 点击事件
|
||||||
|
|
||||||
|
### 4. 检查 pointer-events 样式
|
||||||
|
- 确保 `.g3-task-close` 按钮可点击
|
||||||
|
|
||||||
|
## 修改文件
|
||||||
|
|
||||||
|
- `d:\workspace\code\file-preview\g3-file\index.js`
|
||||||
|
|
||||||
|
## 修改步骤
|
||||||
|
|
||||||
|
1. 在 `renderTaskCenter` 的 HTML 模板中添加 `data-task-id`
|
||||||
|
2. 移除 `renderTaskCenter` 中的直接事件绑定
|
||||||
|
3. 修改 `closeTask` 方法,添加 DOM 删除逻辑
|
||||||
|
4. 在初始化时添加事件委托绑定
|
||||||
|
|
||||||
|
## 风险评估
|
||||||
|
|
||||||
|
- 低风险:修改局部代码,不影响其他功能
|
||||||
|
- 需确保事件委托只绑定一次
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
|
||||||
|
1. 点击关闭按钮,检查控制台是否输出正确的 taskId
|
||||||
|
2. 确认任务数据和 UI 都被正确移除
|
||||||
@@ -0,0 +1,425 @@
|
|||||||
|
# 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 规范化
|
||||||
@@ -5,6 +5,11 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #eef0f2;
|
border: 1px solid #eef0f2;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
--g3-z-dropdown: 100;
|
||||||
|
--g3-z-modal: 200;
|
||||||
|
--g3-z-toast: 300;
|
||||||
|
--g3-z-task: 400;
|
||||||
|
--g3-z-lock: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.g3-file-container.drag-over {
|
.g3-file-container.drag-over {
|
||||||
@@ -32,15 +37,13 @@
|
|||||||
.g3-main {
|
.g3-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 4px 0px;
|
padding: 10px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 10px;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
}
|
box-sizing: border-box;
|
||||||
|
|
||||||
.g3-main > * {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.g3-empty-tip {
|
.g3-empty-tip {
|
||||||
@@ -143,13 +146,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.g3-file-item {
|
.g3-file-item {
|
||||||
width: calc(50% - 26px);
|
flex: none;
|
||||||
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid #eef0f2;
|
border: 1px solid #eef0f2;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.g3-file-item:hover {
|
.g3-file-item:hover {
|
||||||
@@ -379,7 +384,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 100;
|
z-index: var(--g3-z-dropdown);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
border: 1px solid #e8e8e8;
|
border: 1px solid #e8e8e8;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
@@ -537,7 +542,7 @@
|
|||||||
|
|
||||||
.g3-global-menu {
|
.g3-global-menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 9999;
|
z-index: var(--g3-z-dropdown);
|
||||||
width: 160px;
|
width: 160px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
@@ -578,7 +583,7 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 9999;
|
z-index: var(--g3-z-toast);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -734,7 +739,7 @@
|
|||||||
border: 1px solid #eef0f2;
|
border: 1px solid #eef0f2;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
z-index: 9999;
|
z-index: var(--g3-z-task);
|
||||||
display: none;
|
display: none;
|
||||||
animation: taskCenterSlideIn 0.3s ease-out;
|
animation: taskCenterSlideIn 0.3s ease-out;
|
||||||
}
|
}
|
||||||
@@ -857,23 +862,6 @@
|
|||||||
color: #ff4d4f;
|
color: #ff4d4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.g3-complete-button {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 12px;
|
|
||||||
padding: 8px;
|
|
||||||
border: none;
|
|
||||||
background: #165dff;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 13px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g3-complete-button:hover {
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.g3-ui-lock {
|
.g3-ui-lock {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -881,7 +869,7 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(0, 0, 0, 0.05);
|
background: rgba(0, 0, 0, 0.05);
|
||||||
z-index: 9998;
|
z-index: var(--g3-z-lock);
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+300
-296
@@ -9,6 +9,7 @@
|
|||||||
this.$container = $(container);
|
this.$container = $(container);
|
||||||
this.options = $.extend({}, this.defaults, options);
|
this.options = $.extend({}, this.defaults, options);
|
||||||
this.instanceId = G3FileUtils.generateUniqueId();
|
this.instanceId = G3FileUtils.generateUniqueId();
|
||||||
|
this.documentNamespace = ".g3file_" + this.instanceId;
|
||||||
this.files = [];
|
this.files = [];
|
||||||
this.selectedFiles = new Set();
|
this.selectedFiles = new Set();
|
||||||
this.objectUrls = new Map();
|
this.objectUrls = new Map();
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 当前任务(单个任务实例)
|
// 当前任务(单个任务实例)
|
||||||
this.currentTask = null;
|
this.tasks = new Map();
|
||||||
|
|
||||||
// UI锁定状态
|
// UI锁定状态
|
||||||
this.uiLocked = false;
|
this.uiLocked = false;
|
||||||
@@ -32,8 +33,8 @@
|
|||||||
// 阿里云 OSS 凭证缓存
|
// 阿里云 OSS 凭证缓存
|
||||||
this.aliyunKeyCache = null;
|
this.aliyunKeyCache = null;
|
||||||
|
|
||||||
// 预览相关状态
|
// 预览相关状态(基于 fileId 定位,避免删除/新增导致错位)
|
||||||
this.previewIndex = 0;
|
this.currentPreviewFileId = null;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
*/
|
*/
|
||||||
data: [],
|
data: [],
|
||||||
mode: "aliyun",
|
mode: "aliyun",
|
||||||
|
onBeforeDelete: null,
|
||||||
onDelete: null,
|
onDelete: null,
|
||||||
onDownload: null,
|
onDownload: null,
|
||||||
onSelect: null,
|
onSelect: null,
|
||||||
@@ -205,14 +207,6 @@
|
|||||||
// 工具方法
|
// 工具方法
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成唯一ID
|
|
||||||
* @returns {string} 唯一ID
|
|
||||||
*/
|
|
||||||
generateUniqueId: function () {
|
|
||||||
return "file_" + Math.random().toString(36).slice(2) + Date.now();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文件图标
|
* 获取文件图标
|
||||||
* @param {string} filename - 文件名
|
* @param {string} filename - 文件名
|
||||||
@@ -558,7 +552,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.$fileList.html(html);
|
this.$fileList.html(html);
|
||||||
this.bindFileItemEvents();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -620,11 +613,52 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateFileItem: function (fileId) {
|
||||||
|
var file = this.getFileById(fileId);
|
||||||
|
var $item = this.$fileList.find(`.g3-file-item[data-id="${fileId}"]`);
|
||||||
|
if (!file || !$item.length) return;
|
||||||
|
|
||||||
|
$item
|
||||||
|
.find(".g3-file-checkbox")
|
||||||
|
.prop("checked", this.selectedFiles.has(fileId));
|
||||||
|
|
||||||
|
var $badge = $item.find(".g3-status-badge");
|
||||||
|
if (file.status === "pending") {
|
||||||
|
$badge
|
||||||
|
.attr("class", "g3-status-badge g3-status-pending")
|
||||||
|
.text(this.language.pending)
|
||||||
|
.show();
|
||||||
|
} else if (file.status === "uploading") {
|
||||||
|
$badge
|
||||||
|
.attr("class", "g3-status-badge g3-status-uploading")
|
||||||
|
.text(this.language.uploading)
|
||||||
|
.show();
|
||||||
|
} else if (file.status === "failed") {
|
||||||
|
$badge
|
||||||
|
.attr("class", "g3-status-badge g3-status-failed")
|
||||||
|
.text(this.language.uploadFailed)
|
||||||
|
.show();
|
||||||
|
} else if (file.status === "uploaded") {
|
||||||
|
$badge.attr("class", "g3-status-badge g3-status-uploaded").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取已上传的文件列表(仅status为uploaded的文件)
|
* 获取已上传的文件列表(仅status为uploaded的文件)
|
||||||
*/
|
*/
|
||||||
getUploadedFiles: function () {
|
getUploadedFiles: function () {
|
||||||
return this.files;
|
return this.files.filter(function (file) {
|
||||||
|
return file.status === "uploaded";
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
isPreviewableFile: function (fileName) {
|
isPreviewableFile: function (fileName) {
|
||||||
@@ -658,47 +692,34 @@
|
|||||||
return !nonPreviewableExts.includes(ext);
|
return !nonPreviewableExts.includes(ext);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
getPreviewFileIndex: function (fileId) {
|
||||||
* 预览上一个文件
|
var uploadedFiles = this.getUploadedFiles();
|
||||||
*/
|
return uploadedFiles.findIndex(function (f) {
|
||||||
|
return f._id === fileId;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
previewPrev: function () {
|
previewPrev: function () {
|
||||||
var uploadedFiles = this.getUploadedFiles();
|
var uploadedFiles = this.getUploadedFiles();
|
||||||
if (uploadedFiles.length === 0) return;
|
if (uploadedFiles.length === 0) return;
|
||||||
|
|
||||||
if (this.previewIndex > 0) {
|
var currentIndex = this.getPreviewFileIndex(this.currentPreviewFileId);
|
||||||
this.previewIndex--;
|
var newIndex =
|
||||||
} else {
|
currentIndex > 0 ? currentIndex - 1 : uploadedFiles.length - 1;
|
||||||
this.previewIndex = uploadedFiles.length - 1;
|
this.currentPreviewFileId = uploadedFiles[newIndex]._id;
|
||||||
}
|
|
||||||
|
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 预览下一个文件
|
|
||||||
*/
|
|
||||||
previewNext: function () {
|
previewNext: function () {
|
||||||
var uploadedFiles = this.getUploadedFiles();
|
var uploadedFiles = this.getUploadedFiles();
|
||||||
if (uploadedFiles.length === 0) return;
|
if (uploadedFiles.length === 0) return;
|
||||||
|
|
||||||
if (this.previewIndex < uploadedFiles.length - 1) {
|
var currentIndex = this.getPreviewFileIndex(this.currentPreviewFileId);
|
||||||
this.previewIndex++;
|
var newIndex =
|
||||||
} else {
|
currentIndex < uploadedFiles.length - 1 ? currentIndex + 1 : 0;
|
||||||
this.previewIndex = 0;
|
this.currentPreviewFileId = uploadedFiles[newIndex]._id;
|
||||||
}
|
|
||||||
|
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
},
|
},
|
||||||
base64Encode: function (str) {
|
|
||||||
return btoa(
|
|
||||||
encodeURIComponent(str).replace(
|
|
||||||
/%([0-9A-F]{2})/g,
|
|
||||||
function (match, p1) {
|
|
||||||
return String.fromCharCode("0x" + p1);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* 更新预览区域显示
|
* 更新预览区域显示
|
||||||
*/
|
*/
|
||||||
@@ -716,28 +737,35 @@
|
|||||||
this.$previewCounter.text("0/0");
|
this.$previewCounter.text("0/0");
|
||||||
this.$btnPrev.prop("disabled", true);
|
this.$btnPrev.prop("disabled", true);
|
||||||
this.$btnNext.prop("disabled", true);
|
this.$btnNext.prop("disabled", true);
|
||||||
|
this.currentPreviewFileId = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.previewIndex >= total) {
|
if (
|
||||||
this.previewIndex = total - 1;
|
!this.currentPreviewFileId ||
|
||||||
}
|
!this.getFileById(this.currentPreviewFileId)
|
||||||
if (this.previewIndex < 0) {
|
) {
|
||||||
this.previewIndex = 0;
|
this.currentPreviewFileId = allFiles[0]._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentFile = allFiles[this.previewIndex];
|
var currentFile = this.getFileById(this.currentPreviewFileId);
|
||||||
|
if (!currentFile) {
|
||||||
|
this.currentPreviewFileId = allFiles[0]._id;
|
||||||
|
currentFile = allFiles[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentIndex = this.getPreviewFileIndex(this.currentPreviewFileId);
|
||||||
var isUploaded = currentFile.status === "uploaded";
|
var isUploaded = currentFile.status === "uploaded";
|
||||||
var isPreviewable = this.isPreviewableFile(currentFile.name);
|
var isPreviewable = this.isPreviewableFile(currentFile.name);
|
||||||
var fileUrl =
|
var fileUrl =
|
||||||
currentFile.url && currentFile.url !== "" ? currentFile.url : null;
|
currentFile.url && currentFile.url !== "" ? currentFile.url : null;
|
||||||
|
|
||||||
this.$btnPrev.prop("disabled", this.previewIndex === 0);
|
this.$btnPrev.prop("disabled", currentIndex === 0);
|
||||||
this.$btnNext.prop("disabled", this.previewIndex === total - 1);
|
this.$btnNext.prop("disabled", currentIndex === total - 1);
|
||||||
|
|
||||||
this.$previewName.text(currentFile.name);
|
this.$previewName.text(currentFile.name);
|
||||||
var counterText = this.language.previewCount
|
var counterText = this.language.previewCount
|
||||||
.replace("{current}", this.previewIndex + 1)
|
.replace("{current}", currentIndex + 1)
|
||||||
.replace("{total}", total);
|
.replace("{total}", total);
|
||||||
this.$previewCounter.text(counterText);
|
this.$previewCounter.text(counterText);
|
||||||
|
|
||||||
@@ -776,18 +804,10 @@
|
|||||||
this.$previewContent.html(previewContent);
|
this.$previewContent.html(previewContent);
|
||||||
},
|
},
|
||||||
|
|
||||||
isPreviewContentChanged: function (newContent) {
|
|
||||||
var currentContent = this.$previewContent.html();
|
|
||||||
return currentContent !== newContent;
|
|
||||||
},
|
|
||||||
|
|
||||||
setPreviewFile: function (fileId) {
|
setPreviewFile: function (fileId) {
|
||||||
var uploadedFiles = this.getUploadedFiles();
|
var file = this.getFileById(fileId);
|
||||||
var index = uploadedFiles.findIndex(function (f) {
|
if (file) {
|
||||||
return f._id === fileId;
|
this.currentPreviewFileId = fileId;
|
||||||
});
|
|
||||||
if (index >= 0) {
|
|
||||||
this.previewIndex = index;
|
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -801,6 +821,9 @@
|
|||||||
*/
|
*/
|
||||||
bindEvents: function () {
|
bindEvents: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var documentActionMenuEvent =
|
||||||
|
"click.g3file_actionmenu_" + this.instanceId;
|
||||||
|
var documentPasteEvent = "paste" + this.documentNamespace;
|
||||||
|
|
||||||
// 选择全部
|
// 选择全部
|
||||||
this.$checkAll.off("change").on("change", function () {
|
this.$checkAll.off("change").on("change", function () {
|
||||||
@@ -819,6 +842,7 @@
|
|||||||
|
|
||||||
// 批量下载
|
// 批量下载
|
||||||
this.$btnDownload.off("click").on("click", function () {
|
this.$btnDownload.off("click").on("click", function () {
|
||||||
|
if (!self.hasPermission(null, "download")) return;
|
||||||
var selectedFiles = self.getSelectedFiles();
|
var selectedFiles = self.getSelectedFiles();
|
||||||
if (selectedFiles.length === 0) return;
|
if (selectedFiles.length === 0) return;
|
||||||
|
|
||||||
@@ -827,6 +851,7 @@
|
|||||||
|
|
||||||
// 批量删除
|
// 批量删除
|
||||||
this.$btnDelete.off("click").on("click", function () {
|
this.$btnDelete.off("click").on("click", function () {
|
||||||
|
if (!self.hasPermission(null, "delete")) return;
|
||||||
var selectedFiles = self.getSelectedFiles();
|
var selectedFiles = self.getSelectedFiles();
|
||||||
if (selectedFiles.length === 0) return;
|
if (selectedFiles.length === 0) return;
|
||||||
|
|
||||||
@@ -835,11 +860,13 @@
|
|||||||
|
|
||||||
// 选择文件
|
// 选择文件
|
||||||
this.$btnSelect.off("click").on("click", function () {
|
this.$btnSelect.off("click").on("click", function () {
|
||||||
|
if (!self.hasPermission(null, "select")) return;
|
||||||
self.$fileInput.click();
|
self.$fileInput.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 上传
|
// 上传
|
||||||
this.$btnUpload.off("click").on("click", function () {
|
this.$btnUpload.off("click").on("click", function () {
|
||||||
|
if (!self.hasPermission(null, "upload")) return;
|
||||||
var pendingFiles = self.files.filter(
|
var pendingFiles = self.files.filter(
|
||||||
(file) => file.status === "pending",
|
(file) => file.status === "pending",
|
||||||
);
|
);
|
||||||
@@ -864,8 +891,8 @@
|
|||||||
|
|
||||||
// 点击其他地方关闭菜单
|
// 点击其他地方关闭菜单
|
||||||
$(document)
|
$(document)
|
||||||
.off("click.g3file_actionmenu")
|
.off(documentActionMenuEvent)
|
||||||
.on("click.g3file_actionmenu", function (e) {
|
.on(documentActionMenuEvent, function (e) {
|
||||||
if (
|
if (
|
||||||
!$(e.target).closest(".g3-action-more").length &&
|
!$(e.target).closest(".g3-action-more").length &&
|
||||||
!$(e.target).closest(".g3-global-menu").length
|
!$(e.target).closest(".g3-global-menu").length
|
||||||
@@ -909,8 +936,8 @@
|
|||||||
|
|
||||||
// 粘贴文件
|
// 粘贴文件
|
||||||
$(document)
|
$(document)
|
||||||
.off("paste.g3file")
|
.off(documentPasteEvent)
|
||||||
.on("paste.g3file", function (e) {
|
.on(documentPasteEvent, function (e) {
|
||||||
var clipboardData = e.originalEvent.clipboardData;
|
var clipboardData = e.originalEvent.clipboardData;
|
||||||
var files = clipboardData.files;
|
var files = clipboardData.files;
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
@@ -928,81 +955,53 @@
|
|||||||
this.$btnNext.off("click").on("click", function () {
|
this.$btnNext.off("click").on("click", function () {
|
||||||
self.previewNext();
|
self.previewNext();
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
// 文件列表事件委托(只在 init 时绑定一次)
|
||||||
* 绑定文件项事件
|
|
||||||
*/
|
|
||||||
bindFileItemEvents: function () {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// 预览
|
|
||||||
this.$fileList
|
this.$fileList
|
||||||
.off("click", ".g3-action-preview")
|
// 预览按钮
|
||||||
.on("click", ".g3-action-preview", function (e) {
|
.on("click", ".g3-action-preview", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
var id = $(this).closest(".g3-file-item").data("id");
|
var id = $(this).closest(".g3-file-item").data("id");
|
||||||
var file = self.files.find((f) => f._id === id);
|
var file = self.getFileById(id);
|
||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
if (!self.hasPermission(file, "view")) return;
|
||||||
self.setPreviewFile(id);
|
self.setPreviewFile(id);
|
||||||
|
|
||||||
if (typeof self.options.onPreview === "function") {
|
if (typeof self.options.onPreview === "function") {
|
||||||
self.options.onPreview([file]);
|
self.options.onPreview([file]);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
// 下载按钮
|
||||||
// 下载
|
|
||||||
this.$fileList
|
|
||||||
.off("click", ".g3-action-download")
|
|
||||||
.on("click", ".g3-action-download", function (e) {
|
.on("click", ".g3-action-download", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
var id = $(this).closest(".g3-file-item").data("id");
|
var id = $(this).closest(".g3-file-item").data("id");
|
||||||
var file = self.files.find((f) => f._id === id);
|
var file = self.getFileById(id);
|
||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
if (!self.hasPermission(file, "download")) return;
|
||||||
self.handleDownload([file], { batch: false });
|
self.handleDownload([file], { batch: false });
|
||||||
});
|
})
|
||||||
|
|
||||||
// 复选框
|
// 复选框
|
||||||
this.$fileList
|
|
||||||
.off("change", ".g3-file-checkbox")
|
|
||||||
.on("change", ".g3-file-checkbox", function () {
|
.on("change", ".g3-file-checkbox", function () {
|
||||||
var id = $(this).closest(".g3-file-item").data("id");
|
var id = $(this).closest(".g3-file-item").data("id");
|
||||||
|
|
||||||
if ($(this).is(":checked")) {
|
if ($(this).is(":checked")) {
|
||||||
self.selectedFiles.add(id);
|
self.selectedFiles.add(id);
|
||||||
} else {
|
} else {
|
||||||
self.selectedFiles.delete(id);
|
self.selectedFiles.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateUI();
|
self.updateUI();
|
||||||
});
|
})
|
||||||
|
// 删除按钮
|
||||||
// 删除
|
|
||||||
this.$fileList
|
|
||||||
.off("click", ".g3-file-delete-btn")
|
|
||||||
.on("click", ".g3-file-delete-btn", function (e) {
|
.on("click", ".g3-file-delete-btn", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
var id = $(this).closest(".g3-file-item").data("id");
|
var id = $(this).closest(".g3-file-item").data("id");
|
||||||
var file = self.files.find((f) => f._id === id);
|
var file = self.getFileById(id);
|
||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
if (!self.hasPermission(file, "delete")) return;
|
||||||
self.handleDelete([file]);
|
self.handleDelete([file]);
|
||||||
});
|
})
|
||||||
|
// 文件项单击预览
|
||||||
// 文件项点击
|
|
||||||
this.$fileList
|
|
||||||
.off("click", ".g3-file-item")
|
|
||||||
.on("click", ".g3-file-item", function (e) {
|
.on("click", ".g3-file-item", function (e) {
|
||||||
if (
|
if (
|
||||||
$(e.target).closest(
|
$(e.target).closest(
|
||||||
@@ -1011,32 +1010,23 @@
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var id = $(this).data("id");
|
var id = $(this).data("id");
|
||||||
var file = self.files.find((f) => f._id === id);
|
var file = self.getFileById(id);
|
||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
if (!self.hasPermission(file, "view")) return;
|
||||||
if (typeof self.options.onPreview === "function") {
|
if (typeof self.options.onPreview === "function") {
|
||||||
self.options.onPreview([file]);
|
self.options.onPreview([file]);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setPreviewFile(id);
|
self.setPreviewFile(id);
|
||||||
});
|
})
|
||||||
|
|
||||||
// 更多按钮
|
// 更多按钮
|
||||||
this.$fileList
|
|
||||||
.off("click", ".g3-action-more")
|
|
||||||
.on("click", ".g3-action-more", function (e) {
|
.on("click", ".g3-action-more", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
var $el = $(this).closest(".g3-file-action");
|
var $el = $(this).closest(".g3-file-action");
|
||||||
var id = $el.closest(".g3-file-item").data("id");
|
var id = $el.closest(".g3-file-item").data("id");
|
||||||
var file = self.getFileById(id);
|
var file = self.getFileById(id);
|
||||||
|
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.actionMenuState.open &&
|
self.actionMenuState.open &&
|
||||||
self.actionMenuState.fileId === file._id
|
self.actionMenuState.fileId === file._id
|
||||||
@@ -1044,9 +1034,16 @@
|
|||||||
self.closeActionMenu();
|
self.closeActionMenu();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.openActionMenu($el, file);
|
self.openActionMenu($el, file);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 任务关闭按钮事件委托
|
||||||
|
this.$container.on("click", ".g3-task-close", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
var taskId = $(this).closest(".g3-task-center").data("task-id");
|
||||||
|
self.closeTask(taskId);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1156,7 +1153,27 @@
|
|||||||
handleDelete: function (files) {
|
handleDelete: function (files) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (typeof this.options.onDelete === "function") {
|
if (this.uiLocked) {
|
||||||
|
console.warn("UI is locked, please wait...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.options.onBeforeDelete === "function") {
|
||||||
|
var result = this.options.onBeforeDelete(files);
|
||||||
|
if (result === false) return;
|
||||||
|
if (typeof result.then === "function") {
|
||||||
|
result.then(function (confirmed) {
|
||||||
|
if (confirmed) self.internalDelete(files);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof result === "function") {
|
||||||
|
result(function (confirmed) {
|
||||||
|
if (confirmed) self.internalDelete(files);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (typeof this.options.onDelete === "function") {
|
||||||
this.options.onDelete(files, function (success) {
|
this.options.onDelete(files, function (success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
self.internalDelete(files);
|
self.internalDelete(files);
|
||||||
@@ -1372,8 +1389,7 @@
|
|||||||
if (data) {
|
if (data) {
|
||||||
Object.assign(file, data);
|
Object.assign(file, data);
|
||||||
}
|
}
|
||||||
this.renderFileList();
|
this.updateFileItem(fileId);
|
||||||
this.updateUI();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1383,8 +1399,6 @@
|
|||||||
* @param {Function} callback - 上传完成回调
|
* @param {Function} callback - 上传完成回调
|
||||||
*/
|
*/
|
||||||
internalUpload: async function (file, callback) {
|
internalUpload: async function (file, callback) {
|
||||||
const self = this;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const keyData = await this.getValidAliyunKey();
|
const keyData = await this.getValidAliyunKey();
|
||||||
|
|
||||||
@@ -1417,25 +1431,10 @@
|
|||||||
uploadDate: new Date().toISOString(),
|
uploadDate: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.updateFileStatus(file._id, "uploaded", {
|
|
||||||
url: uploadedFile.url,
|
|
||||||
uploadDate: uploadedFile.uploadDate,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof self.options.onUploadSuccess === "function") {
|
|
||||||
self.options.onUploadSuccess(uploadedFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, uploadedFile);
|
callback(null, uploadedFile);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[G3File] 上传失败:", error);
|
console.error("[G3File] 上传失败:", error);
|
||||||
|
|
||||||
self.updateFileStatus(file._id, "failed");
|
|
||||||
|
|
||||||
if (typeof self.options.onUploadError === "function") {
|
|
||||||
self.options.onUploadError(file, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(error);
|
callback(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1445,117 +1444,111 @@
|
|||||||
* @param {Array} files - 文件数组
|
* @param {Array} files - 文件数组
|
||||||
*/
|
*/
|
||||||
handleUpload: function (files) {
|
handleUpload: function (files) {
|
||||||
const self = this;
|
var self = this;
|
||||||
const customUpload = this.options.uploadConfig.customUpload;
|
|
||||||
let successCount = 0;
|
|
||||||
let failCount = 0;
|
|
||||||
|
|
||||||
// 过滤出未上传的文件
|
if (this.uiLocked) {
|
||||||
const pendingFiles = files.filter((file) => file.status === "pending");
|
console.warn("UI is locked, please wait...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var customUpload = this.options.uploadConfig.customUpload;
|
||||||
|
var concurrency = 3;
|
||||||
|
var pendingFiles = files.filter(function (file) {
|
||||||
|
return file.status === "pending";
|
||||||
|
});
|
||||||
|
|
||||||
if (pendingFiles.length === 0) {
|
if (pendingFiles.length === 0) {
|
||||||
// 触发上传完成回调
|
|
||||||
if (typeof self.options.onUploadComplete === "function") {
|
if (typeof self.options.onUploadComplete === "function") {
|
||||||
self.options.onUploadComplete(0, 0);
|
self.options.onUploadComplete(0, 0);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const total = pendingFiles.length;
|
var total = pendingFiles.length;
|
||||||
const taskId = G3FileUtils.generateUniqueId();
|
var taskId = this.startTask({
|
||||||
|
id: G3FileUtils.generateUniqueId(),
|
||||||
// 启动上传任务
|
|
||||||
this.startTask({
|
|
||||||
id: taskId,
|
|
||||||
title: this.language.batchUpload || "批量上传",
|
title: this.language.batchUpload || "批量上传",
|
||||||
total: total,
|
total: total,
|
||||||
current: 0,
|
current: 0,
|
||||||
currentItem: "",
|
currentItem: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
// 逐个上传文件
|
var successCount = 0;
|
||||||
pendingFiles.forEach((file) => {
|
var failCount = 0;
|
||||||
// 更新文件状态为上传中
|
var completedCount = 0;
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
function uploadNext() {
|
||||||
|
if (index >= pendingFiles.length) return Promise.resolve();
|
||||||
|
|
||||||
|
var file = pendingFiles[index++];
|
||||||
self.updateFileStatus(file._id, "uploading");
|
self.updateFileStatus(file._id, "uploading");
|
||||||
|
|
||||||
if (typeof customUpload === "function") {
|
return new Promise(function (resolve) {
|
||||||
// 使用自定义上传方法
|
function handleResult(error, uploadedFile) {
|
||||||
customUpload(file)
|
completedCount++;
|
||||||
.then((uploadedFile) => {
|
|
||||||
// 更新任务进度
|
|
||||||
self.updateTask({
|
|
||||||
id: taskId,
|
|
||||||
current: successCount + failCount,
|
|
||||||
currentItem: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新文件状态
|
|
||||||
self.updateFileStatus(file._id, "uploaded", uploadedFile);
|
|
||||||
|
|
||||||
// 触发上传成功回调
|
|
||||||
if (typeof self.options.onUploadSuccess === "function") {
|
|
||||||
self.options.onUploadSuccess(uploadedFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
successCount++;
|
|
||||||
checkComplete();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
// 更新任务进度
|
|
||||||
self.updateTask({
|
|
||||||
id: taskId,
|
|
||||||
current: successCount + failCount,
|
|
||||||
currentItem: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新文件状态为失败
|
|
||||||
self.updateFileStatus(file._id, "failed");
|
|
||||||
|
|
||||||
// 触发上传失败回调
|
|
||||||
if (typeof self.options.onUploadError === "function") {
|
|
||||||
self.options.onUploadError(file, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
failCount++;
|
|
||||||
checkComplete();
|
|
||||||
});
|
|
||||||
} else if (this.options.mode === "aliyun") {
|
|
||||||
// 使用阿里云内部上传方法
|
|
||||||
self.internalUpload(file, (error, uploadedFile) => {
|
|
||||||
// 更新任务进度
|
|
||||||
self.updateTask({
|
|
||||||
id: taskId,
|
|
||||||
current: successCount + failCount,
|
|
||||||
currentItem: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
failCount++;
|
failCount++;
|
||||||
|
self.updateFileStatus(file._id, "failed");
|
||||||
|
if (typeof self.options.onUploadError === "function") {
|
||||||
|
self.options.onUploadError(file, error);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
successCount++;
|
successCount++;
|
||||||
|
self.updateFileStatus(file._id, "uploaded", uploadedFile);
|
||||||
|
if (typeof self.options.onUploadSuccess === "function") {
|
||||||
|
self.options.onUploadSuccess(uploadedFile);
|
||||||
}
|
}
|
||||||
checkComplete();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.updateTask({
|
||||||
|
id: taskId,
|
||||||
|
current: completedCount,
|
||||||
|
currentItem: file.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 检查是否所有文件都上传完成
|
if (completedCount === total) {
|
||||||
function checkComplete() {
|
|
||||||
if (successCount + failCount === pendingFiles.length) {
|
|
||||||
// 完成任务
|
|
||||||
self.finishTask({
|
self.finishTask({
|
||||||
id: taskId,
|
id: taskId,
|
||||||
total: total,
|
total: total,
|
||||||
success: successCount,
|
success: successCount,
|
||||||
failed: failCount,
|
failed: failCount,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 触发上传完成回调
|
|
||||||
if (typeof self.options.onUploadComplete === "function") {
|
if (typeof self.options.onUploadComplete === "function") {
|
||||||
self.options.onUploadComplete(successCount, failCount);
|
self.options.onUploadComplete(successCount, failCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof customUpload === "function") {
|
||||||
|
customUpload(file)
|
||||||
|
.then(function (uploadedFile) {
|
||||||
|
handleResult(null, uploadedFile);
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
handleResult(error, null);
|
||||||
|
});
|
||||||
|
} else if (self.options.mode === "aliyun") {
|
||||||
|
self.internalUpload(file, function (error, uploadedFile) {
|
||||||
|
handleResult(error, uploadedFile);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleResult(new Error("No upload method configured"), null);
|
||||||
|
}
|
||||||
|
}).then(function () {
|
||||||
|
return uploadNext();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var runners = [];
|
||||||
|
for (var i = 0; i < concurrency && i < pendingFiles.length; i++) {
|
||||||
|
runners.push(uploadNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(runners);
|
||||||
},
|
},
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1654,21 +1647,20 @@
|
|||||||
* @param {string} options.currentItem - 当前处理项
|
* @param {string} options.currentItem - 当前处理项
|
||||||
*/
|
*/
|
||||||
startTask: function (options) {
|
startTask: function (options) {
|
||||||
const { id, title, total = 0, current = 0, currentItem = "" } = options;
|
var task = {
|
||||||
|
id: options.id || G3FileUtils.generateUniqueId(),
|
||||||
this.currentTask = {
|
title: options.title,
|
||||||
id: id || G3FileUtils.generateUniqueId(),
|
total: options.total || 0,
|
||||||
title: title,
|
current: options.current || 0,
|
||||||
total: total,
|
currentItem: options.currentItem || "",
|
||||||
current: current,
|
|
||||||
currentItem: currentItem,
|
|
||||||
success: 0,
|
success: 0,
|
||||||
failed: 0,
|
failed: 0,
|
||||||
status: "running",
|
status: "running",
|
||||||
};
|
};
|
||||||
|
this.tasks.set(task.id, task);
|
||||||
this.lockUI();
|
this.lockUI();
|
||||||
this.renderTaskCenter();
|
this.renderTaskCenter(task.id);
|
||||||
|
return task.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1679,20 +1671,12 @@
|
|||||||
* @param {string} options.currentItem - 当前处理项
|
* @param {string} options.currentItem - 当前处理项
|
||||||
*/
|
*/
|
||||||
updateTask: function (options) {
|
updateTask: function (options) {
|
||||||
const { id, current, currentItem } = options;
|
var task = this.tasks.get(options.id);
|
||||||
|
if (!task) return;
|
||||||
if (!this.currentTask || this.currentTask.id !== id) {
|
if (options.current !== undefined) task.current = options.current;
|
||||||
return;
|
if (options.currentItem !== undefined)
|
||||||
}
|
task.currentItem = options.currentItem;
|
||||||
|
this.updateTaskCenter(options.id);
|
||||||
if (current !== undefined) {
|
|
||||||
this.currentTask.current = current;
|
|
||||||
}
|
|
||||||
if (currentItem !== undefined) {
|
|
||||||
this.currentTask.currentItem = currentItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateTaskCenter();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1704,21 +1688,18 @@
|
|||||||
* @param {number} options.failed - 失败数量
|
* @param {number} options.failed - 失败数量
|
||||||
*/
|
*/
|
||||||
finishTask: function (options) {
|
finishTask: function (options) {
|
||||||
const { id, total, success = 0, failed = 0 } = options;
|
var task = this.tasks.get(options.id);
|
||||||
|
if (!task) return;
|
||||||
if (!this.currentTask || this.currentTask.id !== id) {
|
task.status = "finished";
|
||||||
return;
|
task.total = options.total !== undefined ? options.total : task.total;
|
||||||
}
|
task.success = options.success || 0;
|
||||||
|
task.failed = options.failed || 0;
|
||||||
this.currentTask.status = "finished";
|
task.current = task.total;
|
||||||
this.currentTask.total =
|
this.updateTaskCenter(options.id);
|
||||||
total !== undefined ? total : this.currentTask.total;
|
this.tasks.delete(options.id);
|
||||||
this.currentTask.success = success;
|
if (this.tasks.size === 0) {
|
||||||
this.currentTask.failed = failed;
|
|
||||||
this.currentTask.current = this.currentTask.total;
|
|
||||||
|
|
||||||
this.unlockUI();
|
this.unlockUI();
|
||||||
this.updateTaskCenter();
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1726,23 +1707,33 @@
|
|||||||
* @param {string} id - 任务ID
|
* @param {string} id - 任务ID
|
||||||
*/
|
*/
|
||||||
closeTask: function (id) {
|
closeTask: function (id) {
|
||||||
if (this.currentTask && this.currentTask.id === id) {
|
var task = this.tasks.get(id);
|
||||||
this.$taskCenter.removeClass("is-visible");
|
if (task) {
|
||||||
this.currentTask = null;
|
task.status = "closed";
|
||||||
|
this.tasks.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskCenter = this.$container.find(
|
||||||
|
`.g3-task-center[data-task-id="${id}"]`,
|
||||||
|
);
|
||||||
|
if (taskCenter.length) {
|
||||||
|
taskCenter.remove();
|
||||||
|
}
|
||||||
|
this.$taskCenter = null;
|
||||||
|
|
||||||
|
if (this.tasks.size === 0) {
|
||||||
|
this.unlockUI();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
renderTaskCenter: function (taskId) {
|
||||||
* 渲染任务中心 UI
|
var task = this.tasks.get(taskId);
|
||||||
*/
|
|
||||||
renderTaskCenter: function () {
|
|
||||||
const task = this.currentTask;
|
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
|
|
||||||
let taskCenter = this.$container.find(".g3-task-center");
|
let taskCenter = this.$container.find(".g3-task-center");
|
||||||
if (!taskCenter.length) {
|
if (!taskCenter.length) {
|
||||||
this.$container.append(`
|
this.$container.append(`
|
||||||
<div class="g3-task-center">
|
<div class="g3-task-center" data-task-id="${taskId}">
|
||||||
<div class="g3-task-header">
|
<div class="g3-task-header">
|
||||||
<span class="g3-task-title">${task.title}</span>
|
<span class="g3-task-title">${task.title}</span>
|
||||||
<button class="g3-task-close">×</button>
|
<button class="g3-task-close">×</button>
|
||||||
@@ -1767,31 +1758,19 @@
|
|||||||
<div class="g3-result-label">${this.language.task.failed}</div>
|
<div class="g3-result-label">${this.language.task.failed}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="g3-complete-button" style="display: none;">${this.language.task.close}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
taskCenter = this.$container.find(".g3-task-center");
|
taskCenter = this.$container.find(".g3-task-center");
|
||||||
|
|
||||||
const self = this;
|
|
||||||
taskCenter
|
|
||||||
.find(".task-close, .complete-button")
|
|
||||||
.off("click")
|
|
||||||
.on("click", function () {
|
|
||||||
self.closeTask(self.currentTask?.id);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$taskCenter = taskCenter;
|
this.$taskCenter = taskCenter;
|
||||||
this.$taskCenter.addClass("is-visible");
|
this.$taskCenter.addClass("is-visible");
|
||||||
this.updateTaskCenter();
|
this.updateTaskCenter(taskId);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
updateTaskCenter: function (taskId) {
|
||||||
* 更新任务中心 UI
|
var task = this.tasks.get(taskId);
|
||||||
*/
|
|
||||||
updateTaskCenter: function () {
|
|
||||||
const task = this.currentTask;
|
|
||||||
if (!task || !this.$taskCenter) return;
|
if (!task || !this.$taskCenter) return;
|
||||||
|
|
||||||
const progress =
|
const progress =
|
||||||
@@ -1812,7 +1791,6 @@
|
|||||||
|
|
||||||
if (task.status === "finished") {
|
if (task.status === "finished") {
|
||||||
this.$taskCenter.find(".g3-result-section").show();
|
this.$taskCenter.find(".g3-result-section").show();
|
||||||
this.$taskCenter.find(".g3-complete-button").show();
|
|
||||||
this.$taskCenter
|
this.$taskCenter
|
||||||
.find(".g3-result-item:first .g3-result-value")
|
.find(".g3-result-item:first .g3-result-value")
|
||||||
.text(task.total);
|
.text(task.total);
|
||||||
@@ -1824,7 +1802,6 @@
|
|||||||
.text(task.failed);
|
.text(task.failed);
|
||||||
} else {
|
} else {
|
||||||
this.$taskCenter.find(".g3-result-section").hide();
|
this.$taskCenter.find(".g3-result-section").hide();
|
||||||
this.$taskCenter.find(".g3-complete-button").hide();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1902,10 +1879,13 @@
|
|||||||
* @returns {boolean} 是否有权限
|
* @returns {boolean} 是否有权限
|
||||||
*/
|
*/
|
||||||
hasPermission: function (file, action) {
|
hasPermission: function (file, action) {
|
||||||
|
return this.resolvePermission(file, action);
|
||||||
|
},
|
||||||
|
|
||||||
|
resolvePermission: function (file, action) {
|
||||||
var globalPermission = this.options.globalPermission;
|
var globalPermission = this.options.globalPermission;
|
||||||
var filePermissionResolver = this.options.filePermissionResolver;
|
var filePermissionResolver = this.options.filePermissionResolver;
|
||||||
|
|
||||||
// 1. 优先判断单文件权限解析函数
|
|
||||||
if (typeof filePermissionResolver === "function") {
|
if (typeof filePermissionResolver === "function") {
|
||||||
var fileResult = filePermissionResolver({ file: file, action: action });
|
var fileResult = filePermissionResolver({ file: file, action: action });
|
||||||
if (fileResult !== undefined) {
|
if (fileResult !== undefined) {
|
||||||
@@ -1913,14 +1893,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 判断全局权限
|
|
||||||
if (typeof globalPermission === "function") {
|
if (typeof globalPermission === "function") {
|
||||||
return !!globalPermission({ file: file, action: action });
|
return !!globalPermission({ file: file, action: action });
|
||||||
} else if (typeof globalPermission === "object") {
|
} else if (typeof globalPermission === "object") {
|
||||||
return !!globalPermission[action];
|
return !!globalPermission[action];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认返回 true
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1979,11 +1957,9 @@
|
|||||||
* @returns {G3File} 实例
|
* @returns {G3File} 实例
|
||||||
*/
|
*/
|
||||||
setFiles: function (files) {
|
setFiles: function (files) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this.files = files.map(function (file) {
|
this.files = files.map(function (file) {
|
||||||
return {
|
return {
|
||||||
_id: file._id || self.generateUniqueId(),
|
_id: file._id || G3FileUtils.generateUniqueId(),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
uploadDate: file.uploadDate || new Date().toISOString(),
|
uploadDate: file.uploadDate || new Date().toISOString(),
|
||||||
@@ -1995,7 +1971,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.selectedFiles.clear();
|
this.selectedFiles.clear();
|
||||||
this.previewIndex = 0;
|
this.currentPreviewFileId = null;
|
||||||
this.renderFileList();
|
this.renderFileList();
|
||||||
this.updateUI();
|
this.updateUI();
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
@@ -2012,10 +1988,11 @@
|
|||||||
if (partialState.files) {
|
if (partialState.files) {
|
||||||
this.setFiles(partialState.files);
|
this.setFiles(partialState.files);
|
||||||
} else {
|
} else {
|
||||||
// 其他状态更新时关闭当前任务
|
// 其他状态更新时关闭所有运行中的任务
|
||||||
if (this.currentTask) {
|
var self = this;
|
||||||
this.closeTask(this.currentTask.id);
|
this.tasks.forEach(function (task, taskId) {
|
||||||
}
|
self.closeTask(taskId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (partialState.selectedFiles) {
|
if (partialState.selectedFiles) {
|
||||||
@@ -2024,8 +2001,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
partialState.globalPermission ||
|
Object.prototype.hasOwnProperty.call(
|
||||||
partialState.filePermissionResolver
|
partialState,
|
||||||
|
"globalPermission",
|
||||||
|
) ||
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
partialState,
|
||||||
|
"filePermissionResolver",
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
this.updatePermissions({
|
this.updatePermissions({
|
||||||
globalPermission: partialState.globalPermission,
|
globalPermission: partialState.globalPermission,
|
||||||
@@ -2040,10 +2023,13 @@
|
|||||||
* 销毁
|
* 销毁
|
||||||
*/
|
*/
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
this.$container.off();
|
// 清理 document 事件
|
||||||
|
$(document).off(this.documentNamespace);
|
||||||
|
$(document).off(".g3file_actionmenu_" + this.instanceId);
|
||||||
|
|
||||||
// 清理 ObjectURL
|
// 清理 ObjectURL
|
||||||
this.objectUrls.forEach((url) => {
|
var self = this;
|
||||||
|
this.objectUrls.forEach(function (url) {
|
||||||
try {
|
try {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -2052,10 +2038,24 @@
|
|||||||
});
|
});
|
||||||
this.objectUrls.clear();
|
this.objectUrls.clear();
|
||||||
|
|
||||||
// 清理引用
|
// 清理任务
|
||||||
|
this.tasks.forEach(function (task, taskId) {
|
||||||
|
self.closeTask(taskId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理 DOM
|
||||||
|
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.$container.empty();
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
this.files = [];
|
this.files = [];
|
||||||
this.selectedFiles.clear();
|
this.selectedFiles.clear();
|
||||||
this.$container.empty();
|
this.tasks.clear();
|
||||||
|
this.currentPreviewFileId = null;
|
||||||
|
this.uiLocked = false;
|
||||||
|
|
||||||
console.log("G3File 组件已销毁");
|
console.log("G3File 组件已销毁");
|
||||||
},
|
},
|
||||||
@@ -2085,6 +2085,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (typeof options === "string") {
|
||||||
|
return methodResult !== undefined ? methodResult : this;
|
||||||
|
}
|
||||||
|
|
||||||
return instance || this;
|
return instance || this;
|
||||||
};
|
};
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
Reference in new issue
Block a user