Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is running
Details
continuous-integration/drone/push Build is running
Details
commit
4c7c0cce47
|
|
@ -57,13 +57,13 @@
|
||||||
<svg-icon icon-class="lengthscale" class="svg-icon" />
|
<svg-icon icon-class="lengthscale" class="svg-icon" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 截图 -->
|
<!-- 截图 -->
|
||||||
<!-- <div
|
<div
|
||||||
class="tool-item"
|
class="tool-item"
|
||||||
:title="$t('trials:reading:button:screenShot')"
|
:title="$t('trials:reading:button:screenShot')"
|
||||||
@click.prevent="saveImage"
|
@click.prevent="saveImage"
|
||||||
>
|
>
|
||||||
<svg-icon icon-class="image" class="svg-icon" />
|
<svg-icon icon-class="image" class="svg-icon" />
|
||||||
</div> -->
|
</div>
|
||||||
<!-- 重置 -->
|
<!-- 重置 -->
|
||||||
<div class="tool-item" :title="$t('trials:reading:button:reset')" @click.prevent="resetViewport">
|
<div class="tool-item" :title="$t('trials:reading:button:reset')" @click.prevent="resetViewport">
|
||||||
<svg-icon icon-class="refresh" class="svg-icon" />
|
<svg-icon icon-class="refresh" class="svg-icon" />
|
||||||
|
|
@ -130,7 +130,7 @@
|
||||||
<!-- viewports -->
|
<!-- viewports -->
|
||||||
<div class="viewports-wrapper" v-loading="loading" ref="viewports-wrapper">
|
<div class="viewports-wrapper" v-loading="loading" ref="viewports-wrapper">
|
||||||
<div ref="container" class="grid-container" :style="gridStyle">
|
<div ref="container" class="grid-container" :style="gridStyle">
|
||||||
<div v-for="(v, index) in viewportInfos" v-show="index < cells.length" :key="index" :style="cellStyle"
|
<div v-for="(v, index) in viewportInfos" :ref="`grid-cell-${index}`" :key="index" :style="getCellStyle(index)"
|
||||||
:class="['grid-cell', index === activeCanvasIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
|
:class="['grid-cell', index === activeCanvasIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
|
||||||
@dblclick="toggleFullScreen($event, index)" @click="activeCanvas(index)"
|
@dblclick="toggleFullScreen($event, index)" @click="activeCanvas(index)"
|
||||||
@mouseup="sliderMouseup($event, index)" @mousemove="sliderMousemove($event, index)"
|
@mouseup="sliderMouseup($event, index)" @mousemove="sliderMousemove($event, index)"
|
||||||
|
|
@ -172,7 +172,7 @@
|
||||||
@mousedown.stop="sliderMousedown($event, index)" />
|
@mousedown.stop="sliderMousedown($event, index)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="v.fileType === 'application/pdf' && fullScreenIndex === null" class="content flex_col">
|
<div v-if="v.fileType === 'application/pdf' && fullScreenIndex === null" class="content flex_col">
|
||||||
<div class="content-top" style="height: 50px;">
|
<div class="content-top" style="height: 50px;">
|
||||||
<div class="left-top-text">
|
<div class="left-top-text">
|
||||||
<div v-if="v.taskInfo.IsExistsClinicalData" class="cd-info"
|
<div v-if="v.taskInfo.IsExistsClinicalData" class="cd-info"
|
||||||
|
|
@ -212,6 +212,48 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="v.fileType.includes('mp4')" class="content flex_col">
|
||||||
|
<div class="content-top" style="height: 50px;">
|
||||||
|
<div class="left-top-text">
|
||||||
|
<div v-if="v.taskInfo.IsExistsClinicalData" class="cd-info"
|
||||||
|
:title="$t('trials:reading:button:clinicalData')">
|
||||||
|
<svg-icon style="cursor: pointer;" icon-class="documentation" class="svg-icon"
|
||||||
|
@click.stop="viewCD(v.taskInfo.VisitTaskId)" />
|
||||||
|
</div>
|
||||||
|
<h2 v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo && v.taskInfo" class="subject-info">
|
||||||
|
{{ `${taskInfo.SubjectCode} ${v.taskInfo.TaskBlindName} ` }}
|
||||||
|
</h2>
|
||||||
|
<div v-if="v.currentFileName">{{ v.currentFileName }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder === 1 && v.taskInfo" class="top-center-tool">
|
||||||
|
<div class="toggle-visit-container">
|
||||||
|
<div class="arrw_icon"
|
||||||
|
:style="{ cursor: v.taskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum !== 0 ? '#fff' : '#6b6b6b' }"
|
||||||
|
@click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, -1, v.index)"
|
||||||
|
@dblclick.stop="preventDefault($event)">
|
||||||
|
<i class="el-icon-caret-left" />
|
||||||
|
</div>
|
||||||
|
<div class="arrow_text">
|
||||||
|
{{ v.taskInfo.TaskBlindName }}
|
||||||
|
</div>
|
||||||
|
<div class="arrw_icon"
|
||||||
|
:style="{ cursor: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff' : '#6b6b6b' }"
|
||||||
|
@click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, 1, v.index)"
|
||||||
|
@dblclick.stop="preventDefault($event)">
|
||||||
|
<i class="el-icon-caret-right" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-main" :ref="`video-content-main-${index}`" style="flex: 1;">
|
||||||
|
<video v-if="v.currentFilePath" :ref="`videovideo-${index}`"
|
||||||
|
:src="`${OSSclientConfig.basePath}${v.currentFilePath}`" crossorigin="anonymous"
|
||||||
|
width="100%" height="100%" autoplay controls controlsList="nodownload"></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -946,6 +988,12 @@ export default {
|
||||||
// })
|
// })
|
||||||
// })
|
// })
|
||||||
},
|
},
|
||||||
|
getCellStyle(index) {
|
||||||
|
return {
|
||||||
|
...this.cellStyle,
|
||||||
|
display: index < this.cells.length ? this.cellStyle.display : 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
// 切换全屏
|
// 切换全屏
|
||||||
toggleFullScreen(e, index) {
|
toggleFullScreen(e, index) {
|
||||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
const i = this.viewportInfos.findIndex(i => i.index === index)
|
||||||
|
|
@ -1763,16 +1811,86 @@ export default {
|
||||||
viewport.render()
|
viewport.render()
|
||||||
},
|
},
|
||||||
async saveImage() {
|
async saveImage() {
|
||||||
const divForDownloadViewport = document.querySelector(
|
const viewportIndex = this.viewportInfos.findIndex(v => v.index === this.activeCanvasIndex)
|
||||||
`div[data-viewport-uid="canvas-${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
|
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 base64Str = canvas.toDataURL('image/png', 1)
|
||||||
const i = this.viewportInfos.findIndex(v => v.index === this.activeCanvasIndex)
|
const name = currentViewport.currentFileName || `screenshot-${Date.now()}`
|
||||||
const name = (i > -1 ? this.viewportInfos[i].currentFileName : '') || `screenshot-${Date.now()}`
|
|
||||||
const safeName = String(name).replace(/[\\/:*?"<>|]/g, '_')
|
const safeName = String(name).replace(/[\\/:*?"<>|]/g, '_')
|
||||||
const downloadName = safeName.toLowerCase().endsWith('.png') ? safeName : `${safeName}.png`
|
const downloadName = safeName.toLowerCase().endsWith('.png') ? safeName : `${safeName}.png`
|
||||||
|
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
a.href = base64Str
|
a.href = base64Str
|
||||||
a.download = downloadName
|
a.download = downloadName
|
||||||
|
|
@ -2188,10 +2306,13 @@ export default {
|
||||||
|
|
||||||
.grid-cell {
|
.grid-cell {
|
||||||
border: 1px dashed #ccc;
|
border: 1px dashed #ccc;
|
||||||
;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell_active {
|
.cell_active {
|
||||||
|
|
@ -2294,6 +2415,21 @@ export default {
|
||||||
background: #9e9e9e;
|
background: #9e9e9e;
|
||||||
cursor: move
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,11 +144,11 @@ export default {
|
||||||
},
|
},
|
||||||
// 切换文件
|
// 切换文件
|
||||||
selectFile(study, studyIndex, fileIndex, item) {
|
selectFile(study, studyIndex, fileIndex, item) {
|
||||||
if (item.FileType.includes('mp4')) return this.$preview({
|
// if (item.FileType.includes('mp4')) return this.$preview({
|
||||||
path: item.Path || item.FilePath,
|
// path: item.Path || item.FilePath,
|
||||||
type: 'mp4',
|
// type: 'mp4',
|
||||||
title: item.FileName,
|
// title: item.FileName,
|
||||||
})
|
// })
|
||||||
this.activeStudyIndex = studyIndex
|
this.activeStudyIndex = studyIndex
|
||||||
this.activeFileIndex = fileIndex
|
this.activeFileIndex = fileIndex
|
||||||
const fileList = study.NoneDicomStudyFileList
|
const fileList = study.NoneDicomStudyFileList
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue