diff --git a/src/views/trials/trials-panel/reading/visit-review/components/FileViewer.vue b/src/views/trials/trials-panel/reading/visit-review/components/FileViewer.vue index 73605664..61c12eb2 100644 --- a/src/views/trials/trials-panel/reading/visit-review/components/FileViewer.vue +++ b/src/views/trials/trials-panel/reading/visit-review/components/FileViewer.vue @@ -57,13 +57,13 @@ - +
@@ -130,7 +130,7 @@
-
-
+
+ +
+
+
+
+ +
+

+ {{ `${taskInfo.SubjectCode} ${v.taskInfo.TaskBlindName} ` }} +

+
{{ v.currentFileName }}
+
+
+
+
+ +
+
+ {{ v.taskInfo.TaskBlindName }} +
+
+ +
+
+
+
+
+ +
+ +
+
@@ -946,6 +988,12 @@ export default { // }) // }) }, + getCellStyle(index) { + return { + ...this.cellStyle, + display: index < this.cells.length ? this.cellStyle.display : 'none' + } + }, // 切换全屏 toggleFullScreen(e, index) { const i = this.viewportInfos.findIndex(i => i.index === index) @@ -1763,16 +1811,86 @@ export default { viewport.render() }, async saveImage() { - const divForDownloadViewport = document.querySelector( - `div[data-viewport-uid="canvas-${this.activeCanvasIndex}"]` - ) + const viewportIndex = this.viewportInfos.findIndex(v => v.index === this.activeCanvasIndex) + if (viewportIndex === -1) return + const currentViewport = this.viewportInfos[viewportIndex] + + let divForDownloadViewport = null + let tempImg = null + + if (currentViewport.fileType && currentViewport.fileType.includes('mp4')) { + divForDownloadViewport = this.$refs[`grid-cell-${this.activeCanvasIndex}`][0] + const videoEl = this.$refs[`videovideo-${this.activeCanvasIndex}`][0] + + if (videoEl) { + // 将视频帧画到 canvas 上生成图片 + const tempCanvas = document.createElement('canvas') + // 设置 canvas 尺寸为容器实际尺寸 + const containerWidth = videoEl.clientWidth + const containerHeight = videoEl.clientHeight + tempCanvas.width = containerWidth + tempCanvas.height = containerHeight + + const ctx = tempCanvas.getContext('2d') + // 填充黑色背景 + ctx.fillStyle = '#000' + ctx.fillRect(0, 0, containerWidth, containerHeight) + + // 计算视频保持比例的缩放尺寸和位置 (等同于 object-fit: contain) + const videoWidth = videoEl.videoWidth || containerWidth + const videoHeight = videoEl.videoHeight || containerHeight + + const scale = Math.min(containerWidth / videoWidth, containerHeight / videoHeight) + const drawWidth = videoWidth * scale + const drawHeight = videoHeight * scale + const offsetX = (containerWidth - drawWidth) / 2 + const offsetY = (containerHeight - drawHeight) / 2 + + // 将视频帧居中绘制 + ctx.drawImage(videoEl, offsetX, offsetY, drawWidth, drawHeight) + + const frameBase64 = tempCanvas.toDataURL('image/png') + + // 创建一个 img 标签遮盖在 video 上 + tempImg = document.createElement('img') + tempImg.src = frameBase64 + tempImg.style.position = 'absolute' + tempImg.style.top = '0' + tempImg.style.left = '0' + tempImg.style.width = '100%' + tempImg.style.height = '100%' + tempImg.style.zIndex = '99' + + const videoContainer = this.$refs[`video-content-main-${this.activeCanvasIndex}`][0] + videoContainer.style.position = 'relative' + videoContainer.appendChild(tempImg) + } + } else { + divForDownloadViewport = document.querySelector( + `div[data-viewport-uid="canvas-${this.activeCanvasIndex}"]` + ) + } + if (!divForDownloadViewport) return - const canvas = await html2canvas(divForDownloadViewport) + + // 等待 DOM 更新 + await this.$nextTick() + + const canvas = await html2canvas(divForDownloadViewport, { + useCORS: true, + backgroundColor: '#000000' // 指定截图背景为黑色 + }) + + // 截图后恢复 + if (tempImg) { + tempImg.remove() + } + const base64Str = canvas.toDataURL('image/png', 1) - const i = this.viewportInfos.findIndex(v => v.index === this.activeCanvasIndex) - const name = (i > -1 ? this.viewportInfos[i].currentFileName : '') || `screenshot-${Date.now()}` + const name = currentViewport.currentFileName || `screenshot-${Date.now()}` const safeName = String(name).replace(/[\\/:*?"<>|]/g, '_') const downloadName = safeName.toLowerCase().endsWith('.png') ? safeName : `${safeName}.png` + const a = document.createElement('a') a.href = base64Str a.download = downloadName @@ -2188,10 +2306,13 @@ export default { .grid-cell { border: 1px dashed #ccc; - ; display: flex; align-items: center; justify-content: center; + overflow: hidden; + min-width: 0; + min-height: 0; + z-index: 1; } .cell_active { @@ -2294,6 +2415,21 @@ export default { background: #9e9e9e; cursor: move } + + .content-main { + position: relative; + overflow: hidden; + background: #1e1e1e; + } + + video { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + object-position: center center; + background: transparent; + } } } diff --git a/src/views/trials/trials-panel/reading/visit-review/components/StudyList.vue b/src/views/trials/trials-panel/reading/visit-review/components/StudyList.vue index 9cda8fc5..27bc7441 100644 --- a/src/views/trials/trials-panel/reading/visit-review/components/StudyList.vue +++ b/src/views/trials/trials-panel/reading/visit-review/components/StudyList.vue @@ -144,11 +144,11 @@ export default { }, // 切换文件 selectFile(study, studyIndex, fileIndex, item) { - if (item.FileType.includes('mp4')) return this.$preview({ - path: item.Path || item.FilePath, - type: 'mp4', - title: item.FileName, - }) + // if (item.FileType.includes('mp4')) return this.$preview({ + // path: item.Path || item.FilePath, + // type: 'mp4', + // title: item.FileName, + // }) this.activeStudyIndex = studyIndex this.activeFileIndex = fileIndex const fileList = study.NoneDicomStudyFileList