irc_web/src/views/trials/trials-panel/reading/dicoms3D/components/ReadPage.vue

3604 lines
137 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div
v-loading="loading"
:element-loading-text="loadingText"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
class="read-page-container"
>
<!-- 检查列表 -->
<div class="left-panel">
<div class="task-container">
<div class="task-info">
<div
v-for="(s, index) in visitTaskList"
:key="s.VisitTaskId"
class="task-item"
:class="{'task-item-active': activeTaskId==s.VisitTaskId}"
@click.prevent="toggleTask(s, index)"
>{{ s.TaskBlindName }}</div>
</div>
</div>
<div v-loading="sLoading" class="study-info">
<div
v-for="s in visitTaskList"
v-show="activeTaskId === s.VisitTaskId"
:key="s.VisitTaskId"
style="height:100%;"
>
<study-list
v-if="selectArr.includes(s.VisitTaskId) && s.StudyList.length > 0"
:ref="s.VisitTaskId"
:visit-task-info="s"
:marked-series-ids="markedSeriesIds"
@activeSeries="activeSeries"
@showMultiFrame="showMultiFrame"
/>
</div>
</div>
</div>
<!-- 视口及表单 -->
<div class="middle-panel">
<div class="dicom-viewer">
<!-- tools -->
<div class="tools-wrapper">
<div class="tools-left">
<!-- 布局 -->
<div
:class="['tool-item', isFusion ? 'tool-disabled' : '']"
:title="$t('trials:reading:button:layout')"
@click.stop="showPanel($event, 'layout')"
@mouseleave="toolMouseout"
>
<div class="dropdown">
<div class="icon">
<svg-icon icon-class="layout" class="svg-icon" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content layout-content">
<ul style="width:50px" class="layout-content-ul">
<li class="layout_flex_row" @click.stop="changeLayout(1)">
<div class="layout_box_1_1">
A
</div>
</li>
<li class="layout_flex_row" @click.stop="changeLayout(2)">
<div class="layout_box_1_1">
A
</div>
<div class="layout_box_1_1">
A
</div>
</li>
<li v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder === 1" class="layout_flex_row" @click.stop="changeLayout(3)">
<div class="layout_box_1_1">
A
</div>
<div class="layout_box_1_1">
B
</div>
</li>
<li class="layout_flex_column" @click.stop="changeLayout(4)">
<div style="flex:1;display: flex;width:100%;">
<div class="layout_box_1_2">
A
</div>
<div class="layout_box_1_2">
A
</div>
</div>
<div style="flex:1;display: flex;width:100%;">
<div class="layout_box_1_2">
A
</div>
<div class="layout_box_1_2">
A
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<!-- 调窗 -->
<div
:class="['tool-item', activeTool === 'WindowLevel' ? 'tool-item-active' : '']"
:title="$t('trials:reading:button:wwwc')"
@click.stop="setWindowLevelActive($event)"
@mouseleave="toolMouseout"
>
<div class="dropdown">
<div class="icon">
<svg-icon icon-class="reverse" class="svg-icon" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content">
<ul style="width:165px;">
<li v-for="item in wwwcArr" :key="item.label">
<a href="#" @click.stop="changeVoiRange(item)">
<div v-if="item.wc !== null" style="display:flex;flex-direction: row;justify-content: space-between;">
<div>{{ item.label }}</div>
<div>{{ `${item.ww} / ${item.wc}` }}</div>
</div>
<div v-else style="text-align:left;">
{{ item.label }}
</div>
</a>
<el-divider v-if="item.val === 1" class="divider" content-position="center">
{{ ` ${$t('trials:reading:title:preset') }` }}
</el-divider>
</li>
</ul>
</div>
</div>
</div>
<!-- 反色 -->
<div
class="tool-item"
:title="$t('trials:reading:button:reverseColor')"
@click.prevent="toggleInvert"
>
<svg-icon icon-class="reversecolor" class="svg-icon" />
</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
class="tool-item"
:title="$t('trials:reading:button:rotate')"
@click.stop="showPanel($event)"
@mouseleave="toolMouseout"
>
<div class="dropdown">
<div
class="icon"
data-tool="Rotate"
:class="[activeTool==='Rotate'?'tool_active':'']"
>
<svg-icon icon-class="rotate" class="svg-icon" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content">
<ul style="width:100px;">
<li v-for="rotate in rotateOptions" :key="rotate.label" style="text-align:left;">
<a href="#" @click.prevent="setViewportRotate(rotate.val)">
{{ rotate.label }}
</a>
</li>
</ul>
</div>
</div>
</div>
<!-- 适应图像或窗口 -->
<div
class="tool-item"
:title="forceFitToWindow ? `${$t('trials:reading:button:fitWindow')}` : `${$t('trials:reading:button:fitImage')}`"
@click.prevent="fitToType(forceFitToWindow)"
>
<svg-icon v-if="forceFitToWindow" icon-class="fitToWindow" class="svg-icon" />
<svg-icon v-else icon-class="fitToImage" class="svg-icon" />
</div>
<!--融合-->
<div v-if="readingTool === 2" class="tool-item" :title="$t('trials:lugano:button:fusion')" @click.prevent="openFusion">
<svg-icon icon-class="fusion" 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)"
@mouseenter="enter($event,tool.toolName)"
>
<svg-icon :icon-class="tool.icon" class="svg-icon" />
</div>
<!-- 清除标注 -->
<div
v-if="criterionType === 0"
:class="['tool-item', activeTool === 'Eraser' ? 'tool-item-active' : '']"
:title="$t('trials:dicom-show:Eraser')"
@click.prevent="setToolActive('Eraser')"
>
<svg-icon icon-class="clear" class="svg-icon" />
</div>
<div class="tool-frame">
<!-- 第一帧 -->
<div :title="$t('trials:dicom-show:firstframe')" class="icon" @click.prevent="scrollPage(0)">
<svg-icon icon-class="firstframe" class="svg-icon" />
</div>
<!-- 上一帧 -->
<div :title="$t('trials:dicom-show:previousframe')" class="icon" @click.prevent="scrollPage(-1)">
<svg-icon icon-class="previousframe" class="svg-icon" />
</div>
<!-- 播放/暂停 -->
<div
v-if="clipPlaying"
:title="$t('trials:dicom-show:stop')"
class="icon"
@click.prevent="toggleClipPlay(false)"
>
<svg-icon icon-class="stop" class="svg-icon" />
</div>
<div
v-else
:title="$t('trials:dicom-show:play')"
class="icon"
@click.prevent="toggleClipPlay(true)"
>
<svg-icon icon-class="play" class="svg-icon" />
</div>
<!-- 下一帧 -->
<div :title="$t('trials:dicom-show:nextframe')" class="icon" @click.prevent="scrollPage(1)">
<svg-icon icon-class="nextframe" class="svg-icon" />
</div>
<!-- 最后一帧 -->
<div :title="$t('trials:dicom-show:lastframe')" class="icon" @click.prevent="scrollPage(99999)">
<svg-icon icon-class="lastframe" class="svg-icon" />
</div>
<select v-model="fps" :title="$t('trials:dicom-show:speed')" class="select-wrapper" :disabled="clipPlaying">
<!-- 默认值 -->
<option :value="5">5</option>
<option :value="10">10</option>
<option :value="15">15</option>
<option :value="20">20</option>
<option :value="25">25</option>
<option :value="30">30</option>
</select>
</div>
<!-- 重置 -->
<div
class="tool-item"
:title="$t('trials:reading:button:reset')"
@click.prevent="resetViewport"
>
<svg-icon icon-class="refresh" class="svg-icon" />
</div>
<!-- 更多 -->
<div
v-if="criterionType === 0"
:title="$t('trials:reading:button:more')"
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '']"
@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">
<ul v-if="readingTaskState < 2" style="width:100px;">
<li v-for="i in customizeStandards" :key="i.toolName" style="text-align:left;">
<a href="#" @click.prevent="setMoreToolActive(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>
<!-- 伪彩 -->
<template v-if="readingTool === 2">
<colorMap v-show="isFusion" ref="colorMap" @setColorMap="setColorMap" @voiChange="voiChange" />
</template>
</div>
<div>
<!-- 手册 -->
<el-button
v-if="taskInfo && taskInfo.ExistsManual"
type="text"
@click="previewManuals"
>
{{ $t('trials:reading:button:handbooks') }}
</el-button>
<!-- 临床数据 -->
<el-button
v-if="taskInfo && taskInfo.IsExistsClinicalData"
type="text"
@click="previewCD(taskInfo.VisitTaskId)"
>
{{ $t('trials:reading:button:clinicalData') }}
</el-button>
<!-- 个性化配置 -->
<el-button
type="text"
@click="previewConfig"
>
{{ $t('trials:reading:button:customCfg') }}
</el-button>
</div>
</div>
<div class="content-wrapper">
<!-- viewports -->
<div class="viewports-wrapper">
<div ref="container" class="grid-container">
<div :class="[ 'viewports-box', isFusion ? 'viewports-box-down' : '' ]" :style="gridStyle">
<div
v-for="(v, index) in cellsMax"
v-show="index < cells.length"
:key="`viewport-${index}`"
:style="cellStyle"
:class="['grid-cell', index === activeViewportIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
@dblclick="toggleFullScreen($event, index)"
@click="activeViewport(index)"
>
<Viewport
:ref="`viewport-${index}`"
:data-viewport-uid="`viewport-${index}`"
:rendering-engine-id="renderingEngineId"
:viewport-id="`viewport-${index}`"
:viewport-index="index"
@activeViewport="activeViewport"
@toggleTaskByViewport="toggleTaskByViewport"
@previewCD="previewCD"
@renderAnnotations="renderAnnotations"
/>
</div>
</div>
<div v-if="readingTool === 2" :class="[ 'viewports-box', !isFusion ? 'viewports-box-down' : '' ]" :style="gridStyle">
<div
v-for="(v, index) in cellsMax"
v-show="index < cells.length"
:key="`viewport-fusion-${index}`"
:style="cellStyle"
:class="['grid-cell', index === activeViewportIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
@dblclick="toggleFullScreen($event, index)"
@click="activeViewport(index)"
>
<PetCtViewport
:ref="`viewport-fusion-${index}`"
:data-viewport-uid="`viewport-fusion-${index}`"
:rendering-engine-id="renderingEngineId"
:viewport-id="`viewport-fusion-${index}`"
:viewport-index="index"
@activeViewport="activeViewport"
@toggleTaskByViewport="toggleTaskByViewport"
@previewCD="previewCD"
@renderAnnotations="renderAnnotations"
@upperRangeChange="upperRangeChange"
/>
</div>
</div>
</div>
</div>
<!-- 表单 -->
<div class="form-wrapper">
<div
v-for="s in visitTaskList"
v-show="lastViewportTaskId === s.VisitTaskId"
:key="s.VisitTaskId"
style="height: 100%;"
>
<mRecisit
v-if="lastViewportTaskId && criterionType === 7 && lastViewportTaskIds.includes(s.VisitTaskId)"
:ref="`ecrf_${s.VisitTaskId}`"
:reading-task-state="currentVisitInfo.VisitTaskId === taskInfo.VisitTaskId ? readingTaskState : 2"
:last-viewport-task-id="lastViewportTaskId"
:visit-info="s"
@removeAnnotation="removeAnnotation"
@getScreenshots="getScreenshots"
@setMarkName="setMarkName"
@imageLocation="imageLocation"
@resetAnnotations="resetAnnotations"
@getAnnotations="getAnnotations"
@setToolToTarget="setToolToTarget"
/>
<recisit
v-else-if="lastViewportTaskId && criterionType === 1 && lastViewportTaskIds.includes(s.VisitTaskId)"
:ref="`ecrf_${s.VisitTaskId}`"
:reading-task-state="currentVisitInfo.VisitTaskId === taskInfo.VisitTaskId ? readingTaskState : 2"
:last-viewport-task-id="lastViewportTaskId"
:visit-info="s"
@removeAnnotation="removeAnnotation"
@getScreenshots="getScreenshots"
@setMarkName="setMarkName"
@imageLocation="imageLocation"
@resetAnnotations="resetAnnotations"
@getAnnotations="getAnnotations"
@setToolToTarget="setToolToTarget"
/>
<customize-question-list
v-else-if="lastViewportTaskId && criterionType === 0 && lastViewportTaskIds.includes(s.VisitTaskId)"
:ref="`ecrf_${s.VisitTaskId}`"
:reading-task-state="taskInfo && currentVisitInfo.VisitTaskId === taskInfo.VisitTaskId ? readingTaskState : 2"
:last-viewport-task-id="lastViewportTaskId"
:visit-info="s"
@resetAnnotations="resetAnnotations"
@setReadingTaskState="setReadingTaskState"
@viewCustomAnnotationSeries="viewCustomAnnotationSeries"
@getCustomScreenshots="getCustomScreenshots"
@setReadingToolActive="setReadingToolActive"
@setReadingToolPassive="setReadingToolPassive"
/>
</div>
</div>
</div>
</div>
</div>
<!-- 自定义调窗 -->
<el-dialog
v-if="customWwc.visible"
:visible.sync="customWwc.visible"
:close-on-click-modal="false"
:title="customWwc.title"
width="400px"
custom-class="base-dialog-wrapper"
>
<custom-wwwc-form :ww="activeViewportWW" :wc="activeViewportWC" @close="customWwc.visible = false" @setWwwc="setWwwc" />
</el-dialog>
<!-- 手册 -->
<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" />
</div>
</el-dialog>
<!-- 个性化配置 -->
<el-dialog
v-if="personalConfigDialog.visible"
:visible.sync="personalConfigDialog.visible"
:close-on-click-modal="false"
:title="personalConfigDialog.title"
width="600px"
>
<el-tabs v-model="personalConfigDialog.activeName" class="personal_config">
<!-- 热键 -->
<el-tab-pane :label="$t('trials:reading:tab:hotkeys')" name="1">
<Hotkeys v-if="personalConfigDialog.activeName === '1'" :reading-tool="0" @reset="resetHotkeyList" />
</el-tab-pane>
<!-- W/L模板 -->
<el-tab-pane :label="$t('trials:reading:tab:wlTemplate')" name="2">
<WL v-if="personalConfigDialog.activeName === '2'" @getWwcTpl="getWwcTpl" />
</el-tab-pane>
<!-- 其他 -->
<el-tab-pane :label="$t('trials:reading:tab:others')" name="3">
<Others v-if="personalConfigDialog.activeName === '3'" />
</el-tab-pane>
</el-tabs>
</el-dialog>
<!-- 临床数据 -->
<el-dialog
:visible.sync="clinicalDataVisible"
:custom-class="isClinicalDataFullscreen?'cd-full-dialog-container':'cd-dialog-container'"
:show-close="false"
:close-on-click-modal="false"
:fullscreen="isClinicalDataFullscreen"
>
<span slot="title" class="dialog-footer">
<div style="position: absolute;right: 20px;top: 10px;color: #000;">
<svg-icon :icon-class="isClinicalDataFullscreen?'exit-fullscreen':'fullscreen'" style="cursor: pointer;font-size: 20px;" @click="isClinicalDataFullscreen=!isClinicalDataFullscreen" />
<svg-icon icon-class="dClose" style="cursor: pointer;font-size: 25px;margin-left: 10px;" @click="clinicalDataVisible = false" />
</div>
</span>
<div style="height: 100%;margin:0;display: flex;flex-direction: column;">
<clinical-data
v-if="clinicalDataVisible"
style="flex: 1"
:trial-id="trialId"
:subject-id="taskInfo.SubjectId"
:visit-task-id="cdVisitTaskId"
:is-reading-show-subject-info="taskInfo.IsReadingShowSubjectInfo"
/>
</div>
</el-dialog>
<el-dialog
:visible.sync="fusionVisible"
:close-on-click-modal="false"
:title="$t('trials:lugano:button:record')"
width="850px"
>
<FusionForm v-if="fusionVisible" :active-task-index="activeTaskIndex" :task-list="visitTaskList" @close="closeFusion" @fusion="handleFusion" />
</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"
/>
</div>
</template>
<script>
import { getRelatedVisitTask, getReadingVisitStudyList, getTableAnswerRowInfoList, deleteCustomTag, getCriterionReadingInfo } from '@/api/trials'
import { getDoctorShortcutKey, getUserWLTemplateList } from '@/api/user'
import { getCustomTag, submitCustomTag } from '@/api/reading'
import { getToken } from '@/utils/auth'
import {
RenderingEngine,
Enums,
// imageLoader,
// metaData,
volumeLoader,
getRenderingEngine,
eventTarget,
utilities as csUtils,
cache
} from '@cornerstonejs/core'
import {
annotation
} from '@cornerstonejs/tools'
import * as cornerstoneTools from '@cornerstonejs/tools'
import initLibraries from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initLibraries'
import html2canvas from 'html2canvas'
import { getTools, getCustomizeStandardsTools, config } from './toolConfig'
import StudyList from './StudyList'
import Viewport from './Viewport'
import PetCtViewport from './PetCtViewport'
import mRecisit from './mRecist/QuestionList'
import recisit from './Recist/QuestionList'
import customizeQuestionList from './customize/QuestionList'
import CustomWwwcForm from '@/views/trials/trials-panel/reading/dicoms/components/CustomWwwcForm'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
import Manuals from '@/views/trials/trials-panel/reading/dicoms/components/Manuals'
import Hotkeys from '@/views/trials/trials-panel/reading/dicoms/components/Hotkeys'
import WL from '@/views/trials/trials-panel/reading/dicoms/components/WL'
import Others from '@/views/trials/trials-panel/reading/dicoms/components/Others'
import ClinicalData from '@/views/trials/trials-panel/reading/clinical-data'
import FusionForm from './FusionForm.vue'
import colorMap from './colorMap.vue'
import RectangleROITool from './tools/RectangleROITool'
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
const { visibility } = annotation
const { ViewportType, Events } = Enums
const renderingEngineId = 'myRenderingEngine'
const {
ToolGroupManager,
Enums: csToolsEnums,
StackScrollTool,
ScaleOverlayTool,
PanTool,
ZoomTool,
WindowLevelTool,
WindowLevelRegionTool,
PlanarRotateTool,
LengthTool,
BidirectionalTool,
ArrowAnnotateTool,
// RectangleROITool,
PlanarFreehandROITool,
CircleROITool,
AngleTool,
CobbAngleTool,
EraserTool,
MIPJumpToClickTool,
VolumeRotateTool,
synchronizers
// cursors
} = cornerstoneTools
const { createCameraPositionSynchronizer, createVOISynchronizer } = synchronizers
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'
}
}
annotation.config.style.setDefaultToolStyles(newStyles)
const { MouseBindings, Events: toolsEvents } = csToolsEnums
export default {
name: 'ReadPage',
components: {
StudyList,
Viewport,
PetCtViewport,
mRecisit,
recisit,
customizeQuestionList,
CustomWwwcForm,
Manuals,
Hotkeys,
WL,
Others,
ClinicalData,
FusionForm,
colorMap,
downloadDicomAndNonedicom,
uploadDicomAndNonedicom,
},
props: {
readingTool: {
type: Number,
default: 2
}
},
data() {
return {
loading: false,
trialId: '',
visitTaskList: [],
selectArr: [],
taskInfo: null,
activeTaskId: null,
activeTaskIndex: -1,
activeStudyIndex: -1,
activeSeriesIndex: -1,
currentVisitInfo: null,
layout: 1,
cellsMax: 4,
rows: 1,
cols: 1,
fullScreenIndex: null,
activeViewportIndex: 0,
activeTool: '',
readingTaskState: 2,
renderingEngineId: renderingEngineId,
sLoading: false,
renderedTaskIds: [],
criterionType: null,
tools: [],
rotateOptions: [
{ label: this.$t('trials:reading:button:rotateDefault'), val: 1 },
{ label: this.$t('trials:reading:button:rotateVertical'), val: 2 },
{ label: this.$t('trials:reading:button:rotateHorizontal'), val: 3 },
{ label: this.$t('trials:reading:button:rotateTurnLeft'), val: 4 },
{ label: this.$t('trials:reading:button:rotateTurnRight'), val: 5 }
],
defaultWwwc: [
{ label: this.$t('trials:reading:button:wwwcDefault'), val: -1, ww: null, wc: null }, // 默认值
{ label: this.$t('trials:reading:button:wwwcCustom'), val: 0, ww: null, wc: null }, // 自定义
{ label: this.$t('trials:reading:button:wwwcRegion'), val: 1, ww: null, wc: null }, // 区域窗宽
{ label: 'CT Brain', wc: 40, ww: 80 },
{ label: 'CT Lungs', wc: -400, ww: 1500 },
{ label: 'CT Abdomen', wc: 60, ww: 400 },
{ label: 'CT Liver', wc: 40, ww: 400 },
{ label: 'CT Bone', wc: 300, ww: 1500 },
{ label: 'CT Bone1', wc: 0, ww: 0 }
],
wwwcArr: [],
customWwc: { visible: false, title: this.$t('trials:reading:dagTitle:wwwcCustom') }, // 自定义调窗
personalConfigDialog: { visible: false, title: this.$t('trials:reading:button:customCfg'), activeName: '1' }, // 个性化配置
activeViewportWW: null,
activeViewportWC: null,
clipPlaying: false,
fps: 15,
manualsDialog: { visible: false, isFullscreen: false },
hotKeyList: [],
forceFitToWindow: false,
isShowAnnotations: true,
clinicalDataVisible: false,
isClinicalDataFullscreen: false,
cdVisitTaskId: '',
lastViewportTaskId: '',
digitPlaces: 2,
instanceInfo: {},
lastViewportTaskIds: [],
markedSeriesIds: [],
customizeStandards: [],
fusionVisible: false,
isFusion: false,
studyList: [],
volumeData: {},
fusionSerieId: {},
loadingText: null,
toolNames: ['Length', 'Bidirectional', 'RectangleROI', 'ArrowAnnotate', 'CircleROI', 'Eraser'],
// resetAnnotation: false , // 是否初始化标记 (融合时使用)
saveCustomAnnotationTimer: null,
// 上传
downloadImageVisible: false,
uploadImageVisible: false,
uploadSubjectId: null,
uploadSubjectCode: null,
uploadTrialCriterion: {},
uploadStatus: 'upload',
taskId: '',
isReadingTaskViewInOrder: null,
trialCriterion: {}
}
},
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)
},
viewportKey() {
return this.isFusion ? 'viewport-fusion' : 'viewport'
},
},
watch: {
currentReadingTaskState(){console.log(this.currentReadingTaskState,'currentReadingTaskState')},
activeTaskId: {
immediate: true,
handler(id) {
if (!id) return
// this.addHistoryAnnotations(id)
}
},
activeViewportIndex: {
immediate: true,
handler(index) {
let series = null
if (this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`] && this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0]) {
this.clipPlaying = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].playClipState
series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
} else {
this.clipPlaying = false
this.fps = 15
}
if (index === this.cells.length - 1 && series) {
this.lastViewportTaskId = series.TaskInfo.VisitTaskId
this.currentVisitInfo = series.TaskInfo
}
}
},
lastViewportTaskId: {
immediate: true,
handler(id) {
if (!id) return
if (!this.lastViewportTaskIds.includes(id)) {
this.lastViewportTaskIds.push(id)
}
}
},
readingTaskState: {
immediate: true,
handler(state) {
if (state === 2) {
// 设置标记锁定
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
annotations.map(annotation => {
cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
})
this.setToolsPassive()
}
}
}
},
mounted() {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.isReadingTaskViewInOrder = this.taskInfo.IsReadingTaskViewInOrder
this.criterionType = this.taskInfo.CriterionType
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
if (this.criterionType === 0) {
this.tools = getCustomizeStandardsTools(this.taskInfo.ReadingToolList)
const toolNames = this.tools.map(i => i.toolName)
this.customizeStandards = config.customizeStandards.filter(item => !toolNames.includes(item.toolName))
} else {
this.tools = getTools(this.criterionType)
}
this.trialId = this.$route.query.trialId
this.readingTaskState = this.taskInfo.ReadingTaskState
if (!this.taskInfo.IsBaseLine && this.taskInfo.IsReadingTaskViewInOrder !== 0) {
this.rows = 1
this.cols = 2
this.activeViewportIndex = 1
}
this.$nextTick(() => {
this.loadRelatedTasks()
this.initLoader()
this.getWwcTpl()
})
DicomEvent.$on('isCanActiveNoneDicomTool', data => {
this.open.postMessage({ type: 'isCanActiveNoneDicomTool', data: data }, window.location)
})
DicomEvent.$on('removeNoneDicomMeasureData', data => {
this.open.postMessage({ type: 'removeNoneDicomMeasureData', data: data }, window.location)
})
DicomEvent.$on('addNoneDicomMeasureData', data => {
this.open.postMessage({ type: 'addNoneDicomMeasureData', data: data }, window.location)
})
},
methods: {
// 加载当前任务关联的任务信息
async loadRelatedTasks() {
this.loading = true
try {
const params = {
visitTaskId: this.taskInfo.VisitTaskId
}
const res = await getRelatedVisitTask(params)
let currentTaskIndex = 0
let currentTaskId = ''
this.visitTaskList = []
for (let i = 0; i < res.Result.length; i++) {
const item = res.Result[i]
let studyList = []
let annotations = []
let annotationUIDs = []
let keyImages = []
let isInit = false
if (item.IsCurrentTask) {
currentTaskIndex = i
currentTaskId = item.VisitTaskId
}
if (item.IsCurrentTask || (this.taskInfo.IsReadingTaskViewInOrder === 1 && !this.taskInfo.IsBaseLineTask && item.IsBaseLineTask)) {
const res = await this.loadTaskDetails(item, i)
isInit = true
studyList = res ? res.studyList : []
annotations = res ? res.annotations : []
annotationUIDs = res ? res.annotationUIDs : []
keyImages = res ? res.keyImages : []
if (!this.selectArr.includes(item.VisitTaskId)) {
this.selectArr.push(item.VisitTaskId)
}
if (item.IsCurrentTask) {
this.markedSeriesIds = []
annotations.map(i => {
if (i.MeasureData && i.MeasureData.seriesId) {
this.markedSeriesIds.push(i.MeasureData.seriesId)
}
})
}
}
this.visitTaskList.push({
...item,
StudyList: studyList,
Annotations: annotations,
KeyImages: keyImages,
AnnotationUIDs: annotationUIDs,
IsInit: isInit
})
}
this.activeTaskId = currentTaskId
this.activeTaskIndex = currentTaskIndex
this.$nextTick(() => {
this.setInitSeries()
})
this.loading = false
} catch (e) {
console.log(e)
this.loading = false
}
},
// 加载检查及标注信息
async loadTaskDetails(taskInfo, taskIndex) {
return new Promise(async(resolve, reject) => {
try {
const sujectVisitId = taskInfo.VisitId
const taskId = taskInfo.VisitTaskId
// 获取检查信息
const keyImages = []
const res1 = await getReadingVisitStudyList(this.trialId, sujectVisitId, taskId)
let keyStudyIndex = -1
let keySeriesIndex = -1
const arr = res1.Result
arr.forEach((study, studyIndex) => {
study.SeriesList.forEach((series, seriesIndex) => {
const imageIds = []
const stack = []
series.InstanceInfoList.forEach((instance, instanceIndex) => {
if (study.IsCriticalSequence) {
keyStudyIndex = studyIndex
keySeriesIndex = seriesIndex
keyImages.push({ Id: instance.Id, Path: instance.Path, KeyFramesList: instance.KeyFramesList, KeyStudyIndex: studyIndex, KeySeriesIndex: seriesIndex })
} else {
const i = keyImages.findIndex(k => k.Id === instance.Id)
if (i > -1) {
keyImages[i].StudyIndex = studyIndex
keyImages[i].SeriesIndex = seriesIndex
}
const nFrames = instance.NumberOfFrames || 0
if (nFrames === 0) {
// 单帧
stack.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
} else {
// 多帧
for (let i = 0; i < nFrames; i++) {
const newImageId = `wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&frame=${i + 1}`
stack.push(newImageId)
}
}
imageIds.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
this.instanceInfo[instance.Id] = { taskIndex, studyIndex, seriesIndex }
}
})
series.Stack = stack
series.ImageIds = imageIds
series.SliceIndex = 0
series.LoadedImageCount = 0
series.LoadedImageProgress = 0
series.TaskInfo = Object.assign({}, taskInfo)
series.StudyIndex = studyIndex
series.SeriesIndex = seriesIndex
})
})
if (keyStudyIndex > -1 && keySeriesIndex > -1 && keyImages.length > 0) {
const keyImageIds = []
const keyStack = []
keyImages.forEach(instance => {
if (instance.KeyFramesList.length > 0) {
instance.KeyFramesList.map(i => {
keyStack.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}&frame=${i}`)
})
} else {
keyStack.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
}
keyImageIds.push(`wadouri:${this.OSSclientConfig.basePath}${instance.Path}?instanceId=${instance.Id}`)
})
res1.Result[keyStudyIndex].SeriesList[keySeriesIndex].ImageIds = keyImageIds
res1.Result[keyStudyIndex].SeriesList[keySeriesIndex].Stack = keyStack
}
// 获取标注信息
let res2 = null
if (this.criterionType === 0) {
res2 = await getCustomTag({ visitTaskId: taskId })
} else {
res2 = await getTableAnswerRowInfoList(taskId)
}
const annotationUIDs = []
const annotations = res2.Result.map(i => {
if (typeof i.MeasureData === 'string' && i.MeasureData) {
i.MeasureData = JSON.parse(i.MeasureData)
if (this.criterionType === 0) {
i.MeasureData.id = i.Id
}
annotationUIDs.push(i.MeasureData.annotationUID)
}
return i
})
// return { studyList: arr, annotations: annotations, annotationUIDs, keyImages }
resolve({ studyList: arr, annotations: annotations, annotationUIDs, keyImages })
} catch (e) {
console.log(e)
reject(e)
}
})
},
// 设置初始化序列信息
setInitSeries() {
const seriesArr = []
let activeStudyIndex = -1
let activeSeriesIndex = -1
const studyList = this.visitTaskList[this.activeTaskIndex].StudyList.filter(i => i.IsDicom)
const seriesList = studyList.map(s => s.SeriesList).flat()
if (this.taskInfo.IsReadingTaskViewInOrder === 0) {
// 完全随机(默认一个视口,展示第一个序列)
const series = this.getRelatedSeries(this.visitTaskList[this.activeTaskIndex])
seriesArr.push(series)
activeStudyIndex = series.StudyIndex
activeSeriesIndex = series.SeriesIndex
} else if (this.taskInfo.IsReadingTaskViewInOrder === 1) {
// 按时间顺序(默认基线时显示一个视口,展示第一个序列;随访时显示基线和当前随访的序列)
if (this.taskInfo.IsBaseLine) {
// 基线默认显示第一个序列
const series = this.getRelatedSeries(this.visitTaskList[this.activeTaskIndex])
seriesArr.push(series)
activeStudyIndex = series.StudyIndex
activeSeriesIndex = series.SeriesIndex
} else {
// 随访
const i = this.visitTaskList.findIndex(i => i.IsBaseLineTask)
const baseSeries = this.getRelatedSeries(this.visitTaskList[i])
seriesArr.push(baseSeries)
const followUpSeries = this.getRelatedSeries(this.visitTaskList[this.activeTaskIndex], baseSeries)
seriesArr.push(followUpSeries)
activeStudyIndex = followUpSeries.StudyIndex
activeSeriesIndex = followUpSeries.SeriesIndex
}
} else if (this.taskInfo.IsReadingTaskViewInOrder === 2) {
// 受试者内随机(默认两个视口,显示第一个序列和第二个序列;如果只有一个序列则两个视口均显示这个序列)
if (seriesList.length === 1) {
seriesArr.push(seriesList[0], seriesList[0])
activeStudyIndex = seriesList[0].StudyIndex
activeSeriesIndex = seriesList[0].SeriesIndex
} else if (seriesArr.length > 1) {
seriesArr.push(seriesList[0], seriesList[1])
activeStudyIndex = seriesList[1].StudyIndex
activeSeriesIndex = seriesList[1].SeriesIndex
}
}
if (activeStudyIndex > -1 && activeSeriesIndex > -1) {
seriesArr.map((i, index) => {
this.$refs[`${this.viewportKey}-${index}`][0].setSeriesInfo(i)
})
const visitTaskId = this.visitTaskList[this.activeTaskIndex].VisitTaskId
this.lastViewportTaskId = visitTaskId
this.currentVisitInfo = this.visitTaskList[this.activeTaskIndex]
this.$refs[visitTaskId][0].setSeriesActive(activeStudyIndex, activeSeriesIndex)
}
},
// 初始化加载器
async initLoader() {
await initLibraries()
cache.setMaxCacheSize(6 * 1024 * 1024 * 1024)
let renderingEngine = getRenderingEngine(renderingEngineId)
if (!renderingEngine) {
renderingEngine = new RenderingEngine(renderingEngineId)
}
const element1 = this.$refs['viewport-0'][0].$el
const element2 = this.$refs['viewport-1'][0].$el
const element3 = this.$refs['viewport-2'][0].$el
const element4 = this.$refs['viewport-3'][0].$el
let viewportInputArray = [
{
viewportId: 'viewport-0',
type: ViewportType.STACK,
element: element1
},
{
viewportId: 'viewport-1',
type: ViewportType.STACK,
element: element2
},
{
viewportId: 'viewport-2',
type: ViewportType.STACK,
element: element3
},
{
viewportId: 'viewport-3',
type: ViewportType.STACK,
element: element4
}
]
let viewportIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3']
const fusionViewportIds = ['viewport-fusion-0', 'viewport-fusion-1', 'viewport-fusion-2', 'viewport-fusion-3']
if (this.readingTool === 2) {
const fusionElement1 = this.$refs['viewport-fusion-0'][0].$el
const fusionElement2 = this.$refs['viewport-fusion-1'][0].$el
const fusionElement3 = this.$refs['viewport-fusion-2'][0].$el
const fusionElement4 = this.$refs['viewport-fusion-3'][0].$el
const arr = [
{
viewportId: 'viewport-fusion-0',
type: ViewportType.ORTHOGRAPHIC,
element: fusionElement1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
}
},
{
viewportId: 'viewport-fusion-1',
type: ViewportType.ORTHOGRAPHIC,
element: fusionElement2,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
background: [1, 1, 1]
}
},
{
viewportId: 'viewport-fusion-2',
type: ViewportType.ORTHOGRAPHIC,
element: fusionElement3,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL
}
},
{
viewportId: 'viewport-fusion-3',
type: ViewportType.ORTHOGRAPHIC,
element: fusionElement4,
defaultOptions: {
orientation: Enums.OrientationAxis.CORONAL,
background: [1, 1, 1]
}
}
]
viewportInputArray = [...viewportInputArray, ...arr]
viewportIds = viewportIds.concat(fusionViewportIds)
}
renderingEngine.setViewports(viewportInputArray)
this.addAnnotationListeners()
cornerstoneTools.addTool(StackScrollTool)
cornerstoneTools.addTool(PanTool)
cornerstoneTools.addTool(ZoomTool)
cornerstoneTools.addTool(WindowLevelTool)
cornerstoneTools.addTool(WindowLevelRegionTool)
cornerstoneTools.addTool(PlanarRotateTool)
cornerstoneTools.addTool(ArrowAnnotateTool)
cornerstoneTools.addTool(RectangleROITool)
cornerstoneTools.addTool(PlanarFreehandROITool)
cornerstoneTools.addTool(EraserTool)
cornerstoneTools.addTool(LengthTool)
cornerstoneTools.addTool(BidirectionalTool)
cornerstoneTools.addTool(ScaleOverlayTool)
cornerstoneTools.addTool(CircleROITool)
cornerstoneTools.addTool(AngleTool)
cornerstoneTools.addTool(CobbAngleTool)
cornerstoneTools.addTool(MIPJumpToClickTool)
cornerstoneTools.addTool(VolumeRotateTool)
viewportIds.forEach((viewportId, i) => {
// const toolGroupId = `viewport-${i}`
const toolGroupId = viewportId
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId)
toolGroup.addViewport(viewportId, renderingEngineId)
toolGroup.addTool(StackScrollTool.toolName)
toolGroup.addTool(ScaleOverlayTool.toolName)
toolGroup.addTool(PanTool.toolName)
toolGroup.addTool(ZoomTool.toolName)
toolGroup.addTool(WindowLevelTool.toolName)
toolGroup.addTool(WindowLevelRegionTool.toolName)
toolGroup.addTool(PlanarRotateTool.toolName)
toolGroup.addTool(ArrowAnnotateTool.toolName, {
arrowHeadStyle: 'standard',
changeTextCallback: async(data, eventData, doneChangingTextCallback) => {
return doneChangingTextCallback(data.text)
},
getTextCallback: async(doneChangingTextCallback) => {
return doneChangingTextCallback('Annotation')
}
})
toolGroup.addTool(RectangleROITool.toolName, {
cachedStats: false,
getTextLines: this.criterionType === 0 ? this.getCustomRectangleROIToolTextLines : this.getRectangleROIToolTextLines
})
toolGroup.addTool(PlanarFreehandROITool.toolName, {
allowOpenContours: false,
cachedStats: false,
getTextLines: this.getPlanarFreehandROIToolTextLines
})
toolGroup.addTool(EraserTool.toolName)
toolGroup.addTool(LengthTool.toolName, {
getTextLines: this.getLengthToolTextLines
// cachedStats: false
})
toolGroup.addTool(BidirectionalTool.toolName, {
// cachedStats: true
getTextLines: this.getBidirectionalToolTextLines
})
toolGroup.addTool(CircleROITool.toolName, {
getTextLines: this.getCircleROIToolTextLines
})
toolGroup.addTool(AngleTool.toolName, {
getTextLines: this.getAngleToolTextLines
})
toolGroup.addTool(CobbAngleTool.toolName, {
getTextLines: this.getCobbAngleToolTextLines
})
if (toolGroupId === 'viewport-fusion-3') {
toolGroup.addTool(VolumeRotateTool.toolName)
toolGroup.setToolActive(VolumeRotateTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Wheel // mouse wheel
}
]
})
toolGroup.addTool(MIPJumpToClickTool.toolName, {
targetViewportIds: fusionViewportIds
})
// Set the initial state of the tools, here we set one tool active on left click.
// This means left click will draw that tool.
toolGroup.setToolActive(MIPJumpToClickTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary // Left Click
}
]
})
}
toolGroup.setToolActive(StackScrollTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
toolGroup.setToolActive(ZoomTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Secondary }]
})
toolGroup.setToolActive(PanTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Auxiliary }]
})
// toolGroup.setToolEnabled(ScaleOverlayTool.toolName);
// toolGroup.setToolPassive(PanTool.toolName)
// toolGroup.setToolPassive(ZoomTool.toolName)
toolGroup.setToolPassive(WindowLevelTool.toolName)
toolGroup.setToolPassive(WindowLevelRegionTool.toolName)
toolGroup.setToolPassive(PlanarRotateTool.toolName)
if (this.readingTaskState < 2) {
toolGroup.setToolPassive(ArrowAnnotateTool.toolName)
toolGroup.setToolPassive(RectangleROITool.toolName)
toolGroup.setToolPassive(PlanarFreehandROITool.toolName)
toolGroup.setToolPassive(LengthTool.toolName)
toolGroup.setToolPassive(BidirectionalTool.toolName)
toolGroup.setToolPassive(CircleROITool.toolName)
toolGroup.setToolPassive(AngleTool.toolName)
toolGroup.setToolPassive(CobbAngleTool.toolName)
} else {
toolGroup.setToolEnabled(ArrowAnnotateTool.toolName)
toolGroup.setToolEnabled(RectangleROITool.toolName)
toolGroup.setToolEnabled(PlanarFreehandROITool.toolName)
toolGroup.setToolEnabled(LengthTool.toolName)
toolGroup.setToolEnabled(BidirectionalTool.toolName)
toolGroup.setToolEnabled(CircleROITool.toolName)
toolGroup.setToolEnabled(AngleTool.toolName)
toolGroup.setToolEnabled(CobbAngleTool.toolName)
}
toolGroup.setToolPassive(EraserTool.toolName)
})
eventTarget.addEventListener('cornerstoneimageloadprogress', this.imageLoadProgress)
console.log(Events, toolsEvents)
if (this.readingTool === 2) {
this.setUpSynchronizers()
}
},
// 影像下载进度回调
imageLoadProgress(e) {
const { detail } = e
const percentComplete = detail.percentComplete
const imageId = detail.imageId
const params = this.getInstanceInfo(imageId)
const instanceId = params.instanceId
const idxObj = this.instanceInfo[instanceId]
if (!(idxObj && typeof idxObj === 'object')) return
if (!(this.instanceInfo[instanceId].hasOwnProperty('percentComplete'))) {
this.instanceInfo[instanceId].percentComplete = 0
}
// if (!params.hasOwnProperty('idx')) return
// const indexArr = params.idx.split('|')
// if (indexArr.length < 3) return
const taskIndex = idxObj.taskIndex
const studyIndex = idxObj.studyIndex
const seriesIndex = idxObj.seriesIndex
const isCriticalSequence = this.visitTaskList[taskIndex].StudyList[studyIndex].IsCriticalSequence
const keyImages = this.visitTaskList[taskIndex].KeyImages
const series = this.visitTaskList[taskIndex].StudyList[studyIndex].SeriesList[seriesIndex]
this.setImageLoadedProgress(series, percentComplete, instanceId)
if (!isCriticalSequence && series.IsBeMark && keyImages.length > 0) {
const i = keyImages.findIndex(i => i.Id === params.instanceId)
if (i === -1) return
const keyStudyIndex = parseInt(keyImages[i].KeyStudyIndex)
const keySeriesIndex = parseInt(keyImages[i].KeySeriesIndex)
const keySeries = this.visitTaskList[taskIndex].StudyList[keyStudyIndex].SeriesList[keySeriesIndex]
this.setImageLoadedProgress(keySeries, percentComplete, instanceId)
}
},
// 设置图像下载进度信息
setImageLoadedProgress(series, percentComplete, instanceId) {
let loadedImageCount = series.LoadedImageCount
if (percentComplete === 100) {
++loadedImageCount
this.$set(series, 'LoadedImageCount', loadedImageCount)
}
const newLoadedImageProgress = series.LoadedImageProgress - this.instanceInfo[instanceId].percentComplete + percentComplete
this.instanceInfo[instanceId].percentComplete = percentComplete
this.$set(series, 'LoadedImageProgress', newLoadedImageProgress)
},
addHistoryAnnotations(taskId) {
// if (this.renderedTaskIds.includes(taskId)) return
// this.renderedTaskIds.push(taskId)
// let taskIdx = this.visitTaskList.findIndex(i=>i.VisitTaskId === taskId)
// if (taskIdx=== -1) return
// const annotations = this.visitTaskList[taskIdx].Annotations
// annotations.map(i => {
// if (i.MeasureData) {
// const annotation = i.MeasureData
// cornerstoneTools.annotation.state.addAnnotation(annotation)
// if (this.visitTaskList[taskIdx].ReadingTaskState === 2) {
// cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
// }
// }
// })
},
renderAnnotations(series) {
const taskId = series.TaskInfo.VisitTaskId
if (!taskId || this.renderedTaskIds.includes(taskId)) return
this.renderedTaskIds.push(taskId)
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === taskId)
if (taskIdx === -1) return
const annotations = this.visitTaskList[taskIdx].Annotations
annotations.map(i => {
if (i.MeasureData && !Object.hasOwn(i.MeasureData, 'isDicomReading')) {
const annotation = i.MeasureData
annotation.highlighted = false
cornerstoneTools.annotation.state.addAnnotation(annotation)
if (this.visitTaskList[taskIdx].ReadingTaskState === 2) {
cornerstoneTools.annotation.locking.setAnnotationLocked(annotation.annotationUID)
}
}
})
},
addAnnotationListeners() {
const debouncedCallback = this.debounce((evt) => {
this.criterionType === 0 ? this.customAnnotationModifiedListener(evt) : this.annotationModifiedListener(evt)
}, 100)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_MODIFIED,
(evt) => {
debouncedCallback(evt)
}
)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_COMPLETED,
this.criterionType === 0 ? this.customAnnotationCompletedListener : this.annotationCompletedListener
)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_REMOVED,
this.criterionType === 0 ? this.customAnnotationRemovedListener : this.annotationRemovedListener
)
eventTarget.addEventListener(
toolsEvents.ANNOTATION_SELECTION_CHANGE,
this.criterionType === 0 ? this.customAnnotationSelectionChangeListener : this.annotationSelectionChangeListener
)
// eventTarget.addEventListener(
// toolsEvents.ANNOTATION_ADDED,
// this.annotationAddedListener
// )
// eventTarget.addEventListener(
// toolsEvents.TOOL_MODE_CHANGED,
// this.toolModeChanged
// )
},
toolModeChanged(e) {
console.log(e)
const arr = cornerstoneTools.annotation.state.getAllAnnotations()
// if (arr)
console.log(arr)
},
annotationAddedListener(e) {
},
annotationCompletedListener(e) {
console.log('Completed')
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const referencedImageId = annotation.metadata.referencedImageId
const params = this.getInstanceInfo(referencedImageId)
annotation.visitTaskId = series.TaskInfo.VisitTaskId
annotation.studyId = series.StudyId
annotation.seriesId = series.Id
annotation.instanceId = params.instanceId
annotation.sliceThickness = series.SliceThickness
annotation.numberOfFrames = isNaN(parseInt(params.frame)) ? null : parseInt(params.frame)
annotation.markTool = annotation.metadata.toolName
// this.$refs['ecrf'].setAnnotation({ annotation, toolName: annotation.metadata.toolName })
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].setAnnotation({ annotation, toolName: annotation.metadata.toolName })
this.markedSeriesIds.push(series.Id)
}
this.setToolsPassive()
},
annotationModifiedListener(e) {
console.log('Modified')
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation.highlighted) return
if (!annotation) return
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
// this.$refs['ecrf'].modifyAnnotation({ annotation, toolName: annotation.metadata.toolName })
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].modifyAnnotation({ annotation, toolName: annotation.metadata.toolName })
}
this.setToolsPassive()
},
annotationRemovedListener(e) {
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) {
const index = this.markedSeriesIds.indexOf(annotation.seriesId)
if (index !== -1) {
this.markedSeriesIds.splice(index, 1)
}
}
},
async customAnnotationCompletedListener(e) {
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1) {
this.setToolsPassive()
return
}
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const referencedImageId = annotation.metadata.referencedImageId
const params = this.getInstanceInfo(referencedImageId)
annotation.visitTaskId = series.TaskInfo.VisitTaskId
annotation.studyId = series.StudyId
annotation.seriesId = series.Id
annotation.instanceId = params.instanceId
annotation.sliceThickness = series.SliceThickness
annotation.numberOfFrames = isNaN(parseInt(params.frame)) ? null : parseInt(params.frame)
annotation.markTool = annotation.metadata.toolName
this.markedSeriesIds.push(series.Id)
const operateStateEnum = this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].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.$refs[`ecrf_${series.TaskInfo.VisitTaskId}`][0].bindAnnotationToQuestion(annotation)
} else {
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
this.saveCustomAnnotationTimer = setTimeout(()=>{this.saveCustomAnnotation(annotation)},500)
}
}
}
this.setToolsPassive()
},
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
},
async saveCustomAnnotation(annotation) {
try {
const measureData = Object.assign({}, annotation)
const params = {}
await this.getScreenshots({
visitTaskId: this.visitTaskId,
annotation
},async (base64Str) =>{
const pictureObj = await this.uploadScreenshots(`${Date.now()}`, base64Str)
let picturePath = pictureObj.isSuccess ? this.$getObjectName(pictureObj.result.url) : ''
params.PicturePath = picturePath
params.MarkTool = measureData.markTool
params.OrderMarkName = measureData.data.label
params.VisitTaskId = this.taskInfo.VisitTaskId
params.StudyId = annotation.studyId
params.SeriesId = annotation.seriesId
params.InstanceId = annotation.instanceId
params.Id = annotation.id ? annotation.id : ''
params.MeasureData = JSON.stringify(measureData)
params.NumberOfFrames = annotation.numberOfFrames
params.MarkId = annotation.annotationUID
const res = await submitCustomTag(params)
annotation.id = res.Result
})
} catch (e) {
this.loading = false
console.log(e)
}
},
customAnnotationModifiedListener(e) {
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation.highlighted) return
if (!annotation) return
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1) {
this.setToolsPassive()
return
}
if (!annotation.data.label) return
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const operateStateEnum = this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].operateStateEnum
const isBound = this.$refs[`ecrf_${annotation.visitTaskId}`][0].verifyAnnotationIsBound(annotation)
if (isBound || this.isNumber(operateStateEnum)) {
this.$refs[`ecrf_${series.TaskInfo.VisitTaskId}`][0].updateAnnotationToQuestion(annotation)
} else {
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
this.saveCustomAnnotationTimer = setTimeout(()=>{this.saveCustomAnnotation(annotation)},1000)
}
}
this.setToolsPassive()
},
async customAnnotationRemovedListener(e) {
const { annotation } = e.detail
try {
// if ( this.resetAnnotation && this.isFusion ) return false
if (!annotation) 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) {
// 临时标记
return
}
if (annotation.visitTaskId === this.taskInfo.VisitTaskId) {
const isBound = this.$refs[`ecrf_${annotation.visitTaskId}`][0].verifyAnnotationIsBound(annotation)
if (isBound && this.activeTool === 'Eraser') {
// '该标记已与问题进行绑定,不允许删除!'
this.$alert(this.$t('dicom3D:ReadPage:alert:MarkToQuestionNoDel'))
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
}
}
if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) {
if (this.activeTool === 'Eraser') {
await this.$confirm(
this.$t('trials:trials-list:table:isDeleted') +
annotation.data.label +
'?'
)
if (annotation.id) {
deleteCustomTag(annotation.id)
}
}
const index = this.markedSeriesIds.indexOf(annotation.seriesId)
if (index !== -1) {
this.markedSeriesIds.splice(index, 1)
}
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
} else {
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
}
} catch (e) {
cornerstoneTools.annotation.state.addAnnotation(annotation)
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
console.log(e)
}
},
removeAnnotation(annotation) {
cornerstoneTools.annotation.state.removeAnnotation(annotation.annotationUID)
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
},
customAnnotationSelectionChangeListener(e) {
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 {
this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].bindAnnotationToQuestion(annotations[i])
}
}
},
annotationSelectionChangeListener(e) {},
async getAnnotations(visitTaskId) {
if (this.readingTaskState === 2) return
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
if (taskIdx === -1) return
if (!this.visitTaskList[taskIdx].IsCurrentTask) return
// 获取标注信息
let res = null
if (this.criterionType === 0) {
res = await getCustomTag({ visitTaskId: visitTaskId })
} else {
res = await getTableAnswerRowInfoList(visitTaskId)
}
const annotationUIDs = []
const annotations = res.Result.map(i => {
if (typeof i.MeasureData === 'string' && i.MeasureData) {
i.MeasureData = JSON.parse(i.MeasureData)
if (this.criterionType === 0) {
i.MeasureData.id = i.Id
}
annotationUIDs.push(i.MeasureData.annotationUID)
}
return i
})
this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations)
this.$set(this.visitTaskList[taskIdx], 'AnnotationUIDs', annotationUIDs)
},
async resetAnnotations(visitTaskId) {
if (this.readingTaskState === 2) return
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
if (taskIdx === -1) return
if (!this.visitTaskList[taskIdx].IsCurrentTask) return
// 获取标注信息
let res = null
if (this.criterionType === 0) {
res = await getCustomTag({ visitTaskId: visitTaskId })
} else {
res = await getTableAnswerRowInfoList(visitTaskId)
}
const annotations = res.Result.map(i => {
if (typeof i.MeasureData === 'string' && i.MeasureData) {
i.MeasureData = JSON.parse(i.MeasureData)
if (this.criterionType === 0) {
i.MeasureData.id = i.Id
}
}
return i
})
this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations)
// 移除病灶
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)
this.markedSeriesIds.push(annotation.seriesId)
}
})
const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
},
setMarkName(obj) {
const annotations = cornerstoneTools.annotation.state.getAllAnnotations()
const idx = annotations.findIndex(i => i.annotationUID === obj.annotationUID)
if (idx === -1) return
if (annotations[idx].metadata.toolName === 'ArrowAnnotate') {
annotations[idx].data.text = obj.name
annotations[idx].data.label = obj.name
} else {
annotations[idx].data.label = obj.name
}
annotations[idx].data.status = obj.status
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
},
getLengthToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { length, unit } = cachedVolumeStats
if (length === undefined || length === null || isNaN(length)) {
return
}
const textLines = []
if (data.label) {
textLines.push(data.status ? `${data.label}(${data.status})` : data.label)
}
textLines.push(`${parseFloat(length).toFixed(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)
}
if (area) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${parseFloat(area).toFixed(this.digitPlaces)} ${areaUnit}`
textLines.push(areaLine)
}
if (mean) {
textLines.push(`Mean: ${parseFloat(mean).toFixed(this.digitPlaces)} ${modalityUnit}`)
}
if (Number.isFinite(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`)
}
if (stdDev) {
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`)
}
if (perimeter) {
textLines.push(`Perimeter: ${parseFloat(perimeter).toFixed(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 { area, mean, max, stdDev, areaUnit, modalityUnit } = cachedVolumeStats
const { mean } = cachedVolumeStats
if (mean === undefined) {
return
}
const textLines = []
if (data.label) {
textLines.push(data.status ? `${data.label}(${data.status})` : data.label)
}
return textLines
},
getCustomRectangleROIToolTextLines(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)
}
textLines.push(`Area: ${this.reRound(area, this.digitPlaces)} ${areaUnit}`)
textLines.push(`Mean: ${this.reRound(mean, this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Max: ${this.reRound(max, this.digitPlaces)} ${modalityUnit}`)
textLines.push(`Std Dev: ${this.reRound(stdDev, this.digitPlaces)} ${modalityUnit}`)
return textLines
},
getBidirectionalToolTextLines(data, targetId) {
const { cachedStats, label } = data
const { length, width, unit } = cachedStats[targetId]
const textLines = []
if (label) {
textLines.push(data.status ? `${label}(${data.status})` : label)
}
if (length === undefined) {
return textLines
}
textLines.push(
`L: ${parseFloat(length).toFixed(this.digitPlaces)} ${unit || unit}`,
`S: ${parseFloat(width).toFixed(this.digitPlaces)} ${unit}`
)
return textLines
},
getCircleROIToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const {
radius,
radiusUnit,
area,
mean,
stdDev,
max,
isEmptyArea,
areaUnit,
modalityUnit
} = cachedVolumeStats
const textLines = []
if (data.label) {
// textLines.push(data.label)
textLines.push(data.label)
}
if (radius) {
const radiusLine = isEmptyArea
? `Radius: Oblique not supported`
: `Radius: ${this.reRound(radius, this.digitPlaces)} ${radiusUnit}`
textLines.push(radiusLine)
}
if (area) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${parseFloat(area)} ${areaUnit}`
textLines.push(areaLine)
}
if (mean) {
textLines.push(`Mean: ${this.reRound(mean, this.digitPlaces)} ${modalityUnit}`)
}
if (max) {
textLines.push(`Max: ${this.reRound(max, this.digitPlaces)} ${modalityUnit}`)
}
if (stdDev) {
textLines.push(`Std Dev: ${this.reRound(stdDev, this.digitPlaces)} ${modalityUnit}`)
}
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(`${angle.toFixed(this.digitPlaces)} ${String.fromCharCode(176)}`)
return textLines
},
getCobbAngleToolTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId]
const { angle } = cachedVolumeStats
const { label } = data
if (angle === undefined) {
return
}
const textLines = []
if (label) {
textLines.push(label)
}
textLines.push(`${angle.toFixed(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)
},
// 激活工具
setToolActive(toolName) {
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
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
const toolObj = this.tools.find(i => i.toolName === toolName)
if (!toolObj || toolObj.isDisabled) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
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
}
}
if (this.criterionType === 0 && this.readingTaskState < 2) {
this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].resetOperateState()
}
},
setReadingToolActive(toolName) {
if (this.readingTaskState === 2) return
if (this.activeTool === toolName) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (this.activeTool) {
toolGroup.setToolPassive(this.activeTool)
}
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
this.activeTool = toolName
}
},
setReadingToolPassive() {
if (this.readingTaskState === 2) return
if (this.activeTool && this.toolNames.includes(this.activeTool)) {
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolPassive(this.activeTool)
this.activeTool = ''
}
}
},
setMoreToolActive(toolName) {
if (this.readingTaskState === 2) return
this.setToolsPassive()
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
this.activeTool = toolName
}
},
setToolsPassive() {
if (!this.activeTool) return
const toolGroupIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
toolGroupIds.forEach(toolGroupId => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolPassive(this.activeTool)
})
this.activeTool = ''
},
setToolEnabled() {
if (!this.activeTool) return
const toolGroupIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
toolGroupIds.forEach(toolGroupId => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolEnabled(this.activeTool)
})
this.activeTool = ''
},
// 鼠标移入测量工具时,判断工具是否可激活
enter(e, toolName) {
const i = this.tools.findIndex(i => i.toolName === toolName)
if (i === -1) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
const isCurrentTask = series.TaskInfo.IsCurrentTask
const readingTaskState = this.readingTaskState
if (!isCurrentTask || readingTaskState >= 2) {
this.tools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
if (this.activeTool) {
this.setToolEnabled()
}
} else {
if (this.criterionType === 0) {
this.tools[i].isDisabled = false
e.target.style.cursor = 'pointer'
return
}
const obj = this.$refs[`ecrf_${this.lastViewportTaskId}`][0].validTool(toolName, true)
this.tools[i].disabledReason = obj.reason
if (!obj.isCanActiveTool) {
if (this.activeTool === toolName) {
this.setToolsPassive()
}
this.tools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
} else {
this.tools[i].isDisabled = false
e.target.style.cursor = 'pointer'
}
}
},
// 旋转视口
setViewportRotate(value) {
this.setToolsPassive()
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
const type = parseInt(value)
// 1默认值2垂直翻转3水平翻转4左转90度5右转90度
if (type === 1) {
// viewport.resetCamera()
// viewport.resetProperties()
// viewport.render()
viewport.setViewPresentation({ rotation: 0 })
viewport.render()
} else if (type === 2) {
const { flipVertical } = viewport.getCamera()
viewport.setCamera({ flipVertical: !flipVertical })
viewport.render()
} else if (type === 3) {
const { flipHorizontal } = viewport.getCamera()
viewport.setCamera({ flipHorizontal: !flipHorizontal })
viewport.render()
} else if (type === 4) {
const { rotation } = viewport.getViewPresentation()
viewport.setViewPresentation({ rotation: rotation === 0 ? 270 : rotation - 90 })
viewport.render()
} else if (type === 5) {
const { rotation } = viewport.getViewPresentation()
viewport.setViewPresentation({ rotation: rotation + 90 })
viewport.render()
}
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].rotateOrientationMarkers(type)
},
// 重置视口
resetViewport() {
this.setToolsPassive()
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resetOrientationMarkers()
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true })
viewport.resetProperties()
viewport.render()
renderingEngine.render()
},
// 更改视图布局
async changeLayout(v) {
this.setToolsPassive()
this.fullScreenIndex = null
this.layout = v
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
const seriesArr = []
if (v === 1) {
this.rows = 1
this.cols = 1
this.activeViewportIndex = 0
if (typeof series === 'object') {
seriesArr.push(series)
}
} else if (v === 2) {
this.rows = 1
this.cols = 2
this.activeViewportIndex = 1
if (typeof series === 'object') {
seriesArr.push(series)
seriesArr.push(series)
}
} else if (v === 3) {
this.rows = 1
this.cols = 2
if (series) {
const idx = this.visitTaskList.findIndex(i => i.VisitTaskId === series.TaskInfo.VisitTaskId)
const visitTaskNum = idx > -1 ? this.visitTaskList[idx].VisitTaskNum : -1
if (visitTaskNum > 0) {
let visitTaskIdx = -1
if (this.criterionType === 10) {
visitTaskIdx = this.visitTaskList.findIndex(i => i.VisitTaskNum === visitTaskNum - 1)
} else {
visitTaskIdx = this.visitTaskList.findIndex(i => i.IsBaseLineTask)
}
if (visitTaskIdx > -1) {
if (!this.visitTaskList[visitTaskIdx].IsInit) {
this.sLoading = true
try {
const res = await this.loadTaskDetails(this.visitTaskList[visitTaskIdx], visitTaskIdx)
const studyList = res ? res.studyList : []
const annotations = res ? res.annotations : []
const keyImages = res ? res.keyImages : []
const annotationUIDs = res ? res.annotationUIDs : []
this.$set(this.visitTaskList[visitTaskIdx], 'StudyList', studyList)
this.$set(this.visitTaskList[visitTaskIdx], 'Annotations', annotations)
this.$set(this.visitTaskList[visitTaskIdx], 'KeyImages', keyImages)
this.$set(this.visitTaskList[visitTaskIdx], 'AnnotationUIDs', annotationUIDs)
this.$set(this.visitTaskList[visitTaskIdx], 'IsInit', true)
this.sLoading = false
} catch (e) {
this.sLoading = false
}
}
const relatedSeries = this.getRelatedSeries(this.visitTaskList[visitTaskIdx], series)
seriesArr.push(relatedSeries)
seriesArr.push(series)
}
} else if (visitTaskNum === 0) {
seriesArr.push(series)
seriesArr.push(series)
}
}
this.activeViewportIndex = 1
} else if (v === 4) {
this.rows = 2
this.cols = 2
if (typeof series === 'object') {
seriesArr.push(series)
seriesArr.push(series)
seriesArr.push(series)
seriesArr.push(series)
}
this.activeViewportIndex = 3
}
seriesArr.map((i, index) => {
this.$refs[`${this.viewportKey}-${index}`][0].setSeriesInfo(i)
})
this.$nextTick(() => {
const renderingEngine = getRenderingEngine(renderingEngineId)
renderingEngine.resize(true, false)
})
},
// 更改窗宽窗位
changeVoiRange(v) {
this.setToolsPassive()
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
if (v.val === -1) {
// 默认值
viewport.resetProperties()
viewport.render()
} else if (v.val === 0) {
// 自定义
const wwwc = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].imageInfo.wwwc
this.activeViewportWW = wwwc ? parseInt(wwwc.split('/')[0]) : null
this.activeViewportWC = wwwc ? parseInt(wwwc.split('/')[1]) : null
this.customWwc.visible = true
} else if (v.val === 1) {
// 区域窗宽窗位
this.setToolActive('WindowLevelRegion')
} else {
const lower = v.wc - v.ww / 2
const upper = v.wc + v.ww / 2 - 1
viewport.setProperties({ voiRange: { upper: upper, lower: lower }})
viewport.render()
}
},
setWindowLevelActive(e) {
this.setToolActive('WindowLevel')
this.showPanel(e)
},
// 设置窗宽窗位
setWwwc(v) {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
const lower = v.wc - v.ww / 2
const upper = v.wc + v.ww / 2 - 1
viewport.setProperties({ voiRange: { upper: upper, lower: lower }})
viewport.render()
this.customWwc.visible = false
},
// 反色
toggleInvert() {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(`${this.viewportKey}-${this.activeViewportIndex}`)
const { invert } = viewport.getProperties()
if (this.isFusion) {
viewport.setProperties({ invert: !invert }, this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].volumeId)
}
viewport.setProperties({ invert: !invert })
viewport.render()
},
// 翻页
scrollPage(type) {
this.clipPlaying = false
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].scrollPage(type)
},
// 播放
toggleClipPlay(isPlay) {
this.clipPlaying = !this.clipPlaying
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].toggleClipPlay(isPlay, this.fps)
},
// 获取窗宽窗位模板
async getWwcTpl() {
try {
const res = await getUserWLTemplateList()
const customWwcTpl = []
res.Result.map(i => {
customWwcTpl.push({ label: i.TemplateName, wc: i.WL, ww: i.WW })
})
this.wwwcArr = [...this.defaultWwwc, ...customWwcTpl]
} catch (e) {
console.log(e)
}
},
// 获取热键配置信息
async getHotKeys() {
try {
const res = await getDoctorShortcutKey({ imageToolType: 0 })
res.Result.map(item => {
this.hotKeyList.push({ id: item.Id, altKey: item.AltKey, ctrlKey: item.CtrlKey, shiftKey: item.ShiftKey, metaKey: item.MetaKey, key: item.Keyboardkey, code: item.Code, text: item.Text, shortcutKeyEnum: item.ShortcutKeyEnum })
})
this.bindHotKey()
} catch (e) {
console.log(e)
}
},
// 绑定热键事件
bindHotKey() {
const container = this.$refs['container']
container.addEventListener('keydown', event => {
const idx = this.hotKeyList.findIndex(i => i.code === event.code && i.ctrlKey === event.ctrlKey && i.shiftKey === event.shiftKey && i.altKey === event.altKey)
if (idx === -1) return
const shortcutKeyEnum = this.hotKeyList[idx].shortcutKeyEnum
if (shortcutKeyEnum === 1) {
// 前一图像视口
const viewportIndex = this.activeViewportIndex === 0 ? this.activeViewportIndex : this.activeViewportIndex - 1
this.activeViewport(viewportIndex)
} else if (shortcutKeyEnum === 2) {
// 后一图像视口
const viewportIndex = this.activeViewportIndex === this.cells.length - 1 ? this.activeViewportIndex : this.activeViewportIndex + 1
this.activeViewport(viewportIndex)
} else if (shortcutKeyEnum === 3) {
// 上一个序列
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
this.$refs[this.activeTaskId][0].getPreviousOrNextSeries(-1, series)
} else if (shortcutKeyEnum === 4) {
// 下一个序列
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
this.$refs[this.activeTaskId][0].getPreviousOrNextSeries(1, series)
} else if (shortcutKeyEnum === 5) {
// 上一张图像
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].scrollPage(-1)
} else if (shortcutKeyEnum === 6) {
// 下一张图像
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].scrollPage(1)
} else if (shortcutKeyEnum === 7) {
// 向左旋转
this.setViewportRotate(4)
} else if (shortcutKeyEnum === 8) {
// 向右旋转
this.setViewportRotate(5)
} else if (shortcutKeyEnum === 9) {
// 水平翻转
this.setViewportRotate(3)
} else if (shortcutKeyEnum === 10) {
// 垂直翻转
this.setViewportRotate(2)
} else if (shortcutKeyEnum === 11) {
// 放大
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setZoom(1)
} else if (shortcutKeyEnum === 12) {
// 缩小
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setZoom(-1)
} else if (shortcutKeyEnum === 13) {
// 适应图像
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resize(false)
} else if (shortcutKeyEnum === 14) {
// 适应窗口
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resize(true)
} else if (shortcutKeyEnum === 15) {
// 截图
} else if (shortcutKeyEnum === 16) {
// 反色
this.toggleInvert()
} else if (shortcutKeyEnum === 17) {
// 窗宽/窗位
const wwwcIdx = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].wwwcIdx
const newWwwcIdx = wwwcIdx === this.wwwcArr.length - 1 ? 3 : wwwcIdx + 1
const wwwcTpl = this.wwwcArr[newWwwcIdx]
this.changeVoiRange(wwwcTpl)
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setWwwcIdx(newWwwcIdx)
} else if (shortcutKeyEnum === 18) {
// 重置
this.resetViewport()
} else if (shortcutKeyEnum === 19) {
// 显示或隐藏标记
this.isShowAnnotations = !this.isShowAnnotations
if (this.isShowAnnotations) {
visibility.showAllAnnotations()
} else {
const annotationUIDs = annotation.state.getAllAnnotations().map((a) => a.annotationUID)
annotationUIDs.forEach((annotationUID) => {
visibility.setAnnotationVisibility(annotationUID, false)
})
}
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewportIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
renderingEngine.renderViewports(viewportIds)
}
event.stopImmediatePropagation()
event.stopPropagation()
event.preventDefault()
})
},
// 重置热键信息
resetHotkeyList(arr) {
this.hotKeyList = []
arr.map(item => {
this.hotKeyList.push({ id: item.id, altKey: item.keys.controlKey.altKey, ctrlKey: item.keys.controlKey.ctrlKey, shiftKey: item.keys.controlKey.shiftKey, metaKey: item.keys.controlKey.metaKey, key: item.keys.controlKey.key, code: item.keys.controlKey.code, text: item.keys.text, shortcutKeyEnum: item.label })
})
},
fitToType(forceFitToWindow) {
this.forceFitToWindow = !forceFitToWindow
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resize(forceFitToWindow)
},
// 切换全屏
toggleFullScreen(e, index) {
this.fullScreenIndex = this.fullScreenIndex === index ? null : index
this.activeViewportIndex = index
if (this.isFusion) {
const viewportIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`]
viewportIds.forEach(id => {
const index = this.$refs[id][0].series.SliceIndex
this.$refs[id][0].setFullScreen(index)
})
}
},
async toggleTask(taskInfo, taskIndex) {
if (this.isFusion) {
const confirm = await this.$confirm(this.$t('trials:reading:confirm:changeStack'))
if (!confirm) return false
this.isFusion = false
this.fullScreenIndex = null
this.setToolsPassive()
}
if (taskIndex === this.activeTaskIndex) return
if (!this.selectArr.includes(taskInfo.VisitTaskId)) {
this.selectArr.push(taskInfo.VisitTaskId)
}
this.activeTaskId = taskInfo.VisitTaskId
this.activeTaskIndex = taskIndex
if (!taskInfo.IsInit) {
this.sLoading = true
try {
const res = await this.loadTaskDetails(taskInfo, taskIndex)
const studyList = res ? res.studyList : []
const annotations = res ? res.annotations : []
const annotationUIDs = res ? res.annotationUIDs : []
const keyImages = res ? res.keyImages : []
this.$set(taskInfo, 'StudyList', studyList)
this.$set(taskInfo, 'Annotations', annotations)
this.$set(taskInfo, 'KeyImages', keyImages)
this.$set(taskInfo, 'AnnotationUIDs', annotationUIDs)
this.$set(taskInfo, 'IsInit', true)
this.sLoading = false
} catch (e) {
this.sLoading = false
}
}
// this.setToolsPassive()
},
async toggleTaskByViewport(obj) {
const i = this.visitTaskList.findIndex(v => v.VisitTaskNum === obj.visitTaskNum)
if (i === -1) return
this.activeTaskId = this.visitTaskList[i].VisitTaskId
this.activeTaskIndex = i
if (!this.selectArr.includes(this.activeTaskId)) {
this.selectArr.push(this.activeTaskId)
}
if (!this.visitTaskList[i].IsInit) {
this.sLoading = true
try {
const res = await this.loadTaskDetails(this.visitTaskList[i], i)
const studyList = res ? res.studyList : []
const annotations = res ? res.annotations : []
const annotationUIDs = res ? res.annotationUIDs : []
const keyImages = res ? res.keyImages : []
this.$set(this.visitTaskList[i], 'StudyList', studyList)
this.$set(this.visitTaskList[i], 'Annotations', annotations)
this.$set(this.visitTaskList[i], 'KeyImages', keyImages)
this.$set(this.visitTaskList[i], 'AnnotationUIDs', annotationUIDs)
this.$set(this.visitTaskList[i], 'IsInit', true)
this.sLoading = false
} catch (e) {
this.sLoading = false
}
}
const series = this.getRelatedSeries(this.visitTaskList[i], obj.series)
this.$nextTick(() => {
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series)
this.$refs[this.activeTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
if (this.activeViewportIndex === this.cells.length - 1) {
this.lastViewportTaskId = series.TaskInfo.VisitTaskId
this.currentVisitInfo = series.TaskInfo
}
})
this.setToolsPassive()
},
async activeSeries(obj) {
if (this.isFusion) {
const confirm = await this.$confirm(this.$t('trials:reading:confirm:changeStack'))
if (!confirm) return false
this.isFusion = false
this.setToolsPassive()
this.rows = 1
this.cols = 1
this.activeViewportIndex = 0
this.fullScreenIndex = null
return this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
}
if (!obj.IsDicom) {
return this.previewNoneDicoms(obj)
}
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
this.clipPlaying = false
this.fps = 15
if (this.activeViewportIndex === this.cells.length - 1) {
this.lastViewportTaskId = obj.TaskInfo.VisitTaskId
this.currentVisitInfo = obj.TaskInfo
}
if (obj.TaskInfo.VisitTaskId !== this.taskInfo.VisitTaskId) {
this.setToolsPassive()
}
},
async showMultiFrame(obj) {
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj, true)
this.clipPlaying = false
this.fps = 15
if (this.activeViewportIndex === this.cells.length - 1) {
this.lastViewportTaskId = obj.TaskInfo.VisitTaskId
this.currentVisitInfo = obj.TaskInfo
}
this.setToolsPassive()
},
// 激活视口
activeViewport(index) {
if (this.activeViewportIndex === index) return
this.activeViewportIndex = index
// 切换检查列表
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series) {
const i = this.visitTaskList.findIndex(v => v.VisitTaskId === series.TaskInfo.VisitTaskId)
if (i > -1) {
this.activeTaskId = series.TaskInfo.VisitTaskId
this.activeTaskIndex = i
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
}
}
this.setToolsPassive()
},
getRelatedSeries(visitTaskInfo, baselineSeries) {
let obj = {}
const studyList = visitTaskInfo.StudyList
const annotations = visitTaskInfo.Annotations
if (baselineSeries) {
const seriesList = studyList.filter(i => i.IsDicom).map(s => s.SeriesList).flat()
if (baselineSeries.IsMarked) {
const i = annotations.findIndex(i => Object.keys(i.MeasureData).length > 0 && i.OrderMarkName === baselineSeries.MeasureData.OrderMarkName)
if (i > -1) {
obj = this.getMarkedSeries(studyList, annotations[i].MeasureData)
}
}
if (Object.keys(obj).length === 0) {
// 筛选描述相似
let similarArr = seriesList.map((i, index) => {
return { similar: this.strSimilarity2Percent(i.Description, baselineSeries.Description), index }
})
similarArr = similarArr.sort((a, b) => {
return b.similar - a.similar
})
const idx = similarArr[0] && similarArr[0].similar > 0.85 ? similarArr[0].index : -1
if (idx > -1) {
obj = Object.assign({}, seriesList[idx])
const stack = seriesList[idx].Stack
const sliceIdx = Math.floor(stack.length * ((baselineSeries.SliceIndex + 1) / baselineSeries.Stack.length))
// let sliceIdx = Math.floor(imageIds.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
}
}
if (Object.keys(obj).length === 0) {
// 筛选层厚一致
const idx = seriesList.findIndex(series => series.InstanceCount === baselineSeries.InstanceCount)
if (idx > -1) {
obj = Object.assign({}, seriesList[idx])
const stack = seriesList[idx].Stack
const sliceIdx = Math.floor(stack.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
}
}
if (Object.keys(obj).length === 0) {
// 筛选层厚为5的序列
const idx = seriesList.findIndex(series => series.IsDicom && parseInt(series.SliceThickness) === 5)
if (idx > -1) {
obj = Object.assign({}, seriesList[idx])
const stack = seriesList[idx].Stack
const sliceIdx = Math.floor(stack.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
}
}
} else {
const i = annotations.findIndex(a => Object.keys(a.MeasureData).length > 0)
if (i > -1) {
// 有标记时,显示带标记影像所在序列
obj = this.getMarkedSeries(studyList, annotations[i].MeasureData)
} else {
for (let k = 0; k < studyList.length; k++) {
const seriesIdx = studyList[k].SeriesList.findIndex(s => s.SliceThickness && parseInt(s.SliceThickness) === 5)
if (seriesIdx > -1) {
obj = Object.assign({}, studyList[k].SeriesList[seriesIdx])
const stack = studyList[k].SeriesList[seriesIdx].Stack
const sliceIdx = Math.floor(stack.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
obj.IsMarked = false
break
}
}
}
}
if (Object.keys(obj).length === 0) {
const sIdx = studyList.findIndex(s => s.IsDicom)
if (sIdx > -1) {
obj = Object.assign({}, studyList[sIdx].SeriesList[0])
const stack = studyList[sIdx].SeriesList[0].Stack
const sliceIdx = Math.floor(stack.length / 2)
obj.SliceIndex = sliceIdx > 0 ? sliceIdx - 1 : 0
obj.IsMarked = false
}
}
return obj
},
getMarkedSeries(studyList, annotation) {
let obj = {}
const sIdx = studyList.findIndex(s => s.StudyId === annotation.studyId)
if (sIdx > -1) {
const seriesList = studyList[sIdx].SeriesList
const seriesIdx = seriesList.findIndex(s => s.Id === annotation.seriesId)
if (seriesIdx > -1) {
const instanceIdx = seriesList[seriesIdx].InstanceInfoList.findIndex(i => i.Id === annotation.instanceId)
let filterStr = ''
if (instanceIdx > -1 && seriesList[seriesIdx].IsExistMutiFrames) {
if (seriesList[seriesIdx].InstanceInfoList[instanceIdx].NumberOfFrames > 0) {
filterStr = `instanceId=${annotation.instanceId}&frame=${annotation.numberOfFrames}`
} else {
filterStr = `instanceId=${annotation.instanceId}`
}
} else {
filterStr = `instanceId=${annotation.instanceId}`
}
const stack = seriesList[seriesIdx].Stack
const imageIdIdx = stack.findIndex(is => is.includes(filterStr))
if (imageIdIdx > -1) {
obj = Object.assign({}, seriesList[seriesIdx])
obj.SliceIndex = imageIdIdx
obj.IsMarked = true
obj.MeasureData = annotation
}
}
}
return obj
},
viewCustomAnnotationSeries(obj) {
const i = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.visitTaskId)
if (i === -1) return
const studyList = this.visitTaskList[i].StudyList
const series = this.getMarkedSeries(studyList, obj.annotation)
if (series) {
this.$refs[`${this.viewportKey}-${this.cells.length - 1}`][0].setSeriesInfo(series, true)
this.activeViewportIndex = i
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
}
},
async getCustomScreenshots(obj, callback) {
const i = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.visitTaskId)
if (i === -1) return
const studyList = this.visitTaskList[i].StudyList
const series = this.getMarkedSeries(studyList, obj.annotation)
if (series) {
this.$refs[`${this.viewportKey}-${this.cells.length - 1}`][0].setSeriesInfo(series, true)
this.activeViewportIndex = i
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]`
)
const canvas = await html2canvas(divForDownloadViewport)
const base64Str = canvas.toDataURL('image/png', 1)
callback(base64Str)
}
},
async getScreenshots(measureData, callback) {
if (measureData) {
await this.imageLocation(measureData)
const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]`
)
const canvas = await html2canvas(divForDownloadViewport)
const base64Str = canvas.toDataURL('image/png', 1)
callback(base64Str)
} else {
callback()
}
},
imageLocation(obj) {
return new Promise(async resolve => {
let loading = null
const index = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.visitTaskId)
if (index === -1) {
resolve()
return
}
if (obj.annotationUID) {
obj.StudyId = obj.studyId
obj.SeriesId = obj.seriesId
obj.InstanceId = obj.instanceId
obj.isMarked = true
} else {
obj.isMarked = false
}
let firstAddSeries = {}
let currentAddSeries = {}
if (this.taskInfo.IsReadingTaskViewInOrder === 1) {
// 有序
// 获取病灶第一次出现的访视序列
let firstAddVisitTaskId = null
const idx = this.visitTaskList[index].Annotations.findIndex(item => {
return item.OrderMarkName === obj.lesionName
})
if (idx > -1) {
firstAddVisitTaskId = this.visitTaskList[index].Annotations[idx].FristAddTaskId
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === firstAddVisitTaskId)
if (taskIdx > -1 && !this.visitTaskList[taskIdx].IsInit) {
loading = this.$loading({ fullscreen: true })
try {
const res = await this.loadTaskDetails(this.visitTaskList[taskIdx], taskIdx)
const studyList = res ? res.studyList : []
const annotations = res ? res.annotations : []
const annotationUIDs = res ? res.annotationUIDs : []
const keyImages = res ? res.keyImages : []
this.$set(this.visitTaskList[taskIdx], 'StudyList', studyList)
this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations)
this.$set(this.visitTaskList[taskIdx], 'KeyImages', keyImages)
this.$set(this.visitTaskList[taskIdx], 'AnnotationUIDs', annotationUIDs)
this.$set(this.visitTaskList[taskIdx], 'IsInit', true)
loading.close()
} catch (e) {
loading.close()
}
}
const annotationIdx = this.visitTaskList[taskIdx].Annotations.findIndex(i => i.OrderMarkName === obj.lesionName)
if (annotationIdx > -1) {
firstAddSeries = this.getMarkedSeries(this.visitTaskList[taskIdx].StudyList, this.visitTaskList[taskIdx].Annotations[annotationIdx].MeasureData)
}
}
if (obj.isMarked) {
currentAddSeries = this.getMarkedSeries(this.visitTaskList[index].StudyList, obj)
}
if (Object.keys(firstAddSeries).length === 0 && Object.keys(currentAddSeries).length !== 0) {
firstAddSeries = currentAddSeries
} else if (Object.keys(firstAddSeries).length === 0 && Object.keys(currentAddSeries).length === 0) {
this.setToolsPassive()
if (obj.isActiveTarget) {
this.setToolToTarget(obj)
}
resolve()
return
} else if (Object.keys(firstAddSeries).length !== 0 && Object.keys(currentAddSeries).length === 0) {
// 当前访视序列与首次出现病灶的序列对齐
// currentAddSeries = this.getRelatedSeries(this.visitTaskList[index], firstAddSeries)
// if (Object.keys(currentAddSeries).length === 0) {
// // 未找到对齐的,则就显示当前最后一个窗口现实的序列信息
// currentAddSeries = this.$refs[`viewport-${this.cells.length - 1}`][0].series
// }
// 显示当前序列
currentAddSeries = this.$refs[`${this.viewportKey}-${this.cells.length - 1}`][0].series
}
} else {
// 无序
currentAddSeries = this.getMarkedSeries(this.visitTaskList[index].StudyList, obj)
if (Object.keys(currentAddSeries).length === 0) {
// 未找到标注序列的则就显示当前最后一个窗口现实的序列信息cells.length - 1
currentAddSeries = this.$refs[`${this.viewportKey}-${this.cells.length - 1}`][0].series
}
firstAddSeries = currentAddSeries
}
for (let i = 0; i < this.cells.length; i++) {
if (i === this.cells.length - 1) {
this.$refs[`${this.viewportKey}-${i}`][0].setSeriesInfo(currentAddSeries, true)
this.activeViewportIndex = i
this.$refs[currentAddSeries.TaskInfo.VisitTaskId][0].setSeriesActive(currentAddSeries.StudyIndex, currentAddSeries.SeriesIndex)
} else {
this.$refs[`${this.viewportKey}-${i}`][0].setSeriesInfo(firstAddSeries, true)
}
}
this.setToolsPassive()
if (obj.isActiveTarget) {
this.setToolToTarget(obj)
}
resolve()
})
},
setToolToTarget(obj) {
console.log('setToolToTarget')
if (obj.visitTaskId === this.taskInfo.VisitTaskId && this.readingTaskState !== 2 && obj.markTool && !obj.isMarked) {
const toolName = obj.markTool
if (this.activeTool) {
this.setToolsPassive()
}
const toolGroupId = `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
this.activeTool = toolName
}
},
setReadingTaskState(state) {
this.readingTaskState = state
},
// 两个字符串的相似程度,并返回相似度百分比
strSimilarity2Percent(s, t) {
var l = s.length > t.length ? s.length : t.length
var d = this.strSimilarity2Number(s, t)
return Number((1 - d / l).toFixed(4))
},
strSimilarity2Number(s, t) {
var n = s.length; var m = t.length; var d = []
var i, j, s_i, t_j, cost
if (n === 0) return m
if (m === 0) return n
for (i = 0; i <= n; i++) {
d[i] = []
d[i][0] = i
}
for (j = 0; j <= m; j++) {
d[0][j] = j
}
for (i = 1; i <= n; i++) {
s_i = s.charAt(i - 1)
for (j = 1; j <= m; j++) {
t_j = t.charAt(j - 1)
if (s_i === t_j) {
cost = 0
} else {
cost = 1
}
d[i][j] = this.Minimum(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost)
}
}
return d[n][m]
},
Minimum(a, b, c) {
return a < b ? (a < c ? a : c) : (b < c ? b : c)
},
// 手册
previewManuals() {
this.manualsDialog.isFullscreen = false
this.manualsDialog.visible = true
},
// 临床数据
previewCD(taskId) {
this.isClinicalDataFullscreen = false
this.dialogVisible = true
this.cdVisitTaskId = taskId
this.clinicalDataVisible = true
},
// 个性化配置
previewConfig() {
this.personalConfigDialog.activeName = '1'
this.personalConfigDialog.visible = true
},
getInstanceInfo(imageId) {
const params = {}
const searchParams = new URLSearchParams(imageId.split('?')[1])
for (const [key, value] of searchParams.entries()) {
params[key] = value
}
if (isNaN(params.frame)) {
params.frame = null
}
return params
},
// 输入标记名称自定义弹窗
async customPrompt(isShowCancelButton = true) {
try {
const that = this
// 请输入标记名称
const { value } = await this.$prompt(this.$t('trials:noneDicom:message:msg1'), '', {
showClose: false,
showCancelButton: isShowCancelButton,
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 {
return null
}
},
isNumber(value) {
return typeof value === 'number' && value !== null && !isNaN(value)
},
showPanel(e, toolName) {
if (toolName === 'layout' && this.isFusion) return false
e.currentTarget.firstChild.lastChild.style.display = 'block'
},
toolMouseout(e) {
e.currentTarget.firstChild.lastChild.style.display = 'none'
},
debounce(callback, delay) {
let timerId
return function() {
clearTimeout(timerId)
timerId = setTimeout(() => {
callback.apply(this, arguments)
}, delay)
}
},
previewNoneDicoms(obj) {
var index = this.visitTaskList.findIndex(i => i.VisitTaskId === obj.TaskInfo.VisitTaskId)
var taskBlindName = this.visitTaskList[index].TaskBlindName
var token = getToken()
let subjectId = localStorage.getItem("subjectId")
const routeData = this.$router.resolve({
path: `/nonedicoms?subjectId=${subjectId}&trialId=${this.trialId}&visitTaskId=${obj.TaskInfo.VisitTaskId}&taskBlindName=${taskBlindName}&readingTaskState=${this.visitTaskList[index].ReadingTaskState}&TokenKey=${token}`
})
this.open = window.open(routeData.href, '_blank')
},
// 融合视口相机同步
setUpSynchronizers() {
const axialCameraPositionSynchronizer = createCameraPositionSynchronizer(
'AXIAL_CAMERA_SYNCHRONIZER_ID'
)
const ctVoiSynchronizer = createVOISynchronizer('CT_VOI_SYNCHRONIZER_ID', {
syncInvertState: false,
syncColormap: false
})
const ptVoiSynchronizer = createVOISynchronizer('PT_VOI_SYNCHRONIZER_ID', {
syncInvertState: false,
syncColormap: false
})
const fusionVoiSynchronizer = createVOISynchronizer('FUSION_VOI_SYNCHRONIZER_ID', {
syncInvertState: false,
syncColormap: false
});
[
`viewport-fusion-0`,
`viewport-fusion-1`,
`viewport-fusion-2`
].forEach((viewportId) => {
axialCameraPositionSynchronizer.add({
renderingEngineId: this.renderingEngineId,
viewportId
})
});
[
`viewport-fusion-0`
].forEach((viewportId) => {
ctVoiSynchronizer.add({
renderingEngineId: this.renderingEngineId,
viewportId
})
});
[
`viewport-fusion-3`,
`viewport-fusion-1`
].forEach((viewportId) => {
ptVoiSynchronizer.add({
renderingEngineId: this.renderingEngineId,
viewportId
})
});
[
`viewport-fusion-2`
].forEach((viewportId) => {
fusionVoiSynchronizer.add({
renderingEngineId,
viewportId
})
ctVoiSynchronizer.addTarget({
renderingEngineId,
viewportId
})
ptVoiSynchronizer.addTarget({
renderingEngineId,
viewportId
})
})
},
setColorMap(rgbPresetName) {
const fusionViewportIds = [`viewport-fusion-1`, `viewport-fusion-2`, `viewport-fusion-3`]
fusionViewportIds.forEach(id => {
this.$refs[id][0].setPreset(rgbPresetName)
this.$refs[id][0].renderColorBar(rgbPresetName)
this.$refs[id][0].setColorMap(rgbPresetName)
})
},
voiChange(v) {
const fusionViewportIds = [`viewport-fusion-1`, `viewport-fusion-2`, `viewport-fusion-3`]
fusionViewportIds.forEach(id => {
this.$refs[id][0].voiChange(v)
})
},
async handleFusion(data) {
try {
this.fusionVisible = false
this.isFusion = true
this.rows = 2
this.cols = 2
const { ct, pt } = data
this.loading = true
this.loadingText = this.$t('trials:lugano:message:loadVolumes')
// this.resetAnnotation = true
// console.log(cornerstoneTools.annotation.state.getAllAnnotations(),'cornerstoneTools.annotation.state')
// cornerstoneTools.annotation.state.removeAllAnnotations()
this.renderedTaskIds = []
if (this.verifyFusionData(ct, pt)) {
this.loading = false
this.loadingText = null
this.$refs[`viewport-0`][0].setSeriesInfo(ct)
this.$refs[`viewport-1`][0].setSeriesInfo(pt)
this.$refs[`viewport-2`][0].setSeriesInfo(pt)
this.$refs[`viewport-3`][0].setSeriesInfo(pt)
// this.resetAnnotation = false
return true
}
if (!this.fusionSerieId.ct || this.fusionSerieId.ct !== ct.SeriesInstanceUid) {
this.fusionSerieId.ct = ct.SeriesInstanceUid
}
if (!this.fusionSerieId.pt || this.fusionSerieId.pt !== pt.SeriesInstanceUid) {
this.fusionSerieId.pt = pt.SeriesInstanceUid
}
await this.getVolume(pt)
await this.getVolume(ct)
await this.getVolume(pt, true)
this.loading = false
this.loadingText = null
const ctData = {
data: ct,
volumeId: this.volumeData[ct.SeriesInstanceUid].volumeId
}
const ptData = {
data: pt,
volumeId: this.volumeData[pt.SeriesInstanceUid].volumeId,
volume: this.volumeData[pt.SeriesInstanceUid].volume
}
const fusionData = {
ct,
data: pt,
volumeId: this.volumeData[pt.SeriesInstanceUid].volumeId,
ctVolumeId: this.volumeData[ct.SeriesInstanceUid].volumeId,
ptVolumeId: this.volumeData[pt.SeriesInstanceUid].volumeId,
fusionVolumeId: this.volumeData[`fusion_${pt.SeriesInstanceUid}`].volumeId
}
this.$refs[`viewport-0`][0].setSeriesInfo(ct)
this.$refs[`viewport-1`][0].setSeriesInfo(pt)
this.$refs[`viewport-2`][0].setSeriesInfo(pt)
this.$refs[`viewport-3`][0].setSeriesInfo(pt)
this.$refs[`viewport-fusion-0`][0].setSeriesInfo(ctData)
this.$refs[`viewport-fusion-1`][0].setSeriesInfo(ptData, { colorMap: true })
this.$refs[`viewport-fusion-2`][0].setSeriesInfo(fusionData, { isFusion: true, colorMap: true })
this.$refs[`viewport-fusion-3`][0].setSeriesInfo(ptData, { isMip: true, colorMap: true })
// this.resetAnnotation = false
this.$nextTick(() => {
this.$refs[`colorMap`].init()
})
} catch (err) {
console.log(err)
this.loading = false
this.loadingText = null
}
},
verifyFusionData(ct, pt) {
if (this.fusionSerieId.ct === ct.SeriesInstanceUid && this.fusionSerieId.pt === pt.SeriesInstanceUid && cache.getVolume(this.volumeData[ct.SeriesInstanceUid].volumeId) && cache.getVolume(this.volumeData[pt.SeriesInstanceUid].volumeId) && cache.getVolume(this.volumeData[`fusion_${pt.SeriesInstanceUid}`].volumeId)) {
return true
}
return false
},
async getVolume(serie, isFusion = false) {
return new Promise(async res => {
let volumeId = null; let volume = null
const key = isFusion ? `fusion_${serie.SeriesInstanceUid}` : serie.SeriesInstanceUid
if (!this.volumeData[key] || !cache.getVolume(this.volumeData[key].volumeId)) {
if (serie.Modality === 'PT' && !isFusion) {
serie.ImageIds.forEach(async id => {
const imageLoadObject = cache.getImage(id)
if (imageLoadObject) {
await new Promise(res => {
cache.removeImageLoadObject(id, { force: true }) // 从缓存中删除
res()
})
}
})
}
await this.$refs[`viewport-fusion-0`][0].createImageIdsAndCacheMetaData(serie)
volumeId = `${isFusion ? 'fusion' : serie.Modality}Volume` + ':' + csUtils.uuidv4()
volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: serie.ImageIds })
volume.load()
this.volumeData[key] = {}
this.volumeData[key].volumeId = volumeId
this.volumeData[key].volume = volume
} else {
volumeId = this.volumeData[key].volumeId
volume = this.volumeData[key].volume
}
res({ volumeId, volume })
})
},
upperRangeChange(upper) {
this.$refs.colorMap.upper = upper
this.$refs.colorMap.upperRangeChange(upper)
},
openFusion() {
this.fusionVisible = true
},
closeFusion() {
this.fusionVisible = false
},
getTrialCriterion() {
getCriterionReadingInfo({
TrialId: this.$route.query.trialId,
TrialReadingCriterionId: this.$route.query.TrialReadingCriterionId
})
.then((res) => {
this.trialCriterion = res.Result
})
.catch(() => {})
},
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}/Visit/${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 })
},
openUploadImage(status) {
const idx = this.visitTaskList.findIndex(i => i.IsCurrentTask)
if (idx > -1) {
this.taskId = this.visitTaskList[idx].VisitTaskId
}
this.uploadSubjectCode = localStorage.getItem("subjectCode")
this.uploadSubjectId = localStorage.getItem("subjectId")
this.uploadTrialCriterion = this.trialCriterion
this.uploadStatus = status
this[`${status}ImageVisible`] = true
},
},
beforeDestroy() {
DicomEvent.$off('isCanActiveNoneDicomTool')
DicomEvent.$off('removeNoneDicomMeasureData')
DicomEvent.$off('addNoneDicomMeasureData')
if (this.saveCustomAnnotationTimer) {
clearTimeout(this.saveCustomAnnotationTimer)
this.saveCustomAnnotationTimer = null
}
}
}
</script>
<style lang="scss" scoped>
.read-page-container {
box-sizing: border-box;
height: 100%;
display: flex;
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.left-panel {
display: flex;
width: 200px;
border: 1px solid #727272;
color: #fff;
user-select: none;
::-webkit-scrollbar {
width: 3px;
height: 3px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.task-container {
position: relative;
width: 25px;
overflow-y: auto;
}
.task-info {
position: absolute;
top: 5px;
right: 20px;
transform-origin: right top;
transform: rotate(-90deg);
display: flex;
.task-item {
margin-left: 10px;
white-space: nowrap;
padding: 0px 4px;
border: 1px solid #999999;
border-bottom:none ;
text-align: center;
background-color: #4e4e4e;
color: #d5d5d5;
cursor: pointer;
}
.task-item-active {
background-color: #607d8b;
border: 1px solid #607d8b;
}
}
.study-info {
width: 170px;
border-left: 1px solid #727272;
}
}
.middle-panel {
flex: 1;
border: 1px solid #727272;
margin: 0 5px;
user-select: 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;
.el-dropdown-link {
.svg-icon {
color: #ddd;
font-size: 20px;
}
}
}
.tool-item-active {
background-color: #607d8b;
}
.tool-disabled {
cursor: not-allowed;
}
.tool-frame{
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
// margin-right: 20px;
padding: 1px;
margin: 0 5px;
border: 1px solid #404040;
.icon{
padding: 5px;
border-right: 1px solid #404040;
cursor: pointer;
text-align: center;
.svg-icon{
font-size:20px;
color:#ddd;
}
}
.select-wrapper{
width: 60px;
background-color: black;
color: #ddd;
border: none;
font-size: 13px;
outline: none;
}
.text{
position: relative;
font-size: 12px;
margin-top: 5px;
color: #d0d0d0;
display: none;
}
}
// .icon:hover{
// background-color: #607d8b;
// }
.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;
}
}
.layout-content-ul li:hover{
background-color: #727272;
}
.layout-content ul li{
border-top:1px solid #ddd;
border-left:1px solid #ddd;
}
.layout_flex_row {
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
text-align: center;
margin-bottom: 2px;
}
.layout_flex_column{
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
margin-bottom: 2px;
}
.layout_box_1_1{
flex:1;
line-height: 30px;
font-size: 12px;
text-align: center;
border-bottom:1px solid #ddd;
border-right:1px solid #ddd;
}
.layout_box_1_2{
flex:1;
line-height: 15px;
font-size: 10px;
text-align: center;
border-bottom:1px solid #ddd;
border-right:1px solid #ddd;
}
.divider{
display: block;
height: 1px;
width: 100%;
margin: 10px 0;
}
.el-divider__text{
padding: 0 10px;
font-size: 12px;
width: 80px;
background-color: #383838;
color: #fff;
}
}
.content-wrapper {
flex: 1;
height: calc(100% - 50px);
display: flex;
flex-direction: row;
}
.viewports-wrapper {
flex: 1;
.grid-container {
display: grid;
height: 100%;
width: 100%;
position: relative;
}
.viewports-box {
display: grid;
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 9;
}
.viewports-box-down{
z-index: 1;
}
.grid-cell {
border: 1px dashed #ccc;;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
.cell_active {
border-color: #fafa00!important;
}
.cell-full-screen {
grid-column: 1 / -1;
grid-row: 1 / -1;
z-index: 9;
}
}
.form-wrapper {
width: 400px;
height: 100%;
border: 1px solid #727272;
user-select: none;
}
}
}
.personal_config {
::v-deep .el-tabs__content{
height: 450px;
overflow-y: auto;
}
}
::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;
}
}
::v-deep .cd-dialog-container{
background: #fff !important;
margin-top: 50px !important;
width: 75%;
height: 80%;
.el-dialog__body{
padding: 20px 20px 0 20px;
height: calc(100% - 70px);
}
.el-dialog__header{
position: relative;
}
}
::v-deep .cd-full-dialog-container{
background: #fff !important;
.el-dialog__body{
padding: 10px;
height: calc(100% - 50px) !important;
}
.el-dialog__header{
position: relative;
}
}
::v-deep .el-dropdown-menu{
.el-dropdown-menu__item {
padding: 0 5px;
}
.layout_flex_row {
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
width: 40px;
align-items: center;
text-align: center;
}
.layout_flex_column{
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
}
.layout_box_1_1{
flex:1;
line-height: 30px;
font-size: 12px;
text-align: center;
// border-bottom:1px solid #ddd;
// border-right:1px solid #ddd;
}
.layout_box_1_2{
flex:1;
line-height: 15px;
font-size: 10px;
text-align: center;
// border-bottom:1px solid #ddd;
// border-right:1px solid #ddd;
}
}
}
</style>