irc_web/src/views/trials/trials-panel/reading/visit-review/components/FileViewer.vue

2111 lines
80 KiB
Vue

<template>
<div class="none-dicom-viewer">
<!-- tools -->
<div class="tools-wrapper">
<div class="tools-left">
<!-- 布局 -->
<div class="tool-item" :title="$t('trials:reading:button:layout')">
<el-dropdown @command="handleCommand">
<span class="el-dropdown-link">
<svg-icon icon-class="layout" class="svg-icon" /><i class="el-icon-arrow-down el-icon--right" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="1*1">1*1</el-dropdown-item>
<el-dropdown-item command="1*2">1*2</el-dropdown-item>
<el-dropdown-item command="2*2">2*2</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<!-- 缩放 -->
<div :class="['tool-item', activeTool === 'Zoom' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:zoom')" @click.prevent="setToolActive('Zoom')">
<svg-icon icon-class="magnifier" class="svg-icon" />
</div>
<!-- 移动 -->
<div :class="['tool-item', activeTool === 'Pan' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:move')" @click.prevent="setToolActive('Pan')">
<svg-icon icon-class="move" class="svg-icon" />
</div>
<!-- 旋转 -->
<div :class="['tool-item', activeTool === 'PlanarRotate' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:rotate')" @click.prevent="setToolActive('PlanarRotate')">
<svg-icon icon-class="rotate" class="svg-icon" />
</div>
<div v-for="tool in tools" :key="tool.toolName"
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === tool.toolName ? 'tool-item-active' : '']"
:style="{ cursor: tool.isDisabled ? 'not-allowed' : 'pointer' }"
:title="tool.disabledReason ? tool.disabledReason : $t(`${tool.i18nKey}`)"
@click.prevent="setAnnotateToolActive(tool.toolName)">
<svg-icon :icon-class="tool.icon" class="svg-icon" />
</div>
<!-- <div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'SplineROITool' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:SplineROITool')"
@click.prevent="setAnnotateToolActive('SplineROITool')"
>
<svg-icon icon-class="polygon" class="svg-icon" />
</div> -->
<!--readingTaskState === 2 ? 'tool-disabled' : '',-->
<div :class="['tool-item', activeTool === 'Eraser' ? 'tool-item-active' : '']"
:title="$t('trials:dicom-show:Eraser')" @click.prevent="setAnnotateToolActive('Eraser')">
<svg-icon icon-class="clear" class="svg-icon" />
</div>
<!--比例尺-->
<div
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === 'Lengthscale' ? 'tool-item-active' : '']"
:title="$t('trials:nondicom-show:scale')" @click.prevent="setAnnotateToolActive('Lengthscale')">
<svg-icon icon-class="lengthscale" class="svg-icon" />
</div>
<!-- 截图 -->
<!-- <div
class="tool-item"
:title="$t('trials:reading:button:screenShot')"
@click.prevent="saveImage"
>
<svg-icon icon-class="image" class="svg-icon" />
</div> -->
<!-- 重置 -->
<div class="tool-item" :title="$t('trials:reading:button:reset')" @click.prevent="resetViewport">
<svg-icon icon-class="refresh" class="svg-icon" />
</div>
<!-- 更多:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '']" -->
<div v-if="criterionType === 0" :title="$t('trials:reading:button:more')" :class="['tool-item']"
@click.stop="showPanel($event)" @mouseleave="toolMouseout">
<div class="dropdown">
<div class="icon" data-tool="more">
<svg-icon icon-class="more" class="svg-icon" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content">
<!--v-if="readingTaskState < 2"-->
<ul style="width:100px;">
<li v-for="i in customizeStandardsNoneDicom" :key="i.toolName" style="text-align:left;">
<a href="#" @click.prevent="setAnnotateToolActive(i.toolName)">
<svg-icon :icon-class="i.icon" class="svg-icon" style="margin-right: 5px;" />
{{ $t(i.i18nKey) }}
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="tool-item" :title="$t('trials:reading:button:upload')"
v-if="trialCriterion.ImageUploadEnum > 0 && readingTaskState < 2" v-hasPermi="['role:ir']">
<div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('upload')">
<i class="el-icon-upload2 svg-icon" />
</div>
</div>
</div>
<div v-if="trialCriterion.ImageDownloadEnum > 0" v-hasPermi="[
'role:ir',
'role:mim',
'role:mc',
'role:pm',
'role:apm',
'role:ea',
'role:qa',
]" class="tool-item" :title="$t('trials:reading:button:download')">
<div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('download')">
<i class="el-icon-download svg-icon" />
</div>
</div>
</div>
</div>
<div style="display: flex;">
<!-- 手册 -->
<div class="tool-item" :title="$t('trials:reading:button:handbooks')"
v-if="taskInfo && (taskInfo.ExistsManual || taskInfo.IsHaveKeyFile)" @click.prevent="previewManuals">
<svg-icon icon-class="constitution" class="svg-icon" />
</div>
<!-- 个性化配置 -->
<div class="tool-item" :title="$t('trials:reading:button:customCfg')" @click.prevent="previewConfig">
<svg-icon icon-class="individuation" class="svg-icon" />
</div>
</div>
</div>
<!-- viewports -->
<div class="viewports-wrapper" v-loading="loading">
<div class="grid-container" :style="gridStyle">
<div v-for="(v, index) in viewportInfos" v-show="index < cells.length" :key="index" :style="cellStyle"
: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"
@mouseup="contentMouseup($event, index)">
<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 :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-show="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.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" 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>
</div>
</div>
<el-dialog :title="$t('trials:noneDicom:message:msg2')" :visible.sync="dialogVisible" :close-on-click-modal="false"
:close-on-press-escape="false" :show-close="false" width="400px">
<el-form ref="lengthForm" :model="form" :rules="rules">
<el-form-item label="" prop="length">
<el-input v-model="form.length" type="number">
<template slot="append">mm</template>
</el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="saveForm">{{ $t('common:button:save') }}</el-button>
</span>
</el-dialog>
<el-dialog v-if="personalConfigDialog.visible" :visible.sync="personalConfigDialog.visible"
:close-on-click-modal="false" :title="personalConfigDialog.title" width="600px">
<Others />
</el-dialog>
<upload-dicom-and-nonedicom v-if="uploadImageVisible" :subject-id="uploadSubjectId"
:subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible"
:visit-task-id="taskId" :is-reading-task-view-in-order="isReadingTaskViewInOrder" />
<download-dicom-and-nonedicom v-if="downloadImageVisible" :subject-id="uploadSubjectId"
:subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :task-id="taskId"
:visible.sync="downloadImageVisible" />
<!-- 手册 -->
<el-dialog v-if="manualsDialog.visible" :visible.sync="manualsDialog.visible"
:custom-class="manualsDialog.isFullscreen ? 'manuals-full-dialog-container' : 'manuals-dialog-container'"
:show-close="false" :close-on-click-modal="false" :fullscreen="manualsDialog.isFullscreen">
<span slot="title" class="dialog-footer">
<!-- 手册 -->
<span>{{ $t('trials:reading:button:handbooks') }}</span>
<span style="position: absolute;right: 20px;font-size: 20px;">
<svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
style="margin-right:10px;cursor: pointer;"
@click="manualsDialog.isFullscreen = !manualsDialog.isFullscreen" />
<svg-icon icon-class="close" style="cursor: pointer;" @click="manualsDialog.visible = false" />
</span>
</span>
<div style="height: 100%;margin:0;">
<Manuals :trial-id="trialId" :justKeyDoc="manualsDialog.justKeyDoc" />
<div slot="footer" style="text-align:right;" v-if="openManuals">
<!-- 确认 -->
<el-button type="primary" size="mini" @click="handleSubmitKeyDoc">
{{ $t('common:button:confirm') }}</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { addNoneDicomMark, deleteTrialFileType, getCriterionReadingInfo } from '@/api/trials'
import html2canvas from 'html2canvas'
import {
RenderingEngine,
Enums,
imageLoader,
metaData,
getRenderingEngine,
eventTarget,
utilities as csUtils
// getEnabledElementByIds
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
import initLibraries from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initLibraries'
import hardcodedMetaDataProvider from './../js/hardcodedMetaDataProvider'
import registerWebImageLoader from './../js/registerWebImageLoader'
import { mapGetters } from 'vuex'
import store from '@/store'
import Others from '@/views/trials/trials-panel/reading/dicoms/components/Others'
import Manuals from '@/views/trials/trials-panel/reading/dicoms/components/Manuals'
const { ViewportType } = Enums
const renderingEngineId = 'myRenderingEngine'
import LengthscaleTool from "../tools/LengthscaleTool"
import { getCustomizeStandardsNoneDicomTools, config } from '@/views/trials/trials-panel/reading/dicoms3D/components/toolConfig'
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
const {
ToolGroupManager,
Enums: csToolsEnums,
StackScrollTool,
PanTool,
ZoomTool,
PlanarRotateTool,
ArrowAnnotateTool,
RectangleROITool,
PlanarFreehandROITool,
SplineROITool,
EraserTool,
LengthTool,
EllipticalROITool,
CircleROITool,
AngleTool
// cursors
} = cornerstoneTools
const { MouseBindings, Events: toolsEvents } = csToolsEnums
export default {
name: 'ImageViewer',
components: {
Others,
downloadDicomAndNonedicom,
uploadDicomAndNonedicom,
Manuals
},
props: {
relatedStudyInfo: {
type: Object,
default() {
return {}
}
},
psArr: {
type: Array,
default() {
return []
}
},
ecrf: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
rows: 1,
cols: 1,
fullScreenIndex: null,
imageIds: [],
activeCanvasIndex: 0,
activeCanvasFileType: '',
layout: '1*2',
cellsMax: 4,
viewportInfos: [],
taskInfo: null,
activeTool: '',
readingTaskState: 2,
renderHistoryAnnotationTaskIds: [],
imageType: ['image/jpeg', 'image/jpg', 'image/bmp', 'image/png'],
digitPlaces: 2,
dialogVisible: false,
personalConfigDialog: { visible: false, title: this.$t('trials:reading:button:customCfg') }, // 个性化配置
form: {
length: null,
annotationObj: {}
},
rules: {
length: [
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: 'blur' },
{ pattern: /^\d+$/, message: this.$t('trials:noneDicom:message:msg3'), trigger: ['blur', 'change'] }
]
},
customizeStandardsNoneDicom: [],
tools: [],
criterionType: null,
// 上传
downloadImageVisible: false,
uploadImageVisible: false,
uploadSubjectId: null,
uploadSubjectCode: null,
uploadTrialCriterion: {},
uploadStatus: 'upload',
taskId: '',
isReadingTaskViewInOrder: null,
trialCriterion: {},
saveCustomAnnotationTimer: null,
curOperation: {
type: '',
annotation: null
},
loading: false,
manualsDialog: { visible: false, isFullscreen: false },
trialId: null
}
},
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)
},
...mapGetters(['lastViewportTaskId', 'currentTaskState']),
openManuals() {
return !this.taskInfo.IsReadKeyFile && this.taskInfo.IsHaveKeyFile
}
},
watch: {
openManuals: {
handler() {
if (this.openManuals) this.previewManuals(true)
},
immediate: true
},
relatedStudyInfo: {
immediate: true,
handler(obj) {
if (!obj || Object.keys(obj).length === 0) return
this.updateViewportInfos(0, obj)
}
},
currentTaskState: {
immediate: true,
handler(state) {
if (state === 2) {
this.readingTaskState = 2
// 设置标记锁定
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
annotations.map(annotation => {
cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
})
this.setToolsPassive()
}
}
}
},
mounted() {
this.trialId = this.$route.query.trialId
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.readingTaskState = this.taskInfo.ReadingTaskState
this.criterionType = this.taskInfo.CriterionType
if (this.criterionType === 0) {
this.tools = getCustomizeStandardsNoneDicomTools(this.taskInfo.ReadingToolList)
const toolNames = this.tools.map(i => i.toolName)
this.customizeStandardsNoneDicom = config.customizeStandardsNoneDicom.filter(item => !toolNames.includes(item.toolName))
} else {
this.tools = config.customizeStandardsNoneDicom
}
if (!this.taskInfo.IsBaseLine && this.taskInfo.IsReadingTaskViewInOrder !== 0) {
this.rows = 1
this.cols = 2
this.activeCanvasIndex = 1
}
this.viewportInfos = Array.from({ length: this.cellsMax }, (_, index) => ({
index: index,
taskInfo: '',
studyId: '',
currentImageIdIndex: 0,
viewportId: `canvas-${index}`,
fileType: '',
currentFileName: '',
currentFilePath: '',
imageIds: [],
oldB: null,
oldM: null,
isMove: false,
height: 0
}))
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.initLoader()
window.addEventListener('message', this.handleIframeMessage)
this.getTrialCriterion()
},
beforeDestroy() {
window.removeEventListener('message', this.handleIframeMessage)
},
methods: {
async handleSubmitKeyDoc() {
try {
let data = {
TrialCriterionId: this.$router.currentRoute.query.TrialReadingCriterionId,
}
let res = await setReadKeyFile(data)
if (res.IsSuccess) {
this.manualsDialog.visible = false
}
} catch (err) {
console.log(err)
}
},
// 手册
previewManuals(justKeyDoc = false) {
this.manualsDialog.isFullscreen = false
this.manualsDialog.visible = true
this.manualsDialog.justKeyDoc = justKeyDoc
},
getTrialCriterion() {
getCriterionReadingInfo({
TrialId: this.$route.query.trialId,
TrialReadingCriterionId: this.$route.query.TrialReadingCriterionId
})
.then((res) => {
this.trialCriterion = res.Result
})
.catch(() => { })
},
openUploadImage(status) {
this.taskId = this.taskInfo.VisitTaskId
this.uploadSubjectCode = localStorage.getItem("subjectCode")
this.uploadSubjectId = localStorage.getItem("subjectId")
this.uploadTrialCriterion = this.trialCriterion
this.uploadStatus = status
this[`${status}ImageVisible`] = true
},
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)
this.addAnnotationListeners()
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)
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, {
arrowHeadStyle: 'standard',
// changeTextCallback: async (data, eventData, doneChangingTextCallback) => {
// return doneChangingTextCallback(await this.customPrompt())
// },
// getTextCallback: async (doneChangingTextCallback) => {
// return doneChangingTextCallback(await this.customPrompt())
// }
changeTextCallback: async (data, eventData, doneChangingTextCallback) => {
return doneChangingTextCallback(data.text)
},
getTextCallback: async (doneChangingTextCallback) => {
return doneChangingTextCallback('Annotation')
}
})
toolGroup.addTool(RectangleROITool.toolName, {
cachedStats: false,
getTextLines: this.getRectangleROIToolTextLines
})
toolGroup.addTool(EllipticalROITool.toolName, {
cachedStats: false,
getTextLines: this.getEllipticalROIToolTextLines
})
toolGroup.addTool(CircleROITool.toolName, {
cachedStats: false,
getTextLines: this.getCircleROIToolTextLines
})
toolGroup.addTool(AngleTool.toolName, {
cachedStats: false,
getTextLines: this.getAngleToolTextLines
})
toolGroup.addTool(PlanarFreehandROITool.toolName, {
allowOpenContours: false,
cachedStats: false,
getTextLines: this.getPlanarFreehandROIToolTextLines
})
// const splineConfig = toolGroup.getToolConfiguration(
// splineToolName,
// 'spline'
// )
toolGroup.addTool(SplineROITool.toolName)
toolGroup.addTool(EraserTool.toolName)
toolGroup.addTool(LengthTool.toolName, {
getTextLines: this.getLengthToolTextLines,
cachedStats: false
})
toolGroup.addTool(LengthscaleTool.toolName, {
getTextLines: this.getLengthscaleToolTextLines,
cachedStats: false
})
toolGroup.setToolActive(StackScrollTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
toolGroup.setToolPassive(PanTool.toolName)
toolGroup.setToolPassive(ZoomTool.toolName)
toolGroup.setToolPassive(PlanarRotateTool.toolName)
if (this.readingTaskState < 2) {
toolGroup.setToolPassive(ArrowAnnotateTool.toolName)
toolGroup.setToolPassive(RectangleROITool.toolName)
toolGroup.setToolPassive(EllipticalROITool.toolName)
toolGroup.setToolPassive(CircleROITool.toolName)
toolGroup.setToolPassive(AngleTool.toolName)
toolGroup.setToolPassive(PlanarFreehandROITool.toolName)
toolGroup.setToolPassive(SplineROITool.toolName)
toolGroup.setToolPassive(LengthTool.toolName)
toolGroup.setToolPassive(LengthscaleTool.toolName)
} else {
toolGroup.setToolEnabled(ArrowAnnotateTool.toolName)
toolGroup.setToolEnabled(RectangleROITool.toolName)
toolGroup.setToolEnabled(EllipticalROITool.toolName)
toolGroup.setToolEnabled(CircleROITool.toolName)
toolGroup.setToolEnabled(AngleTool.toolName)
toolGroup.setToolEnabled(PlanarFreehandROITool.toolName)
toolGroup.setToolEnabled(SplineROITool.toolName)
toolGroup.setToolEnabled(LengthTool.toolName)
toolGroup.setToolEnabled(LengthscaleTool.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
this.viewportInfos[i].height = (this.viewportInfos[i].currentImageIdIndex) * 100 / (this.viewportInfos[i].imageIds.length - 1)
const path = detail.imageId.split(`web:${this.OSSclientConfig.basePath}`)[1]
this.$emit('toggleImage', { taskId: this.viewportInfos[i].taskInfo.VisitTaskId, studyId: this.viewportInfos[i].studyId, path: path })
this.renderHistoryAnnotations(this.viewportInfos[i].taskInfo)
},
renderHistoryAnnotations(obj) {
if (this.renderHistoryAnnotationTaskIds.includes(obj.VisitTaskId)) return
this.renderHistoryAnnotationTaskIds.push(obj.VisitTaskId)
// let arr = []
obj.Annotations.map(i => {
const annotation = i.MeasureData
annotation.annotationId = i.Id
cornerstoneTools.annotation.state.addAnnotation(annotation)
if (obj.ReadingTaskState === 2) {
cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
}
})
},
// 渲染图片
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)
viewport.setImageIdIndex(sliceIndex)
viewport.render()
// this.updateViewportInfos()
},
setActiveCanvasImages(obj) {
console.log('setActiveCanvasImages')
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)
}
if (this.activeCanvasIndex === this.cells.length - 1) {
store.dispatch('noneDicomReview/setLastViewportTaskId', obj.visitTaskInfo.VisitTaskId)
}
},
// 激活视图
activeCanvas(index) {
if (this.activeCanvasIndex === index) return
const i = this.viewportInfos.findIndex(i => i.index === index)
if (i === -1) return
this.activeCanvasIndex = index
if (index === this.cells.length - 1) {
store.dispatch('noneDicomReview/setLastViewportTaskId', this.viewportInfos[i].taskInfo.VisitTaskId)
}
this.$emit('toggleTask', this.viewportInfos[i].taskInfo)
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
)
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)
})
}
// 有序阅片 1*1 显示当前图片 1*2 显示基线*当前图片 2*2 显示当前图片
// const obj = this.viewportInfos.find(i => i.index === this.activeCanvasIndex)
// if (obj && this.rows === 1 && this.cols === 1) {
// this.viewportInfos = this.viewportInfos.map((v, i) => {
// if (i === 0) {
// v.taskInfo = obj.taskInfo
// v.currentImageIdIndex === obj.currentImageIdIndex
// v.currentFileName === obj.currentFileName
// v.imageIds === obj.imageIds
// } else {
// v.taskInfo = ''
// v.currentImageIdIndex === 0
// v.currentFileName === ''
// v.imageIds === []
// }
// return v
// })
// this.activeCanvasIndex = 0
// } else if (obj && this.rows === 1 && this.cols === 2 && this.taskInfo.IsReadingTaskViewInOrder === 1) {
// this.viewportInfos = this.viewportInfos.map((v, i) => {
// if (i === 0) {
// v.taskInfo = this.relatedStudyInfo.visitTaskInfo
// v.currentImageIdIndex === this.relatedStudyInfo.fileIndex
// v.currentFileName === this.relatedStudyInfo.fileInfo.FileName
// const imageIds = []
// for (let i = 0; i < this.relatedStudyInfo.fileList.length; i++) {
// const path = this.relatedStudyInfo.fileList[i].Path
// imageIds.push(`web:${this.OSSclientConfig.basePath}${path}`)
// }
// v.imageIds === imageIds
// } else if (i === 1) {
// v.taskInfo = obj.taskInfo
// v.currentImageIdIndex === obj.currentImageIdIndex
// v.currentFileName === obj.currentFileName
// v.imageIds === obj.imageIds
// } else {
// v.taskInfo = ''
// v.currentImageIdIndex === 0
// v.currentFileName === ''
// v.imageIds === []
// }
// return v
// })
// this.activeCanvasIndex = 1
// } else if (obj && this.rows === 1 && this.cols === 2 && this.taskInfo.IsReadingTaskViewInOrder !== 1) {
// this.viewportInfos = this.viewportInfos.map((v, i) => {
// if (i === 0 || i === 1) {
// v.taskInfo = obj.taskInfo
// v.currentImageIdIndex === obj.currentImageIdIndex
// v.currentFileName === obj.currentFileName
// v.imageIds === obj.imageIds
// } else {
// v.taskInfo = ''
// v.currentImageIdIndex === 0
// v.currentFileName === ''
// v.imageIds === []
// }
// return v
// })
// this.activeCanvasIndex = 1
// } else if (obj && (this.rows === 2 && this.cols === 2)) {
// this.viewportInfos = this.viewportInfos.map(v => {
// v.taskInfo = obj.taskInfo
// v.currentImageIdIndex === obj.currentImageIdIndex
// v.currentFileName === obj.currentFileName
// v.imageIds === obj.imageIds
// return v
// })
// this.activeCanvasIndex = 3
// }
// this.$nextTick(() => {
// this.viewportInfos.forEach(v => {
// if (v.imageIds.length > 0) {
// this.renderImage(v.imageIds, v.index, v.currentImageIdIndex)
// }
// })
// })
},
// 切换全屏
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
}
},
// 切换任务
toggleTask(evt, visitTaskNum, i, index) {
this.activeCanvas(index)
const num = visitTaskNum + i
if (num >= 0 && num <= this.taskInfo.VisitNum) {
this.$emit('toggleTaskByViewer', num)
}
evt.stopImmediatePropagation()
evt.stopPropagation()
evt.preventDefault()
},
// 激活工具
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
}
},
// 激活标注工具
setAnnotateToolActive(toolName) {
// if (this.readingTaskState === 2) return
if (this.readingTaskState === 2 && toolName === 'Lengthscale') return
const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
if (i === -1) return
if (this.viewportInfos[i].taskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
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)
}
if (toolName === 'Lengthscale') {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(`canvas-${this.activeCanvasIndex}`)
const imageId = viewport.csImage.imageId
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
const idx = annotations.findIndex(i => i.metadata.referencedImageId === imageId && i.metadata.toolName === 'Lengthscale')
if (idx > -1) {
this.activeTool = ''
// 当前图像已存在比例尺!
this.$message.warning(this.$t('trials:noneDicom:message:msg4'))
return
}
}
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 = ''
// const enabledElement = getEnabledElementByIds(
// `canvas-${this.activeCanvasIndex}`,
// renderingEngineId
// )
// cursors.setCursorForElement(enabledElement.viewport.element, 'default')
},
addAnnotationListeners() {
const debouncedCallback = this.debounce((evt) => {
this.annotationModifiedListener(evt)
}, 0)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_MODIFIED,
(evt) => {
debouncedCallback(evt)
}
)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_COMPLETED,
this.annotationCompletedListener
)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_REMOVED,
this.annotationRemovedListener
)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_SELECTION_CHANGE,
this.annotationSelectionChangeListener
)
},
async annotationRemovedListener(e) {
const { annotation } = e.detail
try {
// if ( this.resetAnnotation && this.isFusion ) return false
if (!annotation) return false
// if (annotation.metadata.toolName === 'Lengthscale') {
// await this.$confirm(
// this.$t('trials:trials-list:table:LengthscaleIsDeleted')
// )
// const errorMsg = { message: 'Lengthscale Not delete' }
// throw errorMsg
// }
if (this.readingTaskState === 2 && !annotation.data.label && annotation.metadata.toolName !== 'Lengthscale') return false
if (this.readingTaskState === 2) {
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
}
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1 && annotation.metadata.toolName !== 'Lengthscale') {
// 临时标记
return
}
if (annotation.visitTaskId === this.taskInfo.VisitTaskId) {
this.$emit('getEcrf', { type: "verifyAnnotationIsBound", VisitTaskId: annotation.visitTaskId, annotation })
this.$nextTick(async () => {
try {
const isBound = this.ecrf.isBound
if (isBound && this.activeTool === 'Eraser') {
// '该标记已与问题进行绑定,不允许删除!'
this.$alert(this.$t('dicom3D:ReadPage:alert:MarkToQuestionNoDel'))
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
} else if (this.activeTool === 'Eraser') {
if (annotation.metadata.toolName === 'Lengthscale') {
await this.$confirm(
this.$t('trials:trials-list:table:isDeleted') +
this.$t('trials:nondicom-show:scale') +
'?'
)
} else {
await this.$confirm(
this.$t('trials:trials-list:table:isDeleted') +
annotation.data.label +
'?'
)
}
if (annotation.id) {
let res = await deleteTrialFileType(annotation.id)
if (!res.IsSuccess) throw ''
}
if (annotation.metadata.toolName === 'Lengthscale') {
let psIndex = this.psArr.findIndex(item => item.Path === annotation.path)
if (psIndex > -1) {
this.psArr.splice(psIndex, 1)
}
this.$emit('getEcrf', { type: "changePlottingScaleChangeAnswer", VisitTaskId: this.taskInfo.VisitTaskId, noneDicomFileId: annotation.noneDicomFileId, path: annotation.path || '', picturePath: null, psArr: this.psArr, isRemovePlottingScale: false })
this.setToolsPassive()
}
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `canvas-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
}
// else {
// console.log(1234567)
// const errorMsg = { message: 'annotation Not allowed to operate' }
// throw errorMsg
// }
} catch (err) {
cornerstoneTools.annotation.state.addAnnotation(annotation)
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `canvas-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
console.log(e)
}
})
}
} catch (e) {
cornerstoneTools.annotation.state.addAnnotation(annotation)
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `canvas-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
console.log(e)
}
},
async annotationModifiedListener(e) {
console.log('Modified')
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation.highlighted) return
if (!annotation) return
// if (!annotation.annotationId) return
let i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1 && annotation.metadata.toolName !== 'Lengthscale') {
this.setToolsPassive()
return
}
i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
const imageId = annotation.metadata.referencedImageId
const path = imageId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const fileList = this.viewportInfos[i].fileList
const fileIndex = fileList.findIndex(f => f.Path === path)
if (fileIndex === -1) return
annotation.path = path
let ps = null
let psIndex = this.psArr.findIndex(i => i.Path === path)
if (psIndex > -1 && this.psArr[psIndex].PS) {
ps = parseFloat(this.psArr[psIndex].PS).toFixed(3)
}
annotation.ps = ps
if (!annotation.data.label && annotation.metadata.toolName !== 'Lengthscale') return
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
if (annotation.metadata.toolName === 'Lengthscale') {
const value = annotation.data.l
if (isNaN(parseFloat(value))) return false
let ps = 1
if (value) {
const cachedStats = Object.keys(annotation.data.cachedStats)
ps = value / annotation.data.cachedStats[cachedStats[0]].length
annotation.data.ps = ps
this.$emit('setPS', { NoneDicomFileId: fileList[fileIndex].Id, Path: fileList[fileIndex].Path, PS: ps })
}
this.setToolsPassive()
if (this.customizeStandardsNoneDicom.find(item => item.toolName === annotation.metadata?.toolName)) return false
this.curOperation.type = 'Modified'
return this.curOperation.annotation = annotation
}
this.$emit("getEcrf", { type: "getOperateStateEnum", VisitTaskId: this.taskInfo.VisitTaskId })
this.$emit('getEcrf', { type: "verifyAnnotationIsBound", VisitTaskId: this.taskInfo.VisitTaskId, annotation })
this.$nextTick(() => {
const operateStateEnum = this.ecrf.operateStateEnum
const isBound = this.ecrf.isBound
if (isBound || this.isNumber(operateStateEnum)) {
this.$emit('getEcrf', { type: "updateAnnotationToQuestion", VisitTaskId: this.taskInfo.VisitTaskId, annotation })
} else {
this.curOperation.type = 'Modified'
this.curOperation.annotation = annotation
}
this.setToolsPassive()
})
},
async annotationCompletedListener(e) {
console.log('Completed')
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
let INDEX = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if ((INDEX === -1 && annotation.metadata.toolName !== 'Lengthscale') || i === -1) {
this.setToolsPassive()
return
}
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
if (this.viewportInfos[i].taskInfo.VisitTaskId !== this.taskInfo.VisitTaskId) return
const imageId = annotation.metadata.referencedImageId
const path = imageId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const fileList = this.viewportInfos[i].fileList
const fileIndex = fileList.findIndex(f => f.Path === path)
if (fileIndex === -1) return
annotation.path = path
let ps = null
let psIndex = this.psArr.findIndex(i => i.Path === path)
if (psIndex > -1 && this.psArr[psIndex].PS) {
ps = parseFloat(this.psArr[psIndex].PS).toFixed(3)
}
annotation.ps = ps
annotation.markTool = annotation.metadata.toolName
annotation.visitTaskId = this.viewportInfos[i].taskInfo.VisitTaskId
annotation.studyId = this.viewportInfos[i].studyId
annotation.noneDicomFileId = this.viewportInfos[i].fileList[this.viewportInfos[i].currentImageIdIndex].Id
if (annotation.metadata.toolName === 'Lengthscale') {
this.form.annotationObj = {
id: '',
visitTaskId: this.viewportInfos[i].taskInfo.VisitTaskId,
studyId: this.viewportInfos[i].studyId,
noneDicomFileId: fileList[fileIndex].Id,
path: fileList[fileIndex].Path,
annotation,
fileList,
fileIndex
}
this.form.length = null
this.dialogVisible = true
this.setToolsPassive()
return
}
// this.markedSeriesIds.push(series.Id)
this.$emit('getEcrf', { type: "getOperateStateEnum", VisitTaskId: this.taskInfo.VisitTaskId })
this.$nextTick(async () => {
const operateStateEnum = this.ecrf.operateStateEnum
const markName = await this.customPrompt(!this.isNumber(operateStateEnum))
if (markName) {
annotation.data.label = markName
if (annotation.metadata.toolName === 'ArrowAnnotate') {
annotation.data.text = markName
}
if (this.isNumber(operateStateEnum)) {
this.$emit('getEcrf', { type: "bindAnnotationToQuestion", VisitTaskId: this.taskInfo.VisitTaskId, annotation })
} else {
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
this.saveCustomAnnotationTimer = setTimeout(() => { this.saveCustomAnnotation(annotation) }, 500)
}
} else {
// if (this.isNumber(operateStateEnum)) {
// this.removeAnnotation(annotation)
// }
}
})
},
annotationSelectionChangeListener(e) {
console.log('selection')
if (this.readingTaskState === 2) return
const { detail } = e
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
const i = annotations.findIndex(i => i.annotationUID === detail.selection[0])
if (i > -1) {
if (annotations[i].visitTaskId !== this.taskInfo.VisitTaskId) {
return
} else {
let annotation = annotations[i]
const imageId = annotation.metadata.referencedImageId
const path = imageId.split(`web:${this.OSSclientConfig.basePath}`)[1]
let psIndex = this.psArr.findIndex(i => i.Path === path)
let ps = null
if (psIndex > -1 && this.psArr[psIndex].PS) {
ps = parseFloat(this.psArr[psIndex].PS).toFixed(3)
}
annotation.ps = ps
this.$emit('getEcrf', { type: "bindAnnotationToQuestion", VisitTaskId: this.taskInfo.VisitTaskId, annotation })
}
}
},
async saveCustomAnnotation(annotation, ps = null) {
try {
const measureData = Object.assign({}, annotation)
const params = {}
const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
this.loading = true
await this.getScreenshots({
visitTaskId: this.taskInfo.VisitTaskId,
annotation
}, async (base64Str) => {
this.loading = false
const pictureObj = await this.uploadScreenshots(`${Date.now()}`, base64Str)
let picturePath = pictureObj.isSuccess ? this.$getObjectName(pictureObj.result.url) : ''
if (annotation.id) params.Id = annotation.id
if (!isNaN(parseFloat(ps))) params.Proportion = ps
params.PicturePath = picturePath
params.Path = annotation.path || ''
params.MarkTool = measureData.markTool
params.OrderMarkName = measureData.data.label
params.VisitTaskId = this.taskInfo.VisitTaskId
params.StudyId = annotation.studyId
params.NoneDicomFileId = annotation.noneDicomFileId
params.MeasureData = JSON.stringify(measureData)
params.MarkId = annotation.annotationUID
const res = await addNoneDicomMark(params)
annotation.markId = params.MarkId
annotation.id = res.Result
if (!isNaN(parseFloat(ps))) this.$emit('getEcrf', { type: "changePlottingScaleChangeAnswer", VisitTaskId: this.taskInfo.VisitTaskId, noneDicomFileId: annotation.noneDicomFileId, path: annotation.path || '', picturePath, psArr: this.psArr, isRemovePlottingScale: ps < 0 ? true : false })
})
} catch (e) {
this.loading = false
console.log(e)
}
},
async getScreenshots(measureData, callback) {
if (measureData) {
// await this.imageLocation(measureData)
this.viewCustomAnnotation(measureData)
const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="canvas-${this.activeCanvasIndex}"]`
)
const canvas = await html2canvas(divForDownloadViewport)
const base64Str = canvas.toDataURL('image/png', 1)
callback(base64Str)
} else {
callback()
}
},
viewCustomAnnotation(obj) {
const i = this.viewportInfos.findIndex(i => i.taskInfo.VisitTaskId === obj.visitTaskId)
const imageId = obj.annotation.metadata.referencedImageId
const path = imageId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const fileList = this.viewportInfos[i].fileList
const fileIndex = fileList.findIndex(f => f.Path === path)
let o = { fileInfo: fileList[fileIndex], fileList, visitTaskInfo: this.viewportInfos[i].taskInfo, studyId: this.viewportInfos[i].studyId }
this.setActiveCanvasImages(o)
},
async uploadScreenshots(fileName, file) {
try {
file = this.convertBase64ToBlob(file)
const trialId = this.$route.query.trialId
const taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
const subjectId = taskInfo.SubjectId
const result = await this.OSSclient.put(`/${trialId}/Read/${subjectId}/${this.taskInfo.VisitTaskId}/${fileName}.png`, file)
return { isSuccess: true, result: result }
} catch (e) {
console.log(e)
return { isSuccess: false, result: e }
}
},
convertBase64ToBlob(imageEditorBase64) {
const base64Arr = imageEditorBase64.split(',')
let imgtype = ''
let base64String = ''
if (base64Arr.length > 1) {
base64String = base64Arr[1]
imgtype = base64Arr[0].substring(
base64Arr[0].indexOf(':') + 1,
base64Arr[0].indexOf(';')
)
}
const bytes = atob(base64String)
const bytesCode = new ArrayBuffer(bytes.length)
const byteArray = new Uint8Array(bytesCode)
for (let i = 0; i < bytes.length; i++) {
byteArray[i] = bytes.charCodeAt(i)
}
return new Blob([bytesCode], { type: imgtype })
},
isNumber(value) {
return typeof value === 'number' && value !== null && !isNaN(value)
},
async saveForm() {
const validate = await this.$refs.lengthForm.validate()
if (!validate) return
const value = this.form.length
const annotation = this.form.annotationObj.annotation
const fileList = this.form.annotationObj.fileList
const fileIndex = this.form.annotationObj.fileIndex
let ps = 1
if (value) {
annotation.data.l = parseFloat(value)
const cachedStats = Object.keys(annotation.data.cachedStats)
ps = parseFloat(value) / annotation.data.cachedStats[cachedStats[0]].length
annotation.data.ps = ps
this.$emit('setPS', { NoneDicomFileId: fileList[fileIndex].Id, Path: fileList[fileIndex].Path, PS: ps })
} else {
cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID)
return
}
if (this.customizeStandardsNoneDicom.find(item => item.toolName === annotation.metadata?.toolName)) return false
this.dialogVisible = false
this.$emit('getEcrf', { type: "verifyFileIsBound", VisitTaskId: this.taskInfo.VisitTaskId, annotation })
this.$nextTick(() => {
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
return this.saveCustomAnnotationTimer = setTimeout(async () => {
if (!this.ecrf.IsHaveBindingQuestion || !this.ecrf.isFileBound) {
this.saveCustomAnnotation(annotation)
} else {
await this.$confirm(
this.$t('trials:trials-list:table:allQuestionChange')
).then(() => {
this.saveCustomAnnotation(annotation, 1)
})
.catch(action => {
this.saveCustomAnnotation(annotation, -1)
});
}
}, 500)
this.saveCustomAnnotationTimer = setTimeout(() => { this.saveCustomAnnotation(annotation) }, 500)
})
},
getLengthscaleToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { length, unit } = cachedVolumeStats
if (length === undefined || length === null || isNaN(length)) {
return
}
if (data.l === undefined || data.l === null || isNaN(data.l)) {
return
}
const textLines = []
if (data.label) {
textLines.push(data.label)
}
textLines.push(`P: ${parseFloat(length).toFixed(this.digitPlaces)} ${unit}`)
if (data.l) {
textLines.push(`L: ${parseFloat(data.l).toFixed(this.digitPlaces)} mm`)
textLines.push(`PS: ${parseFloat(data.l / length).toFixed(3)} mm/px`)
}
return textLines
},
// 直线工具注释
getLengthToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
let { length, unit } = cachedVolumeStats
if (length === undefined || length === null || isNaN(length)) {
return
}
let ps = null
const path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const i = this.psArr.findIndex(i => i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
const textLines = []
if (data.label) {
textLines.push(data.label)
}
if (ps) {
textLines.push(`${this.reRound(csUtils.roundNumber(length * ps), this.digitPlaces)} mm`)
} else {
textLines.push(`${this.reRound(csUtils.roundNumber(length), this.digitPlaces)} ${unit}`)
}
return textLines
},
getPlanarFreehandROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const {
area,
mean,
stdDev,
// length,
perimeter,
max,
isEmptyArea,
unit,
areaUnit,
modalityUnit
} = cachedVolumeStats || {}
const textLines = []
if (data.label) {
textLines.push(data.label)
}
let ps = null
const path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const i = this.psArr.findIndex(i => i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
if (area) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${ps ? this.reRound(csUtils.roundNumber(area * ps * ps), this.digitPlaces) : this.reRound(csUtils.roundNumber(area), this.digitPlaces)} ${ps ? 'mm' + '\xb2' : areaUnit}`
textLines.push(areaLine)
}
if (mean) {
textLines.push(`Mean: ${this.reRound(csUtils.roundNumber(mean), this.digitPlaces)} ${modalityUnit}`)
}
if (max) {
textLines.push(`Max: ${this.reRound(csUtils.roundNumber(max), this.digitPlaces)} ${modalityUnit}`)
}
if (stdDev) {
textLines.push(`Std Dev: ${this.reRound(csUtils.roundNumber(stdDev), this.digitPlaces)} ${modalityUnit}`)
}
if (perimeter) {
if (ps) {
textLines.push(`Perimeter: ${this.reRound(csUtils.roundNumber(perimeter * ps), this.digitPlaces)} mm`)
} else {
textLines.push(`Perimeter: ${this.reRound(csUtils.roundNumber(perimeter), this.digitPlaces)} ${unit}`)
}
}
// if (length) {
// // No need to show length prefix as there is just the single value
// textLines.push(`${csUtils.roundNumber(length)} ${unit}`);
// }
return textLines
},
getRectangleROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const points = data.handles.points
const { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats
if (mean === undefined) {
return
}
const textLines = []
if (data.label) {
textLines.push(data.label)
}
let ps = null
const path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const i = this.psArr.findIndex(i => i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
if (ps) {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area * ps * ps), this.digitPlaces)} ${'mm' + '\xb2'}`)
} else {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area), this.digitPlaces)} ${areaUnit}`)
}
textLines.push(`Mean: ${this.reRound(csUtils.roundNumber(mean), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Max: ${this.reRound(csUtils.roundNumber(max), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Std Dev: ${this.reRound(csUtils.roundNumber(stdDev), this.digitPlaces)} ${modalityUnit}`)
if (Array.isArray(points) && points.length === 4) {
let perimeter = 2 * Math.abs(points[0][0] - points[1][0]) + 2 * Math.abs(points[1][1] - points[2][1])
if (perimeter) {
if (ps) {
textLines.push(`Perimeter: ${this.reRound(csUtils.roundNumber(perimeter * ps), this.digitPlaces)} mm`)
} else {
textLines.push(`Perimeter: ${this.reRound(csUtils.roundNumber(perimeter), this.digitPlaces)} px`)
}
}
}
return textLines
},
// 椭圆工具注释信息
getEllipticalROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats
if (mean === undefined) {
return
}
const textLines = []
if (data.label) {
textLines.push(data.label)
}
let ps = null
const path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const i = this.psArr.findIndex(i => i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
if (ps) {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area * ps * ps), this.digitPlaces)} ${'mm' + '\xb2'}`)
} else {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area), this.digitPlaces)} ${areaUnit}`)
}
textLines.push(`Mean: ${this.reRound(csUtils.roundNumber(mean), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Max: ${this.reRound(csUtils.roundNumber(max), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Std Dev: ${this.reRound(csUtils.roundNumber(stdDev), this.digitPlaces)} ${modalityUnit}`)
return textLines
},
// 圆形工具注释信息
getCircleROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { area, mean, max, stdDev, perimeter, areaUnit, modalityUnit } = cachedVolumeStats
if (mean === undefined) {
return
}
const textLines = []
if (data.label) {
textLines.push(data.label)
}
let ps = null
const path = targetId.split(`web:${this.OSSclientConfig.basePath}`)[1]
const i = this.psArr.findIndex(i => i.Path === path)
if (i > -1 && this.psArr[i].PS) {
ps = parseFloat(this.psArr[i].PS).toFixed(3)
}
if (ps) {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area * ps * ps), this.digitPlaces)} ${'mm' + '\xb2'}`)
} else {
textLines.push(`Area: ${this.reRound(csUtils.roundNumber(area), this.digitPlaces)} ${areaUnit}`)
}
textLines.push(`Mean: ${this.reRound(csUtils.roundNumber(mean), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Max: ${this.reRound(csUtils.roundNumber(max), this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Std Dev: ${this.reRound(csUtils.roundNumber(stdDev), this.digitPlaces)} ${modalityUnit}`)
if (perimeter) {
if (ps) {
textLines.push(`Perimeter: ${this.reRound(csUtils.roundNumber(perimeter * ps), this.digitPlaces)} mm`)
} else {
textLines.push(`Perimeter: ${this.reRound(csUtils.roundNumber(perimeter), this.digitPlaces)} px`)
}
}
return textLines
},
// 角度工具注释信息
getAngleToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { label } = data
const { angle } = cachedVolumeStats
if (angle === undefined) {
return
}
if (isNaN(angle)) {
return [`${angle}`]
}
const textLines = []
if (label) {
textLines.push(label)
}
textLines.push(`${this.reRound(csUtils.roundNumber(angle), this.digitPlaces)} ${String.fromCharCode(176)}`)
return textLines
},
reRound(result, finalPrecision) {
if (typeof result === 'string' && result.includes(', ')) {
const numStrs = result.split(', ')
const processed = numStrs.map(str => this.processSingle(str, finalPrecision))
return processed.join(', ')
}
return this.processSingle(result, finalPrecision)
},
processSingle(str, precision) {
const num = parseFloat(str)
if (isNaN(num)) return 'NaN'
// 保留原极小值处理逻辑
if (Math.abs(num) < 0.0001) return str
const factor = 10 ** precision
return (Math.round(num * factor + 0.0000001) / factor).toFixed(precision)
},
debounce(callback, delay) {
let timerId
return function () {
clearTimeout(timerId)
timerId = setTimeout(() => {
callback.apply(this, arguments)
}, delay)
}
},
// 重置视图
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()
},
resetAnnotations({ annotations, visitTaskId }) {
// 移除病灶
const arr = cornerstoneTools.annotation.state.getAllAnnotations()
arr.map(i => {
if (i.visitTaskId === visitTaskId) {
cornerstoneTools.annotation.state.removeAnnotation(i.annotationUID)
}
})
annotations.map(i => {
if (i.MeasureData) {
const annotation = i.MeasureData
cornerstoneTools.annotation.state.addAnnotation(annotation)
}
})
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `canvas-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
},
// 输入标记名称自定义弹窗
async customPrompt(isShowCancelButton = true) {
try {
const that = this
// 请输入标记名称
let message = this.$t('trials:noneDicom:message:msg1')
// if (isShowCancelButton) {
// message = `<div><p>${this.$t('trials:noneDicom:message:msg1')}</p><p style='font-size:12px' class='customPromptTip'>${this.$t('trials:noneDicom:message:saveTip')}</p></div>`
// }
const { value } = await this.$prompt(message, '', {
showClose: false,
cancelButtonText: isShowCancelButton ? this.$t('trials:reading:button:temporarySave') : this.$t('common:button:cancel'),
confirmButtonText: isShowCancelButton ? this.$t('trials:reading:button:enduringSave') : this.$t('trials:reading:button:relevancy'),
// showCancelButton: isShowCancelButton,
// dangerouslyUseHTMLString: isShowCancelButton,
inputValidator: (res) => {
if (!res) {
return isShowCancelButton ? this.$t('trials:noneDicom:message:saveTip') : this.$t('trials:noneDicom:message:saveTipByRelevancy')
}
},
inputErrorMessage: this.$t('trials:noneDicom:message:saveTip'),
showCancelButton: true,
closeOnClickModal: false,
closeOnPressEscape: false,
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
const value = instance.inputValue
if (!value) {
that.$message.error(this.$t('trials:customReading:error:validMarkName1'))
} else if (this.validMarkName(value)) {
that.$message.error(this.$t('trials:customReading:error:validMarkName'))
} else {
done()
}
} else {
done()
}
}
})
return value
} catch (err) {
console.log(err)
return null
}
},
validMarkName(markName) {
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
const i = annotations.findIndex(i => i.visitTaskId === this.taskInfo.VisitTaskId && i.data.label === markName)
return i > -1
},
// 滚动条
clickSlider(e, index) {
const height = e.offsetY * 100 / this.$refs[`sliderBox-${index}`][0].clientHeight
const i = this.viewportInfos.findIndex(i => i.index === index)
if (i === -1) return
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
)
viewport.setImageIdIndex(sliceIdx)
viewport.render()
// this.$emit('toggleImage', { taskId: this.viewportInfos[i].taskInfo.VisitTaskId, studyId: this.viewportInfos[i].studyId, imageIndex: sliceIdx })
},
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 delta = this.viewportInfos[i].oldB - (this.viewportInfos[i].oldM - e.clientY)
const boxHeight = this.$refs[`sliderBox-${index}`][0].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
)
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) return
const boxHeight = this.$refs[`sliderBox-${index}`][0].clientHeight
this.viewportInfos[i].oldB = parseInt(e.srcElement.style.top) * boxHeight / 100
this.viewportInfos[i].oldM = e.clientY
this.viewportInfos[i].isMove = true
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
},
contentMouseup(e, index) {
console.log('contentMouseup')
const i = this.viewportInfos.findIndex(i => i.index === index)
if (i === -1) return
if (this.curOperation.type === 'Modified') {
let annotation = this.curOperation.annotation
if (annotation.metadata.toolName === 'Lengthscale') {
this.$emit('getEcrf', { type: "verifyFileIsBound", VisitTaskId: this.taskInfo.VisitTaskId, annotation })
this.$nextTick(() => {
if (!this.ecrf.IsHaveBindingQuestion || !this.ecrf.isFileBound) {
this.saveCustomAnnotation(annotation)
} else {
this.$confirm(
this.$t('trials:trials-list:table:allQuestionChange')
).then(() => {
this.saveCustomAnnotation(annotation, 1)
})
.catch(action => {
this.saveCustomAnnotation(annotation, -1)
});
}
})
} else {
this.saveCustomAnnotation(annotation)
}
}
this.curOperation = {
type: '',
annotation: null
}
// e.stopImmediatePropagation()
// e.stopPropagation()
// e.preventDefault()
},
handleIframeMessage(event) {
if (event.data.type === 'pdf-clicked') {
const baseUrl = event.data.data.baseUrl
const urlParams = new URL(baseUrl)
const index = parseInt(urlParams.search.split('=')[1])
this.activeCanvas(index)
}
},
preventDefault(e) {
e.stopImmediatePropagation()
e.stopPropagation()
e.preventDefault()
},
// 查看临床数据
viewCD(id) {
this.$emit('previewCD', id)
},
previewConfig() {
this.personalConfigDialog.visible = true
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .manuals-dialog-container {
margin-top: 50px !important;
width: 75%;
height: 80%;
.el-dialog__body {
padding: 10px;
height: calc(100% - 50px) !important;
}
.el-dialog__header {
position: relative;
}
}
::v-deep .manuals-full-dialog-container {
.el-dialog__body {
padding: 10px;
height: calc(100% - 50px) !important;
}
.el-dialog__header {
position: relative;
}
}
.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;
}
}
.none-dicom-viewer {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
user-select: none;
.tools-wrapper {
height: 50px;
display: flex;
align-items: center;
border-bottom: 1px solid #727272;
color: #ddd;
padding: 0 5px;
.tools-left {
flex: 1;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.tool-item {
padding: 5px;
margin: 0 5px;
border: 1px solid #333;
font-size: 20px;
cursor: pointer;
}
.tool-item-active {
background-color: #607d8b;
}
.tool-disabled {
cursor: not-allowed;
}
}
.viewports-wrapper {
flex: 1;
.grid-container {
display: grid;
height: 100%;
width: 100%;
position: relative;
}
.grid-cell {
border: 1px dashed #ccc;
;
display: flex;
align-items: center;
justify-content: center;
}
.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
}
}
}
::v-deep .el-dialog {
background: #1e1e1e;
border: 1px solid #ddd;
color: #ddd;
.el-dialog__title {
color: #fff;
}
.el-input .el-input__inner {
background-color: transparent;
color: #ddd;
border: 1px solid #5e5e5e;
}
.el-input.is-disabled .el-input__inner {
background-color: #646464a1;
}
.el-form-item__label {
color: #dfdfdf
}
}
}
</style>