Files
irc_web/src/views/trials/trials-panel/reading/dicoms/components/DicomViewer.vue
T
2026-07-27 14:28:55 +08:00

3326 lines
127 KiB
Vue

<template>
<div class="dicom-viewer-wrapper">
<div class="dicom-viewer-container" style="position:relative">
<div class="dicom-tools">
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:layout')}`" placement="bottom">
<div class="tool-wrapper" @click.stop="showPanel($event)" @mouseleave="handleMouseout">
<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="text">{{ $t('trials:reading:button:layout') }}<i class="el-icon-caret-bottom" /></div> -->
<div class="dropdown-content layout-content">
<ul style="width:50px">
<li class="flex_row" @click.stop="changeLayout('A')">
<div class="layout_box_1_1">
A
</div>
</li>
<li class="flex_row" @click.stop="changeLayout('A|A')">
<div class="layout_box_1_1">
A
</div>
<div class="layout_box_1_1">
A
</div>
</li>
<li v-if="isReadingTaskViewInOrder === 1" class="flex_row" @click.stop="changeLayout('A|B')">
<div class="layout_box_1_1">
A
</div>
<div class="layout_box_1_1">
B
</div>
</li>
<li class="flex_column" @click.stop="changeLayout('A|A|A|A')">
<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>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:wwwc')}`" placement="bottom">
<div class="tool-wrapper" @click.stop="showPanel($event)" @mouseleave="handleMouseout">
<div class="dropdown">
<div class="icon" data-tool="Wwwc" :class="[activeTool === 'Wwwc' ? 'tool_active' : '']"
@click.prevent="setToolActive('Wwwc', false)">
<svg-icon icon-class="reverse" class="svg-icon" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<!-- 调窗 -->
<!-- <div class="text">{{ $t('trials:reading:button:wwwc') }}<i class="el-icon-caret-bottom" /></div> -->
<div class="dropdown-content">
<ul style="width:165px;">
<li v-for="item in wwwcArr" :key="item.label">
<a href="#" @click.prevent="setDicomCanvasWwwc(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>
<!-- <div
v-if="item.val === 1"
class="divider"
>
W/L {{ $t('trials:reading:title:preset') }}
</div> -->
<el-divider v-if="item.val === 1" class="divider" content-position="center">{{ `
${$t('trials:reading:title:preset')}` }}</el-divider>
</li>
</ul>
</div>
</div>
</div>
</el-tooltip>
<!-- <div class="tool-wrapper">
<div
class="icon"
data-tool="WwwcRegion"
:class="[activeTool==='WwwcRegion'?'tool_active':'']"
@click.prevent="setToolActive('WwwcRegion',false)"
>
<svg-icon icon-class="wwwcRegion" class="svg-icon" />
</div>
<div class="text">区域调窗</div>
</div> -->
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:reverseColor')}`"
placement="bottom" v-if="AspectRatio >= 1">
<div class="tool-wrapper">
<div class="icon" :class="[activeTool === 'reversecolor' ? 'tool_active' : '']"
@click.prevent="toggleInvert">
<svg-icon icon-class="reversecolor" class="svg-icon" />
</div>
<!-- 反色 -->
<!-- <div class="text">{{ $t('trials:reading:button:reverseColor') }}</div> -->
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:zoom')}`" placement="bottom"
v-if="AspectRatio >= 1">
<div class="tool-wrapper">
<div class="icon" :class="[activeTool === 'Zoom' ? 'tool_active' : '']" data-tool="Zoom"
@click.prevent="setToolActive('Zoom', false)">
<svg-icon icon-class="magnifier" class="svg-icon" />
</div>
<!-- 缩放 -->
<div class="text">{{ $t('trials:reading:button:zoom') }}</div>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:move')}`" placement="bottom"
v-if="AspectRatio >= 1">
<div class="tool-wrapper">
<div class="icon" :class="[activeTool === 'Pan' ? 'tool_active' : '']" data-tool="Pan"
@click.prevent="setToolActive('Pan', false)">
<svg-icon icon-class="move" class="svg-icon" />
</div>
<!-- 移动 -->
<div class="text">{{ $t('trials:reading:button:move') }}</div>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:rotate')}`" placement="bottom">
<div class="tool-wrapper" @click.stop="showPanel($event)" @mouseleave="handleMouseout">
<!-- @click.prevent="setToolActive('Rotate',false)" -->
<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="text">{{ $t('trials:reading:button:rotate') }}<i class="el-icon-caret-bottom" /></div>
<div class="dropdown-content">
<ul style="width:100px;">
<li v-for="rotate in rotateArr" :key="rotate.label" style="text-align:left;">
<a href="#" @click.prevent="setDicomCanvasRotate(rotate.val)">{{ rotate.label }}</a>
</li>
</ul>
</div>
</div>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark"
:content="fitType === 1 ? `${$t('trials:reading:button:fitWindow')}` : `${$t('trials:reading:button:fitImage')}`"
placement="bottom">
<div class="tool-wrapper">
<div class="icon" @click.prevent="fitToType(fitType === 1 ? 'fitToWindow' : 'fitToImage')">
<svg-icon v-if="fitType === 1" icon-class="fitToWindow" class="svg-icon" />
<svg-icon v-else icon-class="fitToImage" class="svg-icon" />
</div>
<div class="text">{{ fitType === 1 ? $t('trials:reading:button:fitWindow') :
$t('trials:reading:button:fitImage')
}}</div>
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:imageIndexSync')}`"
placement="bottom" v-if="AspectRatio >= 1">
<div class="tool-wrapper">
<div class="icon" :class="[isScrollSync ? 'tool_active' : '']" @click.prevent="setImageIndexSync">
<i v-if="isScrollSync" class="el-icon-lock svg-icon" />
<i v-else class="el-icon-unlock svg-icon" />
</div>
<!-- 同步 -->
<div class="text">{{ $t('trials:reading:button:imageIndexSync') }}</div>
</div>
</el-tooltip>
<!--
<div class="tool-wrapper">
<div
class="icon"
@click.prevent="fitToType('fitToImage')"
>
<svg-icon icon-class="fitToImage" class="svg-icon" />
</div>
<div class="text">适应图像</div>
</div> -->
<el-tooltip class="item" effect="dark" :content="`${$t('trials:reading:button:screenShot')}`" placement="bottom"
v-if="AspectRatio >= 1">
<div class="tool-wrapper">
<div class="icon" @click.prevent="saveImage">
<svg-icon icon-class="image" class="svg-icon" />
</div>
<!-- 截屏 -->
<div class="text">{{ $t('trials:reading:button:screenShot') }}</div>
</div>
</el-tooltip>
<el-popover v-model="keySeriesPopoverVisible" placement="bottom" trigger="click"
popper-class="key-series-popper" @show="showKeySeriesPanel">
<ul class="key-series-list">
<li v-if="keyStack && keyStack.taskBlindName" class="key-series-header">
<div class="key-series-header-top">
<div class="key-series-header-title">
<span class="key-series-header-dot"></span>
<div class="key-series-task-name" :title="keyStack.taskBlindName">
{{ keyStack.taskBlindName }}
</div>
</div>
</div>
</li>
<li v-if="keySeriesLoading" class="key-series-empty">
<i class="el-icon-loading"></i>
</li>
<li v-else-if="keySeries.length === 0" class="key-series-empty">
{{ $t('trials:reading:keySeries:noData') }}
</li>
<li v-for="(kf, idx) in keySeries" :key="idx" class="key-series-item"
@click.stop.prevent="jumpToKeySeries(kf)">
<div class="key-series-img">
<img v-if="kf.PicturePath" :src="`${OSSclientConfig.basePath}${kf.PicturePath}`" />
<svg-icon v-else icon-class="image" class="svg-icon default-icon" />
</div>
<div class="key-series-info">
<div v-if="kf.MarkName" class="key-series-meta">
<div class="key-series-label">{{ $t('trials:reading:keySeries:annotationName') }}</div>
<div class="key-series-uid" :title="kf.MarkName">
{{ kf.MarkName }}
</div>
</div>
<div class="key-series-meta">
<div class="key-series-label">{{ $t('trials:reading:keySeries:sopUid') }}</div>
<div class="key-series-uid" :title="kf.SopInstanceUid">
{{ kf.SopInstanceUid }}
</div>
</div>
</div>
<div class="key-series-star">
<svg-icon icon-class="star" class="svg-icon" />
</div>
</li>
</ul>
<div slot="reference" class="tool-wrapper">
<div class="icon" :title="$t('trials:reading:button:keySeries')">
<i class="el-icon-star-off svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:keySeries') }}</div>
</div>
</el-popover>
<div v-for="tool in measuredTools" :key="tool.toolName">
<el-tooltip class="item" effect="dark" placement="bottom">
<div slot="content">
<div v-if="tool.disabledReason">
<h4 style="margin:0;padding-bottom:2px;">{{ tool.text }}</h4>
<br>{{ tool.disabledReason }}
</div>
<div v-else>{{ tool.text }}</div>
</div>
<div class="tool-wrapper">
<div class="icon" :class="[activeTool === tool.toolName ? 'tool_active' : '']"
:style="{ cursor: tool.isDisabled ? 'not-allowed' : 'pointer' }" :data-tool="tool.toolName"
@click.prevent="setToolActive(tool.toolName, true, $event)" @mouseenter="enter($event, tool.toolName)">
<svg-icon :icon-class="tool.icon" class="svg-icon" />
</div>
<div class="text">{{ tool.text }}</div>
</div>
</el-tooltip>
<!-- <div v-else class="tool-wrapper">
<div
class="icon"
:class="[activeTool===tool.toolName?'tool_active':'']"
:style="{cursor:tool.isDisabled?'not-allowed':'pointer'}"
:data-tool="tool.toolName"
@click.prevent="setToolActive(tool.toolName,true,$event)"
@mouseenter="enter($event,tool.toolName)"
>
<svg-icon :icon-class="tool.icon" class="svg-icon" />
</div>
<div class="text">{{ tool.text }}</div>
</div> -->
</div>
<el-tooltip v-if="CriterionType === 2 && studyList.length > 0" class="item" effect="dark"
:content="$t('trials:lugano:button:fusion')" placement="bottom">
<div class="tool-wrapper">
<div class="icon" @click.prevent="handleFusion">
<svg-icon icon-class="fusion" class="svg-icon" />
</div>
<!-- 截屏 -->
<div class="text">{{ $t('trials:lugano:button:fusion') }}</div>
</div>
</el-tooltip>
<div class="tool-frame">
<!-- 第一帧 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:dicom-show:firstframe')" placement="bottom">
<div class="icon" @click.prevent="scrollPage(-99999)">
<svg-icon icon-class="firstframe" class="svg-icon" />
</div>
</el-tooltip>
<!-- 上一帧 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:dicom-show:previousframe')" placement="bottom">
<div class="icon" @click.prevent="scrollPage(-1)">
<svg-icon icon-class="previousframe" class="svg-icon" />
</div>
</el-tooltip>
<!-- 播放/暂停 -->
<el-tooltip class="item" effect="dark"
:content="clipPlaying ? $t('trials:dicom-show:stop') : $t('trials:dicom-show:play')" placement="bottom">
<div v-if="clipPlaying" class="icon" @click.prevent="toggleClipPlay(false)">
<svg-icon icon-class="stop" class="svg-icon" />
</div>
<div v-else class="icon" @click.prevent="toggleClipPlay(true)">
<svg-icon icon-class="play" class="svg-icon" />
</div>
</el-tooltip>
<!-- 下一帧 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:dicom-show:nextframe')" placement="bottom">
<div class="icon" @click.prevent="scrollPage(1)">
<svg-icon icon-class="nextframe" class="svg-icon" />
</div>
</el-tooltip>
<!-- 最后一帧 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:dicom-show:lastframe')" placement="bottom">
<div class="icon" @click.prevent="scrollPage(99999)">
<svg-icon icon-class="lastframe" class="svg-icon" />
</div>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="$t('trials:dicom-show:speed')" placement="bottom">
<select v-model="fps" class="select-wrapper" :disabled="clipPlaying" @change="setDicomCanvasfps($event)">
<!-- 默认值 -->
<!-- <option :value="5">{{ $t('trials:dicom-show:default') }}</option> -->
<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>
</el-tooltip>
</div>
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:reset')" placement="bottom">
<div class="tool-wrapper">
<div class="icon" @click.prevent="resetViewport">
<svg-icon icon-class="refresh" class="svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:reset') }}</div>
</div>
</el-tooltip>
<div :title="$t('trials:reading:button:more')" :class="['tool-item']" @click.stop="showPanel($event)"
@mouseleave="toolMouseout" v-if="AspectRatio < 1">
<div class="dropdown">
<div class="icon" data-tool="more">
<svg-icon icon-class="more" class="svg-icon" style="color:#ddd;font-size: 20px;" />
<i class="el-icon-arrow-down" style="color:#fff;" />
</div>
<div class="dropdown-content">
<!--v-if="readingTaskState < 2"-->
<ul style="width:100px;">
<!--反色-->
<li style="text-align:left;" :class="[activeTool === 'reversecolor' ? 'tool_active' : '']">
<a href="#" @click.prevent="toggleInvert">
<svg-icon icon-class="reversecolor" class="svg-icon" style="margin-right: 5px;" />
{{ $t('trials:reading:button:reverseColor') }}
</a>
</li>
<!--缩放-->
<li style="text-align:left;" :class="[activeTool === 'Zoom' ? 'tool_active' : '']" data-tool="Zoom">
<a href="#" @click.prevent="setToolActive('Zoom', false)">
<svg-icon icon-class="magnifier" class="svg-icon" style="margin-right: 5px;" />
{{ $t('trials:reading:button:zoom') }}
</a>
</li>
<!--移动-->
<li style="text-align:left;" :class="[activeTool === 'Pan' ? 'tool_active' : '']" data-tool="Pan">
<a href="#" @click.prevent="setToolActive('Pan', false)">
<svg-icon icon-class="move" class="svg-icon" style="margin-right: 5px;" />
{{ $t('trials:reading:button:move') }}
</a>
</li>
<!--同步-->
<li style="text-align:left;" :class="[isScrollSync ? 'tool_active' : '']">
<a href="#" @click.prevent="setImageIndexSync">
<i v-if="isScrollSync" class="el-icon-lock svg-icon" style="margin-right: 5px;" />
<i v-else class="el-icon-unlock svg-icon" style="margin-right: 5px;" />
{{ $t('trials:reading:button:imageIndexSync') }}
</a>
</li>
<!--截图-->
<li style="text-align:left;">
<a href="#" @click.prevent="saveImage">
<svg-icon icon-class="image" class="svg-icon" style="margin-right: 5px;" />
{{ $t('trials:reading:button:screenShot') }}
</a>
</li>
</ul>
</div>
</div>
</div>
<el-tooltip v-if="trialCriterion.ImageUploadEnum > 0 && currentReadingTaskState < 2" v-hasPermi="['role:ir']"
class="item" effect="dark" :content="$t('trials:reading:button:upload')" placement="bottom">
<div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('upload')">
<i class="el-icon-upload2 svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:upload') }}</div>
</div>
</el-tooltip>
<el-tooltip v-if="trialCriterion.ImageDownloadEnum > 0" v-hasPermi="[
'role:ir',
'role:mim',
'role:mc',
'role:pm',
'role:apm',
'role:ea',
'role:qa',
]" class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom">
<div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('download')">
<i class="el-icon-download svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:download') }}</div>
</div>
</el-tooltip>
<div style="margin-left:auto;">
<div style="padding:5px;display: flex;">
<!-- 手册 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:handbooks')" placement="bottom"
v-if="(isExistsManual || isHaveKeyFile) && currentReadingTaskState < 2">
<div class="tool-wrapper">
<div class="icon" @click.prevent="previewManuals(false)">
<svg-icon icon-class="constitution" class="svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:handbooks') }}</div>
</div>
</el-tooltip>
<!-- 临床数据 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')"
placement="bottom" v-if="isExistsClinicalData">
<div class="tool-wrapper">
<div class="icon" @click.prevent="previewCD">
<svg-icon icon-class="documentation" class="svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:clinicalData') }}</div>
</div>
</el-tooltip>
<!-- 个性化配置 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:customCfg')" placement="bottom">
<div class="tool-wrapper">
<div class="icon" @click.prevent="previewConfig">
<svg-icon icon-class="individuation" class="svg-icon" />
</div>
<div class="text">{{ $t('trials:reading:button:customCfg') }}</div>
</div>
</el-tooltip>
</div>
</div>
</div>
<div class="dicom-viewers">
<div ref="container" class="viewer-container"
:class="['box', `box_${AspectRatio < 1 ? layoutCol : layoutRow}_${AspectRatio < 1 ? layoutRow : layoutCol}`]">
<div v-for="i in maxCanvas" :key="i" :class="['item', i - 1 === currentDicomCanvasIndex ? 'item_active' : '']"
:data-index="i - 1" :style="i - 1 === currentDicomCanvasIndex ? cornerstoneStyle : {}"
@click="activateDicomCanvas(i - 1)">
<dicom-canvas v-if="canvasW" :ref="`dicomCanvas${i - 1}`"
:style="{ width: fullScreenIndex === i - 1 ? fullScreenWidth : canvasW, height: fullScreenIndex === i - 1 ? fullScreenHeight : canvasH }"
:canvas-index="i - 1" :is-active="i - 1 === currentDicomCanvasIndex" :is-scroll-sync="isScrollSync"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:is-reading-task-view-in-order="isReadingTaskViewInOrder"
:current-dicom-canvas-index="currentDicomCanvasIndex" :custom-wwc-tpl="customWwcTpl"
:data-canvas-uid="`dicomCanvas${i - 1}`" @setCornerstoneStyle="setCornerstoneStyle(i - 1)"
@setMeasureData="setMeasureData" @modifyMeasureData="modifyMeasureData" @scrollSync="scrollSync"
@selectViewCanvas="selectViewCanvas" />
</div>
</div>
<div ref="form-container" :class="{ 'form-container': true, listHide: !listShow }">
<div class="list_btn" @click.stop="clickShow"
:title="listShow ? $t('reading:dicom:dicom-list:hiden') : $t('reading:dicom:dicom-list:show')">
<i :class="listShow ? 'el-icon-s-unfold' : 'el-icon-s-fold'"></i>
</div>
<transition name="listBox">
<div class="form-wrapper" v-show="listShow">
<RecistBMQuestionList v-if="CriterionType === 17" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<IRecistQuestionList v-else-if="CriterionType === 3" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<PCWGQuestionList v-else-if="CriterionType === 10" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<LuganoQuestionList v-else-if="CriterionType === 2" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<LuganoWithoutPETQuestionList v-else-if="CriterionType === 18" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<RecistQuestionList v-else-if="CriterionType === 1" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<IVUSList v-else-if="CriterionType === 19" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<OCTList v-else-if="CriterionType === 20" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<MRIPDFF v-else-if="CriterionType === 21" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<MRIPDFFAdvance v-else-if="CriterionType === 22" ref="measurementList"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-show="isShow" :is-reading-show-subject-info="isReadingShowSubjectInfo"
@handleReadingChart="handleReadingChart" />
<h2 v-else style="color:#ddd">
Developing...
</h2>
<div v-if="iseCRFShowInDicomReading && currentReadingTaskState < 2" v-show="listShow" class="form-footer">
<el-button type="primary" size="small" @click="skipTask">
<!-- 跳过 -->
{{ $t('trials:readingReport:button:skip') }}
</el-button>
<el-button type="primary" size="small" :loading="submitLoading" :disabled="submitLoading"
@click="submit">
<!-- 提交 -->
{{ $t('common:button:submit') }}
</el-button>
</div>
</div>
</transition>
</div>
</div>
<!-- <div v-if="petct.visible" class="petct_wrapper">
<PetCt
:is-reading-show-subject-info="isReadingShowSubjectInfo"
@close="petct.visible = false"
/>
</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">
<CustomWwwcForm :ww="activeCanvasWW" :wc="activeCanvasWC" @close="customWwc.visible = false" @setWwwc="setWwwc" />
</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="activeName" class="personal_config">
<!-- 热键 -->
<el-tab-pane :label="$t('trials:reading:tab:hotkeys')" name="1">
<Hotkeys v-if="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="activeName === '2'" @getWwcTpl="getWwcTpl" />
</el-tab-pane>
<!-- 其他 -->
<el-tab-pane :label="$t('trials:reading:tab:others')" name="3">
<Others v-if="activeName === '3'" :imageToolType="1" />
</el-tab-pane>
</el-tabs>
</el-dialog>
<el-dialog v-if="manualsDialog.visible" :visible.sync="manualsDialog.visible"
:custom-class="isFullscreen ? 'manuals-full-dialog-container' : 'manuals-dialog-container'" :show-close="false"
:close-on-click-modal="false" :fullscreen="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="isFullscreen = !isFullscreen" />
<svg-icon icon-class="close" style="cursor: pointer;" @click="manualsDialog.visible = false" />
</span>
</span>
<div style="height: 100%;margin:0;">
<Manuals :trial-id="trialId" :justKeyDoc="manualsDialog.justKeyDoc" />
<div slot="footer" style="text-align:right;" v-if="openManuals && !ManualsClose">
<!-- 确认 -->
<el-button type="primary" size="mini" :loading="keyDocSubmitting" :disabled="keyDocSubmitting"
@click="handleSubmitKeyDoc">
{{ $t('common:button:confirm') }}</el-button>
</div>
</div>
</el-dialog>
<el-dialog v-if="fusionSeries.visible" :visible.sync="fusionSeries.visible" :close-on-click-modal="false"
:title="$t('trials:lugano:dialogTitle:fusion')" width="850px">
<el-form ref="fusionForm" :model="fusionForm" label-width="120px" label-position="left">
<!-- 任务名称 -->
<el-form-item :label="$t('trials:lugano:fusionDialog:label:taskBlindName')" prop="taskBlindName">
<el-input v-model="fusionForm.taskBlindName" :disabled="true" style="width:200px" />
</el-form-item>
<!-- 检查 -->
<el-form-item :label="$t('trials:lugano:fusionDialog:label:study')" prop="studyID" :rules="[
{ required: true, message: $t('common:ruleMessage:select'), trigger: 'blur' }
]">
<el-select v-model="fusionForm.studyID" clearable @change="handleStudyIdChange">
<el-option v-for="item in studyList" :key="item.StudyId" :label="item.StudyCode" :value="item.StudyId" />
</el-select>
</el-form-item>
<!-- 融合图像 -->
<label style="color: #dfdfdf;line-height: 36px;vertical-align: middle;font-size: 14px;">{{
$t('trials:lugano:fusionDialog:label:fusionImages') }}</label>
<div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom:10px;"
class="series-table">
<div style="border: 1px solid #5e5e5e;border-radius: 5px;padding: 0 10px;">
<!-- CT/MRI序列 -->
<h3 style="color:#ddd">{{ $t('trials:lugano:fusionDialog:label:ctSeries') }}</h3>
<el-table ref="ctSeries" :data="ctSeries" :show-header="false" style="width: 380px" size="mini" height="300"
:default-sort="{ prop: 'instanceCount', order: 'descending' }" @selection-change="handleCTSelectionChange"
@select="handleCTSelect">
<el-table-column type="selection" width="30" />
<el-table-column label="" width="100">
<template slot-scope="scope">
<el-image style="width: 60px; height: 60px" :src="scope.row.previewImageUrl" fit="fit"
crossorigin="anonymous" />
</template>
</el-table-column>
<el-table-column label="" min-width="50" show-overflow-tooltip>
<template slot-scope="scope">
<div>#{{ scope.row.seriesNumber }} </div>
<div>{{ scope.row.modality }}: {{ scope.row.instanceCount }} images T: {{
parseFloat(scope.row.sliceThickness).toFixed(digitPlaces) }}</div>
<div>{{ scope.row.description }}</div>
</template>
</el-table-column>
</el-table>
</div>
<div style="border: 1px solid #5e5e5e;border-radius: 5px;padding: 0 10px;">
<!-- PET序列 -->
<h3 style="color:#ddd">{{ $t('trials:lugano:fusionDialog:label:ptSeries') }}</h3>
<el-table ref="petSeries" :data="petSeries" :show-header="false" style="width: 380px" size="mini"
height="300" @selection-change="handlePTSelectionChange" @select="handlePTSelect">
<el-table-column type="selection" width="30" />
<el-table-column label="" width="100">
<template slot-scope="scope">
<el-image style="width: 60px; height: 60px" :src="scope.row.previewImageUrl" fit="fit"
crossorigin="anonymous" />
</template>
</el-table-column>
<el-table-column label="" min-width="50" show-overflow-tooltip>
<template slot-scope="scope">
<div>#{{ scope.row.seriesNumber }} </div>
<div>{{ scope.row.modality }}: {{ scope.row.instanceCount }} images T: {{
parseFloat(scope.row.sliceThickness).toFixed(digitPlaces) }}</div>
<div>{{ scope.row.description }}</div>
</template>
</el-table-column>
</el-table>
</div>
</div>
<el-form-item style="text-align:right;">
<!-- 取消 -->
<el-button size="mini" @click="fusionSeries.visible = false">{{ $t('common:button:cancel') }}</el-button>
<!-- 确认 -->
<el-button type="primary" size="mini" :loading="fusionSubmitLoading" :disabled="fusionSubmitLoading"
@click="handleSubmitFusionSeries">
{{ $t('common:button:confirm') }}</el-button>
</el-form-item>
</el-form>
</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" :isReading="true" />
<download-dicom-and-nonedicom v-if="downloadImageVisible" :subject-id="uploadSubjectId"
:subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :task-id="taskId"
:visible.sync="downloadImageVisible" :isReading="true" />
<!-- 签名框 -->
<el-dialog v-if="signVisible" :visible.sync="signVisible" :close-on-click-modal="false" width="600px"
custom-class="base-dialog-wrapper">
<div slot="title">
<span style="font-size:18px;">{{ $t('common:dialogTitle:sign') }}</span>
<span style="font-size:12px;margin-left:5px;user-select: text !important;">{{
`(${$t('common:label:sign')}${currentUser})` }}</span>
</div>
<SignForm ref="signForm" :sign-code-enum="signCode" @closeDialog="closeSignDialog" />
</el-dialog>
</div>
</template>
<script>
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneMath from 'cornerstone-math'
import * as cornerstoneTools from 'cornerstone-tools'
import Hammer from 'hammerjs'
cornerstoneTools.external.cornerstone = cornerstone
cornerstoneTools.external.Hammer = Hammer
cornerstoneTools.external.cornerstoneMath = cornerstoneMath
import { getDoctorShortcutKey, getUserWLTemplateList } from '@/api/user'
import { mapGetters } from 'vuex'
import store from '@/store'
import { getToken } from '@/utils/auth'
// import DicomCanvas from './DicomCanvas'
// import PetCt from './Fusion/PetCt'
import DicomCanvas from '@/views/trials/trials-panel/reading/dicoms/components/DicomCanvas.vue'
import RecistQuestionList from './Recist/QuestionList'
import RecistBMQuestionList from './RecistBM/QuestionList'
import IRecistQuestionList from './IRecist/QuestionList'
import PCWGQuestionList from './PCWG/QuestionList'
import LuganoQuestionList from './Lugano/QuestionList'
import LuganoWithoutPETQuestionList from './LuganoWithoutPET/QuestionList'
import IVUSList from './IVUS/QuestionList'
import OCTList from './OCT/QuestionList'
import MRIPDFF from './MRIPDFF/QuestionList'
import MRIPDFFAdvance from './MRIPDFFAdvance/QuestionList'
import CustomWwwcForm from './CustomWwwcForm'
import Manuals from './Manuals'
import Hotkeys from './Hotkeys'
import WL from './WL'
import Others from './Others'
import DicomEvent from './DicomEvent'
import html2canvas from 'html2canvas'
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
import SignForm from '@/views/trials/components/newSignForm'
import { getCriterionReadingInfo, verifyVisitTaskQuestions, submitDicomVisitTask, setReadKeyFile } from '@/api/trials'
import { setSkipReadingCache } from '@/api/reading'
import { getMarkList } from '@/api/reading'
import { getAutoCutNextTask } from '@/api/user'
import const_ from '@/const/sign-code'
import { changeURLStatic } from '@/utils/history.js'
import SystemInfo from "@/utils/systemInfo";
import md5 from 'js-md5'
export default {
name: 'DicomViewer',
components: {
// PetCt,
DicomCanvas,
CustomWwwcForm,
Manuals,
Hotkeys,
WL,
Others,
RecistQuestionList,
PCWGQuestionList,
RecistBMQuestionList,
IRecistQuestionList,
LuganoQuestionList,
LuganoWithoutPETQuestionList,
IVUSList,
OCTList,
MRIPDFF,
MRIPDFFAdvance,
'download-dicom-and-nonedicom': downloadDicomAndNonedicom,
'upload-dicom-and-nonedicom': uploadDicomAndNonedicom,
SignForm
},
props: {
isShow: {
type: Boolean,
required: true
},
isExistsClinicalData: {
type: Boolean,
default: false
},
isExistsNoDicomFile: {
type: Boolean,
default: false
},
isReadingShowSubjectInfo: {
type: Boolean,
required: true
},
isReadingTaskViewInOrder: {
type: Number,
required: true
},
isExistsManual: {
type: Boolean,
required: true
},
isReadKeyFile: {
type: Boolean,
required: true
},
isHaveKeyFile: {
type: Boolean,
required: false
},
iseCRFShowInDicomReading: {
type: Boolean,
required: true
},
questionFormChangeState: {
type: Boolean,
default() {
return false
}
},
questionFormChangeNum: {
type: Number,
default() {
return 0
}
},
studyShow: {
type: Boolean,
default() {
return true
}
}
},
data() {
return {
cornerstoneStyle: {},
layouts: [
{ index: 0, row: 1, col: 1, name: 'A' },
{ index: 1, row: 1, col: 2, name: 'A|A' },
{ index: 2, row: 1, col: 2, name: 'A|B' },
{ index: 3, row: 2, col: 2, name: 'A|A|A|A' }
// { index: 2, row: 2, col: 1 },
// { index: 3, row: 2, col: 2 }
],
rotateArr: [
{ 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 }, // 左转90度
{ label: this.$t('trials:reading:button:rotateTurnRight'), val: 5 }// 右转90度
],
maxCanvas: 1,
layoutRow: 1,
layoutCol: 1,
currentDicomCanvasIndex: 0,
currentDicomCanvas: {
toolState: {
clipPlaying: false
}
},
colormapsList: [],
rotateList: [],
colorList: [],
wwwcList: [],
canvasW: null,
canvasH: null,
activeTool: '',
CriterionType: 0,
measuredTools: [
// 直径测量
{ toolName: 'Length', text: this.$t('trials:reading:button:length'), icon: 'length', isDisabled: false, disabledReason: '' },
// 长短径测量
{ toolName: 'Bidirectional', text: this.$t('trials:reading:button:bidirectional'), icon: 'bidirection', isDisabled: false, disabledReason: '' },
// 矩形工具
{ toolName: 'RectangleRoi', text: this.$t('trials:reading:button:rectangle'), icon: 'rectangle', isDisabled: false, disabledReason: '' },
// 箭头工具
{ toolName: 'ArrowAnnotate', text: this.$t('trials:reading:button:arrowAnnotate'), icon: 'arrow', isDisabled: false, disabledReason: '' }
],
fitType: 0,
isDisabledTool: false,
canvasObj: {},
wwwcArr: [],
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 }
],
activeSeries: {},
seriesStack: [],
trialId: '',
isScrollSync: false,
imageIndexSync: {
sourceCanvas: '',
targetCanvas: []
},
isFirstRender: false,
customWwc: { visible: false, title: null }, // 自定义调窗
personalConfigDialog: { visible: false, title: this.$t('trials:reading:button:customCfg') }, // 个性化配置
layout: '',
isFirstNotLinked: false,
hotKeyList: [],
activeName: '1',
customWwcTpl: [],
isFullscreen: false,
manualsDialog: { visible: false, justKeyDoc: false },
petct: { visible: false },
fusionSeries: { visible: false },
fusionForm: {
studyID: '',
visitTaskId: '',
readingTaskState: 2,
taskBlindName: '',
subjectCode: '',
isBaseLineTask: null,
isCurrentTask: null
},
ctSeriesInfo: {},
petSeriesInfo: {},
ctSeries: [],
petSeries: [],
petctWindow: null,
studyList: [],
digitPlaces: 2,
activeCanvasWW: null,
activeCanvasWC: null,
activeTaskInfo: {},
clipPlaying: false,
fps: 15,
trialCriterion: {},
// 上传
downloadImageVisible: false,
uploadImageVisible: false,
uploadSubjectId: null,
uploadSubjectCode: null,
uploadTrialCriterion: {},
uploadStatus: 'upload',
taskId: '',
keySeries: [],
keyStack: null,
keySeriesPopoverVisible: false,
keySeriesLoading: false,
signVisible: false,
signCode: null,
currentUser: zzSessionStorage.getItem('userName'),
tmpData: null,
submitLoading: false,
fusionSubmitLoading: false,
keyDocSubmitting: false,
AspectRatio: 1,
listShow: true,
fullScreenIndex: -1,
fullScreenWidth: window.innerWidth - 570 + 'px',
fullScreenHeight: window.innerHeight - 130 + 'px',
ManualsClose: false
}
},
computed: {
...mapGetters(['visitTaskList', 'currentReadingTaskState', 'lastCanvasTaskId', 'imageQualityIssues']),
openManuals() {
return !this.isReadKeyFile && this.isHaveKeyFile
}
},
watch: {
openManuals: {
handler() {
if (this.openManuals) this.previewManuals(true)
},
immediate: true
},
currentReadingTaskState: {
immediate: true,
handler(val) {
if (val && this.currentDicomCanvasIndex && this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`]) {
this.canvasObj[this.currentDicomCanvasIndex].readingTaskState = val
if (this.activeTool) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(this.activeTool)
this.activeTool = ''
}
}
}
},
lastCanvasTaskId: {
immediate: true,
handler(v) {
// if (v) {
// console.log('lastCanvasTaskId')
// const i = this.visitTaskList.findIndex(i => i.VisitTaskId === v)
// if (i < 0) return
// var studyList = this.visitTaskList[i].StudyList || []
// studyList = studyList.filter(i => !i.IsCriticalSequence && i.Modalities.indexOf('CT') !== -1 && i.Modalities.indexOf('PT') !== -1)
// if (studyList.length === 0) return
// this.studyList = studyList
// } else {
// this.studyList = []
// }
}
},
activeSeries: {
immediate: true,
handler(v) {
// console.log('activeSeries', v)
if (v && v.isCurrentTask && this.studyList.length === 0) {
this.activeTaskInfo.taskName = v.taskBlindName
this.activeTaskInfo.visitTaskId = v.visitTaskId
const i = this.visitTaskList.findIndex(i => i.VisitTaskId === v.visitTaskId)
if (i < 0) return
this.activeTaskInfo.visitTaskIndex = i
var studyList = this.visitTaskList[i].StudyList || []
studyList = studyList.filter(i => !i.IsCriticalSequence && (i.Modalities.indexOf('CT') !== -1 || i.Modalities.indexOf('MR') !== -1) && i.Modalities.indexOf('PT') !== -1)
if (studyList.length === 0) return
this.studyList = studyList
}
}
},
imageQualityIssues: {
immediate: true,
handler(v) { }
},
currentDicomCanvasIndex: {
immediate: true,
handler(v) {
if (this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`] && this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0]) {
this.clipPlaying = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toolState.clipPlaying
} else {
this.clipPlaying = false
this.fps = 15
}
}
}
},
mounted() {
console.log(this.iseCRFShowInDicomReading, this.currentReadingTaskState, this.listShow)
this.getHotKeys()
this.getWwcTpl()
this.getTrialCriterion()
// cornerstone.imageCache.setMaximumSizeBytes(0)
// const maximumSizeInBytes = 1024 * 1024 * 1024 // 1 GB
// // const maximumSizeInBytes = 1024 * 1024 * 500
// cornerstone.imageCache.setMaximumSizeBytes(maximumSizeInBytes)
this.customWwc = { visible: false, title: this.$t('trials:reading:dagTitle:wwwcCustom') }
this.CriterionType = parseInt(localStorage.getItem('CriterionType'))
if (this.CriterionType === 10) {
this.measuredTools = [{ toolName: 'ArrowAnnotate', text: this.$t('trials:reading:button:arrowAnnotate'), icon: 'arrow', isDisabled: false, disabledReason: '' }, { toolName: 'RectangleRoi', text: this.$t('trials:reading:button:rectangle'), icon: 'rectangle', isDisabled: false, disabledReason: '' }]
} else if (this.CriterionType === 17) {
this.measuredTools = [{ toolName: 'Length', text: this.$t('trials:reading:button:length'), icon: 'length', isDisabled: false, disabledReason: '' }, { toolName: 'RectangleRoi', text: this.$t('trials:reading:button:rectangle'), icon: 'rectangle', isDisabled: false, disabledReason: '' }, { toolName: 'ArrowAnnotate', text: this.$t('trials:reading:button:arrowAnnotate'), icon: 'arrow', isDisabled: false, disabledReason: '' }]
} else if (this.CriterionType === 19) {
this.measuredTools = []
} else if (this.CriterionType === 20) {
this.measuredTools = []
} else if (this.CriterionType === 21) {
this.measuredTools = [{
toolName: 'Probe', text: this.$t('trials:reading:button:circle'), icon: 'oval', isDisabled: false, disabledReason: ''
}]
} else if (this.CriterionType === 22) {
this.measuredTools = [{
toolName: 'Probe', text: this.$t('trials:reading:button:circle'), icon: 'oval', isDisabled: false, disabledReason: ''
}]
}
this.rotateList[0] = '1'
this.colorList[0] = ''
this.wwwcList[0] = '1'
this.colormapsList = cornerstone.colors.getColormapsList()
this.currentDicomCanvas = this.$refs['dicomCanvas0'] ? this.$refs['dicomCanvas0'][0] : ''
this.trialId = this.$router.currentRoute.query.trialId
var digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.setCanvasStyle()
window.addEventListener('resize', this.setCanvasStyle)
DicomEvent.$on('updateImage', (instanceId) => {
for (let i = 0; i < this.maxCanvas; i++) {
var stack = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].stack
if (stack.studyId) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].updateImage(instanceId)
}
}
})
DicomEvent.$on('getMeasureData', () => {
for (let i = 0; i < this.maxCanvas; i++) {
var stack = this.$refs[`dicomCanvas${i}`][0].stack
if (stack.visitTaskId) {
this.$refs[`dicomCanvas${i}`][0].getMeasureData()
}
}
console.log('getMeasureData')
})
DicomEvent.$on('getScreenshots', async (measuredData, callback) => {
if (this.currentDicomCanvasIndex <= -1 || typeof callback !== 'function') return
const shouldRelocate = this.shouldRelocateBeforeScreenshot(measuredData)
if (shouldRelocate) {
await this.imageLocation(measuredData)
}
if (!measuredData || !measuredData.isMarked) {
callback()
return
}
const base64Str = await this.captureActiveViewportScreenshot()
callback(base64Str)
})
DicomEvent.$on('imageLocation', async (measuredData) => {
return new Promise(async resolve => {
if (!measuredData) return
await this.imageLocation(measuredData)
resolve()
})
})
// DicomEvent.$on('imageLocation', async(measuredData, callback) => {
// if (measuredData) {
// await this.imageLocation(measuredData)
// }
// console.log('imageLocation')
// this.$nextTick(() => {
// if (callback) {
// callback()
// }
// })
// })
// DicomEvent.$on('imageLocation', measuredData => {
// if (!measuredData) return
// this.imageLocation(measuredData)
// console.log('imageLocation')
// })
DicomEvent.$on('setReadingState', readingTaskState => {
this.canvasObj[this.currentDicomCanvasIndex].readingTaskState = readingTaskState
if (this.activeTool) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(this.activeTool)
this.activeTool = ''
}
})
DicomEvent.$on('PGWC3Active', res => {
this.setToolActive('ArrowAnnotate', true, null, 'open')
})
DicomEvent.$on('loadImageStacks', seriesList => {
this.loadImageStacks(seriesList)
console.log('loadImageStacks')
})
DicomEvent.$on('loadLinkedImageStack', seriesInfo => {
this.loadLinkedImageStack(seriesInfo)
console.log('loadLinkedImageStack')
})
DicomEvent.$on('setMeasuredToolsPassive', () => {
this.setMeasuredToolsPassive()
})
DicomEvent.$on('readingPageUpdate', (data) => {
if (!this.petctWindow) return
this.petctWindow.postMessage({ type: 'readingPageUpdate', data: data }, window.location)
})
DicomEvent.$on('resetPage', () => {
if (!this.petctWindow) return
this.petctWindow.postMessage({ type: 'resetPage' }, window.location)
})
DicomEvent.$on('setReadingState', (data) => {
if (!this.petctWindow) return
this.petctWindow.postMessage({ type: 'setReadingState', data: data }, window.location)
})
DicomEvent.$on('closePetct', () => {
if (this.petctWindow) {
this.petctWindow.close()
}
})
DicomEvent.$on('addAnnotation', async obj => {
this.tmpData = Object.assign({}, obj.question)
// await this.imageLocation(obj.locateInfo)
if (this.CriterionType === 21) {
this.setToolActive('Probe', true, null, 'tableQuestion')
} else if (this.CriterionType === 22) {
this.setToolActive('Probe', true, null, 'tableQuestion')
}
})
window.addEventListener('beforeunload', () => {
if (this.petctWindow) {
this.petctWindow.close()
}
})
let windowWidth = document.documentElement.clientWidth;
let windowHeight = document.documentElement.clientHeight;
this.AspectRatio = windowWidth / windowHeight
window.onresize = () => {
let windowWidth = document.documentElement.clientWidth;
let windowHeight = document.documentElement.clientHeight;
this.AspectRatio = windowWidth / windowHeight
};
this.getSystemInfoReading()
},
beforeDestroy() {
DicomEvent.$off('updateImage')
DicomEvent.$off('getMeasureData')
DicomEvent.$off('imageLocation')
DicomEvent.$off('setReadingState')
DicomEvent.$off('loadImageStacks')
DicomEvent.$off('loadLinkedImageStack')
DicomEvent.$off('setMeasuredToolsPassive')
DicomEvent.$off('readingPageUpdate')
DicomEvent.$off('setReadingState')
DicomEvent.$off('reload')
DicomEvent.$off('closePetct')
if (this.petctWindow) {
this.petctWindow.close()
}
window.removeEventListener('beforeunload', () => {
if (this.petctWindow) {
this.petctWindow.close()
}
})
},
methods: {
handleReadingChart(e) {
this.$emit('handleReadingChart', e)
},
async handleSubmitKeyDoc() {
if (this.keyDocSubmitting) return
this.keyDocSubmitting = true
try {
let data = {
TrialCriterionId: this.$router.currentRoute.query.TrialReadingCriterionId,
}
let res = await setReadKeyFile(data)
if (res.IsSuccess) {
this.manualsDialog.visible = false
this.ManualsClose = true
}
} catch (err) {
console.log(err)
} finally {
this.keyDocSubmitting = false
}
},
showPanel(e, toolName) {
if (toolName === 'layout') return false
e.currentTarget.firstChild.lastChild.style.display = 'block'
},
toolMouseout(e) {
e.currentTarget.firstChild.lastChild.style.display = 'none'
},
clickShow() {
this.listShow = !this.listShow;
this.changeLayout()
},
getTrialCriterion() {
getCriterionReadingInfo({
TrialId: this.trialId || this.$route.query.trialId,
TrialReadingCriterionId: this.trialReadingCriterionId || this.$route.query.TrialReadingCriterionId
})
.then((res) => {
this.trialCriterion = res.Result
})
.catch(() => { })
},
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
},
async getWwcTpl() {
// const loading = this.$loading({ fullscreen: true })
try {
const res = await getUserWLTemplateList()
this.customWwcTpl = []
res.Result.map(i => {
this.customWwcTpl.push({ label: i.TemplateName, wc: i.WL, ww: i.WW })
})
this.wwwcArr = [...this.defaultWwwc, ...this.customWwcTpl]
} catch (e) {
console.log(e)
}
},
async getHotKeys() {
// const loading = this.$loading({ fullscreen: true })
try {
const res = await getDoctorShortcutKey({ imageToolType: 1 })
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)
}
},
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 })
})
// const loading = this.$loading({ fullscreen: true })
// getDoctorShortcutKey({ imageToolType: 0 }).then(res => {
// 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 })
// })
// loading.close()
// }).catch(() => {
// loading.close()
// })
},
bindHotKey() {
var container = this.$refs['container']
// window.addEventListener
container.addEventListener('keydown', event => {
// console.log(event)
event.preventDefault()
var 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
var shortcutKeyEnum = this.hotKeyList[idx].shortcutKeyEnum
if (shortcutKeyEnum === 1) {
// 前一图像视口
this.selectViewCanvas(-1)
} else if (shortcutKeyEnum === 2) {
// 后一图像视口
this.selectViewCanvas(1)
} else if (shortcutKeyEnum === 3) {
// 上一个序列
const series = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].series
DicomEvent.$emit('selectSeries', { seriesId: series.seriesId, studyId: series.studyId, offset: -1 })
} else if (shortcutKeyEnum === 4) {
// 下一个序列
const series = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].series
DicomEvent.$emit('selectSeries', { seriesId: series.seriesId, studyId: series.studyId, offset: 1 })
} else if (shortcutKeyEnum === 5) {
// 上一张图像
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].scrollPage(-1)
} else if (shortcutKeyEnum === 6) {
// 下一张图像
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].scrollPage(1)
} else if (shortcutKeyEnum === 7) {
// 向左旋转
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setRotate(false, false, -90, 4)
} else if (shortcutKeyEnum === 8) {
// 向右旋转
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setRotate(false, false, 90, 5)
} else if (shortcutKeyEnum === 9) {
// 水平翻转
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setRotate(true, false, 0, 3)
} else if (shortcutKeyEnum === 10) {
// 垂直翻转
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setRotate(false, true, 0, 2)
} else if (shortcutKeyEnum === 11) {
// 放大
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setZoomScale(1)
} else if (shortcutKeyEnum === 12) {
// 缩小
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setZoomScale(0)
} else if (shortcutKeyEnum === 13) {
// 适应图像
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].fitToImage()
} else if (shortcutKeyEnum === 14) {
// 适应窗口
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].fitToWindow()
} else if (shortcutKeyEnum === 15) {
// 截图
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].saveImage()
} else if (shortcutKeyEnum === 16) {
// 反色
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleInvert()
} else if (shortcutKeyEnum === 17) {
// 窗宽/窗位
var wwwcArrIdx = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].wwwcArrIdx
var wwwcArr = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].wwwcArr
if (wwwcArrIdx + 1 === wwwcArr.length) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].wwwcArrIdx = 0
} else {
++this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].wwwcArrIdx
}
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setWwwc(wwwcArr[wwwcArrIdx].ww, wwwcArr[wwwcArrIdx].wc)
} else if (shortcutKeyEnum === 18) {
// 重置
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetViewport()
} else if (shortcutKeyEnum === 19) {
// 显示或隐藏标记
if (this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].readingTaskState < 2) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setMeasureDataVisible()
}
}
})
},
resetViewport() {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetViewport()
},
getKeySeriesVisitTaskId() {
const currentCanvas = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`]
if (currentCanvas && currentCanvas[0] && currentCanvas[0].stack && currentCanvas[0].stack.visitTaskId) {
return currentCanvas[0].stack.visitTaskId
}
return this.activeSeries && this.activeSeries.visitTaskId ? this.activeSeries.visitTaskId : ''
},
async showKeySeriesPanel() {
this.keySeries = []
const currentCanvas = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`]
this.keyStack = currentCanvas && currentCanvas[0] ? currentCanvas[0].stack : null
const visitTaskId = this.keyStack?.visitTaskId
if (!visitTaskId) return
this.keySeriesLoading = true
try {
const res = await getMarkList({ visitTaskId })
if (res && res.Result) {
this.keySeries = res.Result
}
} catch (err) {
console.error(err)
} finally {
this.keySeriesLoading = false
}
},
jumpToKeySeries(kf) {
// this.keySeriesPopoverVisible = false
const visitTaskId = this.keyStack?.visitTaskId
if (!visitTaskId) return
// let annotation = {}
// try {
// annotation = kf.MeasureData ? JSON.parse(kf.MeasureData) : {}
// } catch (err) {
// console.error(err)
// }
const series = this.getSeriesInfoByMark(visitTaskId, { lesionName: kf.MarkName })
if (!series) return
this.loadImageStack(series)
},
previewConfig() {
this.activeName = '1'
this.personalConfigDialog.visible = true
},
setCornerstoneStyle(i) {
if (this.layoutCol === 1 && this.layoutRow === 1) {
this.fullScreenIndex = -1
return
}
if (this.cornerstoneStyle.position) {
this.cornerstoneStyle = {}
this.setCanvasStyle()
this.fullScreenIndex = -1
} else {
this.cornerstoneStyle = {
position: 'absolute',
top: '67px',
left: '0px',
right: '350px',
zIndex: 10,
}
this.fullScreenIndex = this.currentDicomCanvasIndex
let w = 570
if (!this.studyShow) w -= 170
if (!this.listShow) w -= 330
this.fullScreenWidth = window.innerWidth - w + 'px'
}
this.$nextTick(() => {
for (var i = 0; i < this.maxCanvas; i++) {
this.$refs[`dicomCanvas${i}`][0].reloadCanvas()
}
})
},
loadImageStacks(seriesList) {
if (seriesList.length === 0) {
const idx = this.visitTaskList.findIndex(i => i.IsCurrentTask)
if (idx > -1) {
store.dispatch('reading/setLastCanvasTaskId', this.visitTaskList[idx].VisitTaskId)
}
return
}
var l = seriesList.length > 1 ? 'A|B' : 'A'
this.layout = l
var i = this.layouts.findIndex(i => i.name === l)
var layout = this.layouts[i]
this.layoutRow = layout.row
this.layoutCol = layout.col
this.setCanvasStyle()
this.maxCanvas = layout.row * layout.col
this.$nextTick(() => {
const elements = document.querySelectorAll('.cornerstone-element')
Array.from(elements).forEach((element) => {
cornerstone.enable(element)
cornerstone.resize(element)
})
seriesList.map((series, index) => {
this.canvasObj[index] = series
if (series.isCurrentTask) {
this.currentDicomCanvasIndex = index
this.activeSeries = series
// this.$refs['measurementList'].initPage(series)
store.dispatch('reading/setActiveSeries', series)
store.dispatch('reading/setLastCanvasTaskId', series.visitTaskId)
}
const s = Object.assign({}, series)
this.$refs[`dicomCanvas${index}`][0].loadImageStack(s)
if (this.activeTool) {
if (series.isCurrentTask && series.readingTaskState < 2) {
this.$nextTick(() => {
this.$refs[`dicomCanvas${index}`][0].setToolPassive(this.activeTool)
})
} else {
this.$nextTick(() => {
this.$refs[`dicomCanvas${index}`][0].setToolEnabled(this.activeTool)
})
}
this.activeTool = ''
}
})
})
},
loadImageStack(dicomSeries) {
this.clipPlaying = false
this.fps = 15
this.canvasObj[this.currentDicomCanvasIndex] = dicomSeries
this.$nextTick(() => {
this.activeSeries = dicomSeries
const s = Object.assign({}, dicomSeries)
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].loadImageStack(s).then(res => {
if (this.activeTool) {
if (dicomSeries.isCurrentTask && dicomSeries.readingTaskState < 2) {
this.$nextTick(() => {
this.measuredTools.forEach(item => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(item.toolName)
})
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolActive(this.activeTool)
})
} else {
this.$nextTick(() => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(this.activeTool)
})
this.activeTool = ''
}
// this.activeTool = ''
}
})
// this.$refs['measurementList'].initPage(dicomSeries)
store.dispatch('reading/setActiveSeries', dicomSeries)
if (this.currentDicomCanvasIndex === this.maxCanvas - 1) {
store.dispatch('reading/setLastCanvasTaskId', dicomSeries.visitTaskId)
}
})
},
loadLinkedImageStack(dicomSeries) {
this.currentDicomCanvasIndex = dicomSeries.canvasIndex
var canvasIndex = dicomSeries.canvasIndex
this.canvasObj[canvasIndex] = dicomSeries
for (let i = 0; i < this.maxCanvas; i++) {
if (this.activeTool) {
var series = null
if (canvasIndex === i) {
series = dicomSeries
} else {
series = this.$refs[`dicomCanvas${i}`][0].stack
}
if (series.isCurrentTask && series.readingTaskState < 2) {
this.$refs[`dicomCanvas${i}`][0].setToolPassive(this.activeTool)
} else {
this.$refs[`dicomCanvas${i}`][0].setToolEnabled(this.activeTool)
}
}
}
this.activeTool = ''
// if (this.activeTool) {
// if (dicomSeries.isCurrentTask && dicomSeries.readingTaskState < 2) {
// this.$nextTick(() => {
// this.$refs[`dicomCanvas${canvasIndex}`][0].setToolPassive(this.activeTool)
// })
// } else {
// this.$nextTick(() => {
// this.$refs[`dicomCanvas${canvasIndex}`][0].setToolEnabled(this.activeTool)
// })
// }
// this.activeTool = ''
// }
this.$nextTick(() => {
this.activeSeries = dicomSeries
const s = Object.assign({}, dicomSeries)
this.$refs[`dicomCanvas${canvasIndex}`][0].loadImageStack(s)
store.dispatch('reading/setActiveSeries', dicomSeries)
if (this.currentDicomCanvasIndex === this.maxCanvas - 1) {
store.dispatch('reading/setLastCanvasTaskId', dicomSeries.visitTaskId)
}
})
},
// 给显示的画布设置显示的序列信息
async getSeriesShowInCanvas(layout) {
var activeSeries = Object.assign({}, this.activeSeries)
const loading = this.$loading({ fullscreen: true })
if (layout.name === 'A') {
// 判断当前激活窗口是否有序列信息
if (activeSeries) {
this.seriesStack = [activeSeries]
this.currentDicomCanvasIndex = 0
store.dispatch('reading/setLastCanvasTaskId', activeSeries.visitTaskId)
await this.setCanvas(this.seriesStack)
loading.close()
}
} else if (layout.name === 'A|A') {
if (activeSeries) {
this.seriesStack = [activeSeries, activeSeries]
this.currentDicomCanvasIndex = 1
store.dispatch('reading/setLastCanvasTaskId', activeSeries.visitTaskId)
await this.setCanvas(this.seriesStack)
loading.close()
}
} else if (layout.name === 'A|B') {
if (activeSeries) {
const idx = this.visitTaskList.findIndex(i => i.VisitTaskId === activeSeries.visitTaskId)
const visitTaskNum = idx > -1 ? this.visitTaskList[idx].VisitTaskNum : -1
if (visitTaskNum > 0 && this.visitTaskList.length > 1) {
var visitTaskId = ''
if (this.CriterionType === 10) {
const i = this.visitTaskList.findIndex(i => i.VisitTaskNum === visitTaskNum - 1)
visitTaskId = this.visitTaskList[i].VisitTaskId
} else {
const i = this.visitTaskList.findIndex(i => i.IsBaseLineTask)
visitTaskId = this.visitTaskList[i].VisitTaskId
}
this.getFirstSeries(activeSeries, visitTaskId).then(async baseSerires => {
console.log(activeSeries)
this.seriesStack = [baseSerires, activeSeries]
this.currentDicomCanvasIndex = 1
store.dispatch('reading/setLastCanvasTaskId', activeSeries.visitTaskId)
await this.setCanvas(this.seriesStack)
loading.close()
})
} else {
this.seriesStack = [activeSeries, activeSeries]
this.currentDicomCanvasIndex = 1
store.dispatch('reading/setLastCanvasTaskId', activeSeries.visitTaskId)
await this.setCanvas(this.seriesStack)
loading.close()
}
}
} else if (layout.name === 'A|A|A|A') {
if (activeSeries) {
this.seriesStack = [activeSeries, activeSeries, activeSeries, activeSeries]
this.currentDicomCanvasIndex = 3
store.dispatch('reading/setLastCanvasTaskId', activeSeries.visitTaskId)
await this.setCanvas(this.seriesStack)
loading.close()
}
}
},
// 设置画布序列信息
setCanvas(seriesStack) {
return new Promise(resolve => {
var promiseArr = []
for (let i = 0; i < this.maxCanvas && i < seriesStack.length; i++) {
this.canvasObj[i] = seriesStack[i]
const s = Object.assign({}, seriesStack[i])
promiseArr.push(this.$refs[`dicomCanvas${i}`][0].loadImageStack(s))
}
Promise.all(promiseArr).then(() => {
// this.activateDicomCanvas(this.currentDicomCanvasIndex)
resolve()
}).catch(() => { resolve() })
})
},
// 根据病灶标识定位图像
// 最后一个窗口显示当前访视下病灶所在的影像序列
// 其他窗口显示该病灶第一次出现的序列信息
imageLocation(obj) {
return new Promise(async resolve => {
var loading = null
var loadImagePromises = []
var isScrollSync = this.isScrollSync
if (this.isScrollSync) {
// 取消滚动同步
this.isScrollSync = false
}
var activeCanvasTaskId = obj.visitTaskId
var index = this.visitTaskList.findIndex(i => i.VisitTaskId === activeCanvasTaskId)
if (index === -1) {
resolve()
return
}
var firstAddSeries = null
var currentAddSeries = null
if (this.isReadingTaskViewInOrder === 1) {
// 有序
// 获取病灶第一次出现的访视序列
var firstAddVisitTaskId = null
var idx = this.visitTaskList[index].MeasureData.findIndex(item => {
return item.OrderMarkName === obj.lesionName
})
if (idx > -1) {
firstAddVisitTaskId = this.visitTaskList[index].MeasureData[idx].FristAddTaskId
}
if (firstAddVisitTaskId) {
var taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === firstAddVisitTaskId)
if (taskIdx > -1 && !this.visitTaskList[taskIdx].IsInit) {
loading = this.$loading({ fullscreen: true })
if (!this.visitTaskList[taskIdx].measureDataInit) {
await store.dispatch('reading/getMeasuredData', this.visitTaskList[taskIdx].VisitTaskId)
}
if (!this.visitTaskList[taskIdx].studyListInit) {
await store.dispatch('reading/getStudyInfo', { trialId: this.trialId, subjectVisitId: this.visitTaskList[taskIdx].VisitId, visitTaskId: this.visitTaskList[taskIdx].VisitTaskId, taskBlindName: this.visitTaskList[taskIdx].TaskBlindName })
}
if (!this.visitTaskList[taskIdx].readingQuestionsInit) {
await store.dispatch('reading/getReadingQuestionAndAnswer', { trialId: this.trialId, visitTaskId: this.visitTaskList[taskIdx].VisitTaskId })
}
if (!this.visitTaskList[taskIdx].questionsInit) {
await store.dispatch('reading/getDicomReadingQuestionAnswer', { trialId: this.trialId, visitTaskId: this.visitTaskList[taskIdx].VisitTaskId })
}
await store.dispatch('reading/setStatus', { visitTaskId: this.visitTaskList[taskIdx].VisitTaskId })
}
if (this.CriterionType === 3 && obj.lesionType === 7 && obj.fromMark) {
const measureDataInfo = Object.assign({}, obj)
measureDataInfo.lesionName = obj.fromMark
firstAddSeries = this.getSeriesInfoByMark(firstAddVisitTaskId, measureDataInfo)
} else {
firstAddSeries = this.getSeriesInfoByMark(firstAddVisitTaskId, obj)
}
}
// 获取当前访视病灶所在的序列
currentAddSeries = this.getSeriesInfoByMark(activeCanvasTaskId, obj)
if (!firstAddSeries && currentAddSeries) {
// 存在首次出现的病灶及当前病灶
firstAddSeries = currentAddSeries
} else if (!firstAddSeries && !currentAddSeries) {
// 当前新病灶,且未画标记则显示当前的序列信息,不做处理
loading && loading.close()
this.isScrollSync = isScrollSync
this.setToolToTarget(obj)
resolve()
return
} else if (firstAddSeries && !currentAddSeries) {
// 当前访视序列与首次出现病灶的序列对齐
currentAddSeries = this.getLinkedSeries(firstAddSeries, activeCanvasTaskId)
if (!currentAddSeries) {
// 未找到对齐的,则就显示当前最后一个窗口现实的序列信息
currentAddSeries = this.$refs[`dicomCanvas${this.maxCanvas - 1}`][0].series
}
}
} else {
// 无序
currentAddSeries = this.getSeriesInfoByMark(activeCanvasTaskId, obj)
if (!currentAddSeries) {
// 未找到对齐的,则就显示当前最后一个窗口现实的序列信息
currentAddSeries = this.$refs[`dicomCanvas${this.maxCanvas - 1}`][0].series
}
firstAddSeries = currentAddSeries
}
for (let i = 0; i < this.maxCanvas; i++) {
if (i === this.maxCanvas - 1) {
const s = Object.assign({}, currentAddSeries)
loadImagePromises.push(this.$refs[`dicomCanvas${i}`][0].loadImageStack(s))
this.currentDicomCanvasIndex = i
this.activeSeries = currentAddSeries
store.dispatch('reading/setActiveSeries', currentAddSeries)
} else {
const s = Object.assign({}, firstAddSeries)
loadImagePromises.push(this.$refs[`dicomCanvas${i}`][0].loadImageStack(s))
}
}
Promise.all(loadImagePromises)
.then(() => {
this.isScrollSync = isScrollSync
this.setToolToTarget(obj)
resolve()
}).catch(() => {
resolve()
})
if (loading) {
loading.close()
}
})
},
shouldRelocateBeforeScreenshot(measuredData) {
if (!measuredData) return false
const currentCanvas = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`] && this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0]
if (!currentCanvas || !currentCanvas.stack) return true
const currentStack = currentCanvas.stack
if (currentStack.visitTaskId !== measuredData.visitTaskId) return true
// 当前序列与病灶目标序列一致时,避免重复 imageLocation 引发的重载等待
const targetSeries = this.getSeriesInfoByMark(measuredData.visitTaskId, measuredData)
if (!targetSeries) return false
if (currentStack.seriesId !== targetSeries.seriesId) return true
if (typeof targetSeries.imageIdIndex === 'number' &&
targetSeries.imageIdIndex !== currentStack.currentImageIdIndex) {
return true
}
return false
},
async captureActiveViewportScreenshot() {
// const currentCanvas = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`] && this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0]
await this.$nextTick()
const divForDownloadViewport = document.querySelector(
`div[data-canvas-uid="dicomCanvas${this.currentDicomCanvasIndex}"]`
)
if (divForDownloadViewport) {
try {
const canvas = await html2canvas(divForDownloadViewport, {
logging: false,
useCORS: true,
scale: 1
})
return canvas.toDataURL('image/png')
} catch (e) {
console.log(e)
}
}
return ''
},
setToolToTarget(obj) {
if (obj.readingTaskState < 2 && obj.markTool && !obj.isMarked) {
if (this.activeTool) {
this.measuredTools.forEach(item => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(item.toolName)
})
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(this.activeTool)
}
this.activeTool = obj.markTool
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolActive(obj.markTool)
} else {
if (this.activeTool) {
this.measuredTools.forEach(item => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(item.toolName)
})
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(this.activeTool)
this.activeTool = ''
}
}
},
setToolPassive() {
if (this.activeTool) {
this.measuredTools.forEach(item => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(item.toolName)
})
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(this.activeTool)
this.activeTool = ''
}
},
selectViewCanvas(offset) {
var canvasIndex = this.currentDicomCanvasIndex + offset
if (canvasIndex < 0 || canvasIndex >= this.maxCanvas) return
this.activateDicomCanvas(canvasIndex)
},
// 根据标记获取标记所在的序列信息
getSeriesInfoByMark(visitTaskId, obj) {
var seriesInfo = null
// 根据任务ID测量病灶信息
var index = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
if (index > -1) {
var idx = -1
idx = this.visitTaskList[index].MeasureData.findIndex(item => item.OrderMarkName === obj.lesionName)
// if (this.CriterionType === 10) {
// idx = this.visitTaskList[index].MeasureData.findIndex(item => item.OrderMarkName === obj.lesionName)
// } else {
// idx = this.visitTaskList[index].MeasureData.findIndex(item => item.QuestionId === obj.questionId && item.RowIndex === obj.rowIndex)
// }
if (idx > -1 && this.visitTaskList[index].MeasureData[idx].MeasureData) {
var studyList = this.visitTaskList[index].StudyList
var studyId = this.visitTaskList[index].MeasureData[idx].StudyId
var seriesId = this.visitTaskList[index].MeasureData[idx].SeriesId
var instanceId = this.visitTaskList[index].MeasureData[idx].InstanceId
var studyIdx = studyList.findIndex(study => study.StudyId === studyId)
if (studyIdx > -1) {
var seriesIdx = studyList[studyIdx].SeriesList.findIndex(s => s.seriesId === seriesId)
if (seriesIdx > -1) {
var series = studyList[studyIdx].SeriesList[seriesIdx]
const frame = this.visitTaskList[index].MeasureData[idx].MeasureData.frame
// const filterStr = series.isExistMutiFrames ? `frame=${frame}&instanceId=${instanceId}` : `instanceId=${instanceId}`
let n = series.instanceInfoList.findIndex(k => k.Id === instanceId)
let filterStr = ''
if (n > -1 && series.isExistMutiFrames) {
if (series.instanceInfoList[n].NumberOfFrames > 0) {
filterStr = `frame=${frame}&instanceId=${instanceId}`
} else {
filterStr = `instanceId=${instanceId}`
}
} else {
filterStr = `instanceId=${instanceId}`
}
var instanceIdx = series.imageIds.findIndex(imageId => imageId.includes(filterStr))
if (instanceIdx > -1) {
series.imageIdIndex = instanceIdx
seriesInfo = series
}
}
}
}
}
return seriesInfo
},
getLinkedSeries(baseSeries, visitTaskId) {
var index = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
var visitTaskInfo = this.visitTaskList[index]
var obj = null
var studyList = visitTaskInfo.StudyList
// 查找筛选描述一致
var seriesList = studyList.map(s => s.SeriesList).flat()
var similarArr = seriesList.map((i, index) => {
return { similar: this.strSimilarity2Percent(i.description, baseSeries.description), index }
})
similarArr = similarArr.sort((a, b) => {
return b.similar - a.similar
})
const i = similarArr[0] && similarArr[0].similar > 0.95 ? similarArr[0].index : -1
if (i > -1) {
obj = seriesList[i]
}
// if (!obj) {
// // 描述不一致,则筛选层厚一致
// const idx = seriesList.findIndex(series => series.sliceThickness === baseSeries.sliceThickness)
// idx > -1 ? obj = seriesList[idx] : ''
// }
// if (!obj) {
// // 层厚不一致,则筛选图像数量一致
// const idx = seriesList.findIndex(series => series.instanceCount === baseSeries.instanceCount)
// idx > -1 ? obj = seriesList[idx] : ''
// }
if (obj) {
const index = Math.floor(obj.imageIds.length * ((baseSeries.imageIdIndex + 1) / baseSeries.instanceCount))
obj.imageIdIndex = index > 0 ? index - 1 : 0
}
return obj
},
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]
},
// 两个字符串的相似程度,并返回相似度百分比
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))
},
Minimum(a, b, c) {
return a < b ? (a < c ? a : c) : (b < c ? b : c)
},
// 获取某个任务下可对齐的序列信息
async getFirstSeries(baseSeries, visitTaskId) {
var index = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
if (!this.visitTaskList[index].studyListInit) {
await store.dispatch('reading/getStudyInfo', { trialId: this.trialId, subjectVisitId: this.visitTaskList[index].VisitId, visitTaskId: this.visitTaskList[index].VisitTaskId, taskBlindName: this.visitTaskList[index].TaskBlindName })
await store.dispatch('reading/getMeasuredData', this.visitTaskList[index].VisitTaskId)
}
var visitTaskInfo = this.visitTaskList[index]
var obj = null
var studyList = visitTaskInfo.StudyList
// 查找筛选描述一致
var seriesList = studyList.map(s => s.SeriesList).flat()
var similarArr = seriesList.map((i, index) => {
return { similar: this.strSimilarity2Percent(i.description, baseSeries.description), index }
})
similarArr = similarArr.sort((a, b) => {
return b.similar - a.similar
})
const i = similarArr[0] && similarArr[0].similar > 0.85 ? similarArr[0].index : -1
if (i > -1) {
obj = seriesList[i]
}
// if (!obj) {
// // 描述不一致,则筛选层厚一致
// const idx = seriesList.findIndex(series => series.sliceThickness === baseSeries.sliceThickness)
// idx > -1 ? obj = seriesList[idx] : ''
// }
// if (!obj) {
// // 层厚不一致,则筛选图像数量一致
// const idx = seriesList.findIndex(series => series.instanceCount === baseSeries.instanceCount)
// idx > -1 ? obj = seriesList[idx] : ''
// }
if (!obj) {
// 都不符合要求, 判断是否存在层厚为5的序列,否则显示第一个检查的第一个序列
const idx = seriesList.findIndex(series => series.isDicom && parseInt(series.instanceCount) === 5)
idx > -1 ? obj = seriesList[idx] : obj = seriesList[0]
}
if (obj) {
const index = Math.floor(obj.imageIds.length * ((baseSeries.imageIdIndex + 1) / baseSeries.instanceCount))
obj.imageIdIndex = index > 0 ? index - 1 : 0
}
return obj
},
async getFirstSeries2(visitTaskList) {
if (visitTaskList && !visitTaskList.studyListInit) {
await store.dispatch('reading/getStudyInfo', { trialId: this.trialId, subjectVisitId: visitTaskList.VisitId, visitTaskId: visitTaskList.VisitTaskId, taskBlindName: visitTaskList.TaskBlindName })
await store.dispatch('reading/getMeasuredData', visitTaskList.VisitTaskId)
}
var index = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskList.VisitTaskId)
var studyList = this.visitTaskList[index].StudyList
var idx = studyList.findIndex(i => i.SeriesList.length > 0)
if (idx > -1) {
const seriesIdx = studyList[idx].SeriesList.findIndex(i => i.isDicom)
return seriesIdx > -1 ? studyList[idx].SeriesList[seriesIdx] : ''
} else {
return ''
}
},
// 设置画布大小
setCanvasStyle() {
let w = 570
if (!this.studyShow) w -= 170
if (!this.listShow) w -= 330
this.canvasW = (window.innerWidth - w) / (this.AspectRatio < 1 ? this.layoutRow : this.layoutCol) + 'px'
this.canvasH = (window.innerHeight - 130) / (this.AspectRatio < 1 ? this.layoutCol : this.layoutRow) + 'px'
},
// 切换布局
changeLayout(name) {
this.fullScreenIndex = -1
if (!name) name = this.layout ? this.layout : 'A'
if (this.activeTool) {
if (this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].isCurrentTask && this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].readingTaskState < 2) {
this.$nextTick(() => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(this.activeTool)
})
} else {
this.$nextTick(() => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(this.activeTool)
})
}
this.activeTool = ''
}
this.isScrollSync = false
this.activeSeries = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].stack
this.layout = name
var i = this.layouts.findIndex(i => i.name === name)
var layout = this.layouts[i]
this.layoutRow = layout.row
this.layoutCol = layout.col
this.setCanvasStyle()
this.maxCanvas = layout.row * layout.col
this.$nextTick(() => {
const elements = document.querySelectorAll('.cornerstone-element')
Array.from(elements).forEach((element) => {
cornerstone.enable(element)
cornerstone.resize(element)
})
this.getSeriesShowInCanvas(layout)
this.cornerstoneStyle = {}
this.setCanvasStyle()
})
},
// 更新画布
updateCanvas(measureData) {
for (let i = 0; i < this.maxCanvas; i++) {
var stack = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].stack
if (stack.studyId) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].removeToolState(measureData)
}
}
},
// 旋转
setDicomCanvasRotate(value) {
const type = parseInt(value)
if (type === 1) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetRotate()
} else if (type === 2) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setRotate(false, true, 0, type)
} else if (type === 3) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setRotate(true, false, 0, type)
} else if (type === 4) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setRotate(false, false, -90, type)
} else if (type === 5) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setRotate(false, false, 90, type)
}
},
// 激活画布
activateDicomCanvas(index) {
console.log('激活画布')
if (index !== this.currentDicomCanvasIndex) {
this.currentDicomCanvasIndex = index
// var dicomSeries = this.canvasObj[this.currentDicomCanvasIndex]
var dicomSeries = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].stack
this.activeSeries = dicomSeries
for (let i = 0; i < this.maxCanvas; i++) {
if (this.activeTool) {
var series = this.$refs[`dicomCanvas${i}`][0].stack
if (series.isCurrentTask && series.readingTaskState < 2) {
this.$refs[`dicomCanvas${i}`][0].setToolPassive(this.activeTool)
} else {
this.$refs[`dicomCanvas${i}`][0].setToolEnabled(this.activeTool)
}
}
}
this.activeTool = ''
this.isScrollSync = false
this.currentDicomCanvasIndex = index
this.currentDicomCanvas = this.$refs[`dicomCanvas${index}`]
this.currentDicomCanvas.tabIndex = 0
if (!this.rotateList[this.currentDicomCanvasIndex]) {
this.rotateList[this.currentDicomCanvasIndex] = '1'
}
if (!this.colorList[this.currentDicomCanvasIndex]) {
this.colorList[this.currentDicomCanvasIndex] = ''
}
if (!this.wwwcList[this.currentDicomCanvasIndex]) {
this.wwwcList[this.currentDicomCanvasIndex] = '1'
}
this.$refs['container'].focus()
// if (stack.studyId) {
// DicomEvent.$emit('toggleVisitList', stack)
// }
store.dispatch('reading/setActiveSeries', dicomSeries)
}
},
// 鼠标移入测量工具时,判断工具是否可激活
enter(e, toolName) {
var i = this.measuredTools.findIndex(item => item.toolName === toolName)
if (i === -1) return
var isCurrentTask = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].isCurrentTask
var readingTaskState = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].readingTaskState
if (!isCurrentTask) {
this.measuredTools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
if (this.activeTool) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(toolName)
this.activeTool = ''
}
} else if (isCurrentTask && readingTaskState >= 2) {
this.measuredTools[i].isDisabled = false
e.target.style.cursor = 'pointer'
} else if (isCurrentTask && readingTaskState < 2) {
// var obj = this.$refs['measurementList'].isCanActiveTool(toolName, true)
var obj = this.$refs['measurementList'].isCanActiveTool(toolName, true)
this.measuredTools[i].disabledReason = obj.reason
if (!obj.isCanActiveTool) {
if (this.activeTool === toolName) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(toolName)
this.activeTool = ''
}
this.measuredTools[i].isDisabled = true
e.target.style.cursor = 'not-allowed'
} else {
this.measuredTools[i].isDisabled = false
e.target.style.cursor = 'pointer'
}
}
},
setMeasuredToolsPassive() {
this.measuredTools.forEach(item => {
if (this.activeTool === item.toolName) {
this.activeTool = ''
}
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(item.toolName)
})
},
// 设置测量工具启用(不会对输入作出反应)
setToolActive(toolName, isMeasuredTool, e, type) {
if (!type) {
this.tmpData = null
}
if (isMeasuredTool) {
// var i = this.measuredTools.findIndex(item => item.toolName === toolName)
// if (i === -1 && this.measuredTools[i].isDisabled) return
var toolObj = this.measuredTools.find(i => i.toolName === toolName)
if (!toolObj || toolObj.isDisabled) return
var dicomSeries = this.canvasObj[this.currentDicomCanvasIndex]
if (dicomSeries.isCurrentTask && isMeasuredTool) {
if (this.activeTool) {
this.measuredTools.forEach(item => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(item.toolName)
})
if (this.activeTool === toolName) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(toolName)
this.activeTool = ''
} else {
this.activeTool = toolName
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolActive(toolName)
}
} else {
this.measuredTools.forEach(item => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(item.toolName)
})
this.activeTool = toolName
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolActive(toolName)
}
} else {
if (!this.activeTool) return
this.measuredTools.forEach(item => {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(item.toolName)
})
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(toolName)
this.activeTool = ''
}
} else {
if (this.activeTool === toolName) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(toolName)
this.activeTool = ''
} else {
this.activeTool = toolName
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolActive(toolName)
}
}
},
// 设置测量工具禁用(不会对输入作出反应)
setToolEnabled() {
var dicomSeries = this.canvasObj[this.currentDicomCanvasIndex]
if (!dicomSeries.isCurrentTask) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(this.activeTool)
this.activeTool = ''
return
}
if (this.activeTool) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolEnabled(this.activeTool)
this.activeTool = ''
}
},
// 影像适应图像/适应窗口
fitToType(toolName) {
if (this.activeTool !== 'fitToWindow' && this.activeTool !== 'fitToImage') {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolPassive(this.activeTool)
}
if (toolName === 'fitToWindow') {
this.fitType = 0
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].fitToWindow()
} else if (toolName === 'fitToImage') {
this.fitType = 1
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].fitToImage()
}
this.activeTool = toolName
},
// 切换帧
scrollPage(i) {
// 获取影像是否下载完成
const isLoaded = this.getSeriesLoadStatus()
if (!isLoaded) return
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].scrollPage(i)
},
// 播放/暂停
toggleClipPlay(isPlay) {
// 获取影像是否下载完成
const isLoaded = this.getSeriesLoadStatus()
if (!isLoaded) return
this.clipPlaying = isPlay
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setFps(this.fps)
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleClipPlay(isPlay)
},
getSeriesLoadStatus() {
const index = this.visitTaskList.findIndex(i => i.VisitTaskId === this.activeSeries.visitTaskId)
if (index === -1) return false
const loadStatus = this.visitTaskList[index].StudyList[this.activeSeries.studyIndex].SeriesList[this.activeSeries.seriesIndex].loadStatus
return loadStatus
},
setDicomCanvasfps(event) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setFps(event.target.value)
},
// 添加标记
setMeasureData(data) {
if (this.CriterionType === 21 || this.CriterionType === 22) {
if (this.tmpData) {
data.tableQuestionId = this.tmpData.Id
data.tableQuestionMark = this.tmpData.QuestionMark
this.$refs['measurementList'].setMeasuredData(data)
}
} else {
this.$refs['measurementList'].setMeasuredData(data)
}
this.activeTool = ''
},
// 修改标记
modifyMeasureData(data) {
if ((this.CriterionType === 21 || this.CriterionType === 22) && data.measureData.tableQuestionId) {
this.$refs['measurementList'].modifyMeasuredData(data)
} else {
this.$refs['measurementList'].modifyMeasuredData(data)
}
this.activeTool = ''
},
async saveImage() {
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].saveImage()
// canvas为转换后的Canvas对象
const divForDownloadViewport = document.querySelector(
`div[data-canvas-uid="dicomCanvas${this.currentDicomCanvasIndex}"]`
)
var canvas = await html2canvas(divForDownloadViewport)
let oImg = new Image()
oImg = canvas.toDataURL('image/png', 1)// 导出图片
var oA = document.createElement('a')
var subjectCode = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].subjectCode
var taskBlindName = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].stack.taskBlindName
var timestamp = Date.now()
oA.download = `${subjectCode}_${taskBlindName}_${timestamp}`// 设置下载的文件名,默认是'下载'
oA.href = oImg
document.body.appendChild(oA)
oA.click()
oA.remove() // 下载之后把创建的元素删除
},
// 设置窗宽/窗位
setDicomCanvasWwwc(v) {
if (v.val === -1) {
// 默认值
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetWwwc()
} else if (v.val === 0) {
// 自定义
this.setCustomWwwc()
} else if (v.val === 1) {
// 区域窗宽
// this.setToolActive('WwwcRegion', false)
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setToolActive('WwwcRegion')
} else if (v.val === 8) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleInvert()
} else {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setWwwc(v.ww, v.wc)
}
},
setCustomWwwc() {
var wwwc = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].dicomInfo.wwwc
this.activeCanvasWW = wwwc ? parseInt(wwwc.split('/')[0]) : null
this.activeCanvasWC = wwwc ? parseInt(wwwc.split('/')[1]) : null
this.customWwc.visible = true
},
setWwwc(v) {
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].setWwwc(v.ww, v.wc)
this.customWwc.visible = false
},
toggleInvert() {
if (this.activeTool === 'reversecolor') {
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].resetWwwc()
this.activeTool = ''
} else {
this.activeTool = 'reversecolor'
// this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleInvert()
}
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].toggleInvert()
},
setImageIndexSync() {
this.isScrollSync = !this.isScrollSync
},
scrollSync(obj) {
for (var i = 0; i < this.maxCanvas; i++) {
if (i !== this.currentDicomCanvasIndex) {
this.$refs[`dicomCanvas${i}`][0].scrollPage(obj.offset)
}
}
},
showPanel(e) {
e.currentTarget.firstChild.lastChild.style.display = 'block'
},
handleMouseout(e) {
e.currentTarget.firstChild.lastChild.style.display = 'none'
},
// 预览临床数据
previewCD() {
this.$emit('previewCD')
},
previewNoneDicoms() {
this.$emit('previewNoneDicoms')
},
previewManuals(justKeyDoc = false) {
this.isFullscreen = false
this.manualsDialog.visible = true
this.manualsDialog.justKeyDoc = justKeyDoc
},
async skipTask() {
// 是否确认跳过?
const confirm = await this.$confirm(
this.$t('trials:readingReport:message:skipConfirm'),
{
type: 'warning',
distinguishCancelAndClose: true
}
)
if (confirm !== 'confirm') return
// const loading = this.$loading({ fullscreen: true })
try {
const idx = this.visitTaskList.findIndex(i => i.IsCurrentTask)
if (idx > -1) {
var visitTaskId = this.visitTaskList[idx].VisitTaskId
const res = await setSkipReadingCache({ visitTaskId: visitTaskId })
if (res.IsSuccess) {
window.location.reload()
}
}
// loading.close()
} catch (e) {
// loading.close()
console.log(e)
}
},
async submit() {
if (this.submitLoading) return
this.submitLoading = true
const loading = this.$loading({ fullscreen: true })
try {
const idx = this.visitTaskList.findIndex(i => i.IsCurrentTask)
if (idx > -1) {
var visitTaskId = this.visitTaskList[idx].VisitTaskId
this.taskId = visitTaskId
await verifyVisitTaskQuestions({ visitTaskId: visitTaskId })
const { ImageAssessmentReportConfirmation } = const_.processSignature
this.signCode = ImageAssessmentReportConfirmation
this.signVisible = true
}
loading.close()
} catch (e) {
loading.close()
} finally {
this.submitLoading = false
}
},
// 关闭签名框
closeSignDialog(isSign, signInfo) {
if (isSign) {
this.signConfirm(signInfo)
} else {
this.signVisible = false
}
},
// 签名并确认
async signConfirm(signInfo) {
this.loading = true
try {
var params = {
data: {
visitTaskId: this.taskId
},
signInfo: signInfo
}
const res = await submitDicomVisitTask(params)
if (res.IsSuccess) {
this.$message.success(this.$t('common:message:savedSuccessfully'))
if (this.$refs['signForm']) {
this.$refs['signForm'].btnLoading = false
}
this.signVisible = false
// 设置当前任务阅片状态为已读
this.readingTaskState = 2
await store.dispatch('reading/setVisitTaskReadingTaskState', { visitTaskId: this.visitTaskId, readingTaskState: 2 })
await store.dispatch('reading/setCurrentReadingTaskState', 2)
const res = await getAutoCutNextTask({ imageToolType: 1 })
var isAutoTask = res.Result.AutoCutNextTask
if (isAutoTask) {
window.location.reload()
} else {
// '当前阅片任务已完成,是否进入下一个阅片任务?'
this.$confirm(this.$t('trials:readingReport:message:msg4'), {
type: 'warning',
distinguishCancelAndClose: true
})
.then(() => {
window.location.reload()
})
.catch(action => {
changeURLStatic('visitTaskId', this.visitTaskId)
})
}
window.opener.postMessage('refreshTaskList', window.location)
}
this.loading = false
} catch (e) {
this.loading = false
if (this.$refs['signForm'] && this.$refs['signForm'].btnLoading) {
this.$refs['signForm'].btnLoading = false
}
}
},
handleFusion() {
// https 或者 本地开发环境才是安全上下文
// if (!window.isSecureContext) {
// this.$alert(this.$t('components:uploadvideo:message:xf3'))
// return
// }
// // 顶部文档是否开启跨源隔离
// if (!window.crossOriginIsolated) {
// this.$alert(this.$t('components:uploadvideo:message:xf4'))
// return
// }
if (this.imageQualityIssues && parseInt(this.imageQualityIssues) === 6) {
this.$alert(this.$t('trials:lugano:message:fusionWarn'))
return
}
const i = this.activeTaskInfo.visitTaskIndex
if (i < 0) return
if (this.studyList.length > 0) {
this.fusionForm.studyID = this.studyList[0].StudyId
this.handleStudyIdChange(this.studyList[0].StudyId)
} else {
this.fusionForm.studyID = ''
this.ctSeries = []
this.petSeries = []
}
this.fusionForm.visitTaskId = this.visitTaskList[i].VisitTaskId
this.fusionForm.readingTaskState = this.visitTaskList[i].ReadingTaskState
this.fusionForm.taskBlindName = this.visitTaskList[i].TaskBlindName
this.fusionForm.subjectCode = this.visitTaskList[i].SubjectCode
this.fusionForm.isBaseLineTask = this.visitTaskList[i].IsBaseLineTask
this.fusionForm.isCurrentTask = this.visitTaskList[i].IsCurrentTask
this.fusionSeries.visible = true
},
handleStudyIdChange(v) {
var i = this.studyList.findIndex(i => i.StudyId === v)
if (i === -1) {
this.ctSeries = []
this.petSeries = []
} else {
var seriesList = this.studyList[i].SeriesList
var series = seriesList.filter(series => series.modality === 'CT' || series.modality === 'MR')
this.ctSeries = series.sort((a, b) => b.instanceCount - a.instanceCount)
series = seriesList.filter(series => series.modality === 'PT')
this.petSeries = series.sort((a, b) => b.instanceCount - a.instanceCount)
}
},
handleCTSelectionChange(rows) {
if (rows.length > 0) {
this.ctSeriesInfo = rows[0]
} else {
this.ctSeriesInfo = {}
}
},
handleCTSelect(selection) {
if (selection.length > 1) {
const del_row = selection.shift()
this.$refs.ctSeries.toggleRowSelection(del_row, false)
}
},
handlePTSelect(selection) {
if (selection.length > 1) {
const del_row = selection.shift()
this.$refs.petSeries.toggleRowSelection(del_row, false)
}
},
handlePTSelectionChange(rows) {
if (rows.length > 0) {
this.petSeriesInfo = rows[0]
} else {
this.petSeriesInfo = {}
}
},
async handleSubmitFusionSeries() {
if (this.fusionSubmitLoading) return
this.fusionSubmitLoading = true
try {
const valid = await this.$refs['fusionForm'].validate()
if (!valid) return
if (Object.keys(this.ctSeriesInfo).length === 0 || Object.keys(this.petSeriesInfo).length === 0) {
// '请选择序列信息!'
await this.$confirm(this.$t('trials:lugano:fusionDialog:message:checkSeries'), this.$t('trials:lugano:fusionDialog:warning'), {
showCancelButton: false,
type: 'warning'
}).catch(() => { })
return
}
if (this.ctSeriesInfo.instanceCount > 400) {
let res = await this.getSystemInfo()
if (!res) return false
}
var count = Math.abs(this.ctSeriesInfo.instanceCount - this.petSeriesInfo.instanceCount)
if (count > 10) {
// '选择的两组序列,影像数存在较大差距, 是否继续?'
const confirm = await this.$confirm(this.$t('trials:lugano:fusionDialog:message:validSeries'), this.$t('trials:lugano:fusionDialog:warning'), {
type: 'warning'
}).catch(() => false)
if (confirm !== 'confirm') return
}
this.fusion()
} finally {
this.fusionSubmitLoading = false
}
},
fusion() {
// sessionStorage.setItem('ctSeriesInfo', JSON.stringify(this.ctSeriesInfo))
// sessionStorage.setItem('petSeriesInfo', JSON.stringify(this.petSeriesInfo))
this.fusionSeries.visible = false
var token = getToken()
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var trialId = this.$router.currentRoute.query.trialId
var digitPlaces = Number(localStorage.getItem('digitPlaces'))
var path = `/petct?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&visitTaskId=${this.fusionForm.visitTaskId}&taskBlindName=${this.fusionForm.taskBlindName}&subjectCode=${this.fusionForm.subjectCode}&readingTaskState=${this.fusionForm.readingTaskState}&isBaseLineTask=${this.fusionForm.isBaseLineTask}&isCurrentTask=${this.fusionForm.isCurrentTask}&isReadingShowSubjectInfo=${this.isReadingShowSubjectInfo}&studyId=${this.fusionForm.studyID}&ctseriesId=${this.ctSeriesInfo.seriesId}&ptseriesId=${this.petSeriesInfo.seriesId}&digitPlaces=${digitPlaces}&TokenKey=${token}&lang=${this.$i18n.locale}`
var routeData = this.$router.resolve({ path })
if (this.petctWindow) {
this.petctWindow.close()
}
this.petctWindow = window.open(routeData.href, '_blank')
this.$nextTick(() => { this.petct.visible = true })
},
getSystemInfo() {
return new Promise(async resolve => {
const systemInfo = new SystemInfo();
const allInfo = systemInfo.getAllInfo();
let deviceMemory = allInfo.hardware.deviceMemory; // 设备内存
let { width, height } = allInfo.screen; // 分辨率
let discrete = allInfo.webgl.gpuType.discrete; // 是否独立显卡
let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; // 显卡内存
// parseFloat(deviceMemory) < 16 ||
if (width < 1920 || height < 1080 || !discrete || parseFloat(estimatedMemory) < 2) {
let res = await this.$confirm(this.$t('browser:tip:ReadingConfiguration'))
resolve(res)
} else {
resolve(true)
}
})
},
async getSystemInfoReading() {
return new Promise(async resolve => {
let whitelisting = localStorage.getItem('whitelisting') ? JSON.parse(localStorage.getItem('whitelisting')) : []
let user = md5(sessionStorage.getItem('identityUserId'))
let r = whitelisting.some(item => item === user)
if (r) return resolve(true)
const systemInfo = new SystemInfo();
const allInfo = systemInfo.getAllInfo();
let deviceMemory = allInfo.hardware.deviceMemory; // 设备内存
let { width, height } = allInfo.screen; // 分辨率
// let discrete = allInfo.webgl.gpuType.discrete; // 是否独立显卡
// let estimatedMemory = allInfo.webgl.memoryInfo.estimatedMemory; // 显卡内存
// parseFloat(deviceMemory) < 16 ||
if (width < 1920 || height < 1080) {
let res = await this.$confirm(this.$t('browser:tip:Configuration'))
whitelisting.push(user)
localStorage.setItem('whitelisting', JSON.stringify(whitelisting))
resolve(res)
} else {
resolve(true)
}
})
},
}
}
</script>
<style lang="scss" scoped>
.dicom-viewer-wrapper {
height: 100%;
padding: 5px 0px 5px 5px;
box-sizing: border-box;
.divider {
display: block;
height: 1px;
width: 100%;
margin: 10px 0;
// background-color: #383838;
// color: #fff;
// background-color: #DCDFE6;
}
.el-divider__text {
padding: 0 10px;
color: #fff;
background-color: #383838;
font-size: 12px;
width: 80px;
}
.dicom-viewer-container {
display: flex;
flex-direction: column;
height: 100%;
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.dicom-tools {
box-sizing: border-box;
width: 100%;
height: 80px;
padding: 0 5px;
border: 1px solid #727272;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
.tool-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 20px;
.icon {
padding: 5px;
border: 1px solid #404040;
cursor: pointer;
text-align: center;
.svg-icon {
font-size: 20px;
color: #ddd;
}
}
.text {
position: relative;
font-size: 12px;
margin-top: 5px;
color: #d0d0d0;
display: none;
}
}
.tool-frame {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin-right: 20px;
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;
}
}
.tool_active {
background-color: #607d8b;
}
.tool_disabled {
cursor: not-allowed
}
.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:hover {
background-color: #727272;
}
}
// .dropdown:hover .dropdown-content {
// display: block;
// }
.layout-content ul li {
border-top: 1px solid #ddd;
border-left: 1px solid #ddd;
}
.layout-content ul .flex_row {
// border: 1px solid #ddd;
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: center;
// padding: 2px;
margin-bottom: 2px;
}
.layout-content ul .flex_column {
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
margin-bottom: 2px;
}
.layout_box_1_1 {
flex: 1;
// border: 1px solid #ddd;
line-height: 30px;
font-size: 12px;
text-align: center;
border-bottom: 1px solid #ddd;
border-right: 1px solid #ddd;
// padding: 0 5px;
}
.layout_box_1_2 {
flex: 1;
// border: 1px solid #ddd;
line-height: 15px;
font-size: 10px;
text-align: center;
border-bottom: 1px solid #ddd;
border-right: 1px solid #ddd;
// padding: 0 5px;
}
.layout-content li .layout_box_1_1 :last-child {
color: red;
}
// .layout_li:last-child{
// .layout_box{
// border-bottom: none;
// }
// }
.layout-content li:hover {
cursor: pointer;
background-color: #727272;
}
}
.dicom-viewers {
box-sizing: border-box;
flex: 1;
// width: 100%;
margin-top: 5px;
height: 100%;
display: flex;
flex-direction: row;
justify-content: flex-start;
.form-container {
// box-sizing: border-box;
width: 350px;
// height: 100vh;
border: 1px solid #727272;
// position: relative;
display: flex;
flex-direction: column;
transition: .5s;
position: relative;
}
.list_btn {
position: absolute;
width: 25px;
height: 25px;
line-height: 25px;
text-align: center;
border: 1px solid #607d8b;
top: 0px;
left: -25px;
z-index: 99;
color: #d5d5d5;
background-color: #424242;
cursor: pointer;
}
.listHide {
width: 20px;
transition: .5s;
}
.form-wrapper {
flex-grow: 1;
height: 0;
overflow: hidden;
}
.form-footer {
background: #000;
padding: 5px 0;
text-align: center;
}
.viewer-container {
box-sizing: border-box;
flex: 1;
height: 100%;
border: 1px solid #727272;
}
.measurement-container {
// height: 100%;
overflow-y: auto;
}
.box {
display: grid;
box-sizing: border-box;
height: 100%;
padding: 0;
.item {
box-sizing: border-box;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.21);
position: relative;
&_active {
// border: 2px solid #ffeb3b;fff;#428bca
// border: 1.5px dashed #ffeb3b;
border: 1px dashed rgb(250, 250, 0);
}
}
}
.box_1_1 {
grid-template-columns: repeat(1, 100%); //1列,占100%
grid-template-rows: repeat(1, 100%); //1行,占100%
}
.box_1_2 {
grid-template-columns: repeat(2, 50%); //1列,占50%
grid-template-rows: repeat(1, 100%); //1行,占100%
}
.box_2_1 {
grid-template-columns: repeat(1, 100%); //1列,占100%
grid-template-rows: repeat(2, 50%); //1行,占50%
}
.box_2_2 {
grid-template-columns: repeat(2, 50%); //1列,占50%
grid-template-rows: repeat(2, 50%); //1行,占50%
}
// .box_3_1{
// grid-template-columns: repeat(3, 100%); //1列,占100%
// grid-template-rows: repeat(3, 33.33%); //1行,占100%
// }
// .box_3_2{
// grid-template-columns: repeat(3, 50%); //1列,占100%
// grid-template-rows: repeat(3, 50%); //1行,占100%
// }
// .box_3_3{
// grid-template-columns: repeat(3, 33.33%); //1列,占100%
// grid-template-rows: repeat(3, 33.33%); //1行,占100%
// }
}
.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;
}
}
.petct_wrapper {
position: absolute;
left: -205px;
right: 345px;
height: 100%;
background-color: #000000;
}
.series-table {
::v-deep .el-table {
background-color: #1e1e1e !important;
color: #dfdfdf;
}
::v-deep .el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #dfdfdf;
}
.el-table--border::after,
.el-table--group::after,
.el-table::before {
background-color: #1e1e1e;
}
::v-deep .el-table__header-wrapper {
th {
background-color: #1e1e1e !important;
color: #dfdfdf;
}
}
::v-deep .el-table__body-wrapper {
tr {
background-color: #1e1e1e !important;
color: #dfdfdf;
}
tr:hover>td {
background-color: #1e1e1e !important;
}
}
::v-deep .el-table__empty-block {
background-color: #1e1e1e !important;
}
}
}
.listBox-leave-active,
.listBox-enter-active {
transition: all 0.5s ease;
}
.listBox-leave-active,
.listBox-enter {
// width: 0px !important;
flex-grow: 0 !important;
}
.listBox-leave,
.listBox-enter-active {
// width: 350px;
flex-grow: 1;
}
</style>
<style lang="scss">
.key-series-popper {
padding: 8px !important;
background-color: #1e1e1e !important;
border: 1px solid #4a4a4a !important;
.key-series-list {
width: 340px;
max-height: 420px;
overflow-y: auto;
margin: 0;
padding: 0;
list-style: none;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-thumb {
background: #4a4a4a;
border-radius: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.key-series-header {
padding: 5px 12px;
margin-bottom: 10px;
background: linear-gradient(180deg, rgba(64, 64, 64, 0.92) 0%, rgba(43, 43, 43, 0.98) 100%);
border: 1px solid #474747;
border-radius: 6px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 6px 18px rgba(0, 0, 0, 0.2);
}
.key-series-header-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.key-series-header-title {
display: flex;
align-items: center;
min-width: 0;
}
.key-series-header-dot {
width: 8px;
height: 8px;
margin-right: 8px;
border-radius: 50%;
background: linear-gradient(180deg, #ffd76a 0%, #f39c12 100%);
box-shadow: 0 0 8px rgba(243, 156, 18, 0.45);
}
.key-series-task-name {
color: #fff;
font-size: 15px;
font-weight: 600;
line-height: 22px;
word-break: break-all;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.key-series-empty {
text-align: center;
color: #999;
padding: 10px;
}
.key-series-item {
text-align: left;
display: flex;
align-items: center;
padding: 10px;
cursor: pointer;
background: linear-gradient(180deg, rgba(46, 46, 46, 0.96) 0%, rgba(40, 40, 40, 0.98) 100%);
border-radius: 6px;
margin-bottom: 8px;
border: 1px solid #353535;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
// transition: all 0.2s ease;
&:last-child {
margin-bottom: 0;
}
&:hover {
background: linear-gradient(180deg, rgba(54, 54, 54, 0.98) 0%, rgba(46, 46, 46, 1) 100%);
border-color: #5a5a5a;
// transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
}
.key-series-img {
width: 60px;
height: 60px;
flex-shrink: 0;
background-color: #000;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
margin-right: 12px;
border-radius: 4px;
border: 1px solid #3a3a3a;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
.default-icon {
font-size: 24px;
color: #666;
}
}
.key-series-info {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
padding-right: 5px;
.key-series-meta {
width: 100%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
.key-series-label {
font-size: 13px;
color: #aaa;
margin-right: 4px;
white-space: nowrap;
}
.key-series-uid {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
color: #ddd;
line-height: 1.2;
}
}
.key-series-star {
margin-left: 10px;
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba(243, 156, 18, 0.08);
border: 1px solid rgba(243, 156, 18, 0.16);
.svg-icon {
color: #f39c12;
font-size: 16px;
}
}
}
}
</style>