diff --git a/code/file-preview/.trae/documents/add-features-plan.md b/code/file-preview/.trae/documents/add-features-plan.md
new file mode 100644
index 00000000..94f61063
--- /dev/null
+++ b/code/file-preview/.trae/documents/add-features-plan.md
@@ -0,0 +1,127 @@
+# 功能添加计划
+
+## 需求分析
+
+用户要求添加两个功能:
+
+### 功能一:双击事件扩展
+
+- 添加 `onDblClick` 回调选项,供外部使用
+- 如果外部注册了 `onDblClick`,则优先执行外部回调
+- 如果外部未注册 `onDblClick`,则双击文件时跳转到文件预览页面(fullPreviewUrl)
+
+### 功能二:onUploadBefore 回调
+
+- 添加 `onUploadBefore` 回调选项,参考 `onBeforeDelete` 的实现方式
+- 支持返回值类型:
+ - `false`: 阻止上传
+ - Promise: 异步确认后再上传
+ - Function: 回调确认后再上传
+ - 其他/undefined: 继续执行上传
+- 同时将 `onBeforeDelete 名称修改为 onDeleteBefore`
+
+## 修改文件
+
+1. **index.js**:
+ - 在 defaults 配置中添加 `onDblClick` 和 `onUploadBefore`
+ - 在 bindEvents 中添加文件项双击事件处理
+ - 在 handleUpload 方法中添加 onUploadBefore 逻辑
+
+## 实现步骤
+
+### 步骤一:添加配置项
+
+在 `defaults` 对象中添加:
+
+```javascript
+onDblClick: null,
+onUploadBefore: null,
+```
+
+### 步骤二:添加双击事件处理
+
+在文件列表事件委托中添加双击事件:
+
+```javascript
+// 文件项双击
+.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;
+ if (!self.hasPermission(file, "view")) return;
+
+ // 如果外部注册了 onDblClick,优先执行外部回调
+ if (typeof self.options.onDblClick === "function") {
+ self.options.onDblClick(file);
+ return;
+ }
+
+ // 否则跳转到预览页面
+ var isUploaded = file.status === "uploaded";
+ var isPreviewable = self.isPreviewableFile(file.name);
+ var fileUrl = file.url && file.url !== "" ? file.url : null;
+
+ if (isUploaded && fileUrl && isPreviewable) {
+ var previewUrl = self.options.previewUrl || "";
+ var fullPreviewUrl = previewUrl
+ ? previewUrl + G3FileUtils.base64Encode(fileUrl)
+ : fileUrl;
+ window.open(fullPreviewUrl, "_blank");
+ }
+})
+```
+
+### 步骤三:添加 onUploadBefore 逻辑
+
+在 `handleUpload` 方法开头添加:
+
+```javascript
+// onUploadBefore 处理
+if (typeof self.options.onUploadBefore === "function") {
+ var result = self.options.onUploadBefore(pendingFiles);
+ if (result === false) return;
+ if (typeof result.then === "function") {
+ result.then(function (confirmed) {
+ if (confirmed) self.internalHandleUpload(pendingFiles, taskId);
+ });
+ return;
+ }
+ if (typeof result === "function") {
+ result(function (confirmed) {
+ if (confirmed) self.internalHandleUpload(pendingFiles, taskId);
+ });
+ return;
+ }
+}
+```
+
+需要将原有的上传逻辑提取为 `internalHandleUpload` 方法。
+
+## 风险处理
+
+1. **兼容性风险**:确保修改不会影响现有功能
+2. **异步处理风险**:正确处理 Promise 和回调函数的情况
+3. **权限检查**:双击事件需要检查 view 权限
+
+## 测试要点
+
+1. 测试双击文件时:
+ - 注册 onDblClick 时是否优先执行外部回调
+ - 未注册 onDblClick 时是否正确跳转到预览页面
+ - 未上传的文件双击时不跳转
+ - 无权限文件双击时不响应
+2. 测试上传时:
+ - onUploadBefore 返回 false 时是否阻止上传
+ - onUploadBefore 返回 Promise 时是否等待异步结果
+ - onUploadBefore 返回回调函数时是否正确处理
+
+## 依赖分析
+
+- 依赖 `isPreviewableFile` 方法判断文件是否可预览
+- 依赖 `G3FileUtils.base64Encode` 方法编码 URL
+- 依赖 `getFileById` 方法获取文件信息
+
diff --git a/code/file-preview/.trae/documents/rename-callbacks-plan.md b/code/file-preview/.trae/documents/rename-callbacks-plan.md
new file mode 100644
index 00000000..80976788
--- /dev/null
+++ b/code/file-preview/.trae/documents/rename-callbacks-plan.md
@@ -0,0 +1,52 @@
+# 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 都被正确移除
+
diff --git a/code/file-preview/demo.html b/code/file-preview/demo.html
new file mode 100644
index 00000000..7530090c
--- /dev/null
+++ b/code/file-preview/demo.html
@@ -0,0 +1,431 @@
+
+
+
+
+
@@ -845,7 +830,10 @@
var $currentIframe = $content.find(".g3-preview-iframe.current");
var $nextIframe = $content.find(".g3-preview-iframe.next");
- if ($nextIframe.length && this.previewCache.nextUrl === fullPreviewUrl) {
+ if (
+ $nextIframe.length &&
+ this.previewCache.nextUrl === fullPreviewUrl
+ ) {
$currentIframe.removeClass("current").addClass("next").hide();
$nextIframe.removeClass("next").addClass("current").show();
this.previewCache.currentUrl = fullPreviewUrl;
@@ -880,12 +868,14 @@
newIframe.on("error", function () {
newIframe.remove();
- self.showPlaceholder($content, `
${self.language.urlError}
`);
+ self.showPlaceholder(
+ $content,
+ `
${self.language.urlError}
`,
+ );
});
$content.append(newIframe);
this.previewCache.nextUrl = fullPreviewUrl;
-
} else {
this.previewCache = null;
var tipsHtml = "";
@@ -910,285 +900,6 @@
`);
},
- showModalPlaceholder: function ($container, tipsHtml) {
- $container.find(".g3-modal-preview-iframe").remove();
- $container.html(`
-
- ${tipsHtml}
-
- `);
- },
-
- openModal: function (options) {
- var defaults = {
- title: "",
- content: "",
- width: "80%",
- height: "80%",
- showToggle: true,
- showClose: true
- };
-
- var opts = $.extend({}, defaults, options);
-
- this.$modalTitle.text(opts.title || "");
- this.$modalBody.html(opts.content || "");
-
- this.$modal.css({
- width: opts.width,
- height: opts.height
- });
-
- this.$modalBtnToggle.toggle(opts.showToggle);
- this.$modalBtnClose.toggle(opts.showClose);
-
- this.$modalOverlay.addClass("is-visible");
- this.$modal.removeClass("is-maximized");
- this.$modalBtnToggle.html(G3FileIcons.modal.maximize);
- this.$modalBtnToggle.attr("title", this.language.modal.maximize);
- },
-
- openPreviewModal: function (fileId) {
- var file = this.getFileById(fileId);
- if (!file) return;
-
- var isUploaded = file.status === "uploaded";
- var isPreviewable = this.isPreviewableFile(file.name);
- var fileUrl = file.url && file.url !== "" ? file.url : null;
-
- var toolbarHtml = `
-
- `;
-
- var contentHtml = "";
- if (isUploaded && fileUrl && isPreviewable) {
- var previewUrl = this.options.previewUrl || "";
- var fullPreviewUrl = previewUrl
- ? previewUrl + G3FileUtils.base64Encode(fileUrl)
- : fileUrl;
-
- contentHtml = `
-
-
-
- `;
- } else {
- var tipsHtml = "";
- if (!isUploaded) {
- tipsHtml = `
${this.language.uploadToPreview}
`;
- } else if (!isPreviewable) {
- tipsHtml = `
${this.language.downloadToPreview}
`;
- } else if (!fileUrl) {
- tipsHtml = `
${this.language.urlError}
`;
- }
-
- contentHtml = `
-
- `;
- }
-
- var previewContent = toolbarHtml + contentHtml;
-
- this.currentModalFileId = fileId;
-
- this.openModal({
- title: this.language.filePreview,
- content: previewContent,
- width: "90%",
- height: "90%",
- });
-
- var self = this;
- setTimeout(function () {
- self.$modal.find(".g3-modal-preview-prev").off("click").on("click", function () {
- self.modalPrev();
- });
- self.$modal.find(".g3-modal-preview-next").off("click").on("click", function () {
- self.modalNext();
- });
- self.updateModalPreviewCounter(fileId);
- }, 0);
- },
-
- updateModalPreviewCounter: function (fileId) {
- var allFiles = this.getUploadedFiles();
- var currentIndex = allFiles.findIndex(function (f) { return f._id === fileId; });
- var total = allFiles.length;
-
- var $counter = this.$modal.find(".g3-modal-preview-counter");
- var $prevBtn = this.$modal.find(".g3-modal-preview-prev");
- var $nextBtn = this.$modal.find(".g3-modal-preview-next");
-
- if ($counter.length) {
- $counter.text((currentIndex + 1) + "/" + total);
- }
- if ($prevBtn.length) {
- $prevBtn.prop("disabled", currentIndex === 0);
- }
- if ($nextBtn.length) {
- $nextBtn.prop("disabled", currentIndex === total - 1);
- }
- },
-
- modalPrev: function () {
- var uploadedFiles = this.getUploadedFiles();
- if (uploadedFiles.length === 0) return;
-
- var currentIndex = this.getPreviewFileIndex(this.currentModalFileId);
- var newIndex = currentIndex > 0 ? currentIndex - 1 : uploadedFiles.length - 1;
- this.currentModalFileId = uploadedFiles[newIndex]._id;
- this.updateModalPreviewWithFile(uploadedFiles[newIndex]);
- },
-
- modalNext: function () {
- var uploadedFiles = this.getUploadedFiles();
- if (uploadedFiles.length === 0) return;
-
- var currentIndex = this.getPreviewFileIndex(this.currentModalFileId);
- var newIndex = currentIndex < uploadedFiles.length - 1 ? currentIndex + 1 : 0;
- this.currentModalFileId = uploadedFiles[newIndex]._id;
- this.updateModalPreviewWithFile(uploadedFiles[newIndex]);
- },
-
- updateModalPreviewWithFile: function (file) {
- var $content = this.$modal.find(".g3-modal-preview-content");
- var $title = this.$modal.find(".g3-modal-title");
- var $filename = this.$modal.find(".g3-modal-preview-filename");
-
- if (!file || !$content.length) return;
-
- $title.text(this.language.filePreview);
- if ($filename.length) {
- $filename.text(file.name);
- }
-
- var isUploaded = file.status === "uploaded";
- var isPreviewable = this.isPreviewableFile(file.name);
- var fileUrl = file.url && file.url !== "" ? file.url : null;
-
- if (isUploaded && fileUrl && isPreviewable) {
- var previewUrl = this.options.previewUrl || "";
- var fullPreviewUrl = previewUrl
- ? previewUrl + G3FileUtils.base64Encode(fileUrl)
- : fileUrl;
-
- var newIframe = $(`
-
- `);
-
- $content.html(newIframe);
- } else {
- var tipsHtml = "";
- if (!isUploaded) {
- tipsHtml = `
${this.language.uploadToPreview}
`;
- } else if (!isPreviewable) {
- tipsHtml = `
${this.language.downloadToPreview}
`;
- } else if (!fileUrl) {
- tipsHtml = `
${this.language.urlError}
`;
- }
-
- $content.html(`
-
- ${tipsHtml}
-
- `);
- }
-
- this.updateModalPreviewCounter(file._id);
- },
-
- closeModal: function () {
- this.$modalOverlay.removeClass("is-visible");
- this.$modalBody.html("");
- this.$modalTitle.text("");
- this.setModalFooter("");
- this.currentModalFileId = null;
- },
-
- toggleModalSize: function () {
- var isMaximized = this.$modal.hasClass("is-maximized");
-
- if (isMaximized) {
- this.$modal.removeClass("is-maximized");
- this.$modal.css({
- width: "80%",
- height: "80%",
- maxWidth: "none",
- borderRadius: "8px"
- });
- this.$modalBtnToggle.html(G3FileIcons.modal.maximize);
- this.$modalBtnToggle.attr("title", this.language.modal.maximize);
- } else {
- this.$modal.addClass("is-maximized");
- this.$modal.css({
- width: "100%",
- height: "100%",
- maxWidth: "none",
- borderRadius: "0"
- });
- this.$modalBtnToggle.html(G3FileIcons.modal.restore);
- this.$modalBtnToggle.attr("title", this.language.modal.restore);
- }
- },
-
- setModalTitle: function (title) {
- if (this.$modalTitle) {
- this.$modalTitle.text(title || "");
- }
- },
-
- setModalContent: function (content) {
- if (this.$modalBody) {
- this.$modalBody.html(content || "");
- }
- },
-
- setModalSize: function (width, height) {
- if (!this.$modal) return;
-
- this.$modal.css({
- width: width || "80%",
- height: height || "80%"
- });
- },
-
- setModalFooter: function (content) {
- if (!this.$modalFooter) return;
-
- if (content && content.trim()) {
- this.$modalFooter.html(content);
- this.$modalFooter.addClass("has-content");
- } else {
- this.$modalFooter.html("");
- this.$modalFooter.removeClass("has-content");
- }
- },
-
setPreviewFile: function (fileId) {
var file = this.getFileById(fileId);
if (file) {
@@ -1352,9 +1063,6 @@
if (!file) return;
if (!self.hasPermission(file, "view")) return;
self.setPreviewFile(id);
- if (typeof self.options.onPreview === "function") {
- self.options.onPreview([file]);
- }
})
// 下载按钮
.on("click", ".g3-action-download", function (e) {
@@ -1399,12 +1107,9 @@
var file = self.getFileById(id);
if (!file) return;
if (!self.hasPermission(file, "view")) return;
- if (typeof self.options.onPreview === "function") {
- self.options.onPreview([file]);
- }
self.setPreviewFile(id);
})
- // 文件项双击打开 modal 预览
+ // 文件项双击
.on("dblclick", ".g3-file-item", function (e) {
if (
$(e.target).closest(
@@ -1417,8 +1122,25 @@
var file = self.getFileById(id);
if (!file) return;
if (!self.hasPermission(file, "view")) return;
- self.openPreviewModal(id);
+
+ if (typeof self.options.onDblClick === "function") {
+ self.options.onDblClick(file);
+ return;
+ }
+
+ var isUploaded = file.status === "uploaded";
+ var isPreviewable = self.isPreviewableFile(file.name);
+ var fileUrl = file.url && file.url !== "" ? file.url : null;
+
+ if (isUploaded && fileUrl && isPreviewable) {
+ var previewUrl = self.options.previewUrl || "";
+ var fullPreviewUrl = previewUrl
+ ? previewUrl + G3FileUtils.base64Encode(fileUrl)
+ : fileUrl;
+ window.open(fullPreviewUrl, "_blank");
+ }
})
+
// 更多按钮
.on("click", ".g3-action-more", function (e) {
e.preventDefault();
@@ -1437,23 +1159,6 @@
self.openActionMenu($el, file);
});
- // Modal 事件绑定
- this.$modalBtnClose.off("click").on("click", function () {
- self.closeModal();
- });
-
- this.$modalBtnToggle.off("click").on("click", function (e) {
- e.preventDefault();
- e.stopPropagation();
- self.toggleModalSize();
- });
-
- this.$modalOverlay.off("click").on("click", function (e) {
- if (e.target === this) {
- self.closeModal();
- }
- });
-
// 任务关闭按钮事件委托
this.$container.on("click", ".g3-task-close", function (e) {
e.preventDefault();
@@ -1575,8 +1280,8 @@
return;
}
- if (typeof this.options.onBeforeDelete === "function") {
- var result = this.options.onBeforeDelete(files);
+ if (typeof this.options.onDeleteBefore === "function") {
+ var result = this.options.onDeleteBefore(files);
if (result === false) return;
if (typeof result.then === "function") {
result.then(function (confirmed) {
@@ -1881,6 +1586,39 @@
return;
}
+ if (typeof this.options.onUploadBefore === "function") {
+ var result = this.options.onUploadBefore(pendingFiles);
+ if (result === false) return;
+ if (typeof result.then === "function") {
+ result.then(function (confirmed) {
+ if (confirmed)
+ self.internalHandleUpload(
+ pendingFiles,
+ customUpload,
+ concurrency,
+ );
+ });
+ return;
+ }
+ if (typeof result === "function") {
+ result(function (confirmed) {
+ if (confirmed)
+ self.internalHandleUpload(
+ pendingFiles,
+ customUpload,
+ concurrency,
+ );
+ });
+ return;
+ }
+ }
+
+ self.internalHandleUpload(pendingFiles, customUpload, concurrency);
+ },
+
+ internalHandleUpload: function (pendingFiles, customUpload, concurrency) {
+ var self = this;
+
var total = pendingFiles.length;
var taskId = this.startTask({
id: G3FileUtils.generateUniqueId(),
@@ -2273,7 +2011,9 @@
*/
openActionMenu: function ($el, file) {
var items =
- (this.options.getActionMenu && this.options.getActionMenu(file)) || [];
+ (this.options.customActionMenu &&
+ this.options.customActionMenu(file)) ||
+ [];
this.actionMenuState = {
open: true,
@@ -2404,10 +2144,7 @@
}
if (
- Object.prototype.hasOwnProperty.call(
- partialState,
- "globalPermission",
- )
+ Object.prototype.hasOwnProperty.call(partialState, "globalPermission")
) {
this.updatePermissions(partialState.globalPermission);
}
diff --git a/code/file-preview/g3-file/language.js b/code/file-preview/g3-file/language.js
index 03ece5d2..01c35de9 100644
--- a/code/file-preview/g3-file/language.js
+++ b/code/file-preview/g3-file/language.js
@@ -57,12 +57,7 @@ var G3FileLanguages = {
currentItem: "当前项",
close: "关闭",
},
- modal: {
- maximize: "全屏",
- restore: "恢复",
- close: "关闭",
},
- },
"en-US": {
selectAll: "Select All",
download: "Download",
@@ -123,12 +118,7 @@ var G3FileLanguages = {
currentItem: "Current",
close: "Close",
},
- modal: {
- maximize: "Maximize",
- restore: "Restore",
- close: "Close",
},
- },
"ru": {
selectAll: "Выделить все",
download: "Скачать",
@@ -187,10 +177,5 @@ var G3FileLanguages = {
currentItem: "Текущий",
close: "Закрыть",
},
- modal: {
- maximize: "На весь экран",
- restore: "Восстановить",
- close: "Закрыть",
},
- },
};
\ No newline at end of file
diff --git a/code/file-preview/iframe-demo.html b/code/file-preview/iframe-demo.html
new file mode 100644
index 00000000..a333690f
--- /dev/null
+++ b/code/file-preview/iframe-demo.html
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
G3File iframe 演示
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
功能特性
+
+ - 文件列表展示与管理
+ - 文件上传(支持阿里云 OSS)
+ - 文件预览与下载
+ - 文件删除
+ - 双击事件支持
+ - 自定义操作菜单
+ - 上传/删除前确认回调
+ - 多选与批量操作
+
+
+
+
+
+
\ No newline at end of file
diff --git a/code/file-preview/index.html b/code/file-preview/index.html
index 9f462c2c..0e6946ce 100644
--- a/code/file-preview/index.html
+++ b/code/file-preview/index.html
@@ -1,241 +1,164 @@
-
-
+
+
-
文件预览测试
+
+
+
G3File 基础示例
+
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
-
\ No newline at end of file
diff --git a/code/file-preview/test.html b/code/file-preview/test.html
deleted file mode 100644
index 2540f218..00000000
--- a/code/file-preview/test.html
+++ /dev/null
@@ -1,227 +0,0 @@
-
-
-
-
-
-
G3 File UI Demo
-
-
-
-
-
-
-
-
-
-
-
-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
-
-
-
finished.pdf
-
- 1.1 MB
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
-
-
-
error-image.png
-
- 5.6 MB
- 上传失败
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file