QC质控查看非dicom文件页面更改
parent
f03db6f995
commit
92da42f010
|
|
@ -128,6 +128,11 @@ export const constantRoutes = [
|
|||
hidden: true,
|
||||
component: () => import('@/views/none-dicom-show/index')
|
||||
},
|
||||
{
|
||||
path: '/noneDicoms',
|
||||
hidden: true,
|
||||
component: () => import('@/views/none-dicoms/index')
|
||||
},
|
||||
{
|
||||
path: '/noneDicomReading',
|
||||
name: 'noneDicomReading',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,868 @@
|
|||
<template>
|
||||
<div class="none-dicom-viewer">
|
||||
<!-- viewports -->
|
||||
<div class="viewports-wrapper" v-loading="loading" ref="viewports-wrapper">
|
||||
<div ref="container" class="grid-container" :style="gridStyle">
|
||||
<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' : '']"
|
||||
@dblclick="toggleFullScreen($event, index)" @click="activeCanvas(index)"
|
||||
@mouseup="sliderMouseup($event, index)" @mousemove="sliderMousemove($event, index)"
|
||||
@mouseleave="sliderMouseleave($event, index)">
|
||||
<div v-show="imageType.includes(v.fileType)" :ref="`canvas-${index}`" class="content">
|
||||
<div class="left-top-text">
|
||||
<div v-if="v.currentFileName">{{ v.currentFileName }}</div>
|
||||
</div>
|
||||
<div :ref="`sliderBox-${index}`" class="right-slider-box" @click.stop="clickSlider($event, index)">
|
||||
<div :style="{ top: v.height + '%' }" class="slider" @click.stop.prevent="() => { return }"
|
||||
@mousedown.stop="sliderMousedown($event, index)" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="v.fileType === 'application/pdf' && fullScreenIndex === null" class="content flex_col">
|
||||
<div class="content-top" style="height: 50px;">
|
||||
<div class="left-top-text">
|
||||
<div v-if="v.currentFileName">{{ v.currentFileName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-main" style="flex: 1;">
|
||||
<iframe v-if="v.currentFilePath" :ref="`iframe-${index}`"
|
||||
:src="`/static/pdfjs/web/viewer.html?file=${OSSclientConfig.basePath}${v.currentFilePath}?index=${index}`"
|
||||
width="100%" height="100%" frameborder="0" crossorigin="anonymous" />
|
||||
</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.currentFileName">{{ v.currentFileName }}</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>
|
||||
<!-- tools -->
|
||||
<div class="dicom-tools">
|
||||
<div class="measureTool-wrapper">
|
||||
<div class="sideTool-title">{{ $t('trials:reading:button:layout') }}</div>
|
||||
<div class="sideTool-wrapper">
|
||||
<label>{{ $t('trials:reading:button:layout') }}:</label>
|
||||
<select class="sidetool-select" style="width:90px" :value="layout.replace('*', 'x')" @change="handleLayoutChange">
|
||||
<option value="1x1">1x1</option>
|
||||
<option value="1x2">1x2</option>
|
||||
<option value="2x1">2x1</option>
|
||||
<option value="2x2">2x2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 图像变换 -->
|
||||
<div class="measureTool-wrapper">
|
||||
<div class="sideTool-title">{{ $t('trials:dicom-show:transform') }}</div>
|
||||
<div class="sideTool-wrapper">
|
||||
<button :title="$t('trials:reading:button:zoom')" :class="{ 'btn-link': true, activeTool: activeTool === 'Zoom' }"
|
||||
data-tool="Zoom" @click="setToolActive('Zoom')">
|
||||
<svg-icon icon-class="magnifier" style="font-size:20px;" />
|
||||
</button>
|
||||
<button :title="$t('trials:reading:button:move')" :class="{ 'btn-link': true, activeTool: activeTool === 'Pan' }"
|
||||
data-tool="Pan" @click="setToolActive('Pan')">
|
||||
<svg-icon icon-class="move" style="font-size:20px;" />
|
||||
</button>
|
||||
<button :title="$t('trials:reading:button:rotate')" :class="{ 'btn-link': true, activeTool: activeTool === 'PlanarRotate' }"
|
||||
data-tool="PlanarRotate" @click="setToolActive('PlanarRotate')">
|
||||
<svg-icon icon-class="rotate" style="font-size:20px;" />
|
||||
</button>
|
||||
<button :title="$t('trials:reading:button:reset')" class="btn-link" @click="resetViewport">
|
||||
<svg-icon icon-class="refresh" style="font-size:20px;" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 测量工具 -->
|
||||
<!-- <div class="measureTool-wrapper">
|
||||
<div class="sideTool-title">{{ $t('trials:dicom-show:measurementLabeling') }}</div>
|
||||
<div class="sideTool-wrapper">
|
||||
<button :title="$t('trials:reading:button:screenShot')" class="btn-link" @click="saveImage">
|
||||
<svg-icon icon-class="image" style="font-size:20px;" />
|
||||
</button>
|
||||
<button :title="$t('trials:reading:button:reset')" class="btn-link" @click="resetViewport">
|
||||
<svg-icon icon-class="refresh" style="font-size:20px;" />
|
||||
</button>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import {
|
||||
RenderingEngine,
|
||||
Enums,
|
||||
imageLoader,
|
||||
metaData,
|
||||
getRenderingEngine,
|
||||
eventTarget,
|
||||
utilities as csUtils
|
||||
// getEnabledElementByIds
|
||||
} from '@cornerstonejs/core'
|
||||
|
||||
import * as cornerstoneTools from '@cornerstonejs/tools'
|
||||
import LengthscaleTool from "@/views/trials/trials-panel/reading/visit-review/tools/LengthscaleTool"
|
||||
import initLibraries from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initLibraries'
|
||||
import hardcodedMetaDataProvider from '@/views/trials/trials-panel/reading/visit-review/js/hardcodedMetaDataProvider'
|
||||
import registerWebImageLoader from '@/views/trials/trials-panel/reading/visit-review/js/registerWebImageLoader'
|
||||
import store from '@/store'
|
||||
const { ViewportType } = Enums
|
||||
const renderingEngineId = 'myRenderingEngine'
|
||||
const {
|
||||
ToolGroupManager,
|
||||
Enums: csToolsEnums,
|
||||
StackScrollTool,
|
||||
PanTool,
|
||||
ZoomTool,
|
||||
PlanarRotateTool,
|
||||
ArrowAnnotateTool,
|
||||
RectangleROITool,
|
||||
PlanarFreehandROITool,
|
||||
SplineROITool,
|
||||
EraserTool,
|
||||
LengthTool,
|
||||
EllipticalROITool,
|
||||
CircleROITool,
|
||||
AngleTool,
|
||||
BidirectionalTool,
|
||||
// cursors
|
||||
} = cornerstoneTools
|
||||
const newStyles = {
|
||||
global: {
|
||||
color: 'rgb(255, 0, 0)',
|
||||
colorHighlighted: 'rgb(0, 255, 0)',
|
||||
colorSelected: 'rgb(255, 0, 0)',
|
||||
colorLocked: 'rgb(255, 0, 0)',
|
||||
lineWidth: '1',
|
||||
lineDash: '',
|
||||
shadow: true,
|
||||
textBoxVisibility: true,
|
||||
textBoxFontFamily: 'Helvetica Neue, Helvetica, Arial, sans-serif',
|
||||
textBoxFontSize: '14px',
|
||||
textBoxColor: 'rgb(255, 0, 0)',
|
||||
textBoxColorHighlighted: 'rgb(0, 255, 0)',
|
||||
textBoxColorSelected: 'rgb(255, 0, 0)',
|
||||
textBoxColorLocked: 'rgb(255, 0, 0)',
|
||||
textBoxBackground: '',
|
||||
textBoxLinkLineWidth: '1',
|
||||
textBoxLinkLineDash: '2,3',
|
||||
textBoxShadow: true,
|
||||
markerSize: '10'
|
||||
}
|
||||
}
|
||||
cornerstoneTools.annotation.config.style.setDefaultToolStyles(newStyles)
|
||||
const { MouseBindings, Events: toolsEvents } = csToolsEnums
|
||||
export default {
|
||||
name: 'ImageViewer',
|
||||
data() {
|
||||
return {
|
||||
rows: 1,
|
||||
cols: 1,
|
||||
fullScreenIndex: null,
|
||||
imageIds: [],
|
||||
layout: '1*1',
|
||||
cellsMax: 4,
|
||||
activeTool: '',
|
||||
imageType: ['image/jpeg', 'image/jpg', 'image/bmp', 'image/png'],
|
||||
loading: false,
|
||||
trialId: null,
|
||||
activeName: '1',
|
||||
tools: [],
|
||||
viewportInfos: [],
|
||||
activeCanvasIndex: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
gridStyle() {
|
||||
return {
|
||||
display: 'grid',
|
||||
gridTemplateRows: `repeat(${this.rows}, 1fr)`,
|
||||
gridTemplateColumns: `repeat(${this.cols}, 1fr)`,
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}
|
||||
},
|
||||
cellStyle() {
|
||||
return {
|
||||
border: '1px dashed #ccc',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}
|
||||
},
|
||||
cells() {
|
||||
return Array(this.rows * this.cols).fill(0)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.tools = config.customizeStandardsNoneDicom
|
||||
this.trialId = this.$route.query.trialId
|
||||
this.viewportInfos = Array.from({ length: this.cellsMax }, (_, index) => ({
|
||||
index: index,
|
||||
studyId: '',
|
||||
currentImageIdIndex: 0,
|
||||
viewportId: `canvas-${index}`,
|
||||
fileType: '',
|
||||
currentFileName: '',
|
||||
currentFilePath: '',
|
||||
imageIds: [],
|
||||
oldB: null,
|
||||
oldM: null,
|
||||
isMove: false,
|
||||
height: 0
|
||||
}))
|
||||
this.initLoader()
|
||||
this.$refs['viewports-wrapper'].addEventListener('wheel', (e) => {
|
||||
this.setToolsPassive()
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
showPanel(e) {
|
||||
e.currentTarget.firstChild.lastChild.style.display = 'block'
|
||||
},
|
||||
toolMouseout(e) {
|
||||
e.currentTarget.firstChild.lastChild.style.display = 'none'
|
||||
},
|
||||
// 初始化加载器
|
||||
async initLoader() {
|
||||
registerWebImageLoader(imageLoader)
|
||||
await initLibraries()
|
||||
|
||||
let renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
if (!renderingEngine) {
|
||||
renderingEngine = new RenderingEngine(renderingEngineId)
|
||||
}
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
if (renderingEngine) {
|
||||
renderingEngine.resize(true, false)
|
||||
}
|
||||
})
|
||||
const element1 = this.$refs['canvas-0'][0]
|
||||
const element2 = this.$refs['canvas-1'][0]
|
||||
const element3 = this.$refs['canvas-2'][0]
|
||||
const element4 = this.$refs['canvas-3'][0]
|
||||
const elements = [
|
||||
element1,
|
||||
element2,
|
||||
element3,
|
||||
element4
|
||||
]
|
||||
elements.forEach((element, i) => {
|
||||
element.oncontextmenu = (e) => e.preventDefault()
|
||||
resizeObserver.observe(element)
|
||||
element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage)
|
||||
})
|
||||
const viewportInputArray = [
|
||||
{
|
||||
viewportId: 'canvas-0',
|
||||
type: ViewportType.STACK,
|
||||
element: element1
|
||||
},
|
||||
{
|
||||
viewportId: 'canvas-1',
|
||||
type: ViewportType.STACK,
|
||||
element: element2
|
||||
},
|
||||
{
|
||||
viewportId: 'canvas-2',
|
||||
type: ViewportType.STACK,
|
||||
element: element3
|
||||
},
|
||||
{
|
||||
viewportId: 'canvas-3',
|
||||
type: ViewportType.STACK,
|
||||
element: element4
|
||||
}
|
||||
]
|
||||
const viewportIds = ['canvas-0', 'canvas-1', 'canvas-2', 'canvas-3']
|
||||
renderingEngine.setViewports(viewportInputArray)
|
||||
cornerstoneTools.addTool(StackScrollTool)
|
||||
cornerstoneTools.addTool(PanTool)
|
||||
cornerstoneTools.addTool(ZoomTool)
|
||||
cornerstoneTools.addTool(PlanarRotateTool)
|
||||
cornerstoneTools.addTool(ArrowAnnotateTool)
|
||||
cornerstoneTools.addTool(RectangleROITool)
|
||||
cornerstoneTools.addTool(EllipticalROITool)
|
||||
cornerstoneTools.addTool(CircleROITool)
|
||||
cornerstoneTools.addTool(AngleTool)
|
||||
cornerstoneTools.addTool(PlanarFreehandROITool)
|
||||
cornerstoneTools.addTool(SplineROITool)
|
||||
cornerstoneTools.addTool(EraserTool)
|
||||
cornerstoneTools.addTool(LengthTool)
|
||||
cornerstoneTools.addTool(LengthscaleTool)
|
||||
cornerstoneTools.addTool(BidirectionalTool)
|
||||
viewportIds.forEach((viewportId, i) => {
|
||||
const toolGroupId = `canvas-${i}`
|
||||
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId)
|
||||
toolGroup.addViewport(viewportId, renderingEngineId)
|
||||
toolGroup.addTool(StackScrollTool.toolName)
|
||||
toolGroup.addTool(PanTool.toolName)
|
||||
toolGroup.addTool(ZoomTool.toolName)
|
||||
toolGroup.addTool(PlanarRotateTool.toolName)
|
||||
toolGroup.addTool(ArrowAnnotateTool.toolName)
|
||||
toolGroup.addTool(RectangleROITool.toolName)
|
||||
toolGroup.addTool(BidirectionalTool.toolName)
|
||||
toolGroup.addTool(EllipticalROITool.toolName)
|
||||
toolGroup.addTool(CircleROITool.toolName)
|
||||
toolGroup.addTool(AngleTool.toolName)
|
||||
toolGroup.addTool(PlanarFreehandROITool.toolName)
|
||||
toolGroup.addTool(SplineROITool.toolName)
|
||||
|
||||
toolGroup.addTool(EraserTool.toolName)
|
||||
toolGroup.addTool(LengthTool.toolName)
|
||||
toolGroup.addTool(LengthscaleTool.toolName)
|
||||
|
||||
toolGroup.setToolActive(StackScrollTool.toolName, {
|
||||
bindings: [{ mouseButton: MouseBindings.Wheel }]
|
||||
})
|
||||
toolGroup.setToolPassive(PanTool.toolName)
|
||||
toolGroup.setToolPassive(ZoomTool.toolName)
|
||||
toolGroup.setToolPassive(PlanarRotateTool.toolName)
|
||||
toolGroup.setToolPassive(EraserTool.toolName)
|
||||
})
|
||||
},
|
||||
lengthscaleToolDisabled(saved) {
|
||||
const viewportIds = ['canvas-0', 'canvas-1', 'canvas-2', 'canvas-3']
|
||||
viewportIds.forEach(toolGroupId => {
|
||||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
||||
if (saved) {
|
||||
toolGroup.setToolPassive(LengthscaleTool.toolName)
|
||||
} else {
|
||||
toolGroup.setToolEnabled(LengthscaleTool.toolName)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 加载图片回调
|
||||
stackNewImage(e) {
|
||||
const { detail } = e
|
||||
const i = this.viewportInfos.findIndex(i => i.viewportId === detail.viewportId)
|
||||
if (i === -1) return
|
||||
this.viewportInfos[i].currentImageIdIndex = detail.imageIdIndex
|
||||
const obj = this.viewportInfos[i].fileList[detail.imageIdIndex]
|
||||
if (!obj) return
|
||||
this.viewportInfos[i].currentFileName = obj.FileName
|
||||
const total = this.viewportInfos[i].imageIds.length
|
||||
this.viewportInfos[i].height = total > 1 ? (this.viewportInfos[i].currentImageIdIndex) * 100 / (total - 1) : 0
|
||||
this.$emit('toggleImage', {
|
||||
studyId: this.viewportInfos[i].studyId,
|
||||
path: obj.Path
|
||||
})
|
||||
},
|
||||
// 渲染图片
|
||||
async renderImage(imageIds, canvasIndex, sliceIndex) {
|
||||
metaData.addProvider((type, imageId) => hardcodedMetaDataProvider(type, imageId, imageIds), 10000)
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(`canvas-${canvasIndex}`)
|
||||
await viewport.setStack(imageIds)
|
||||
this.setToolsPassive()
|
||||
viewport.setImageIdIndex(sliceIndex)
|
||||
viewport.render()
|
||||
// this.updateViewportInfos()
|
||||
},
|
||||
setActiveCanvasImages(obj) {
|
||||
if (!obj || Object.keys(obj).length === 0) return
|
||||
const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
|
||||
if (i === -1) return
|
||||
if (obj.studyId === this.viewportInfos[i].studyId && this.imageType.includes(this.viewportInfos[i].fileType) && this.imageType.includes(obj.fileInfo.FileType)) {
|
||||
const fileIndex = this.viewportInfos[i].fileList.findIndex(i => i.Path === obj.fileInfo.Path)
|
||||
this.sliceIndex(fileIndex)
|
||||
} else {
|
||||
if (!this.imageType.includes(obj.fileInfo.FileType)) {
|
||||
this.fullScreenIndex = null
|
||||
}
|
||||
this.updateViewportInfos(this.activeCanvasIndex, obj)
|
||||
}
|
||||
this.setToolsPassive()
|
||||
},
|
||||
// 激活视图
|
||||
activeCanvas(index) {
|
||||
if (this.activeCanvasIndex === index) return
|
||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
||||
if (i === -1) return
|
||||
this.activeCanvasIndex = index
|
||||
this.setToolsPassive()
|
||||
},
|
||||
// 更新视图信息
|
||||
updateViewportInfos(index, obj) {
|
||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
||||
if (i === -1) return
|
||||
|
||||
this.viewportInfos[i].taskInfo = obj.visitTaskInfo
|
||||
|
||||
this.viewportInfos[i].currentFileName = obj.fileInfo.FileName
|
||||
this.viewportInfos[i].studyId = obj.studyId
|
||||
this.viewportInfos[i].fileType = obj.fileInfo.FileType
|
||||
if (this.imageType.includes(obj.fileInfo.FileType)) {
|
||||
const arr = obj.fileList.filter(i => this.imageType.includes(i.FileType))
|
||||
this.viewportInfos[i].fileList = arr
|
||||
const fileIndex = this.viewportInfos[i].fileList.findIndex(i => i.Path === obj.fileInfo.Path)
|
||||
this.viewportInfos[i].currentImageIdIndex = fileIndex
|
||||
const imageIds = []
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const path = arr[i].Path
|
||||
imageIds.push(`web:${this.OSSclientConfig.basePath}${path}`)
|
||||
}
|
||||
this.viewportInfos[i].imageIds = imageIds
|
||||
if (imageIds.length > 0) {
|
||||
this.renderImage(imageIds, index, fileIndex)
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
if (renderingEngine) {
|
||||
renderingEngine.resize(true, false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.viewportInfos[i].currentFilePath = obj.fileInfo.Path
|
||||
}
|
||||
},
|
||||
// 切换图片
|
||||
sliceIndex(index) {
|
||||
const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
|
||||
if (i === -1) return
|
||||
if (index < 0 || index >= this.viewportInfos[i].imageIds.length) return
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
this.viewportInfos[i].viewportId
|
||||
)
|
||||
this.setToolsPassive()
|
||||
viewport.setImageIdIndex(index)
|
||||
viewport.render()
|
||||
},
|
||||
// 更改视图布局
|
||||
handleCommand(command) {
|
||||
this.fullScreenIndex = null
|
||||
this.layout = command
|
||||
this.rows = parseInt(command.split('*')[0])
|
||||
this.cols = parseInt(command.split('*')[1])
|
||||
if (this.rows === 1 && this.cols === 1) {
|
||||
this.$nextTick(() => {
|
||||
this.activeCanvas(0)
|
||||
})
|
||||
}
|
||||
},
|
||||
handleLayoutChange(event) {
|
||||
this.handleCommand(event.target.value.replace('x', '*'))
|
||||
},
|
||||
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)
|
||||
if (i === -1) return
|
||||
if (!this.viewportInfos[i].currentFileName) return
|
||||
if (this.imageType.includes(this.viewportInfos[i].fileType)) {
|
||||
this.fullScreenIndex = this.fullScreenIndex === index ? null : index
|
||||
this.activeCanvasIndex = index
|
||||
}
|
||||
},
|
||||
// 激活工具
|
||||
setToolActive(toolName) {
|
||||
const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
|
||||
if (i === -1) return
|
||||
const toolGroupId = `canvas-${this.viewportInfos[i].index}`
|
||||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
||||
if (this.activeTool === toolName) {
|
||||
toolGroup.setToolPassive(this.activeTool)
|
||||
this.activeTool = ''
|
||||
} else {
|
||||
if (this.activeTool) {
|
||||
toolGroup.setToolPassive(this.activeTool)
|
||||
}
|
||||
toolGroup.setToolActive(toolName, {
|
||||
bindings: [{ mouseButton: MouseBindings.Primary }]
|
||||
})
|
||||
this.activeTool = toolName
|
||||
}
|
||||
},
|
||||
setToolsPassive() {
|
||||
if (!this.activeTool) return
|
||||
const toolGroupIds = ['canvas-0', 'canvas-1', 'canvas-2', 'canvas-3']
|
||||
toolGroupIds.forEach(toolGroupId => {
|
||||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
|
||||
toolGroup.setToolPassive(this.activeTool)
|
||||
})
|
||||
this.activeTool = ''
|
||||
},
|
||||
// 重置视图
|
||||
resetViewport() {
|
||||
this.setToolsPassive()
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
const viewportId = `canvas-${this.activeCanvasIndex}`
|
||||
const viewport = (renderingEngine.getViewport(viewportId))
|
||||
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true })
|
||||
renderingEngine.render()
|
||||
},
|
||||
setZoomScale(isZoomIn) {
|
||||
this.setToolsPassive()
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
const viewportId = `canvas-${this.activeCanvasIndex}`
|
||||
const viewport = renderingEngine?.getViewport(viewportId)
|
||||
if (!viewport) return
|
||||
const camera = viewport.getCamera()
|
||||
const factor = 1.1
|
||||
const current = Number(camera.parallelScale)
|
||||
if (!Number.isFinite(current) || current <= 0) return
|
||||
const next = isZoomIn ? current / factor : current * factor
|
||||
const parallelScale = Math.max(0.0001, Math.min(1000000, next))
|
||||
viewport.setCamera({ parallelScale })
|
||||
viewport.render()
|
||||
},
|
||||
// 滚动条
|
||||
clickSlider(e, index) {
|
||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
||||
if (i === -1) return
|
||||
const sliderBox = this.$refs[`sliderBox-${index}`] && this.$refs[`sliderBox-${index}`][0]
|
||||
if (!sliderBox || !this.imageType.includes(this.viewportInfos[i].fileType)) return
|
||||
const boxHeight = sliderBox.clientHeight
|
||||
if (!boxHeight || !this.viewportInfos[i].imageIds.length) return
|
||||
const height = e.offsetY * 100 / boxHeight
|
||||
this.viewportInfos[i].height = height
|
||||
let sliceIdx = Math.trunc(this.viewportInfos[i].imageIds.length * height / 100)
|
||||
sliceIdx = sliceIdx >= this.viewportInfos[i].imageIds.length ? this.viewportInfos[i].imageIds.length - 1 : sliceIdx < 0 ? 0 : sliceIdx
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
this.viewportInfos[i].viewportId
|
||||
)
|
||||
this.setToolsPassive()
|
||||
viewport.setImageIdIndex(sliceIdx)
|
||||
viewport.render()
|
||||
},
|
||||
sliderMouseup(e, index) {
|
||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
||||
if (i === -1 || !this.imageType.includes(this.viewportInfos[i].fileType)) return
|
||||
this.viewportInfos[i].isMove = false
|
||||
},
|
||||
sliderMousemove(e, index) {
|
||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
||||
if (i === -1 || !this.imageType.includes(this.viewportInfos[i].fileType)) return
|
||||
if (!this.viewportInfos[i].isMove) return
|
||||
const sliderBox = this.$refs[`sliderBox-${index}`] && this.$refs[`sliderBox-${index}`][0]
|
||||
if (!sliderBox || !this.viewportInfos[i].imageIds.length) return
|
||||
const delta = this.viewportInfos[i].oldB - (this.viewportInfos[i].oldM - e.clientY)
|
||||
const boxHeight = sliderBox.clientHeight
|
||||
if (delta < 0) return
|
||||
if (delta > boxHeight) return
|
||||
const height = delta * 100 / boxHeight
|
||||
let sliceIdx = Math.trunc(this.viewportInfos[i].imageIds.length * height / 100)
|
||||
sliceIdx = sliceIdx >= this.viewportInfos[i].imageIds.length ? this.viewportInfos[i].imageIds.length - 1 : sliceIdx < 0 ? 0 : sliceIdx
|
||||
this.viewportInfos[i].height = height
|
||||
// if (this.viewportInfos[i].currentImageIdIndex !== i) {
|
||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
||||
const viewport = renderingEngine.getViewport(
|
||||
this.viewportInfos[i].viewportId
|
||||
)
|
||||
this.setToolsPassive()
|
||||
viewport.setImageIdIndex(sliceIdx)
|
||||
viewport.render()
|
||||
// }
|
||||
// this.$emit('toggleImage', { taskId: this.viewportInfos[i].taskInfo.VisitTaskId, studyId: this.viewportInfos[i].studyId, imageIndex: sliceIdx })
|
||||
},
|
||||
sliderMouseleave(e, index) {
|
||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
||||
if (i === -1 || !this.imageType.includes(this.viewportInfos[i].fileType)) return
|
||||
if (!this.viewportInfos[i].isMove) return
|
||||
this.viewportInfos[i].isMove = false
|
||||
},
|
||||
sliderMousedown(e, index) {
|
||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
||||
if (i === -1 || !this.imageType.includes(this.viewportInfos[i].fileType)) return
|
||||
const sliderBox = this.$refs[`sliderBox-${index}`] && this.$refs[`sliderBox-${index}`][0]
|
||||
if (!sliderBox || !this.viewportInfos[i].imageIds.length) return
|
||||
const boxHeight = sliderBox.clientHeight
|
||||
const sliderTop = parseFloat(e.currentTarget.style.top) || 0
|
||||
this.viewportInfos[i].oldB = sliderTop * boxHeight / 100
|
||||
this.viewportInfos[i].oldM = e.clientY
|
||||
this.viewportInfos[i].isMove = true
|
||||
e.stopImmediatePropagation()
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #383838;
|
||||
color: #fff;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
z-index: 9999;
|
||||
font-size: 12px;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
|
||||
li {
|
||||
a {
|
||||
display: block;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul li a:hover {
|
||||
background-color: #727272;
|
||||
}
|
||||
|
||||
.customize-standards-list {
|
||||
width: max-content;
|
||||
white-space: nowrap;
|
||||
|
||||
li a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.none-dicom-viewer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
background: #000;
|
||||
|
||||
.dicom-tools {
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background-color: #323232;
|
||||
margin: 0 0 0 2px;
|
||||
padding: 0;
|
||||
color: #d0d0d0;
|
||||
font-size: 13px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sideTool-title {
|
||||
padding: 5px 8px;
|
||||
background-color: #525252;
|
||||
color: #bebdbd;
|
||||
font-size: 14px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.sideTool-wrapper {
|
||||
border-bottom: 1px solid gray;
|
||||
margin: 0 4px;
|
||||
padding: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sidetool-select {
|
||||
height: 30px;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: #d0d0d0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #5b5b5b;
|
||||
}
|
||||
|
||||
.sidetool-select > option {
|
||||
background-color: #323232;
|
||||
}
|
||||
|
||||
.sideTool-wrapper button.btn-link {
|
||||
height: 40px;
|
||||
padding: 8px !important;
|
||||
border: 1px solid rgba(37, 37, 37, 1);
|
||||
margin: 1px 1px;
|
||||
}
|
||||
|
||||
.sideTool-wrapper .btn-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
color: #cccccc;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.activeTool {
|
||||
background: #16477b90 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.sideTool-wrapper label {
|
||||
display: inline-block;
|
||||
min-width: 44px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.viewports-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.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 {
|
||||
border-color: #fafa00 !important;
|
||||
}
|
||||
|
||||
.cell-full-screen {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 1 / -1;
|
||||
}
|
||||
|
||||
.flex_col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.left-top-text {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 5px;
|
||||
color: #ddd;
|
||||
z-index: 1;
|
||||
font-size: 12px;
|
||||
|
||||
.cd-info {
|
||||
color: #ddd;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.subject-info {
|
||||
color: #f44336;
|
||||
padding: 5px 0px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.top-center-tool {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 5px;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
|
||||
.toggle-visit-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.arrw_icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: #3f3f3f;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
.arrow_text {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
background-color: #00000057;
|
||||
color: #fff;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.right-slider-box {
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
height: calc(100% - 140px);
|
||||
transform: translateY(-50%);
|
||||
top: calc(50% - 30px);
|
||||
width: 10px;
|
||||
background: #333;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.right-slider-box:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.slider {
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,456 @@
|
|||
<template>
|
||||
<div v-loading="loading" class="none-dicom-study-wrapper">
|
||||
<div class="study-info">
|
||||
{{ $t('trials:none-dicom-show:fileList') }}
|
||||
</div>
|
||||
<div class="ps" ref="studyBox_ps">
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.Id}`" :name="`${study.Id}`">
|
||||
<template slot="title">
|
||||
<div class="dicom-desc">
|
||||
<div style="text-overflow: ellipsis;overflow: hidden;">
|
||||
<span :title="study.CodeView">{{ study.CodeView }}</span>
|
||||
<span
|
||||
v-if="otherInfo && otherInfo.IsShowStudyName"
|
||||
:title="study.StudyName"
|
||||
style="margin-left: 5px;"
|
||||
>
|
||||
{{study.StudyName }}
|
||||
</span>
|
||||
</div>
|
||||
<div style="text-overflow: ellipsis;overflow: hidden;">
|
||||
<span :title="study.BodyPart">{{ getBodyPart(study.BodyPart, study.BodyPartForEditOther) }}</span>
|
||||
<span style="margin-left: 5px;" :title="study.Modality">{{ study.Modality }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="file-list-container">
|
||||
<div v-for="(k, i) in study.NoneDicomStudyFileList" :key="i" style="position:relative;margin-top:1px;"
|
||||
@click="selectFile(study, index, i, k)" :ref="`noneDicomRef_${index}_${i}`">
|
||||
<div :class="{ 'file-active': index === activeStudyIndex && i === activeFileIndex }" class="file-wrapper">
|
||||
<div class="file-main">
|
||||
<div class="file-image">
|
||||
<el-image
|
||||
v-if="k.FileType === 'image/jpeg' || k.FileType === 'image/jpg' || k.FileType === 'image/bmp' || k.FileType === 'image/png'"
|
||||
style="width: 100%;height: 100%;"
|
||||
:src="`${OSSclientConfig.basePath}${k.Path}?x-oss-process=image/resize,w_50,h_50/format,png`"
|
||||
fit="contain" crossorigin="anonymous" />
|
||||
<el-image v-else-if="k.FileType === 'application/pdf'" style="width: 100%;height: 100%;" :src="pdf"
|
||||
fit="contain" crossorigin="anonymous" />
|
||||
<el-image v-else-if="
|
||||
!!~k.FileType.indexOf('mp4')
|
||||
" style="width: 100%; height: 100%" :src="mp4" fit="contain" crossorigin="anonymous" />
|
||||
</div>
|
||||
<div class="file-text" :title="k.FileName">
|
||||
{{ k.FileName }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isQcCheck" class="switchBox">
|
||||
<div class="item">
|
||||
<span>{{ $t('trials:audit:table:isReading') }}</span>
|
||||
<el-switch
|
||||
v-model="k.IsReading"
|
||||
:disabled="k.IsDeleted || isAudit"
|
||||
:active-text="$fd('YesOrNo', true)"
|
||||
:inactive-text="$fd('YesOrNo', false)"
|
||||
@change="changeReadingStatus($event, study, k)"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span>{{ $t('trials:audit:table:isDelete') }}</span>
|
||||
<el-switch
|
||||
v-model="k.IsDeleted"
|
||||
:disabled="isAudit"
|
||||
:active-text="$fd('YesOrNo', true)"
|
||||
:inactive-text="$fd('YesOrNo', false)"
|
||||
@change="changeDeleteStatus($event, study, k)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getNoneDicomStudyList, setNodicomStudyState } from '@/api/trials'
|
||||
import pdf from '@/assets/pdf.png'
|
||||
import mp4 from '@/assets/mp4.png'
|
||||
export default {
|
||||
name: 'StudyList',
|
||||
props: {
|
||||
visitTaskInfo: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
activeNames: [],
|
||||
activeStudyIndex: -1,
|
||||
activeFileIndex: -1,
|
||||
studyList: [],
|
||||
pdf,
|
||||
mp4,
|
||||
BodyPart: {},
|
||||
subjectVisitId: '',
|
||||
studyId: '',
|
||||
otherInfo: null,
|
||||
criterionType: null,
|
||||
isQcCheck: false,
|
||||
isAudit: false
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.subjectVisitId = this.$route.query.subjectVisitId
|
||||
this.studyId = this.$route.query.studyId
|
||||
this.isQcCheck = !!this.$route.query.isQcCheck
|
||||
this.getNoneDicoms()
|
||||
|
||||
// this.$nextTick(() => {
|
||||
// this.activeStudy(this.studyList[0].Id)
|
||||
// })
|
||||
this.BodyPart.Bodypart = await this.$getBodyPart(this.$route.query.trialId)
|
||||
},
|
||||
methods: {
|
||||
scrollActiveFileIntoView() {
|
||||
this.$nextTick(() => {
|
||||
const refKey = `noneDicomRef_${this.activeStudyIndex}_${this.activeFileIndex}`
|
||||
const targetRef = this.$refs[refKey]
|
||||
const element = Array.isArray(targetRef) ? targetRef[0] : targetRef
|
||||
if (!element) return
|
||||
this.scrollElementToVisible(element)
|
||||
})
|
||||
},
|
||||
scrollElementToVisible(element, options = {}) {
|
||||
const container = this.$refs['studyBox_ps']
|
||||
if (!container || !element) return
|
||||
const elementTop = element.offsetTop
|
||||
const elementBottom = elementTop + element.offsetHeight
|
||||
const visibleTop = container.scrollTop
|
||||
const visibleBottom = visibleTop + container.clientHeight
|
||||
if (elementTop >= visibleTop && elementBottom <= visibleBottom) return
|
||||
const offset = options.offset != null
|
||||
? options.offset
|
||||
: Math.max((container.clientHeight - element.offsetHeight) / 2, 0)
|
||||
const targetPosition = Math.max(elementTop - offset, 0)
|
||||
container.scrollTo({
|
||||
top: targetPosition,
|
||||
behavior: options.behavior || 'smooth'
|
||||
})
|
||||
},
|
||||
async getNoneDicoms() {
|
||||
const loading = this.$loading({ fullscreen: true })
|
||||
try {
|
||||
let res = await getNoneDicomStudyList(
|
||||
this.subjectVisitId,
|
||||
this.studyId,
|
||||
false,
|
||||
this.$route.query.visitTaskId,
|
||||
!!this.$route.query.isReading,
|
||||
this.$route.query.isImageSegmentLabel,
|
||||
)
|
||||
this.studyList = res.Result
|
||||
this.otherInfo = res.OtherInfo
|
||||
this.isAudit = res.OtherInfo.AuditState * 1 > 6
|
||||
const studyIndex = this.studyList.findIndex((item) => {
|
||||
return item.NoneDicomStudyFileList.length > 0
|
||||
})
|
||||
if (studyIndex > -1) {
|
||||
this.selectFile(this.studyList[studyIndex], studyIndex, 0)
|
||||
let studyId = this.studyList[studyIndex].Id
|
||||
this.activeNames = [studyId]
|
||||
}
|
||||
if (this.criterionType === 19 || this.criterionType === 20) {
|
||||
this.sortFile()
|
||||
}
|
||||
loading.close()
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
loading.close()
|
||||
}
|
||||
|
||||
},
|
||||
selected() {
|
||||
|
||||
},
|
||||
sortFile() {
|
||||
|
||||
},
|
||||
changeReadingStatus(callback, row, file) {
|
||||
let statusStr = ''
|
||||
if (callback) {
|
||||
statusStr = this.$t('trials:audit:label:setSeriesReading')
|
||||
file.IsReading = false
|
||||
} else {
|
||||
statusStr = this.$t('trials:audit:label:setSeriesNotReading')
|
||||
file.IsReading = true
|
||||
}
|
||||
let message = this.$t('trials:audit:message:changeSeriesStatus').replace('xxx', statusStr)
|
||||
message = message.replace('yyy', this.$fd('YesOrNo', !file.IsReading))
|
||||
this.$confirm(message, {
|
||||
distinguishCancelAndClose: true,
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
const state = file.IsReading ? 1 : 2
|
||||
this.loading = true
|
||||
const params = {
|
||||
SubjectVisitId: row.SubjectVisitId,
|
||||
NoneDicomStudyId: row.Id,
|
||||
State: state,
|
||||
NoneDicomStudyFileId: file.Id,
|
||||
}
|
||||
return setNodicomStudyState(params)
|
||||
.then((res) => {
|
||||
this.loading = false
|
||||
if (res.IsSuccess) {
|
||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
file.IsReading = !file.IsReading
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
.catch(() => { })
|
||||
},
|
||||
changeDeleteStatus(callback, row, file) {
|
||||
let statusStr = ''
|
||||
if (callback) {
|
||||
statusStr = this.$t('trials:audit:label:setSeriesDeleted')
|
||||
file.IsDeleted = false
|
||||
} else {
|
||||
statusStr = this.$t('trials:audit:label:setSeriesNotDelete')
|
||||
file.IsDeleted = true
|
||||
}
|
||||
let message = this.$t('trials:audit:message:changeSeriesStatus').replace('xxx', statusStr)
|
||||
message = message.replace('yyy', this.$fd('YesOrNo', !file.IsDeleted))
|
||||
this.$confirm(message, {
|
||||
distinguishCancelAndClose: true,
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
const state = file.IsDeleted ? 5 : 4
|
||||
this.loading = true
|
||||
const params = {
|
||||
SubjectVisitId: row.SubjectVisitId,
|
||||
NoneDicomStudyId: row.Id,
|
||||
State: state,
|
||||
NoneDicomStudyFileId: file.Id,
|
||||
}
|
||||
return setNodicomStudyState(params)
|
||||
.then((res) => {
|
||||
this.loading = false
|
||||
if (res.IsSuccess) {
|
||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||||
file.IsDeleted = !file.IsDeleted
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
})
|
||||
.catch(() => { })
|
||||
},
|
||||
getBodyPart(bodyPart, other) {
|
||||
if (!bodyPart && !other) return ''
|
||||
var separator = ','
|
||||
if (bodyPart.indexOf('|') > -1) {
|
||||
separator = '|'
|
||||
} else if (bodyPart.indexOf(',') > -1) {
|
||||
separator = ','
|
||||
} else if (bodyPart.indexOf(',') > -1) {
|
||||
separator = ','
|
||||
}
|
||||
var arr = bodyPart.split(separator)
|
||||
var newArr = arr.map((i) => {
|
||||
return this.$fd('Bodypart', i.trim(), 'Code', this.BodyPart, 'Name')
|
||||
})
|
||||
if (other) {
|
||||
newArr.push(other)
|
||||
}
|
||||
newArr = newArr.filter(Boolean)
|
||||
return newArr.join(' | ')
|
||||
},
|
||||
// 切换文件
|
||||
selectFile(study, studyIndex, fileIndex) {
|
||||
this.activeStudyIndex = studyIndex
|
||||
this.activeFileIndex = fileIndex
|
||||
const fileList = study.NoneDicomStudyFileList
|
||||
this.$emit('selectFile', { fileInfo: fileList[fileIndex], fileList, visitTaskInfo: this.visitTaskInfo, studyId: study.Id })
|
||||
this.scrollActiveFileIntoView()
|
||||
},
|
||||
activeImage(obj) {
|
||||
if (!obj || this.studyList.length === 0) return
|
||||
const studyIndex = this.studyList.findIndex(i => i.Id === obj.studyId)
|
||||
if (studyIndex === -1) return
|
||||
this.activeStudy(`${obj.studyId}`)
|
||||
this.activeStudyIndex = studyIndex
|
||||
const fileList = this.studyList[studyIndex].NoneDicomStudyFileList || []
|
||||
const fileIndex = fileList.findIndex(f => f.Path === obj.path)
|
||||
if (fileIndex === -1) return
|
||||
this.activeFileIndex = fileIndex
|
||||
this.scrollActiveFileIntoView()
|
||||
},
|
||||
activeStudy(id) {
|
||||
const studyId = `${id}`
|
||||
if (this.activeNames.indexOf(studyId) > -1) return
|
||||
this.activeNames.push(studyId)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.none-dicom-study-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.study-info {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #ddd;
|
||||
// padding: 5px 0px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
background-color: #4c4c4c;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.dicom-desc {
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
color: #d0d0d0;
|
||||
padding: 2px 8px 2px 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ps {
|
||||
flex: 1;
|
||||
overflow-anchor: none;
|
||||
touch-action: auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.file-active {
|
||||
// background: #16477b;
|
||||
background-color: #183553 !important;
|
||||
border: 1px solid #23527b !important;
|
||||
}
|
||||
|
||||
::v-deep .el-progress__text {
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.file-list-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
.file-wrapper {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
cursor: pointer;
|
||||
background-color: #3a3a3a;
|
||||
|
||||
// color: #ddd;
|
||||
// white-space: nowrap;
|
||||
// overflow: hidden;
|
||||
// text-overflow: ellipsis;
|
||||
.file-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.el-progress__text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-progress-bar {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.file-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.file-text {
|
||||
flex: 1;
|
||||
padding-left: 5px;
|
||||
color: #ddd;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.switchBox {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
color: #ddd;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-collapse {
|
||||
border: none;
|
||||
|
||||
.el-collapse-item {
|
||||
background-color: #000 !important;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0px;
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
.el-collapse-item__header {
|
||||
background-color: #353535 !important;
|
||||
color: #ddd;
|
||||
border-bottom-color: #5a5a5a;
|
||||
padding-left: 5px;
|
||||
height: 60px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
::v-deep .el-progress-bar__inner {
|
||||
transition: width 0s ease;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<div class="read-page-container">
|
||||
<!-- 检查列表 -->
|
||||
<div class="left-panel">
|
||||
<div class="study-info">
|
||||
<study-list ref="studyList" @selectFile="selectFile"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图像 -->
|
||||
<div class="middle-panel">
|
||||
|
||||
<file-viewer ref="fileViewer"
|
||||
@toggleImage="toggleImage"
|
||||
@selectFile="selectFile" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import store from '@/store'
|
||||
import { changeURLStatic } from '@/utils/history.js'
|
||||
import StudyList from './components/study-list'
|
||||
import FileViewer from './components/file-viewer'
|
||||
export default {
|
||||
name: 'NoneDicoms',
|
||||
components: {
|
||||
StudyList,
|
||||
FileViewer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.$route.query.TokenKey) {
|
||||
store.dispatch('user/setToken', this.$route.query.TokenKey)
|
||||
changeURLStatic('TokenKey', '')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleImage(obj) {
|
||||
this.$refs['studyList'].activeImage(obj)
|
||||
},
|
||||
selectFile(obj) {
|
||||
if (obj.fileInfo && obj.fileList && obj.studyId) {
|
||||
this.$refs['fileViewer'].setActiveCanvasImages(obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.read-page-container {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background-color: #444;
|
||||
padding: 5px;
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background: #d0d0d0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
display: flex;
|
||||
width: 230px;
|
||||
border: 1px solid #727272;
|
||||
color: #fff;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background: #d0d0d0;
|
||||
}
|
||||
|
||||
.study-info {
|
||||
width: 100%;
|
||||
border-left: 1px solid #727272;
|
||||
}
|
||||
}
|
||||
|
||||
.middle-panel {
|
||||
flex: 1;
|
||||
border: 1px solid #727272;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
// .right-panel {
|
||||
// width: 400px;
|
||||
// border: 1px solid #727272;
|
||||
// padding: 0 10px;
|
||||
// overflow-y: auto;
|
||||
|
||||
// .text-info {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: space-between;
|
||||
// color: #ddd;
|
||||
// }
|
||||
// }
|
||||
|
||||
::v-deep .dialog-container {
|
||||
margin-top: 50px !important;
|
||||
width: 75%;
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 20px 20px 0 20px;
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.full-dialog-container {
|
||||
::v-deep .is-fullscreen .el-dialog__body {
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -107,12 +107,18 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
scrollElementToTop(element, options = {}) {
|
||||
const absoluteTop = element.offsetTop;
|
||||
const targetPosition = absoluteTop - (options.offset || 0);
|
||||
this.$refs['studyBox_ps'].scrollTo({
|
||||
const container = this.$refs['studyBox_ps']
|
||||
if (!container || !element) return
|
||||
const elementTop = element.offsetTop
|
||||
const elementBottom = elementTop + element.offsetHeight
|
||||
const visibleTop = container.scrollTop
|
||||
const visibleBottom = visibleTop + container.clientHeight
|
||||
if (elementTop >= visibleTop && elementBottom <= visibleBottom) return
|
||||
const targetPosition = Math.max(elementTop - (options.offset || 0), 0)
|
||||
container.scrollTo({
|
||||
top: targetPosition,
|
||||
behavior: options.behavior || 'smooth'
|
||||
});
|
||||
})
|
||||
},
|
||||
getBodyPart(bodyPart, other) {
|
||||
if (!bodyPart && !other) return ''
|
||||
|
|
|
|||
|
|
@ -2504,10 +2504,10 @@ export default {
|
|||
var token = getToken()
|
||||
const routeData = isReading
|
||||
? this.$router.resolve({
|
||||
path: `/showNoneDicoms?trialId=${trialId}&subjectVisitId=${this.data.Id}&TokenKey=${token}&isReading=1`,
|
||||
path: `/noneDicoms?trialId=${trialId}&subjectVisitId=${this.data.Id}&TokenKey=${token}&isReading=1`,
|
||||
})
|
||||
: this.$router.resolve({
|
||||
path: `/showNoneDicoms?trialId=${trialId}&subjectVisitId=${this.data.Id}&TokenKey=${token}&isQcCheck=${this.SecondReviewState > 0 ? 0 : 1}`,
|
||||
path: `/noneDicoms?trialId=${trialId}&subjectVisitId=${this.data.Id}&TokenKey=${token}&isQcCheck=${this.SecondReviewState > 0 ? 0 : 1}`,
|
||||
})
|
||||
this.open = window.open(routeData.href, '_blank')
|
||||
},
|
||||
|
|
@ -2520,7 +2520,7 @@ export default {
|
|||
let trialId = this.$route.query.trialId
|
||||
var token = getToken()
|
||||
const routeData = this.$router.resolve({
|
||||
path: `/showNoneDicoms?trialId=${trialId}&subjectVisitId=${this.data.Id}&studyId=${row.Id}&TokenKey=${token}&isQcCheck=${this.SecondReviewState > 0 ? 0 : 1}`,
|
||||
path: `/noneDicoms?trialId=${trialId}&subjectVisitId=${this.data.Id}&studyId=${row.Id}&TokenKey=${token}&isQcCheck=${this.SecondReviewState > 0 ? 0 : 1}`,
|
||||
})
|
||||
this.open = window.open(routeData.href, '_blank')
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue