From 6986c9547b81a6f3887107286cdd92ea594f763c Mon Sep 17 00:00:00 2001 From: Yann <1319542051@qq.com> Date: Wed, 19 Nov 2025 16:26:23 +0800 Subject: [PATCH 1/5] =?UTF-8?q?update=20=E5=AE=8C=E5=96=84=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=88=86=E4=BA=AB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 16 ------- .../src/main/resources/application-fs.yml | 17 +++++++ fs-admin/src/main/resources/application.yml | 10 ++--- .../file/controller/FileShareController.java | 16 +++++++ .../fs/file/domain/qry/FileShareQry.java | 22 +++++++++ .../fs/file/domain/vo/FileShareThinVO.java | 45 +++++++++++++++++++ .../fs/file/service/FileShareItemService.java | 14 ++++++ .../fs/file/service/FileShareService.java | 14 ++++++ .../impl/FileShareItemServiceImpl.java | 12 +++++ .../service/impl/FileShareServiceImpl.java | 34 ++++++++++++++ 10 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 fs-admin/src/main/resources/application-fs.yml create mode 100644 fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/domain/qry/FileShareQry.java create mode 100644 fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/domain/vo/FileShareThinVO.java diff --git a/fs-admin/src/main/resources/application-dev.yml b/fs-admin/src/main/resources/application-dev.yml index c4374d0d..282c1796 100644 --- a/fs-admin/src/main/resources/application-dev.yml +++ b/fs-admin/src/main/resources/application-dev.yml @@ -60,22 +60,6 @@ redisson: connectionPoolSize: 64 connectionMinimumIdleSize: 10 -fs: - preview: - # 预览文件最大大小(字节),默认500MB - max-file-size: 524288000 - - # 单次Range请求最大大小(字节),默认10MB - # 用于视频/音频的分片传输,防止内存溢出 - max-range-size: 10485760 - - # 小文件直接传输阈值(字节),默认10MB - # 小于此大小的文件将一次性读取到内存 - small-file-size: 10485760 - - # 缓冲区大小(字节),默认8KB - buffer-size: 8192 - mybatis-flex: # sql审计 audit_enable: true diff --git a/fs-admin/src/main/resources/application-fs.yml b/fs-admin/src/main/resources/application-fs.yml new file mode 100644 index 00000000..86aec5fc --- /dev/null +++ b/fs-admin/src/main/resources/application-fs.yml @@ -0,0 +1,17 @@ +# fs配置 +fs: + # 分享配置 + share: + domain: http://localhost:5173 + # 预览配置 + preview: + # 预览文件最大大小(字节),默认500MB + max-file-size: 524288000 + # 单次Range请求最大大小(字节),默认10MB + # 用于视频/音频的分片传输,防止内存溢出 + max-range-size: 10485760 + # 小文件直接传输阈值(字节),默认10MB + # 小于此大小的文件将一次性读取到内存 + small-file-size: 10485760 + # 缓冲区大小(字节),默认8KB + buffer-size: 8192 \ No newline at end of file diff --git a/fs-admin/src/main/resources/application.yml b/fs-admin/src/main/resources/application.yml index 735af64a..d8050005 100644 --- a/fs-admin/src/main/resources/application.yml +++ b/fs-admin/src/main/resources/application.yml @@ -12,7 +12,7 @@ spring: application: name: free-fs profiles: - active: dev + active: fs,dev jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss @@ -24,10 +24,10 @@ spring: main: allow-circular-references: true # 模版引擎配置 - thymeleaf: - mode: LEGACYHTML5 - prefix: classpath:/templates/ - encoding: UTF-8 +# thymeleaf: +# mode: LEGACYHTML5 +# prefix: classpath:/templates/ +# encoding: UTF-8 # 邮件配置 mail: diff --git a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/controller/FileShareController.java b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/controller/FileShareController.java index 5dc82e23..cd4bbefb 100644 --- a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/controller/FileShareController.java +++ b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/controller/FileShareController.java @@ -1,10 +1,14 @@ package com.xddcodec.fs.file.controller; +import cn.dev33.satoken.annotation.SaIgnore; import com.xddcodec.fs.file.domain.dto.CreateShareCmd; import com.xddcodec.fs.file.domain.dto.VerifyShareCodeCmd; import com.xddcodec.fs.file.domain.qry.FileSharePageQry; +import com.xddcodec.fs.file.domain.qry.FileShareQry; import com.xddcodec.fs.file.domain.vo.FileShareAccessRecordVO; +import com.xddcodec.fs.file.domain.vo.FileShareThinVO; import com.xddcodec.fs.file.domain.vo.FileShareVO; +import com.xddcodec.fs.file.domain.vo.FileVO; import com.xddcodec.fs.file.service.FileShareAccessRecordService; import com.xddcodec.fs.file.service.FileShareService; import com.xddcodec.fs.framework.common.domain.PageResult; @@ -64,4 +68,16 @@ public class FileShareController { boolean result = fileShareService.verifyShareCode(cmd); return Result.ok(result); } + + @SaIgnore + @Operation(summary = "获取分享页数据", description = "获取分享页数据") + @GetMapping("/{shareId}") + public Result getFileShareThin(@PathVariable String shareId) { + return Result.ok(fileShareService.getFileShareThinVO(shareId)); + } + + @GetMapping("/items") + public Result> getShareFileItems(@Validated FileShareQry qry) { + return Result.ok(fileShareService.getShareFileItems(qry)); + } } diff --git a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/domain/qry/FileShareQry.java b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/domain/qry/FileShareQry.java new file mode 100644 index 00000000..d21f69ed --- /dev/null +++ b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/domain/qry/FileShareQry.java @@ -0,0 +1,22 @@ +package com.xddcodec.fs.file.domain.qry; + + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * + * @author Yann + * @date 2025/11/19 15:50 + */ +@Data +public class FileShareQry { + + /** 分享ID */ + @NotBlank(message = "分享ID不能为空") + private String shareId; + /** + * 文件父ID + */ + private String parentId; +} diff --git a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/domain/vo/FileShareThinVO.java b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/domain/vo/FileShareThinVO.java new file mode 100644 index 00000000..3f9f3ebe --- /dev/null +++ b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/domain/vo/FileShareThinVO.java @@ -0,0 +1,45 @@ +package com.xddcodec.fs.file.domain.vo; + + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.xddcodec.fs.file.domain.FileShare; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * + * @author Yann + * @date 2025/11/19 13:54 + */ +@Data +@AutoMapper(target = FileShare.class) +public class FileShareThinVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + /** + * id + */ + private String id; + /** + * 分享名称 + */ + private String shareName; + /** + * 到期时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime expireTime; + /** + * 文件数量 + */ + private Long fileCount; + /** + * 是否需要验证码 + */ + private Boolean hasCheckCode; +} diff --git a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/FileShareItemService.java b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/FileShareItemService.java index 4e77dd65..01cdc561 100644 --- a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/FileShareItemService.java +++ b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/FileShareItemService.java @@ -27,4 +27,18 @@ public interface FileShareItemService extends IService { * @param shareId */ void removeByShareId(String shareId); + + /** + * 获取分享对应的文件数量 + * @param shareId + * @return + */ + Long countByShareId(String shareId); + + /** + * 获取分享对应的文件IDS + * @param shareId + * @return + */ + List getShareFileIds(String shareId); } diff --git a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/FileShareService.java b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/FileShareService.java index cf1bee3a..d32fecb2 100644 --- a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/FileShareService.java +++ b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/FileShareService.java @@ -5,9 +5,14 @@ import com.xddcodec.fs.file.domain.FileShare; import com.xddcodec.fs.file.domain.dto.CreateShareCmd; import com.xddcodec.fs.file.domain.dto.VerifyShareCodeCmd; import com.xddcodec.fs.file.domain.qry.FileSharePageQry; +import com.xddcodec.fs.file.domain.qry.FileShareQry; +import com.xddcodec.fs.file.domain.vo.FileShareThinVO; import com.xddcodec.fs.file.domain.vo.FileShareVO; +import com.xddcodec.fs.file.domain.vo.FileVO; import com.xddcodec.fs.framework.common.domain.PageResult; +import java.util.List; + /** * 文件分享服务接口 * @@ -44,4 +49,13 @@ public interface FileShareService extends IService { * @param cmd */ boolean verifyShareCode(VerifyShareCodeCmd cmd); + + /** + * 获取文件分享页对象 + * @param shareId 分享id + * @return vo + */ + FileShareThinVO getFileShareThinVO(String shareId); + + List getShareFileItems(FileShareQry qry); } diff --git a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/impl/FileShareItemServiceImpl.java b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/impl/FileShareItemServiceImpl.java index c1f9bb4d..97f8e04f 100644 --- a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/impl/FileShareItemServiceImpl.java +++ b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/impl/FileShareItemServiceImpl.java @@ -30,4 +30,16 @@ public class FileShareItemServiceImpl extends ServiceImpl getShareFileIds(String shareId) { + return this.listAs(new QueryWrapper() + .select(FILE_SHARE_ITEM.FILE_ID) + .where(FILE_SHARE_ITEM.SHARE_ID.eq(shareId)), String.class); + } } diff --git a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/impl/FileShareServiceImpl.java b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/impl/FileShareServiceImpl.java index 9f13d0eb..f364ff11 100644 --- a/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/impl/FileShareServiceImpl.java +++ b/fs-modules/fs-file/src/main/java/com/xddcodec/fs/file/service/impl/FileShareServiceImpl.java @@ -10,8 +10,12 @@ import com.xddcodec.fs.file.domain.FileInfo; import com.xddcodec.fs.file.domain.FileShare; import com.xddcodec.fs.file.domain.dto.CreateShareCmd; import com.xddcodec.fs.file.domain.dto.VerifyShareCodeCmd; +import com.xddcodec.fs.file.domain.qry.FileQry; import com.xddcodec.fs.file.domain.qry.FileSharePageQry; +import com.xddcodec.fs.file.domain.qry.FileShareQry; +import com.xddcodec.fs.file.domain.vo.FileShareThinVO; import com.xddcodec.fs.file.domain.vo.FileShareVO; +import com.xddcodec.fs.file.domain.vo.FileVO; import com.xddcodec.fs.file.mapper.FileShareMapper; import com.xddcodec.fs.file.service.FileInfoService; import com.xddcodec.fs.file.service.FileShareItemService; @@ -185,4 +189,34 @@ public class FileShareServiceImpl extends ServiceImpl getShareFileItems(FileShareQry qry) { + FileShare fileShare = this.getById(qry.getShareId()); + if (fileShare == null) { + throw new BusinessException("该分享不存在或已删除"); + } + if (StringUtils.isNotEmpty(qry.getParentId())) { + // 若有父文件ID参数, 则需要查询子数据集 + FileQry fileQry = new FileQry(); + fileQry.setParentId(qry.getParentId()); + return fileInfoService.getList(fileQry); + } + // 获取分享明细 + List shareFileIds = fileShareItemService.getShareFileIds(qry.getShareId()); + return fileInfoService.getByFileIds(shareFileIds); + } } -- Gitee From 3d89362626a161a1611acefc7ef93dffe5fb5bfc Mon Sep 17 00:00:00 2001 From: Freedom <459102951@qq.com> Date: Wed, 19 Nov 2025 16:59:31 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 3 - fs-admin/src/main/resources/application.yml | 14 +- .../handler/GlobalExceptionHandler.java | 55 +++ fs-framework/fs-preview/pom.xml | 17 + .../resources/templates/preview/audio.html | 103 ++-- .../resources/templates/preview/code.html | 172 +++++-- .../resources/templates/preview/error.html | 74 +-- .../resources/templates/preview/image.html | 453 ++++++++++-------- .../resources/templates/preview/markdown.html | 134 ++++-- .../main/resources/templates/preview/pdf.html | 63 +-- .../resources/templates/preview/video.html | 98 ++-- .../file/controller/FileStreamController.java | 52 +- 12 files changed, 791 insertions(+), 447 deletions(-) diff --git a/fs-admin/src/main/resources/application-dev.yml b/fs-admin/src/main/resources/application-dev.yml index c4374d0d..f976aebb 100644 --- a/fs-admin/src/main/resources/application-dev.yml +++ b/fs-admin/src/main/resources/application-dev.yml @@ -64,15 +64,12 @@ fs: preview: # 预览文件最大大小(字节),默认500MB max-file-size: 524288000 - # 单次Range请求最大大小(字节),默认10MB # 用于视频/音频的分片传输,防止内存溢出 max-range-size: 10485760 - # 小文件直接传输阈值(字节),默认10MB # 小于此大小的文件将一次性读取到内存 small-file-size: 10485760 - # 缓冲区大小(字节),默认8KB buffer-size: 8192 diff --git a/fs-admin/src/main/resources/application.yml b/fs-admin/src/main/resources/application.yml index 735af64a..9a5b3ac0 100644 --- a/fs-admin/src/main/resources/application.yml +++ b/fs-admin/src/main/resources/application.yml @@ -20,6 +20,15 @@ spring: multipart: max-file-size: 500MB max-request-size: 500MB + web: + resources: + # 添加静态资源位置 + static-locations: + - classpath:/META-INF/resources/ + - classpath:/resources/ + - classpath:/static/ + - classpath:/public/ + - classpath:/.well-known/ # 开启循环依赖 main: allow-circular-references: true @@ -53,11 +62,6 @@ spring: enable: true required: true - -# web: -# resources: -# static-locations: classpath:/static - --- # mybatis-flex配置 mybatis-flex: # 搜索指定包别名 diff --git a/fs-framework/fs-common-core/src/main/java/com/xddcodec/fs/framework/common/exception/handler/GlobalExceptionHandler.java b/fs-framework/fs-common-core/src/main/java/com/xddcodec/fs/framework/common/exception/handler/GlobalExceptionHandler.java index 841de7fd..68393ae7 100644 --- a/fs-framework/fs-common-core/src/main/java/com/xddcodec/fs/framework/common/exception/handler/GlobalExceptionHandler.java +++ b/fs-framework/fs-common-core/src/main/java/com/xddcodec/fs/framework/common/exception/handler/GlobalExceptionHandler.java @@ -174,6 +174,25 @@ public class GlobalExceptionHandler { return Result.error(HttpStatus.BAD_REQUEST.value(), message, null); } + /** + * 处理客户端断开连接异常 + * 这种异常通常发生在用户刷新页面、快速切换文件、网络中断等场景 + * 不需要记录为错误日志 + */ + @ExceptionHandler({ + org.springframework.web.context.request.async.AsyncRequestNotUsableException.class, + java.io.IOException.class + }) + public Result handleClientAbortException(Exception e) { + if (isClientAbortException(e)) { + log.debug("客户端断开连接: {}", e.getMessage()); + // 返回空结果,避免二次异常 + return null; + } + // 不是客户端断开异常,按正常异常处理 + return defHandler("系统异常,请联系管理员!", e); + } + /** * 处理所有不可知的异常 */ @@ -189,4 +208,40 @@ public class GlobalExceptionHandler { log.error(msg, e); return Result.error(msg); } + + /** + * 判断是否为客户端断开连接异常 + */ + private boolean isClientAbortException(Throwable e) { + if (e == null) { + return false; + } + + String className = e.getClass().getName(); + String message = e.getMessage(); + + // 检查异常类型 + if (className.contains("ClientAbortException") + || className.contains("AsyncRequestNotUsableException") + || className.contains("EOFException") + || className.contains("SocketException")) { + return true; + } + + // 检查异常消息 + if (message != null) { + String lowerMessage = message.toLowerCase(); + if (lowerMessage.contains("broken pipe") + || lowerMessage.contains("connection reset") + || lowerMessage.contains("connection abort") + || lowerMessage.contains("stream closed") + || lowerMessage.contains("client abort") + || lowerMessage.contains("outputstream failed")) { + return true; + } + } + + // 递归检查 cause + return isClientAbortException(e.getCause()); + } } diff --git a/fs-framework/fs-preview/pom.xml b/fs-framework/fs-preview/pom.xml index 0cd22e16..12ff2e1f 100644 --- a/fs-framework/fs-preview/pom.xml +++ b/fs-framework/fs-preview/pom.xml @@ -18,5 +18,22 @@ com.xddcodec.fs fs-common-core + + org.docx4j + docx4j-export-fo + 11.5.3 + + + + org.docx4j + docx4j-JAXB-ReferenceImpl + 11.5.3 + + + + + + + diff --git a/fs-framework/fs-preview/src/main/resources/templates/preview/audio.html b/fs-framework/fs-preview/src/main/resources/templates/preview/audio.html index 29148516..c7674039 100644 --- a/fs-framework/fs-preview/src/main/resources/templates/preview/audio.html +++ b/fs-framework/fs-preview/src/main/resources/templates/preview/audio.html @@ -6,13 +6,13 @@ @@ -178,7 +225,21 @@ 文件名 language - +
+ +
+ + + + +
+
@@ -196,16 +257,13 @@
- - -
加载失败
请检查网络连接或稍后重试
+
已复制到剪贴板
+ @@ -247,6 +305,30 @@ } } + function copyCode() { + const code = document.getElementById('code-block').textContent; + navigator.clipboard.writeText(code).then(() => { + showToast('已复制到剪贴板'); + }).catch(err => { + console.error('复制失败:', err); + showToast('复制失败'); + }); + } + + function showToast(msg) { + const toast = document.getElementById('toast'); + toast.textContent = msg; + toast.classList.add('show'); + setTimeout(() => { + toast.classList.remove('show'); + }, 2000); + } + + // Disable context menu + document.addEventListener('contextmenu', (e) => { + e.preventDefault(); + }); + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadCode); } else { @@ -254,4 +336,4 @@ } - + \ No newline at end of file diff --git a/fs-framework/fs-preview/src/main/resources/templates/preview/error.html b/fs-framework/fs-preview/src/main/resources/templates/preview/error.html index 61784f03..5331dca4 100644 --- a/fs-framework/fs-preview/src/main/resources/templates/preview/error.html +++ b/fs-framework/fs-preview/src/main/resources/templates/preview/error.html @@ -6,13 +6,13 @@ -
-
- 图片文件名 -
-
- - - - +
+
filename.jpg
+
+ + + +
-
加载图片中...
+
加载中...
-
- - + - + +
+ +
+ + + + + +
100%
@@ -214,139 +261,151 @@ const container = document.getElementById('image-container'); let scale = 1; - let zoomTimeout; + let rotate = 0; + let translateX = 0; + let translateY = 0; + let isDragging = false; - let startX, startY, scrollLeft, scrollTop; + let startX, startY; + let lastTranslateX = 0; + let lastTranslateY = 0; - // 图片加载完成 + // Image Load image.onload = function () { loading.style.display = 'none'; image.style.display = 'block'; }; - // 图片加载失败 image.onerror = function () { loading.style.display = 'none'; image.style.display = 'none'; errorDiv.style.display = 'flex'; }; - // 放大 + function updateTransform() { + image.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale}) rotate(${rotate}deg)`; + } + + function showZoomInfo() { + zoomInfo.textContent = Math.round(scale * 100) + '%'; + zoomInfo.classList.add('show'); + clearTimeout(window.zoomTimeout); + window.zoomTimeout = setTimeout(() => { + zoomInfo.classList.remove('show'); + }, 1000); + } + function zoomIn() { scale = Math.min(scale + 0.25, 5); - updateZoom(); + updateTransform(); + showZoomInfo(); } - // 缩小 function zoomOut() { - scale = Math.max(scale - 0.25, 0.25); - updateZoom(); + scale = Math.max(scale - 0.25, 0.1); + updateTransform(); + showZoomInfo(); } - // 重置 function resetZoom() { scale = 1; - updateZoom(); - // 重置位置 - // container.scrollLeft = (container.scrollWidth - container.clientWidth) / 2; + rotate = 0; + translateX = 0; + translateY = 0; + lastTranslateX = 0; + lastTranslateY = 0; + updateTransform(); + showZoomInfo(); } - // 更新缩放 - function updateZoom() { - image.style.transform = `scale(${scale})`; - - // 显示缩放比例 - zoomInfo.textContent = Math.round(scale * 100) + '%'; - zoomInfo.classList.add('show'); + function rotateLeft() { + rotate -= 90; + updateTransform(); + } - clearTimeout(zoomTimeout); - zoomTimeout = setTimeout(() => { - zoomInfo.classList.remove('show'); - }, 1000); + function rotateRight() { + rotate += 90; + updateTransform(); + } - if (scale > 1) { - image.classList.add('zoomed'); + function toggleFullscreen() { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen(); } else { - image.classList.remove('zoomed'); + if (document.exitFullscreen) { + document.exitFullscreen(); + } } } - // 点击图片切换缩放 (仅在未拖拽时) - let isMoved = false; - image.onmousedown = function (e) { - isMoved = false; - if (scale > 1) { - isDragging = true; - startX = e.pageX - container.offsetLeft; - startY = e.pageY - container.offsetTop; - scrollLeft = container.scrollLeft; - scrollTop = container.scrollTop; - image.style.cursor = 'grabbing'; + document.addEventListener('fullscreenchange', () => { + const btn = document.getElementById('fullscreen-btn'); + const svg = btn.querySelector('svg'); + if (document.fullscreenElement) { + btn.setAttribute('data-title', '退出全屏'); + svg.innerHTML = ''; + } else { + btn.setAttribute('data-title', '全屏'); + svg.innerHTML = ''; } - } + }); + + // Drag Logic + container.addEventListener('mousedown', (e) => { + if (e.button !== 0) return; // Only left click + isDragging = true; + startX = e.clientX; + startY = e.clientY; + container.style.cursor = 'grabbing'; + }); - image.onmousemove = function (e) { + window.addEventListener('mousemove', (e) => { if (!isDragging) return; e.preventDefault(); - isMoved = true; - const x = e.pageX - container.offsetLeft; - const y = e.pageY - container.offsetTop; - const walkX = (x - startX) * 1.5; - const walkY = (y - startY) * 1.5; - container.scrollLeft = scrollLeft - walkX; - container.scrollTop = scrollTop - walkY; - } + const deltaX = e.clientX - startX; + const deltaY = e.clientY - startY; - image.onmouseup = function (e) { - isDragging = false; - image.style.cursor = scale > 1 ? 'grab' : 'zoom-in'; - if (!isMoved) { - // Handle click for zoom toggle - if (scale === 1) { - scale = 2; - } else { - scale = 1; - } - updateZoom(); - } - } + translateX = lastTranslateX + deltaX; + translateY = lastTranslateY + deltaY; - image.onmouseleave = function () { - isDragging = false; - if (scale > 1) image.style.cursor = 'grab'; - } + updateTransform(); + }); + window.addEventListener('mouseup', () => { + if (isDragging) { + isDragging = false; + lastTranslateX = translateX; + lastTranslateY = translateY; + container.style.cursor = 'grab'; + } + }); - // 滚轮缩放 - container.addEventListener('wheel', function (e) { - if (e.ctrlKey || e.metaKey) { - e.preventDefault(); - if (e.deltaY < 0) { - zoomIn(); - } else { - zoomOut(); - } + // Wheel Zoom + container.addEventListener('wheel', (e) => { + e.preventDefault(); + if (e.deltaY < 0) { + zoomIn(); + } else { + zoomOut(); } }, {passive: false}); - // 键盘快捷键 - document.addEventListener('keydown', function (e) { + // Keyboard + document.addEventListener('keydown', (e) => { switch (e.key) { - case '+': - case '=': - zoomIn(); - break; - case '-': - zoomOut(); - break; - case '0': - resetZoom(); - break; - case 'Escape': - window.close(); - break; + case '+': case '=': zoomIn(); break; + case '-': zoomOut(); break; + case '0': resetZoom(); break; + case 'ArrowLeft': rotateLeft(); break; + case 'ArrowRight': rotateRight(); break; + case 'Escape': window.close(); break; } }); + + // Disable context menu + document.addEventListener('contextmenu', (e) => { + e.preventDefault(); + }); - + \ No newline at end of file diff --git a/fs-framework/fs-preview/src/main/resources/templates/preview/markdown.html b/fs-framework/fs-preview/src/main/resources/templates/preview/markdown.html index bae69aa3..db1496d2 100644 --- a/fs-framework/fs-preview/src/main/resources/templates/preview/markdown.html +++ b/fs-framework/fs-preview/src/main/resources/templates/preview/markdown.html @@ -7,18 +7,19 @@ - +
Markdown 预览 - +
+ + + + +
@@ -254,11 +311,6 @@
- - -
加载失败
文件内容获取失败
@@ -376,20 +428,17 @@ function updateActiveTOC() { const headings = document.querySelectorAll('.markdown-body h1, .markdown-body h2, .markdown-body h3'); const tocLinks = document.querySelectorAll('.toc-list a'); - const scrollContainer = document.getElementById('scroll-container'); - // Find the heading that is closest to the top of the viewport let current = null; for (let i = 0; i < headings.length; i++) { const heading = headings[i]; const rect = heading.getBoundingClientRect(); - // rect.top is relative to viewport if (rect.top > 0 && rect.top < window.innerHeight / 3) { current = i; break; } if (rect.top <= 0) { - current = i; // Keep updating as we scroll down past headings + current = i; } } @@ -398,6 +447,11 @@ }); } + // Disable context menu + document.addEventListener('contextmenu', (e) => { + e.preventDefault(); + }); + if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { @@ -405,4 +459,4 @@ } - + \ No newline at end of file diff --git a/fs-framework/fs-preview/src/main/resources/templates/preview/pdf.html b/fs-framework/fs-preview/src/main/resources/templates/preview/pdf.html index bf9d368c..6fce2fe0 100644 --- a/fs-framework/fs-preview/src/main/resources/templates/preview/pdf.html +++ b/fs-framework/fs-preview/src/main/resources/templates/preview/pdf.html @@ -6,13 +6,13 @@