From 3620369d471d63849208e17164fca37e9cc17066 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Wed, 29 Apr 2026 17:29:52 +0800 Subject: [PATCH] u --- .../.trae/documents/file_actions_plan.md | 55 ++ .../.trae/documents/modal_iframe_fix_plan.md | 60 ++ .../.trae/documents/modal_preview_plan.md | 143 ++++ code/file-preview/g3-file/icon.js | 27 +- code/file-preview/g3-file/index.css | 278 +++++++- code/file-preview/g3-file/index.js | 522 ++++++++++++-- code/file-preview/g3-file/language.js | 76 ++ code/file-preview/index.html | 80 +-- code/file-preview/test.html | 664 +++++------------- 9 files changed, 1270 insertions(+), 635 deletions(-) create mode 100644 code/file-preview/.trae/documents/file_actions_plan.md create mode 100644 code/file-preview/.trae/documents/modal_iframe_fix_plan.md create mode 100644 code/file-preview/.trae/documents/modal_preview_plan.md diff --git a/code/file-preview/.trae/documents/file_actions_plan.md b/code/file-preview/.trae/documents/file_actions_plan.md new file mode 100644 index 00000000..befa7b93 --- /dev/null +++ b/code/file-preview/.trae/documents/file_actions_plan.md @@ -0,0 +1,55 @@ +# g3-file-actions 修改计划 + +## 需求分析 + +用户要求修改 `g3-file-item` 中的 `g3-file-actions`: + +1. **默认不显示,hover 时显示**:操作按钮默认隐藏,鼠标悬停在文件项上时才显示 +2. **根据 getActionMenu 动态显示 more 按钮**:如果 `getActionMenu` 返回空数组或为空,则不显示「更多」(more) 操作按钮 + +## 修改内容 + +### 1. CSS 修改(index.css) + +修改 `.g3-file-actions` 的样式,使其默认隐藏,hover 时显示: + +```css +.g3-file-actions { + display: none; /* 默认隐藏 */ + /* ... 其他样式 */ +} + +.g3-file-item:hover .g3-file-actions { + display: flex; /* hover 时显示 */ +} +``` + +### 2. JavaScript 修改(index.js) + +修改 `createFileItem` 方法,动态判断是否渲染 `g3-action-more` 按钮: + +- 在生成文件项 HTML 前,先调用 `getActionMenu(file)` 获取自定义菜单 +- 如果返回结果为空(空数组或 undefined),则不渲染 `g3-action-more` 按钮 + +## 文件列表 + +- `d:\workspace\code\file-preview\g3-file\index.css` - 修改样式 +- `d:\workspace\code\file-preview\g3-file\index.js` - 修改 createFileItem 方法 + +## 风险评估 + +1. **兼容性风险**:修改 hover 显示可能影响现有交互逻辑,需确保不影响其他功能 +2. **性能风险**:每次渲染文件项都会调用 getActionMenu,需确保该函数执行效率 +3. **样式风险**:hover 显示可能影响布局,需要适当调整定位 + +## 实施步骤 + +1. 修改 CSS 文件,添加 hover 显示逻辑 +2. 修改 createFileItem 方法,根据 getActionMenu 结果动态渲染 more 按钮 +3. 测试验证功能是否正常工作 + +## 预期效果 + +- 文件项默认只显示基本信息(文件名、大小、状态等) +- 鼠标悬停时显示操作按钮(预览、下载、更多) +- 如果没有自定义菜单,「更多」按钮不显示 diff --git a/code/file-preview/.trae/documents/modal_iframe_fix_plan.md b/code/file-preview/.trae/documents/modal_iframe_fix_plan.md new file mode 100644 index 00000000..8184b46e --- /dev/null +++ b/code/file-preview/.trae/documents/modal_iframe_fix_plan.md @@ -0,0 +1,60 @@ +# g3-file-actions 修改计划 + +## 需求分析 + +用户要求修改 `g3-file-item` 中的 `g3-file-actions`: + +1. **默认不显示,hover 时显示**:操作按钮默认隐藏,鼠标悬停在文件项上时才显示 +2. **根据 getActionMenu 动态显示 more 按钮**:如果 `getActionMenu` 返回空数组或为空,则不显示「更多」(more) 操作按钮 + +## 修改内容 + +### 1. CSS 修改(index.css) + +修改 `.g3-file-actions` 的样式,使其默认隐藏,hover 时显示: + +```css +.g3-file-actions { + display: none; /* 默认隐藏 */ + /* ... 其他样式 */ +} + +.g3-file-item:hover .g3-file-actions { + display: flex; /* hover 时显示 */ +} +``` + +### 2. JavaScript 修改(index.js) + +修改 `createFileItem` 方法,动态判断是否渲染 `g3-action-more` 按钮: + +* 在生成文件项 HTML 前,先调用 `getActionMenu(file)` 获取自定义菜单 + +* 如果返回结果为空(空数组或 undefined),则不渲染 `g3-action-more` 按钮 + +## 文件列表 + +* `d:\workspace\code\file-preview\g3-file\index.css` - 修改样式 + +* `d:\workspace\code\file-preview\g3-file\index.js` - 修改 createFileItem 方法 + +## 风险评估 + +1. **兼容性风险**:修改 hover 显示可能影响现有交互逻辑,需确保不影响其他功能 +2. **性能风险**:每次渲染文件项都会调用 getActionMenu,需确保该函数执行效率 +3. **样式风险**:hover 显示可能影响布局,需要适当调整定位 + +## 实施步骤 + +1. 修改 CSS 文件,添加 hover 显示逻辑 +2. 修改 createFileItem 方法,根据 getActionMenu 结果动态渲染 more 按钮 +3. 测试验证功能是否正常工作 + +## 预期效果 + +* 文件项默认只显示基本信息(文件名、大小、状态等) + +* 鼠标悬停时显示操作按钮(预览、下载、更多) + +* 如果没有自定义菜单,「更多」按钮不显示 + diff --git a/code/file-preview/.trae/documents/modal_preview_plan.md b/code/file-preview/.trae/documents/modal_preview_plan.md new file mode 100644 index 00000000..ae557ee7 --- /dev/null +++ b/code/file-preview/.trae/documents/modal_preview_plan.md @@ -0,0 +1,143 @@ +# Modal 预览功能实现计划 + +## 需求分析 + +用户需要实现双击文件项弹出 modal 预览的功能,具体要求: + +1. 双击 `file-item` 触发 modal 预览 +2. modal 需设计为通用组件,便于后续复用 +3. modal header:title 居中显示文件名,右侧包含最小化/最大化/关闭按钮 +4. modal content:预览区域,与现有单击预览布局一致 +5. 支持左右切换预览文件 +6. 文件名作为 modal 的 title + +## 实现方案 + +### 1. CSS 修改 (index.css) + +添加通用 modal 样式,包括: + +* `.g3-modal`: modal 遮罩层 + +* `.g3-modal-content`: modal 主体容器 + +* `.g3-modal-header`: 头部区域,包含 title 和操作按钮 + +* `.g3-modal-title`: 居中的标题 + +* `.g3-modal-actions`: 右侧操作按钮组(min/max/close) + +* `.g3-modal-body`: 预览内容区域 + +* `.g3-modal-preview`: 预览内容布局(与现有预览一致) + +* `.g3-modal-footer`: 底部区域,现在预览用不到,但是仍然需要保留,方便以后使用 + +### 2. JavaScript 修改 (index.js) + +添加以下功能: + +* `openModal(fileId)`: 打开 modal 并显示指定文件预览 + +* `closeModal()`: 关闭 modal + +* `toggleModalSize()`: 切换 modal 大小(最大化/还原) + +* `modalPrev()`: modal 内预览上一个文件 + +* `modalNext()`: modal 内预览下一个文件 + +* `updateModalPreview()`: 更新 modal 预览内容 + +* 双击事件绑定 + +* 同时需要考虑国际化,国际化配置在 language.js + +* 图标配置在 icon.js + +### 3. 关键设计 + +**双预览状态分离**: + +* `currentPreviewFileId`: 右侧面板预览的文件 ID + +* `currentModalFileId`: modal 预览的文件 ID(独立状态) + +**通用 modal 设计**: + +* modal 使用固定定位,居中显示 + +* 支持最大化/还原 + +* 点击遮罩层可关闭 + +**预览内容复用**: + +* 提取公共预览内容生成逻辑 + +* modal 内预览布局与右侧面板保持一致 + +## 修改文件列表 + +| 文件 | 修改类型 | 说明 | +| ------------------- | ---- | ------------------ | +| `g3-file/index.css` | 添加 | 添加 modal 相关样式 | +| `g3-file/index.js` | 修改 | 添加 modal 相关方法和事件绑定 | + +## 步骤分解 + +1. **添加 modal CSS 样式** + + * 遮罩层样式 + + * modal 主体样式 + + * header 样式(title 居中,按钮组) + + * body 样式(预览区域) + +2. **添加 modal HTML 结构** + + * 在 render 方法中添加 modal 模板 + +3. **添加 modal 控制方法** + + * `openModal(fileId)` + + * `closeModal()` + + * `toggleModalSize()` + + * `modalPrev()` + + * `modalNext()` + + * `updateModalPreview()` + +4. **绑定双击事件** + + * 在文件列表事件委托中添加 dblclick 事件 + +5. **初始化 modal DOM 引用** + + * 在 render 方法中保存 modal 相关 DOM 引用 + +## 风险考虑 + +1. **状态冲突**:双击和单击可能同时触发,需确保状态隔离 +2. **内存泄漏**:modal 关闭时需清理 iframe 资源 +3. **样式冲突**:modal 样式需独立命名空间,避免与现有样式冲突 +4. **响应式布局**:modal 需要适配不同屏幕尺寸 + +## 预期效果 + +* 双击文件项弹出居中的 modal + +* modal 头部显示文件名,右侧有操作按钮 + +* 预览内容与右侧面板布局一致,支持左右切换 + +* 最大化时 modal 占满整个屏幕 + +* 点击遮罩层或 close 按钮关闭 modal + diff --git a/code/file-preview/g3-file/icon.js b/code/file-preview/g3-file/icon.js index 08a48aa3..68369b88 100644 --- a/code/file-preview/g3-file/icon.js +++ b/code/file-preview/g3-file/icon.js @@ -20,24 +20,29 @@ var G3FileIcons = { more: ` `, - + }, file: { default: '', - audio:``, - video:``, - txt:``, - pdf:``, - docs:``, - zip:``, + audio: ``, + video: ``, + txt: ``, + pdf: ``, + docs: ``, + zip: ``, }, preview: { - prev: '', - next: '', - imgLoadError:` - + prev: '', + next: '', + imgLoadError: ` + ` }, + modal: { + minimize: '', + maximize: '', + close: '' + }, toast: { success: '', error: '', diff --git a/code/file-preview/g3-file/index.css b/code/file-preview/g3-file/index.css index f883932c..c581cea0 100644 --- a/code/file-preview/g3-file/index.css +++ b/code/file-preview/g3-file/index.css @@ -18,7 +18,7 @@ } .g3-left-panel { - width: 50%; + width: 30%; display: flex; flex-direction: column; border-right: 1px solid #eef0f2; @@ -255,19 +255,23 @@ align-items: center; justify-content: flex-end; overflow: hidden; - border-top: 1px solid #f0f2f5; + padding: 4px; + /* border-top: 1px solid #f0f2f5; */ } .g3-file-action { - padding: 8px 2px; - flex: 1; + padding: 8px; + /* flex: 1; */ + border: 1px solid #eee; + margin-left: 6px; + border-radius: 999px; display: flex; align-items: center; justify-content: center; gap: 4px; cursor: pointer; font-size: 12px; - border-right: 1px solid #f0f2f5; + /* border-right: 1px solid #f0f2f5; */ } .g3-file-action:hover { @@ -286,17 +290,17 @@ } .g3-file-action:first-child { - border-radius: 0 0 0 8px; + /* border-radius: 0 0 0 8px; */ } .g3-file-action:last-child { - border-right: none; - border-radius: 0 0 8px 0; + /* border-right: none; + border-radius: 0 0 8px 0; */ } .g3-file-action svg { - width: 20px; - height: 20px; + width: 16px; + height: 16px; color: #666; } @@ -386,8 +390,8 @@ width: 20px; height: 20px; border-radius: 50%; - background: #ebebeb; - color: #666; + background: #ffebeb; + color: #ff4d4f; display: flex; align-items: center; justify-content: center; @@ -421,7 +425,6 @@ height: 50px; display: flex; align-items: center; - justify-content: space-between; padding: 0 16px; background: #ffffff; border-bottom: 1px solid #eef0f2; @@ -480,10 +483,11 @@ font-size: 15px; color: #333; font-weight: 600; + max-width: 300px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - margin-left: 12px; + margin-left: 16px; } .g3-preview-content { @@ -522,6 +526,24 @@ margin: 0; padding: 0; background: #ffffff; + position: absolute; + top: 0; + left: 0; + transition: opacity 0.3s ease; +} + +.g3-preview-content .g3-preview-iframe.current { + opacity: 1; + z-index: 1; +} + +.g3-preview-content .g3-preview-iframe.next { + opacity: 0; + z-index: 2; +} + +.g3-preview-content { + position: relative; } .g3-preview-content .preview-file-info { @@ -884,3 +906,231 @@ .g3-ui-lock.is-locked { display: block; } + +/* ============================================================================ + Modal 样式 + ============================================================================ */ + +.g3-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: var(--g3-z-modal); + display: none; + align-items: center; + justify-content: center; +} + +.g3-modal-overlay.is-visible { + display: flex; +} + +.g3-modal { + width: 90%; + height: 90%; + background: #ffffff; + border-radius: 8px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + display: flex; + flex-direction: column; + overflow: hidden; + transition: + transform 0.3s ease, + opacity 0.3s ease; + transform: scale(0.95); + opacity: 0; +} + +.g3-modal-overlay.is-visible .g3-modal { + transform: scale(1); + opacity: 1; +} + +.g3-modal.is-maximized { + width: 100%; + height: 100%; + max-width: none; + border-radius: 0; +} + +.g3-modal-header { + height: 48px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 16px; + background: #fafbfc; + border-bottom: 1px solid #eef0f2; + flex-shrink: 0; +} + +.g3-modal-title { + font-size: 14px; + font-weight: 500; + color: #333; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.g3-modal-actions { + display: flex; + align-items: center; + gap: 4px; +} + +.g3-modal-btn { + width: 32px; + height: 32px; + border: none; + background: transparent; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background-color 0.2s ease; + color: #666; +} + +.g3-modal-btn:hover { + background: #f0f2f5; + color: #333; +} + +.g3-modal-btn-close:hover { + background: #ff4d4f; + color: #fff; +} + +.g3-modal-body { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.g3-modal-preview-toolbar { + height: 48px; + display: flex; + align-items: center; + padding: 0 16px; + background: #ffffff; + border-bottom: 1px solid #eef0f2; + flex-shrink: 0; +} + +.g3-modal-preview-nav { + display: flex; + align-items: center; + gap: 12px; +} + +.g3-modal-preview-nav .g3-modal-preview-counter { + font-size: 14px; + color: #666; + font-weight: 500; + min-width: 50px; + text-align: center; +} + +.g3-modal-preview-filename { + font-size: 14px; + color: #333; + font-weight: 500; + margin-left: 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.g3-modal-preview-btn { + width: 32px; + height: 32px; + border: 1px solid #d9d9d9; + border-radius: 4px; + background: #ffffff; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + padding: 0; + color: #666; +} + +.g3-modal-preview-btn:hover:not(:disabled) { + border-color: #165dff; + background: rgba(22, 93, 255, 0.05); + color: #165dff; +} + +.g3-modal-preview-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.g3-modal-preview-content { + flex: 1; + position: relative; + padding: 20px; + overflow: auto; + background: #fafbfc; +} + +.g3-modal-preview-content .g3-modal-preview-placeholder { + text-align: center; + color: #999; + font-size: 24px; +} + +.g3-modal-preview-content .g3-modal-preview-iframe { + width: 100%; + height: 100%; + border: none; + margin: 0; + padding: 0; + background: #ffffff; + position: absolute; + top: 0; + left: 0; + transition: opacity 0.3s ease; +} + +.g3-modal-preview-content .g3-modal-preview-iframe.current { + opacity: 1; + z-index: 1; +} + +.g3-modal-preview-content .g3-modal-preview-iframe.next { + opacity: 0; + z-index: 2; +} + +.g3-modal-footer { + display: none; + height: 48px; + display: flex; + align-items: center; + justify-content: flex-end; + padding: 0 16px; + background: #fafbfc; + border-top: 1px solid #eef0f2; + flex-shrink: 0; + visibility: hidden; + height: 0; + padding: 0; + border-top: none; +} + +.g3-modal-footer.has-content { + display: flex; + visibility: visible; + height: 48px; + padding: 0 16px; + border-top: 1px solid #eef0f2; +} diff --git a/code/file-preview/g3-file/index.js b/code/file-preview/g3-file/index.js index 2f9ab38e..ba769b0b 100644 --- a/code/file-preview/g3-file/index.js +++ b/code/file-preview/g3-file/index.js @@ -88,14 +88,11 @@ }, // 全局权限(支持对象或函数) globalPermission: { - select: true, upload: true, download: true, delete: true, view: true, }, - // 单文件权限解析函数(仅支持函数) - filePermissionResolver: null, }, // ============================================================================ @@ -268,7 +265,7 @@ @@ -316,6 +313,23 @@
+
+
+
+ +
+ + +
+
+
+ +
+
`; @@ -339,6 +353,14 @@ this.$previewName = this.$container.find(".g3-preview-name"); this.$previewCounter = this.$container.find(".g3-preview-counter"); + this.$modalOverlay = this.$container.find(".g3-modal-overlay"); + this.$modal = this.$container.find(".g3-modal"); + this.$modalTitle = this.$container.find(".g3-modal-title"); + this.$modalBody = this.$container.find(".g3-modal-body"); + this.$modalBtnToggle = this.$container.find(".g3-modal-btn-toggle"); + this.$modalBtnClose = this.$container.find(".g3-modal-btn-close"); + this.$modalFooter = this.$container.find(".g3-modal-footer"); + return this; }, @@ -403,9 +425,13 @@ var $anchor = $(actionMenuState.anchorEl).closest(".g3-action-more"); + if (!$anchor.length) { + return; + } + var rect = $anchor[0].getBoundingClientRect(); var $mainContainer = this.$container.find(".main"); - var mainRect = $mainContainer[0].getBoundingClientRect(); + var mainRect = $mainContainer.length > 0 ? $mainContainer[0].getBoundingClientRect() : null; var menuWidth = 160; var itemHeight = 36; @@ -414,29 +440,51 @@ var top = rect.bottom; var left = rect.right - menuWidth; - var spaceBottom = mainRect.bottom - rect.bottom; // main 容器底部到锚点的距离 - var spaceTop = rect.top - mainRect.top; // 锚点到 main 容器顶部的距离 + if (mainRect) { + var spaceBottom = mainRect.bottom - rect.bottom; // main 容器底部到锚点的距离 + var spaceTop = rect.top - mainRect.top; // 锚点到 main 容器顶部的距离 - var openUp = spaceBottom < menuHeight && spaceTop > menuHeight; + var openUp = spaceBottom < menuHeight && spaceTop > menuHeight; - if (openUp) { - top = rect.top - menuHeight; // 在锚点上方显示 - } + if (openUp) { + top = rect.top - menuHeight; // 在锚点上方显示 + } - // 确保菜单不超出 main 容器左右边界 - if (left < mainRect.left) { - left = mainRect.left; - } - if (left + menuWidth > mainRect.right) { - left = mainRect.right - menuWidth; - } + // 确保菜单不超出 main 容器左右边界 + if (left < mainRect.left) { + left = mainRect.left; + } + if (left + menuWidth > mainRect.right) { + left = mainRect.right - menuWidth; + } - // 确保菜单不超出 main 容器上下边界 - if (top < mainRect.top) { - top = mainRect.top; - } - if (top + menuHeight > mainRect.bottom) { - top = mainRect.bottom - menuHeight; + // 确保菜单不超出 main 容器上下边界 + if (top < mainRect.top) { + top = mainRect.top; + } + if (top + menuHeight > mainRect.bottom) { + top = mainRect.bottom - menuHeight; + } + } else { + // 如果没有找到 main 容器,使用视口边界 + var viewportWidth = window.innerWidth; + var viewportHeight = window.innerHeight; + + // 确保菜单不超出视口左右边界 + if (left < 0) { + left = 0; + } + if (left + menuWidth > viewportWidth) { + left = viewportWidth - menuWidth; + } + + // 确保菜单不超出视口上下边界 + if (top < 0) { + top = 0; + } + if (top + menuHeight > viewportHeight) { + top = viewportHeight - menuHeight; + } } actionMenu.css({ @@ -499,6 +547,15 @@ break; } + // ${G3FileUtils.formatDate(file.uploadDate)} + + var actionMenuItems = (this.options.getActionMenu && this.options.getActionMenu(file)) || []; + var moreActionHtml = actionMenuItems && actionMenuItems.length > 0 ? ` +
+ ${G3FileIcons.action.more} +
+ ` : ""; + return `
@@ -511,7 +568,7 @@
${file.name} - ${typeof file.size === "string" ? file.size || "-" : G3FileUtils.formatFileSize(file.size)} + ${G3FileUtils.formatFileSize(file.size)} ${G3FileUtils.formatDate(file.uploadDate)}
@@ -524,9 +581,7 @@ ${G3FileIcons.action.download}
-
- ${G3FileIcons.action.more} -
+ ${moreActionHtml} `; @@ -738,6 +793,7 @@ this.$btnPrev.prop("disabled", true); this.$btnNext.prop("disabled", true); this.currentPreviewFileId = null; + this.previewCache = null; return; } @@ -769,20 +825,168 @@ .replace("{total}", total); this.$previewCounter.text(counterText); - var previewContent = ""; + var self = this; if (isUploaded && fileUrl && isPreviewable) { var previewUrl = this.options.previewUrl || ""; var fullPreviewUrl = previewUrl ? previewUrl + G3FileUtils.base64Encode(fileUrl) : fileUrl; - previewContent = ` + + if (!this.previewCache) { + this.previewCache = { currentUrl: null, nextUrl: null }; + } + + if (this.previewCache.currentUrl === fullPreviewUrl) { + return; + } + + var $content = this.$previewContent; + var $currentIframe = $content.find(".g3-preview-iframe.current"); + var $nextIframe = $content.find(".g3-preview-iframe.next"); + + if ($nextIframe.length && this.previewCache.nextUrl === fullPreviewUrl) { + $currentIframe.removeClass("current").addClass("next").hide(); + $nextIframe.removeClass("next").addClass("current").show(); + this.previewCache.currentUrl = fullPreviewUrl; + this.previewCache.nextUrl = null; + return; + } + + if ($nextIframe.length) { + $nextIframe.remove(); + } + + var newIframe = $(` + `); + + newIframe.on("load", function () { + if ($currentIframe.length) { + $currentIframe.css("opacity", "0"); + setTimeout(function () { + $currentIframe.remove(); + }, 300); + } + newIframe.removeClass("next").addClass("current"); + self.previewCache.currentUrl = fullPreviewUrl; + self.previewCache.nextUrl = null; + }); + + newIframe.on("error", function () { + newIframe.remove(); + self.showPlaceholder($content, `

${self.language.urlError}

`); + }); + + $content.append(newIframe); + this.previewCache.nextUrl = fullPreviewUrl; + + } else { + this.previewCache = null; + var tipsHtml = ""; + if (!isUploaded) { + tipsHtml = `

${this.language.uploadToPreview}

`; + } else if (!isPreviewable) { + tipsHtml = `

${this.language.downloadToPreview}

`; + } else if (!fileUrl) { + tipsHtml = `

${this.language.urlError}

`; + } + + this.showPlaceholder(this.$previewContent, tipsHtml); + } + }, + + showPlaceholder: function ($container, tipsHtml) { + $container.find(".g3-preview-iframe").remove(); + $container.html(` +
+ ${tipsHtml} +
+ `); + }, + + 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 = ` +
+
+ + 0/0 + +
+
${file.name}
+
+ `; + + var contentHtml = ""; + if (isUploaded && fileUrl && isPreviewable) { + var previewUrl = this.options.previewUrl || ""; + var fullPreviewUrl = previewUrl + ? previewUrl + G3FileUtils.base64Encode(fileUrl) + : fileUrl; + + contentHtml = ` +
+ +
`; } else { var tipsHtml = ""; @@ -794,14 +998,195 @@ tipsHtml = `

${this.language.urlError}

`; } - previewContent = ` -
- ${tipsHtml} + contentHtml = ` +
+
+ ${tipsHtml} +
`; } - this.$previewContent.html(previewContent); + 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) { @@ -1019,6 +1404,21 @@ } self.setPreviewFile(id); }) + // 文件项双击打开 modal 预览 + .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; + self.openPreviewModal(id); + }) // 更多按钮 .on("click", ".g3-action-more", function (e) { e.preventDefault(); @@ -1037,6 +1437,23 @@ 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(); @@ -1884,19 +2301,12 @@ resolvePermission: function (file, action) { var globalPermission = this.options.globalPermission; - var filePermissionResolver = this.options.filePermissionResolver; - - if (typeof filePermissionResolver === "function") { - var fileResult = filePermissionResolver({ file: file, action: action }); - if (fileResult !== undefined) { - return !!fileResult; - } - } if (typeof globalPermission === "function") { return !!globalPermission({ file: file, action: action }); } else if (typeof globalPermission === "object") { - return !!globalPermission[action]; + var permissionKey = action === "select" ? "upload" : action; + return !!globalPermission[permissionKey]; } return true; @@ -1904,19 +2314,12 @@ /** * 更新权限配置 - * @param {Object} config - 权限配置 + * @param {Object} permissions - 权限对象 */ - updatePermissions: function (config) { - if (!config) return; - - if (config.globalPermission !== undefined) { - this.options.globalPermission = config.globalPermission; - } - - if (config.filePermissionResolver !== undefined) { - this.options.filePermissionResolver = config.filePermissionResolver; - } + updatePermissions: function (permissions) { + if (!permissions) return; + this.options.globalPermission = permissions; this.updateUI(); }, @@ -2004,16 +2407,9 @@ Object.prototype.hasOwnProperty.call( partialState, "globalPermission", - ) || - Object.prototype.hasOwnProperty.call( - partialState, - "filePermissionResolver", ) ) { - this.updatePermissions({ - globalPermission: partialState.globalPermission, - filePermissionResolver: partialState.filePermissionResolver, - }); + this.updatePermissions(partialState.globalPermission); } return this; diff --git a/code/file-preview/g3-file/language.js b/code/file-preview/g3-file/language.js index 38abff69..03ece5d2 100644 --- a/code/file-preview/g3-file/language.js +++ b/code/file-preview/g3-file/language.js @@ -4,6 +4,7 @@ var G3FileLanguages = { download: "下载", downloadBatch: "批量下载", delete: "删除", + deleteBatch: "批量删除", upload: "上传", select: "选择", more: "更多", @@ -56,12 +57,18 @@ var G3FileLanguages = { currentItem: "当前项", close: "关闭", }, + modal: { + maximize: "全屏", + restore: "恢复", + close: "关闭", + }, }, "en-US": { selectAll: "Select All", download: "Download", downloadBatch: "Batch Download", delete: "Delete", + deleteBatch: "Batch Delete", upload: "Upload", select: "Select", more: "More", @@ -116,5 +123,74 @@ var G3FileLanguages = { currentItem: "Current", close: "Close", }, + modal: { + maximize: "Maximize", + restore: "Restore", + close: "Close", + }, + }, + "ru": { + selectAll: "Выделить все", + download: "Скачать", + downloadBatch: "Пакетная загрузка", + delete: "Удалить", + deleteBatch: "Пакетное удаление", + upload: "Загрузить", + select: "Выбрать", + more: "Дополнительно", + confirmDelete: "Вы уверены, что хотите удалить {count} выбранный(ые) файл(ы)?", + downloading: "Подготовка к загрузке {count} файла(ов)", + deleting: "Удаление...", + name: "Имя", + size: "Размер", + date: "Дата", + pending: "Ожидает загрузки", + selectFiles: "Выберите файлы", + emptyTip1: "Перетащите файлы сюда ...", + emptyTip2: "Поддерживается множественная загрузка/скачивание файлов", + emptyTip3: "Поддерживается вставка изображений для загрузки", + fileSizeError: "Размер файла превышает лимит", + preview: "Предпросмотр", + filePreview: "Предпросмотр файла", + fileName: "Имя файла", + fileSize: "Размер файла", + uploadDate: "Дата загрузки", + fileStatus: "Статус", + notUploaded: "Не загружен", + uploaded: "Загружен", + cannotPreview: "Этот тип файла не поддерживает предпросмотр", + previewCount: "{current}/{total}", + uploadToPreview: "Пожалуйста, загрузите для предпросмотра", + downloadToPreview: "Пожалуйста, загрузите для предпросмотра", + urlError: "Неверный URL, пожалуйста, проверьте ссылку", + uploading: "Загрузка", + uploadFailed: "Загрузка не удалась", + downloadingStatus: "Загрузка:", + downloadingFile: "Загружается:", + downloadComplete: "Загрузка завершена", + downloadResult: "Всего {total} файлов, {success} успешно, {failed} не удалось", + close: "Закрыть", + batchDownload: "Пакетная загрузка", + batchUpload: "Пакетная загрузка", + error: "Ошибка", + downloadError: "Загрузка не удалась, пожалуйста, проверьте, действителен ли файл", + toast: { + success: "Успех", + error: "Ошибка", + info: "Информация", + warning: "Предупреждение", + }, + task: { + total: "Всего", + success: "Успешно", + failed: "Не удалось", + currentItem: "Текущий", + close: "Закрыть", + }, + modal: { + maximize: "На весь экран", + restore: "Восстановить", + close: "Закрыть", + }, }, }; \ No newline at end of file diff --git a/code/file-preview/index.html b/code/file-preview/index.html index b6c68952..9f462c2c 100644 --- a/code/file-preview/index.html +++ b/code/file-preview/index.html @@ -34,6 +34,8 @@ background: #f5f7fa; display: flex; flex-direction: column; + align-items: center; + justify-content: center; } .button-group { @@ -76,7 +78,7 @@
-
+
@@ -121,7 +123,7 @@ language: 'zh-CN', previewUrl: 'https://sxox.g3soft.cn:8012/onlinePreview?url=', data: [ - { name: 'logo.png', size: 1024000, uploadDate: '2024-01-15T10:30:00', url: 'https://g3hdzjsj.oss-cn-hangzhou.aliyuncs.com/g3hd/2026/04/28/ccb964f4-124d-4169-8981-20333bea2587.docx' }, + { name: 'DC2A4692147D10C9C7B48A7EA29BCBA8.jpg', size: 1024000, uploadDate: '2024-01-15T10:30:00', url: 'http://118.89.70.199:9000/test/DC2A4692147D10C9C7B48A7EA29BCBA8.jpg' }, ], uploadConfig: { aliyun: { @@ -195,80 +197,6 @@ getActionMenu: function (file) { const menuItems = []; - // 根据权限添加菜单项 - if (userPermissions.approve) { - menuItems.push({ - title: '审批', - onClick: function (f) { - console.log('审批文件:', f.name); - alert('审批功能:' + f.name); - } - }); - } - - if (userPermissions.edit) { - menuItems.push({ - title: '修改', - onClick: function (f) { - console.log('修改文件:', f.name); - alert('修改功能:' + f.name); - } - }); - } - - if (userPermissions.rename) { - menuItems.push({ - title: '重命名', - onClick: function (f) { - console.log('重命名文件:', f.name); - const newName = prompt('请输入新文件名:', f.name); - if (newName && newName !== f.name) { - console.log('文件已重命名为:', newName); - } - } - }); - } - - if (userPermissions.copy) { - menuItems.push({ - title: '复制', - onClick: function (f) { - console.log('复制文件:', f.name); - alert('复制功能:' + f.name); - } - }); - } - - if (userPermissions.move) { - menuItems.push({ - title: '移动到文件夹', - onClick: function (f) { - console.log('移动文件:', f.name); - alert('移动功能:' + f.name); - } - }); - } - - if (userPermissions.share) { - menuItems.push({ - title: '分享', - onClick: function (f) { - console.log('分享文件:', f.name); - alert('分享功能:' + f.name); - } - }); - } - - if (userPermissions.history) { - menuItems.push({ - title: '历史版本', - onClick: function (f) { - console.log('查看历史版本:', f.name); - alert('历史版本功能:' + f.name); - } - }); - } - return menuItems; } }); diff --git a/code/file-preview/test.html b/code/file-preview/test.html index 69f5d327..2540f218 100644 --- a/code/file-preview/test.html +++ b/code/file-preview/test.html @@ -1,505 +1,227 @@ -<%@ page language="java" import="java.util.*,com.g3.general.*" pageEncoding="utf-8" %> - <%@ include file="../ini.jsp" %> - <% if (orgid.equals("")) { return; } String id=request.getParameter("id"); String - mx_moduleid=request.getParameter("mx_moduleid"); String sh_state=request.getParameter("sh_state"); String - iread=request.getParameter("iread"); String tree_where=request.getParameter("tree_where"); String - mx_secondfather=request.getParameter("mx_secondfather"); if (mx_secondfather==null) { mx_secondfather="" ; } - String mx_secondfather_fz=request.getParameter("mx_secondfather_fz"); if (mx_secondfather_fz==null) { - mx_secondfather_fz="" ; } String default_click=request.getParameter("default_click"); if (default_click==null) { - default_click="" ; } %> + + - - + + + G3 File UI Demo - - - - 采购下单浏览 - + - + /* 操作 */ + .g3-file-actions { + display: flex; + gap: 6px; + margin-left: 10px; + opacity: 0; + } - -
-
-
    -
    + .g3-file-item:hover .g3-file-actions { + opacity: 1; + } -
    + .g3-btn { + border: none; + background: #f0f0f0; + padding: 4px 8px; + font-size: 12px; + border-radius: 4px; + cursor: pointer; + } -
    -
    -
    + .g3-btn:hover { + background: #e6e6e6; + } -
    + .g3-btn.danger { + color: #ff4d4f; + } -
    -
    文件预览位置
    + /* 状态控制 */ + .is-success .g3-file-progress { + display: none; + } + + .is-error .g3-file-progress { + display: none; + } + + .is-error .g3-file-error { + display: inline; + } + + + + + +
    + + +
    +
    + +
    + +
    +
    uploading-file.zip
    +
    + 3.2 MB +
    +
    +
    - - if (!files || files.length === 0) { - alert(alertSelectFile); - callback(false); - return; - } + - $.messager.defaults = { - ok: confirmOk, - cancel: confirmCancel - }; - - $.messager.confirm(confirmTitle, confirmMessage.replace('{0}', files.length), function (ok) { - if (!ok) { - callback(false); - return; - } - - var ids = files.map(item => item.subid); - - if (ids.length === 0) { - alert(alertSelectFile); - callback(false); - return; - } - - $.ajax({ - type: 'post', - url: 'jattachfiletype/DelSubRecordBath.jsp', - data: { - module: tablename, - bucketName: modid, - ids: ids.join(",") - }, - success: function (res) { - var last = res[res.length - 1]; - if (last.result) { - alert(alertDeleteSuccess); - } else { - alert(alertDeletePartial.replace('{0}', last.success).replace('{1}', last.fail)); - } - callback(true); - refreshFileList(); - }, - error: function () { - alert(alertDeleteFail); - callback(false); - } - }); - }); - } - - /** - * 刷新文件列表(删除后重新请求数据) - */ - function refreshFileList() { - var queryWhere = buildQueryWhere(mx_orderid); - searchData(queryWhere); - } - - /** - * 构建查询条件 - * @param {string} orderId - 订单ID - * @returns {string} - 查询条件字符串 - */ - function buildQueryWhere(orderId) { - var queryWhere = "mx_moduleid='" + Fmoduleid + "' and father='" + vid1 + "'"; - - if (tree_where && tree_where !== '' && tree_where != 'null') { - queryWhere += " and mx_orderid='" + tree_where + "'"; - } - if (orderId && orderId !== '') { - queryWhere = "mx_moduleid='" + Fmoduleid + "' and father='" + vid1 + "' and mx_orderid='" + orderId + "'"; - } - if (mx_secondfather && mx_secondfather !== '' && mx_secondfather != 'null') { - queryWhere += " and mx_secondfather='" + mx_secondfather + "'"; - } - - return queryWhere; - } - - /** - * 搜索文件数据 - * @param {string} condition - 查询条件 - */ - function searchData(condition) { - if (!g3FileInstance) { - initG3File(); - } - - $.ajax({ - url: "jattachfiletype/loaddata_attach.jsp", - type: "post", - async: true, - data: { - viewname: "bf_files", - searchcondition: condition, - orderfield: '', - soption: '' - }, - success: function (res) { - var fileList = []; - for (var i = 0; i < res.length; i++) { - fileList.push({ - name: res[i].mx_filename, - size: res[i].mx_filesize, - uploadDate: res[i].mx_addtime, - url: res[i].mx_mapfilename, - ...res[i] - }); - } - g3FileInstance.setFiles(fileList); - }, - error: function () { - g3FileInstance.setFiles([]); - } - }); - } - - /** - * 创建文件类型树 - */ - function createTree() { - $('#otherbaseTree').tree('append', { - data: [{ id: '', text: tabtitle }] - }); - var node = $('#otherbaseTree').tree('find', ''); - - var queryWhere = ''; - if (tree_where !== '' && tree_where != 'null') { - queryWhere = "and b_id='" + tree_where + "'"; - } - - $.ajax({ - type: 'post', - url: 'jsp/searchdata.jsp', - cache: false, - async: false, - data: { - soption: 'main', - searchcondition: "2>1 and b_module_id='" + Fmoduleid + "'" + queryWhere, - viewname: 'bf_files_type', - orderfield: 'b_xh' - }, - dataType: 'json', - success: function (result) { - for (var i in result) { - $('#otherbaseTree').tree('append', { - parent: node.target, - data: [{ id: result[i].b_id, text: result[i].b_name }] - }); - } - if (default_click && default_click != 'null') { - setTimeout(function () { - var node = $('#otherbaseTree').tree('find', default_click); - if (node) { - $('#otherbaseTree').tree('select', node.target); - onClickNode(node); - } - }, 200); - } - } - }); - } - - /** - * 加载子节点数据 - * @param {Object} node - 树节点对象 - */ - function loadChildNodes(node) { - $.ajax({ - type: 'post', - url: 'jsp/searchdata.jsp', - cache: false, - async: false, - data: { - soption: 'main', - searchcondition: "b_module_id ='" + node.id + "'", - viewname: 'bf_files_type', - orderfield: 'b_xh' - }, - dataType: 'json', - success: function (result) { - for (var i in result) { - $('#otherbaseTree').tree('append', { - parent: node.target, - data: [{ id: result[i].b_id, text: result[i].b_name }] - }); - } - } - }); - } - - /** - * 树节点点击事件处理 - * @param {Object} node - 被点击的树节点 - */ - function onClickNode(node) { - mx_orderid = node.id; - var queryWhere = buildQueryWhere(node.id); - - if ($('#otherbaseTree').tree('getLevel', node.target) > 0 && $('#otherbaseTree').tree('isLeaf', node.target)) { - loadChildNodes(node); - } - - searchData(queryWhere); - } - - /** - * 初始化权限控制 - */ - function initPowerControl() { - if (orgid != 'demo') { - jpowerqx = getUserPowerQX('bf_files', userid); - for (var key in jpowerqx) { - if (jpowerqx[key] != '1') { - $("a[tag='" + key + "']").linkbutton('disable'); - $("button[tag='" + key + "']").attr('disabled', true); - $("input[tag='" + key + "']").attr('disabled', true); - } - } - } - if (getModulePowerByTag(Fmoduleid, userid, '4') != '1') { - sh_state = 0; - } - } - - /** - * 页面初始化入口 - */ - $(function () { - initLanguage(); - bindSplit('split1', document.querySelector('[data-pane="left"]')); - bindSplit('split2', document.querySelector('[data-pane="middle"]')); - createTree(); - - $('#otherbaseTree').tree({ - onClick: onClickNode - }); - - initPowerControl(); - }); - - - - \ No newline at end of file + \ No newline at end of file