1530 lines
54 KiB
Vue
1530 lines
54 KiB
Vue
<template>
|
||
<div ref="dicom-wrapper" class="dicom-wrapper">
|
||
<div ref="dicomViewer" class="dicom-viewer">
|
||
<!-- <div v-for="i in layoutRow" :key="i" class="dicom-row" :style="{height: rowHeight}">
|
||
<div v-for="j in layoutCol" :key="j" class="dicom-item" oncontextmenu="return false">
|
||
<dicom-canvas ref="dicomCanvas" style="width:100%;height:100%" />
|
||
</div>
|
||
</div>-->
|
||
<div class="Anonymous" v-if="isAnonymous">
|
||
<div
|
||
:class="{ btn: true, activeBtn: activeTool === 'Note_RectangleRoi' && !isComparison, isNoted: isComparison }"
|
||
@click="setToolActive($event, 'Note_RectangleRoi')">{{
|
||
$t('DicomViewer:anonymous:Note_RectangleRoi') }}</div>
|
||
<div :class="{ btn: true, activeBtn: activeTool === 'Eraser' && !isComparison, isNoted: isComparison }"
|
||
@click="setToolActive($event, 'Eraser')">{{
|
||
$t('DicomViewer:anonymous:Eraser') }}
|
||
</div>
|
||
<div :class="{ btn: true, isNoted: isComparison }" @click="anonymousImage(false)">{{
|
||
$t('DicomViewer:anonymous:Application') }}</div>
|
||
<div :class="{ btn: true, isNoted: isComparison }" @click="anonymousImage(true)">{{
|
||
$t('DicomViewer:anonymous:ApplicationAll') }}</div>
|
||
<!-- <div class="btn">刷新图像</div> -->
|
||
<div class="btn" v-if="!isComparison" @click="comparison(true)">{{
|
||
$t('DicomViewer:anonymous:Comparison') }}</div>
|
||
<div class="btn" v-else @click="comparison(false)">{{
|
||
$t('DicomViewer:anonymous:Exit') }}</div>
|
||
<div :class="{ btn: true, isNoted: isComparison }" @click="recovery(false)">{{
|
||
$t('DicomViewer:anonymous:Recovery') }}</div>
|
||
</div>
|
||
<div v-show="layoutRow >= 1" class="dicom-row" :style="{ height: rowHeight }">
|
||
<div v-show="layoutRow >= 1 && layoutCol >= 1" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas0' }" data-index="0" @click="activateDicomCanvas(0)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas0" style="width:100%;height:100%" />
|
||
</div>
|
||
<div v-show="layoutRow >= 1 && layoutCol >= 2" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas1' }" data-index="1" @click="activateDicomCanvas(1)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas1" style="width:100%;height:100%" />
|
||
</div>
|
||
<div v-show="layoutRow >= 1 && layoutCol >= 3" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas2' }" data-index="2" @click="activateDicomCanvas(2)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas2" style="width:100%;height:100%" />
|
||
</div>
|
||
</div>
|
||
|
||
<div v-show="layoutRow >= 2" class="dicom-row" :style="{ height: rowHeight }">
|
||
<div v-show="layoutRow >= 2 && layoutCol >= 1" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas3' }" data-index="3" @click="activateDicomCanvas(3)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas3" style="width:100%;height:100%" />
|
||
</div>
|
||
<div v-show="layoutRow >= 2 && layoutCol >= 2" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas4' }" data-index="4" @click="activateDicomCanvas(4)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas4" style="width:100%;height:100%" />
|
||
</div>
|
||
<div v-show="layoutRow >= 2 && layoutCol >= 3" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas5' }" data-index="5" @click="activateDicomCanvas(5)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas5" style="width:100%;height:100%" />
|
||
</div>
|
||
</div>
|
||
|
||
<div v-show="layoutRow == 3" class="dicom-row" :style="{ height: rowHeight }">
|
||
<div v-show="layoutRow == 3 && layoutCol >= 1" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas6' }" data-index="6" @click="activateDicomCanvas(6)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas6" style="width:100%;height:100%" />
|
||
</div>
|
||
<div v-show="layoutRow == 3 && layoutCol >= 2" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas7' }" data-index="7" @click="activateDicomCanvas(7)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas7" style="width:100%;height:100%" />
|
||
</div>
|
||
<div v-show="layoutRow == 3 && layoutCol >= 3" class="dicom-item"
|
||
:class="{ 'activeItem': activeItem == 'dicomCanvas8' }" data-index="8" @click="activateDicomCanvas(8)"
|
||
@dblclick="setFullScreen($event)">
|
||
<dicom-canvas ref="dicomCanvas8" style="width:100%;height:100%" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div ref="dicomTools" class="dicom-tools">
|
||
<!-- 布局 -->
|
||
<div class="measureTool-wrapper">
|
||
<div class="sideTool-title">{{ $t('trials:reading:button:layout') }}</div>
|
||
<div class="sideTool-wrapper">
|
||
<label>{{ $t('trials:reading:button:layout') }}:</label>
|
||
<select class="sidetool-select" style="width:90px" :disabled="isAnonymous" @change="changeLayout($event)">
|
||
<option value="1x1" selected>1x1</option>
|
||
<option value="1x2">1x2</option>
|
||
<option value="2x1">2x1</option>
|
||
<option value="2x2">2x2</option>
|
||
<option value="1x3">1x3</option>
|
||
<option value="3x1">3x1</option>
|
||
<option value="2x3">2x3</option>
|
||
<option value="3x2">3x2</option>
|
||
<option value="3x3">3x3</option>
|
||
</select>
|
||
<div class="btnBox" v-if="hasAnonymous" @click="openAnonymous">{{ $t('DicomViewer:anonymous:PixelAnonymity')
|
||
}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 图像变换 -->
|
||
<div class="measureTool-wrapper">
|
||
<div class="sideTool-title">{{ $t('trials:dicom-show:transform') }}</div>
|
||
<div class="sideTool-wrapper">
|
||
<button :title="$t('trials:reading:button:wwwc')" class="btn-link" data-tool="Wwwc"
|
||
@click="setToolActive($event, 'Wwwc')">
|
||
<svg-icon icon-class="reverse" style="font-size:20px;" />
|
||
</button>
|
||
<!-- <button
|
||
title="区域调窗"
|
||
class="btn-link"
|
||
data-tool="WwwcRegion"
|
||
@click="setToolActive($event,'WwwcRegion')"
|
||
>
|
||
<svg-icon icon-class="wwwcRegion" style="font-size:20px;" />
|
||
</button>-->
|
||
<button :title="$t('trials:reading:button:reverseColor')" class="btn-link" @click="toggleInvert">
|
||
<svg-icon icon-class="reversecolor" style="font-size:20px;" />
|
||
</button>
|
||
<button :title="$t('trials:reading:button:zoom')" class="btn-link" data-tool="Zoom"
|
||
@click="setToolActive($event, 'Zoom')">
|
||
<svg-icon icon-class="magnifier" style="font-size:20px;" />
|
||
</button>
|
||
<button :title="$t('trials:dicom-show:lens')" class="btn-link" data-tool="Magnify"
|
||
@click="setToolActive($event, 'Magnify')">
|
||
<svg-icon icon-class="zoom" style="font-size:20px;" />
|
||
</button>
|
||
<button :title="$t('trials:reading:button:rotate')" class="btn-link dropdown" data-tool="Rotate">
|
||
<svg-icon icon-class="rotate" style="font-size:20px;" />
|
||
<div class="dropdown-content">
|
||
<div @click.stop="setDicomCanvasRotate(1)">{{ $t('trials:reading:button:rotateDefault') }}</div>
|
||
<div @click.stop="setDicomCanvasRotate(2)">{{ $t('trials:reading:button:rotateHorizontal') }}</div>
|
||
<div @click.stop="setDicomCanvasRotate(3)">{{ $t('trials:reading:button:rotateVertical') }}</div>
|
||
<div @click.stop="setDicomCanvasRotate(4)">{{ $t('trials:reading:button:rotateTurnLeft') }}</div>
|
||
<div @click.stop="setDicomCanvasRotate(5)">{{ $t('trials:reading:button:rotateTurnRight') }}</div>
|
||
</div>
|
||
</button>
|
||
<button :title="$t('trials:reading:button:move')" class="btn-link" data-tool="Pan"
|
||
@click="setToolActive($event, 'Pan')">
|
||
<svg-icon icon-class="move" style="font-size:20px;" />
|
||
</button>
|
||
<button :title="$t('trials:reading:button:fitWindow')" class="btn-link" data-tool="fitToWindow"
|
||
@click="fitToType($event, 'fitToWindow')">
|
||
<svg-icon icon-class="fitToWindow" style="font-size:20px;" />
|
||
</button>
|
||
<button :title="$t('trials:reading:button:fitImage')" class="btn-link" data-tool="fitToImage"
|
||
@click="fitToType($event, 'fitToImage')">
|
||
<svg-icon icon-class="fitToImage" style="font-size:20px;" />
|
||
</button>
|
||
<!-- <button title="旋转" class="btn-link dropdown" data-tool="Rotate" @click="setToolActive($event,'Rotate')"> -->
|
||
</div>
|
||
</div>
|
||
<!-- 测量标注 -->
|
||
<div class="measureTool-wrapper">
|
||
<div class="sideTool-title">{{ $t('trials:dicom-show:measurementLabeling') }}</div>
|
||
<div class="sideTool-wrapper">
|
||
<!-- 探针 -->
|
||
<button :title="$t('trials:dicom-show:Probe')" class="btn-link" data-tool="Probe"
|
||
@click="setToolActive($event, 'Probe')">
|
||
<svg-icon icon-class="pixel" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 长度测量 -->
|
||
<button :title="$t('trials:dicom-show:Length')" class="btn-link" data-tool="Length"
|
||
@click="setToolActive($event, 'Length')">
|
||
<svg-icon icon-class="length" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 角度测量 -->
|
||
<button :title="$t('trials:dicom-show:Angle')" class="btn-link" data-tool="Angle"
|
||
@click="setToolActive($event, 'Angle')">
|
||
<svg-icon icon-class="angle" style="font-size:20px;" />
|
||
</button>
|
||
<!-- Cobb测量 -->
|
||
<button :title="$t('trials:dicom-show:CobbAngle')" class="btn-link" data-tool="CobbAngle"
|
||
@click="setToolActive($event, 'CobbAngle')">
|
||
<svg-icon icon-class="cobb" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 椭圆测量 -->
|
||
<button :title="$t('trials:dicom-show:EllipticalRoi')" class="btn-link" data-tool="EllipticalRoi"
|
||
@click="setToolActive($event, 'EllipticalRoi')">
|
||
<svg-icon icon-class="oval" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 矩形测量 -->
|
||
<button :title="$t('trials:dicom-show:RectangleRoi')" class="btn-link" data-tool="RectangleRoi"
|
||
@click="setToolActive($event, 'RectangleRoi')">
|
||
<svg-icon icon-class="rectangle" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 多边形标记 -->
|
||
<button :title="$t('trials:dicom-show:FreehandRoi')" class="btn-link" data-tool="FreehandRoi"
|
||
@click="setToolActive($event, 'FreehandRoi')">
|
||
<svg-icon icon-class="polygon" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 十字线 -->
|
||
<button :title="$t('trials:dicom-show:Bidirectional')" class="btn-link" data-tool="Bidirectional"
|
||
@click="setToolActive($event, 'Bidirectional')">
|
||
<svg-icon icon-class="bidirection" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 文字标注 -->
|
||
<button :title="$t('trials:dicom-show:ArrowAnnotate')" class="btn-link" data-tool="ArrowAnnotate"
|
||
@click="setToolActive($event, 'ArrowAnnotate')">
|
||
<svg-icon icon-class="label" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 清除测量和标记 -->
|
||
<button :title="$t('trials:dicom-show:Eraser')" class="btn-link" data-tool="Eraser"
|
||
@click="setToolActive($event, 'Eraser')">
|
||
<svg-icon icon-class="clear" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 截屏 -->
|
||
<button :title="$t('trials:dicom-show:image')" class="btn-link" @click="currentDicomCanvas.saveImage()">
|
||
<svg-icon icon-class="image" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 标签 -->
|
||
<button :title="$t('trials:dicom-show:tags')" class="btn-link" @click="currentDicomCanvas.showTags()">
|
||
<svg-icon icon-class="dictionary" style="font-size:20px;" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="measureTool-wrapper">
|
||
<!-- 播放 -->
|
||
<div class="sideTool-title">{{ $t('trials:dicom-show:play') }}</div>
|
||
<div class="sideTool-wrapper">
|
||
<!-- 第一帧 -->
|
||
<button class="btn-link" :title="$t('trials:dicom-show:firstframe')"
|
||
@click="currentDicomCanvas.scrollPage(-9999)">
|
||
<svg-icon icon-class="firstframe" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 显示上一张影像 -->
|
||
<button class="btn-link" :title="$t('trials:dicom-show:previousframe')"
|
||
@click="currentDicomCanvas.scrollPage(-1)">
|
||
<svg-icon icon-class="previousframe" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 播放 -->
|
||
<button class="btn-link" :title="$t('trials:dicom-show:play')" @click="clipPlay">
|
||
<svg-icon :icon-class="currentDicomCanvas.toolState.clipPlaying ? 'stop' : 'play'"
|
||
style="font-size:20px;" />
|
||
</button>
|
||
<!-- 下一帧 -->
|
||
<button class="btn-link" :title="$t('trials:dicom-show:nextframe')" @click="currentDicomCanvas.scrollPage(1)">
|
||
<svg-icon icon-class="nextframe" style="font-size:20px;" />
|
||
</button>
|
||
<!-- 最后一帧 -->
|
||
<button class="btn-link" :title="$t('trials:dicom-show:lastframe')"
|
||
@click="currentDicomCanvas.scrollPage(9999)">
|
||
<svg-icon icon-class="lastframe" style="font-size:20px;" />
|
||
</button>
|
||
<select v-model="fps" class="sidetool-select" style="width:60px" @change="setDicomCanvasfps($event)">
|
||
<!-- 默认值 -->
|
||
<option :value="5">5</option>
|
||
<option :value="10">10</option>
|
||
<option :value="15">15</option>
|
||
<option :value="20">20</option>
|
||
<option :value="30">30</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="measureTool-wrapper">
|
||
<!-- 颜色 -->
|
||
<div class="sideTool-title">{{ $t('trials:dicom-show:color') }}</div>
|
||
<div class="sideTool-wrapper">
|
||
<!-- 预设窗位值 -->
|
||
<label>{{ $t('trials:dicom-show:dicomCanvasWwwc') }}:</label>
|
||
<select v-model="wwwcList[currentDicomCanvasIndex]" class="sidetool-select" style="width:100px"
|
||
@change="setDicomCanvasWwwc($event)">
|
||
<!-- 默认值 -->
|
||
<option :value="-1">{{ $t('trials:dicom-show:default') }}</option>
|
||
<!-- 自定义 -->
|
||
<option :value="0">{{ $t('trials:dicom-show:custom') }}</option>
|
||
<!-- 区域窗宽 -->
|
||
<option :value="1" style="border-bottom:1px solid #fff;">{{ $t('trials:reading:button:wwwcRegion') }}
|
||
</option>
|
||
<option :value="2">CT Abdomen</option>
|
||
<option :value="3">CT Angio</option>
|
||
<option :value="4">CT Bone</option>
|
||
<option :value="5">CT Brain</option>
|
||
<option :value="6">CT Chest</option>
|
||
<option :value="7">CT Lungs</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="sideTool-wrapper">
|
||
<!-- 伪彩色 -->
|
||
<label>{{ $t('trials:dicom-show:pseudocolor') }}:</label>
|
||
<select v-model="colorList[currentDicomCanvasIndex]" class="sidetool-select" style="width:90px"
|
||
@change="setColormap($event)">
|
||
<!-- 默认值 -->
|
||
<option value>{{ $t('trials:dicom-show:default') }}</option>
|
||
<option v-for="(item, index) in colormapsList" :key="index" :value="item.id">{{ item.name }}</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 患者信息 -->
|
||
<div class="measureTool-wrapper patient-form"
|
||
v-if="type === 'Study' && modality && ['PT、CT', 'CT、PT', 'PET-CT'].includes(modality)">
|
||
<div class="sideTool-title">{{ $t('trials:tab:patientData') }}</div>
|
||
<div class="sideTool-wrapper">
|
||
<el-form ref="patientForm" size="mini" :model="formData" :rules="rules" label-width="150" v-loading="formLoading">
|
||
<!-- 性别 -->
|
||
<el-form-item :label="$t('trials:ptData:label:patientSex')" prop="PatientSex">
|
||
<el-select v-model="formData.PatientSex" :placeholder="$t('common:ruleMessage:select')"
|
||
style="width: 100%" :disabled="!isEdit">
|
||
<el-option :label="$t('trials:patientSex:male')" value="M"></el-option>
|
||
<el-option :label="$t('trials:patientSex:female')" value="F"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
<!-- 体重(kg) 例如 70.5-->
|
||
<el-form-item :label="$t('trials:ptData:label:patientWeight')" prop="PatientWeight">
|
||
<el-input v-model.number="formData.PatientWeight" :placeholder="$t('trials:patientWeight:eg')"
|
||
style="width: 100%" :disabled="!isEdit"></el-input>
|
||
</el-form-item>
|
||
<!-- 总剂量(Bq) 例如 740000000-->
|
||
<el-form-item :label="$t('trials:ptData:label:totalDose')" prop="RadionuclideTotalDose">
|
||
<el-input v-model.number="formData.RadionuclideTotalDose" :placeholder="$t('trials:totalDose:eg')"
|
||
style="width: 100%" :disabled="!isEdit"></el-input>
|
||
</el-form-item>
|
||
<!-- 半衰期(s) 例如 21600-->
|
||
<el-form-item :label="$t('trials:ptData:label:halfLife')" prop="RadionuclideHalfLife">
|
||
<el-input v-model.number="formData.RadionuclideHalfLife" :placeholder="$t('trials:halfLife:eg')"
|
||
style="width: 100%" :disabled="!isEdit"></el-input>
|
||
</el-form-item>
|
||
<!-- 注射时间(s) Unix 秒 或 相对秒-->
|
||
<el-form-item :label="$t('trials:ptData:label:injectTime')" prop="RadiopharmaceuticalStartTime">
|
||
<el-input v-model.number="formData.RadiopharmaceuticalStartTime" :placeholder="$t('trials:injectTime:eg')"
|
||
style="width: 100%" @input="computeTimeRelation" :disabled="!isEdit"></el-input>
|
||
</el-form-item>
|
||
<!-- 成像时间(s) Unix 秒 或 相对秒-->
|
||
<el-form-item :label="$t('trials:ptData:label:acquisitionTime')" prop="AcquisitionTime">
|
||
<el-input v-model.number="formData.AcquisitionTime" :placeholder="$t('trials:injectTime:eg')"
|
||
style="width: 100%" @input="computeTimeRelation" :disabled="!isEdit"></el-input>
|
||
</el-form-item>
|
||
<!-- 时间一致性检查 -->
|
||
<!-- <el-form-item :label="$t('trials:ptData:label:timeCheck')">
|
||
<el-input v-model="formData.TimeCheck" disabled style="width: 100%"></el-input>
|
||
</el-form-item> -->
|
||
|
||
<!-- 提交 -->
|
||
<el-form-item style="margin-top: 20px;text-align: right;" v-if="isEdit">
|
||
<el-button type="primary" @click="submitForm" size="small">{{ $t('trials:ptData:button:submit')
|
||
}}</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<el-dialog v-if="customWwc.visible" :visible.sync="customWwc.visible" :close-on-click-modal="false"
|
||
:title="customWwc.title" width="400px" custom-class="base-dialog-wrapper" append-to-body>
|
||
<CustomWwwcForm @close="customWwc.visible = false" @setWwwc="setWwwc" />
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import DicomCanvas from './DicomCanvas'
|
||
import CustomWwwcForm from './CustomWwwcForm'
|
||
import * as cornerstone from 'cornerstone-core'
|
||
import * as cornerstoneMath from 'cornerstone-math'
|
||
import * as cornerstoneTools from 'cornerstone-tools'
|
||
import Hammer from 'hammerjs'
|
||
// cornerstone.imageCache.setMaximumSizeBytes(0)
|
||
|
||
cornerstoneTools.external.cornerstone = cornerstone
|
||
cornerstoneTools.external.Hammer = Hammer
|
||
cornerstoneTools.external.cornerstoneMath = cornerstoneMath
|
||
console.log(cornerstoneTools, 'cornerstoneTools')
|
||
console.log(cornerstone, 'cornerstone')
|
||
import '@/utils/dialog'
|
||
import { studyMaskImage, studyUndoMaskImage } from "@/api/reading"
|
||
import {
|
||
getPatientInfo,
|
||
editPatientInfo
|
||
} from '@/api/trials'
|
||
import { setPTClinicalDataForInstance, clearPTClinicalDataCache } from '@/utils/ptClinicalDataCache'
|
||
export default {
|
||
name: 'DicomsViewer',
|
||
components: {
|
||
DicomCanvas,
|
||
CustomWwwcForm,
|
||
},
|
||
props: {
|
||
loading: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
Comparison: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
modality: {
|
||
type: String,
|
||
default: ''
|
||
}
|
||
},
|
||
watch: {
|
||
modality: {
|
||
immediate: true,
|
||
handler(v) {
|
||
if (v) {
|
||
if (this.type === 'Study' && ['PT、CT', 'CT、PT', 'PET-CT'].includes(v)) {
|
||
this.$nextTick(()=>{
|
||
this.getPatientInfo()
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
hasAnonymous: false,
|
||
isAnonymous: false,
|
||
isComparison: false,
|
||
activeTool: '',
|
||
activeItem: 'dicomCanvas0',
|
||
layoutRow: 1,
|
||
layoutCol: 1,
|
||
currentDicomCanvasIndex: 0,
|
||
currentDicomCanvas: {
|
||
toolState: {
|
||
clipPlaying: false,
|
||
},
|
||
},
|
||
rowHeight: '100%',
|
||
sync: {
|
||
Wwwc: null,
|
||
},
|
||
colormapsList: [],
|
||
rotateList: [],
|
||
colorList: [],
|
||
wwwcList: [],
|
||
layout: null,
|
||
seriesList: [],
|
||
series: {},
|
||
customWwc: { visible: false, title: null },
|
||
fps: 15,
|
||
formData: {
|
||
Id: '',
|
||
PatientSex: '',
|
||
PatientWeight: null,
|
||
RadionuclideTotalDose: null,
|
||
RadionuclideHalfLife: null,
|
||
RadiopharmaceuticalStartTime: null,
|
||
AcquisitionTime: null,
|
||
TimeCheck: ''
|
||
},
|
||
rules: {
|
||
PatientSex: [
|
||
{ required: true, message: this.$t('common:ruleMessage:select'), trigger: 'change' }
|
||
],
|
||
PatientWeight: [
|
||
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: 'blur' },
|
||
{ type: 'number', min: 0, message: this.$t('trials:ptData:ruleMessage:number1'), trigger: 'blur' }//数值必须大于0
|
||
],
|
||
RadionuclideTotalDose: [
|
||
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: 'blur' },
|
||
{ type: 'number', min: 0, message: this.$t('trials:ptData:ruleMessage:number1'), trigger: 'blur' }//数值必须大于0
|
||
],
|
||
RadionuclideHalfLife: [
|
||
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: 'blur' },
|
||
{ type: 'number', min: 0, message: this.$t('trials:ptData:ruleMessage:number1'), trigger: 'blur' }
|
||
],
|
||
RadiopharmaceuticalStartTime: [
|
||
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: 'blur' },
|
||
{ type: 'number', message: this.$t('trials:ptData:ruleMessage:number2'), trigger: 'blur' }//请输入数字
|
||
],
|
||
AcquisitionTime: [
|
||
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: 'blur' },
|
||
{ type: 'number', message: this.$t('trials:ptData:ruleMessage:number2'), trigger: 'blur' },//请输入数字
|
||
// 自定义校验:确保成像时间不早于注射时间
|
||
{ validator: this.validateTime, trigger: 'blur' }
|
||
]
|
||
},
|
||
formLoading: false,
|
||
type: '',
|
||
isEdit: 0
|
||
}
|
||
},
|
||
mounted() {
|
||
let type = this.$router.currentRoute.query.type ? this.$router.currentRoute.query.type : ''
|
||
this.hasAnonymous = type === 'Study'
|
||
this.customWwc = {
|
||
visible: false,
|
||
title: this.$t('DicomViewer:data:customWwc'),
|
||
}
|
||
this.rotateList[0] = '1'
|
||
this.colorList[0] = ''
|
||
this.wwwcList[0] = '-1'
|
||
this.colormapsList = cornerstone.colors.getColormapsList()
|
||
this.currentDicomCanvas = this.$refs['dicomCanvas0']
|
||
this.type = this.$route.query.type
|
||
this.isEdit = parseInt(this.$route.query.showDelete)
|
||
},
|
||
beforeDestroy() {
|
||
clearPTClinicalDataCache()
|
||
},
|
||
|
||
methods: {
|
||
comparison(f) {
|
||
this.isComparison = f
|
||
this.$emit("update:Comparison", f)
|
||
const elements = document.querySelectorAll('.dicom-item')
|
||
const scope = this
|
||
Array.from(elements).forEach((element, index) => {
|
||
if (element.style.display !== 'none') {
|
||
scope.$refs[`dicomCanvas${index}`].setToolPassive(scope.activeTool)
|
||
}
|
||
})
|
||
scope.activeTool = null
|
||
if (this.isComparison) {
|
||
this.$refs[`dicomCanvas0`].getNote_RectangleRoi().then(obj => {
|
||
let { image } = obj
|
||
let instanceInfo = this.series.instanceInfoList.find(item => item.ImageId === image.imageId)
|
||
if (!instanceInfo.IsMasked) return this.$confirm(this.$t("DicomViewer:anonymous:notMasked"))
|
||
this.changeLayout('1x2')
|
||
let serie = {}
|
||
Object.keys(this.series).forEach(key => {
|
||
if (key !== 'instanceInfoList' && key !== 'imageIds') {
|
||
serie[key] = this.series[key]
|
||
}
|
||
})
|
||
let imageId = image.imageId
|
||
let newImageId = imageId.split("?")[0].split(".MaskDicom_")[0] + "?" + imageId.split("?")[1]
|
||
let info0 = {}, info1 = {}
|
||
Object.keys(instanceInfo).forEach(key => {
|
||
info0[key] = instanceInfo[key]
|
||
info1[key] = instanceInfo[key]
|
||
})
|
||
info0.ImageId = imageId
|
||
info1.ImageId = newImageId
|
||
console.log(info0)
|
||
console.log(info1)
|
||
let dicomCanvas0_info = Object.assign({
|
||
instanceInfoList: [
|
||
info0
|
||
],
|
||
imageIds: [imageId]
|
||
}, serie)
|
||
let dicomCanvas1_info = Object.assign({
|
||
instanceInfoList: [
|
||
info1
|
||
],
|
||
imageIds: [newImageId]
|
||
}, serie)
|
||
console.log(dicomCanvas0_info, 'dicomCanvas0_info')
|
||
console.log(dicomCanvas1_info, 'dicomCanvas0_info')
|
||
this.$refs[`dicomCanvas0`].loadImageStack(dicomCanvas0_info)
|
||
this.$refs[`dicomCanvas1`].loadImageStack(dicomCanvas1_info)
|
||
})
|
||
} else {
|
||
this.changeLayout('1x1')
|
||
this.$refs[`dicomCanvas0`].loadImageStack(this.series)
|
||
}
|
||
|
||
},
|
||
recovery(isAll = false) {
|
||
if (this.isComparison) return false
|
||
this.$refs[`dicomCanvas0`].getNote_RectangleRoi().then(async obj => {
|
||
let { image } = obj
|
||
let instanceInfo = this.series.instanceInfoList.find(item => item.ImageId === image.imageId)
|
||
if (!instanceInfo.IsMasked) return this.$confirm(this.$t("DicomViewer:anonymous:notMasked"))
|
||
let data = {
|
||
// SeriesId: this.series.seriesId,
|
||
instanceIdList: [instanceInfo.Id]
|
||
}
|
||
if (isAll) {
|
||
data.SeriesId = this.series.seriesId
|
||
delete data.instanceIdList
|
||
}
|
||
let res = await this.studyUndoMaskImage(data)
|
||
if (!res) return false
|
||
this.$emit("update:loading", true)
|
||
if (!isAll) {
|
||
let strs = image.imageId.split("?")
|
||
let newImageId = `wadouri:${localStorage.getItem('location') !== 'USA' ? this.OSSclientConfig.basePath : this.OSSclientConfig.basePath}${res[0].Path}?${strs[1]}`
|
||
this.series.instanceInfoList.some(item => {
|
||
if (item.Id === instanceInfo.Id) {
|
||
item.ImageId = newImageId
|
||
item.IsMasked = false
|
||
}
|
||
return item.Id === instanceInfo.Id
|
||
})
|
||
let index = this.series.imageIds.findIndex(item => item === image.imageId)
|
||
this.series.imageIds.splice(index, 1, newImageId)
|
||
await this.$refs[`dicomCanvas0`].reloadImage(newImageId)
|
||
} else {
|
||
let arr = []
|
||
this.series.instanceInfoList.forEach(item => {
|
||
let strs = item.ImageId.split("?")
|
||
let info = res.find(i => item.Id === i.Id)
|
||
let newImageId = `wadouri:${localStorage.getItem('location') !== 'USA' ? this.OSSclientConfig.basePath : this.OSSclientConfig.basePath}${info.Path}?${strs[1]}`
|
||
item.ImageId = newImageId
|
||
item.IsMasked = false
|
||
arr.push(newImageId)
|
||
})
|
||
this.series.imageIds = arr
|
||
console.log(this.series, 'this.series')
|
||
// this.loadImageStack(this.series)
|
||
this.$refs[`dicomCanvas0`].loadImageStack(this.series)
|
||
}
|
||
this.$emit("update:loading", false)
|
||
this.$emit('loadStudy', false)
|
||
})
|
||
},
|
||
anonymousImage(isAll = false) {
|
||
if (this.isComparison) return false
|
||
this.$refs[`dicomCanvas0`].getNote_RectangleRoi().then(async obj => {
|
||
let { toolInfo, image } = obj
|
||
if (!toolInfo || toolInfo.data.length <= 0) return this.$confirm(this.$t("DicomViewer:anonymous:notMark"))
|
||
let instanceInfo = this.series.instanceInfoList.find(item => item.ImageId === image.imageId)
|
||
let data = {
|
||
// SeriesId: this.series.seriesId,
|
||
instanceIdList: [instanceInfo.Id],
|
||
MaskRegionList: []
|
||
}
|
||
if (isAll) {
|
||
data.SeriesId = this.series.seriesId
|
||
delete data.instanceIdList
|
||
}
|
||
toolInfo.data.forEach(item => {
|
||
let currentStart = item.handles.start
|
||
let currentEnd = item.handles.end
|
||
let start = {
|
||
x: currentStart.x > currentEnd.x ? Math.round(currentEnd.x) : Math.round(currentStart.x),
|
||
y: currentStart.y > currentEnd.y ? Math.round(currentEnd.y) : Math.round(currentStart.y),
|
||
}
|
||
let end = {
|
||
x: currentStart.x > currentEnd.x ? Math.round(currentStart.x) : Math.round(currentEnd.x),
|
||
y: currentStart.y > currentEnd.y ? Math.round(currentStart.y) : Math.round(currentEnd.y),
|
||
}
|
||
let width = end.x - start.x
|
||
let height = end.y - start.y
|
||
data.MaskRegionList.push({
|
||
X: start.x,
|
||
Y: start.y,
|
||
Width: width,
|
||
Height: height
|
||
})
|
||
})
|
||
let res = await this.studyMaskImage(data)
|
||
if (!res) return false
|
||
this.$emit("update:loading", true)
|
||
if (!isAll) {
|
||
let strs = image.imageId.split("?")
|
||
let newImageId = `wadouri:${localStorage.getItem('location') !== 'USA' ? this.OSSclientConfig.basePath : this.OSSclientConfig.basePath}${res[0].Path}?${strs[1]}`
|
||
this.series.instanceInfoList.some(item => {
|
||
if (item.Id === instanceInfo.Id) {
|
||
item.ImageId = newImageId
|
||
item.IsMasked = true
|
||
}
|
||
return item.Id === instanceInfo.Id
|
||
})
|
||
let index = this.series.imageIds.findIndex(item => item === image.imageId)
|
||
this.series.imageIds.splice(index, 1, newImageId)
|
||
await this.$refs[`dicomCanvas0`].reloadImage(newImageId)
|
||
} else {
|
||
let arr = []
|
||
this.series.instanceInfoList.forEach(item => {
|
||
let strs = item.ImageId.split("?")
|
||
let info = res.find(i => item.Id === i.Id)
|
||
let newImageId = `wadouri:${localStorage.getItem('location') !== 'USA' ? this.OSSclientConfig.basePath : this.OSSclientConfig.basePath}${info.Path}?${strs[1]}`
|
||
item.ImageId = newImageId
|
||
item.IsMasked = true
|
||
arr.push(newImageId)
|
||
})
|
||
this.series.imageIds = arr
|
||
console.log(this.series, 'this.series')
|
||
// this.loadImageStack(this.series)
|
||
this.$refs[`dicomCanvas0`].loadImageStack(this.series)
|
||
}
|
||
this.$emit("update:loading", false)
|
||
this.$emit('loadStudy', false)
|
||
})
|
||
},
|
||
openAnonymous() {
|
||
this.isAnonymous = !this.isAnonymous
|
||
if (!this.isAnonymous) {
|
||
const elements = document.querySelectorAll('.dicom-item')
|
||
const scope = this
|
||
Array.from(elements).forEach((element, index) => {
|
||
if (element.style.display !== 'none') {
|
||
scope.$refs[`dicomCanvas${index}`].setToolPassive(scope.activeTool)
|
||
}
|
||
})
|
||
scope.activeTool = null
|
||
} else {
|
||
this.activateDicomCanvas(0)
|
||
this.changeLayout('1x1')
|
||
}
|
||
},
|
||
async studyMaskImage(data) {
|
||
try {
|
||
this.$emit("update:loading", true)
|
||
let res = await studyMaskImage(data)
|
||
this.$emit("update:loading", false)
|
||
if (res.IsSuccess) {
|
||
return res.Result.OkList
|
||
}
|
||
return false
|
||
} catch (err) {
|
||
console.log(err)
|
||
this.$emit("update:loading", false)
|
||
return false
|
||
}
|
||
},
|
||
async studyUndoMaskImage(data) {
|
||
try {
|
||
this.$emit("update:loading", true)
|
||
let res = await studyUndoMaskImage(data)
|
||
this.$emit("update:loading", false)
|
||
if (res.IsSuccess) {
|
||
return res.Result.OkList
|
||
}
|
||
return false
|
||
} catch (err) {
|
||
console.log(err)
|
||
this.$emit("update:loading", false)
|
||
return false
|
||
}
|
||
},
|
||
loadImageStack(dicomSeries) {
|
||
this.currentDicomCanvas.toolState.clipPlaying = false
|
||
this.$nextTick(() => {
|
||
this.series = Object.assign({}, dicomSeries)
|
||
this.seriesList = [this.series]
|
||
this.currentDicomCanvas.loadImageStack(this.series)
|
||
if (
|
||
this.formData.PatientWeight != null ||
|
||
this.formData.RadionuclideTotalDose != null ||
|
||
this.formData.RadionuclideHalfLife != null ||
|
||
this.formData.RadiopharmaceuticalStartTime != null ||
|
||
this.formData.AcquisitionTime != null
|
||
) {
|
||
this.cachePtClinicalDataToInstances()
|
||
}
|
||
})
|
||
},
|
||
loadOtherImageStack(seriesList) {
|
||
this.$nextTick(() => {
|
||
this.seriesList = seriesList
|
||
const elements = document.querySelectorAll('.dicom-item')
|
||
Array.from(elements).forEach((element, index) => {
|
||
const canvasIndex = element.getAttribute('data-index')
|
||
if (index < seriesList.length && element.style.display !== 'none') {
|
||
const series = Object.assign({}, seriesList[index])
|
||
this.$refs[`dicomCanvas${canvasIndex}`].loadImageStack(series)
|
||
}
|
||
})
|
||
if (
|
||
this.formData.PatientWeight != null ||
|
||
this.formData.RadionuclideTotalDose != null ||
|
||
this.formData.RadionuclideHalfLife != null ||
|
||
this.formData.RadiopharmaceuticalStartTime != null ||
|
||
this.formData.AcquisitionTime != null
|
||
) {
|
||
this.cachePtClinicalDataToInstances()
|
||
}
|
||
})
|
||
},
|
||
activateDicomCanvas(index) {
|
||
if (index !== this.currentDicomCanvasIndex) {
|
||
this.currentDicomCanvasIndex = index
|
||
this.currentDicomCanvas = this.$refs[`dicomCanvas${index}`]
|
||
this.currentDicomCanvas.tabIndex = 0
|
||
this.activeItem = `dicomCanvas${index}`
|
||
if (this.sync.ReferenceLines) {
|
||
if (this.sync['ReferenceLines']) {
|
||
const elements = this.sync['ReferenceLines'].getSourceElements()
|
||
if (elements.length > 0) {
|
||
this.$refs[`dicomCanvas${index}`].removeTarget(
|
||
this.sync['ReferenceLines']
|
||
)
|
||
this.sync['ReferenceLines'].addTarget(elements[0])
|
||
this.sync['ReferenceLines'].removeSource(elements[0])
|
||
this.$refs[`dicomCanvas${index}`].addSourceElement(
|
||
this.sync['ReferenceLines']
|
||
)
|
||
}
|
||
}
|
||
}
|
||
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'
|
||
}
|
||
}
|
||
},
|
||
changeLayout(event) {
|
||
const arr = event.target ? event.target.value.split('x') : event.split('x')
|
||
this.layoutRow = parseInt(arr[0])
|
||
this.layoutCol = parseInt(arr[1])
|
||
this.rowHeight = 100 / this.layoutRow + '%'
|
||
this.$forceUpdate()
|
||
this.$nextTick(() => {
|
||
const elements = document.querySelectorAll('.cornerstone-element')
|
||
Array.from(elements).forEach((element) => {
|
||
cornerstone.enable(element)
|
||
cornerstone.resize(element)
|
||
})
|
||
})
|
||
},
|
||
setFullScreen(e) {
|
||
if (this.layoutRow === 1 && this.layoutCol === 1) return
|
||
if (this.layout) {
|
||
e.currentTarget.classList.remove('dicom-item-fullscreen')
|
||
const arr = this.layout.split('x')
|
||
this.layoutRow = parseInt(arr[0])
|
||
this.layoutCol = parseInt(arr[1])
|
||
this.rowHeight = 100 / this.layoutRow + '%'
|
||
this.$forceUpdate()
|
||
this.$nextTick(() => {
|
||
const elements = document.querySelectorAll('.cornerstone-element')
|
||
Array.from(elements).forEach((element) => {
|
||
cornerstone.enable(element)
|
||
cornerstone.resize(element)
|
||
})
|
||
})
|
||
this.layout = null
|
||
} else {
|
||
this.layout = `${this.layoutRow}x${this.layoutCol}`
|
||
e.currentTarget.classList.add('dicom-item-fullscreen')
|
||
cornerstone.enable(e.currentTarget.children[0])
|
||
cornerstone.resize(e.currentTarget.children[0])
|
||
}
|
||
},
|
||
fillColor() {
|
||
const elements = document.querySelectorAll('.dicom-item')
|
||
const scope = this
|
||
Array.from(elements).forEach((element, index) => {
|
||
if (element.style.display !== 'none') {
|
||
scope.$refs[`dicomCanvas${index}`].fillColor()
|
||
}
|
||
})
|
||
},
|
||
setToolActive(e, toolName) {
|
||
const elements = document.querySelectorAll('.dicom-item')
|
||
if (e.currentTarget.classList.contains('activeTool')) {
|
||
e.currentTarget.classList.remove('activeTool')
|
||
const scope = this
|
||
scope.activeTool = null
|
||
Array.from(elements).forEach((element, index) => {
|
||
if (element.style.display !== 'none') {
|
||
scope.$refs[`dicomCanvas${index}`].setToolPassive(toolName)
|
||
}
|
||
})
|
||
} else {
|
||
const toolButtons = document.querySelectorAll('[data-tool]')
|
||
Array.from(toolButtons).forEach((toolBtn) => {
|
||
toolBtn.classList.remove('activeTool')
|
||
})
|
||
e.currentTarget.classList.add('activeTool')
|
||
|
||
const scope = this
|
||
scope.activeTool = toolName
|
||
Array.from(elements).forEach((element, index) => {
|
||
if (element.style.display !== 'none') {
|
||
scope.$refs[`dicomCanvas${index}`].setToolActive(toolName)
|
||
}
|
||
})
|
||
}
|
||
},
|
||
setDicomCanvasWwwc(event) {
|
||
this.wwwcList[this.currentDicomCanvasIndex] = event.target.value
|
||
const type = parseInt(event.target.value)
|
||
if (type === -1) {
|
||
// 默认值
|
||
this.currentDicomCanvas.resetWwwc()
|
||
} else if (type === 0) {
|
||
// 自定义
|
||
this.customWwc.visible = true
|
||
} else if (type === 1) {
|
||
// 区域窗宽
|
||
this.currentDicomCanvas.setToolActive('WwwcRegion')
|
||
} else if (type === 2) {
|
||
this.currentDicomCanvas.setWwwc(400, 60)
|
||
} else if (type === 3) {
|
||
this.currentDicomCanvas.setWwwc(600, 300)
|
||
} else if (type === 4) {
|
||
this.currentDicomCanvas.setWwwc(1500, 300)
|
||
} else if (type === 5) {
|
||
this.currentDicomCanvas.setWwwc(80, 40)
|
||
} else if (type === 6) {
|
||
this.currentDicomCanvas.setWwwc(400, 40)
|
||
} else if (type === 7) {
|
||
this.currentDicomCanvas.setWwwc(1500, -400)
|
||
}
|
||
},
|
||
setWwwc(v) {
|
||
this.currentDicomCanvas.setWwwc(v.ww, v.wc)
|
||
this.customWwc.visible = false
|
||
},
|
||
toggleInvert() {
|
||
this.currentDicomCanvas.toggleInvert()
|
||
},
|
||
setDicomCanvasRotate(value) {
|
||
const type = parseInt(value)
|
||
if (type === 1) {
|
||
this.currentDicomCanvas.resetRotate()
|
||
} else if (type === 2) {
|
||
this.currentDicomCanvas.setRotate(true, false, 0, type)
|
||
} else if (type === 3) {
|
||
this.currentDicomCanvas.setRotate(false, true, 0, type)
|
||
} else if (type === 4) {
|
||
this.currentDicomCanvas.setRotate(false, false, -90, type)
|
||
} else if (type === 5) {
|
||
this.currentDicomCanvas.setRotate(false, false, 90, type)
|
||
}
|
||
},
|
||
setColormap(event) {
|
||
this.colorList[this.currentDicomCanvasIndex] = event.target.value
|
||
this.currentDicomCanvas.setColormap(event.target.value)
|
||
},
|
||
setDicomCanvasfps(event) {
|
||
this.currentDicomCanvas.setFps(event.target.value)
|
||
},
|
||
clipPlay() {
|
||
this.currentDicomCanvas.setFps(this.fps)
|
||
this.currentDicomCanvas.toggleClipPlay()
|
||
},
|
||
fitToType(e, type) {
|
||
const toolButtons = document.querySelectorAll('[data-tool]')
|
||
Array.from(toolButtons).forEach((toolBtn) => {
|
||
toolBtn.classList.remove('activeTool')
|
||
})
|
||
e.currentTarget.classList.add('activeTool')
|
||
if (type === 'fitToWindow') {
|
||
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`].fitToWindow()
|
||
} else {
|
||
this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`].fitToImage()
|
||
}
|
||
},
|
||
setAnnotationSync(e) {
|
||
if (this.layoutRow * this.layoutCol === 1) {
|
||
return
|
||
}
|
||
this.resetSync('annotationSync')
|
||
let isActive = true
|
||
const toolBtn = e.currentTarget
|
||
if (toolBtn.classList.contains('activeTool')) {
|
||
toolBtn.classList.remove('activeTool')
|
||
isActive = false
|
||
} else {
|
||
toolBtn.classList.add('activeTool')
|
||
}
|
||
if (!this.sync.annotationSync) {
|
||
this.sync.annotationSync = new cornerstoneTools.Synchronizer(
|
||
'cornerstoneimagerendered',
|
||
cornerstoneTools.updateImageSynchronizer
|
||
)
|
||
}
|
||
const scope = this
|
||
const elements = document.querySelectorAll('.dicom-item')
|
||
Array.from(elements).forEach((element, index) => {
|
||
if (element.style.display !== 'none') {
|
||
if (isActive) {
|
||
if (index !== scope.currentDicomCanvasIndex) {
|
||
scope.$refs[`dicomCanvas${index}`].addTargetElement(
|
||
scope.sync['annotationSync']
|
||
)
|
||
} else {
|
||
scope.$refs[`dicomCanvas${index}`].addSourceElement(
|
||
scope.sync['annotationSync']
|
||
)
|
||
}
|
||
scope.$refs[`dicomCanvas${index}`].activeAnnotationSync(
|
||
scope.sync.annotationSync
|
||
)
|
||
} else {
|
||
scope.$refs[`dicomCanvas${index}`].disabledAnnotationSync(
|
||
scope.sync.annotationSync
|
||
)
|
||
}
|
||
}
|
||
})
|
||
if (!isActive) {
|
||
scope.sync.annotationSync = null
|
||
}
|
||
},
|
||
resetSync(prop) {
|
||
for (const key in this.sync) {
|
||
if (key !== prop) {
|
||
if (this.sync[prop]) {
|
||
this.sync[prop] = null
|
||
}
|
||
}
|
||
}
|
||
},
|
||
// 时间一致性校验
|
||
validateTime(rule, value, callback) {
|
||
const { RadiopharmaceuticalStartTime } = this.formData
|
||
if (value && RadiopharmaceuticalStartTime !== null && value < RadiopharmaceuticalStartTime) {
|
||
callback(new Error(this.$t('trials:ptData:ruleMessage:number3')))//成像时间不能早于注射时间
|
||
} else {
|
||
callback()
|
||
}
|
||
},
|
||
computeTimeRelation() {
|
||
const startTime = this.formData.RadiopharmaceuticalStartTime
|
||
const acquireTime = this.formData.AcquisitionTime
|
||
|
||
if (!startTime || !acquireTime) {
|
||
this.formData.TimeCheck = ''
|
||
return
|
||
}
|
||
|
||
if (startTime <= acquireTime) {
|
||
this.formData.TimeCheck = this.$t('trials:ptData:timeCheck:val1') //注射时间 ≤ 成像时间
|
||
} else {
|
||
this.formData.TimeCheck = this.$t('trials:ptData:timeCheck:val2') //注射时间 > 成像时间
|
||
}
|
||
},
|
||
async getPatientInfo() {
|
||
try {
|
||
this.formLoading = true
|
||
let studyId = this.$route.query.studyId
|
||
let res = await getPatientInfo({studyId: studyId})
|
||
this.formData = {
|
||
Id: res.Result.Id || '',
|
||
PatientSex: res.Result.PatientSex || '',
|
||
PatientWeight: parseFloat(res.Result.PatientWeight) || null,
|
||
RadionuclideTotalDose: parseFloat(res.Result.RadionuclideTotalDose) || null,
|
||
RadionuclideHalfLife: parseFloat(res.Result.RadionuclideHalfLife) || null,
|
||
RadiopharmaceuticalStartTime: parseFloat(res.Result.RadiopharmaceuticalStartTime) || '',
|
||
AcquisitionTime: parseFloat(res.Result.AcquisitionTime) || '',
|
||
TimeCheck: ''
|
||
}
|
||
this.computeTimeRelation()
|
||
// 缓存 PT 临床数据:用于 2D SUV 计算时优先使用接口/人工录入值
|
||
this.cachePtClinicalDataToInstances()
|
||
this.formLoading = false
|
||
} catch(e) {
|
||
this.formLoading = false
|
||
console.log(e)
|
||
}
|
||
},
|
||
cachePtClinicalDataToInstances() {
|
||
const clinicalData = {
|
||
PatientSex: this.formData.PatientSex,
|
||
PatientWeight: this.formData.PatientWeight,
|
||
RadionuclideTotalDose: this.formData.RadionuclideTotalDose,
|
||
RadionuclideHalfLife: this.formData.RadionuclideHalfLife,
|
||
RadiopharmaceuticalStartTime: this.formData.RadiopharmaceuticalStartTime,
|
||
AcquisitionTime: this.formData.AcquisitionTime
|
||
}
|
||
const seriesList = Array.isArray(this.seriesList) ? this.seriesList : []
|
||
seriesList.forEach(series => {
|
||
const instanceInfoList = series?.instanceInfoList
|
||
if (Array.isArray(instanceInfoList) && instanceInfoList.length > 0) {
|
||
instanceInfoList.forEach(instance => {
|
||
if (instance && instance.Id != null) {
|
||
setPTClinicalDataForInstance(instance.Id, clinicalData)
|
||
}
|
||
})
|
||
return
|
||
}
|
||
const imageIds = series?.imageIds
|
||
if (Array.isArray(imageIds) && imageIds.length > 0) {
|
||
imageIds.forEach(imageId => {
|
||
const instanceId = this.getInstanceIdFromImageId(imageId)
|
||
if (instanceId) {
|
||
setPTClinicalDataForInstance(instanceId, clinicalData)
|
||
}
|
||
})
|
||
}
|
||
})
|
||
},
|
||
getInstanceIdFromImageId(imageId) {
|
||
try {
|
||
const qIndex = String(imageId).indexOf('?')
|
||
if (qIndex === -1) return null
|
||
const params = new URLSearchParams(String(imageId).slice(qIndex + 1))
|
||
const instanceId = params.get('instanceId')
|
||
return instanceId ? String(instanceId).trim() : null
|
||
} catch (e) {
|
||
return null
|
||
}
|
||
},
|
||
refreshDicomAfterClinicalDataChanged() {
|
||
// 患者信息保存后,强制刷新画布与标注统计,确保 SUV 等数据显示使用最新的 PT 临床数据口径
|
||
const toolTypes = [
|
||
'Probe',
|
||
'RectangleRoi',
|
||
'EllipticalRoi',
|
||
'FreehandRoi',
|
||
'Bidirectional',
|
||
'Length',
|
||
'ArrowAnnotate',
|
||
'Angle',
|
||
'CobbAngle'
|
||
]
|
||
const elements = document.querySelectorAll('.dicom-item')
|
||
Array.from(elements).forEach((wrapper) => {
|
||
const index = wrapper.getAttribute('data-index')
|
||
const canvasComp = index !== null ? this.$refs[`dicomCanvas${index}`] : null
|
||
const element = canvasComp?.$refs?.canvas
|
||
if (!element) return
|
||
toolTypes.forEach((toolType) => {
|
||
const toolState = cornerstoneTools.getToolState(element, toolType)
|
||
if (toolState && Array.isArray(toolState.data)) {
|
||
toolState.data.forEach((d) => {
|
||
if (d && typeof d === 'object') {
|
||
d.invalidated = true
|
||
}
|
||
})
|
||
}
|
||
})
|
||
cornerstone.updateImage(element, true)
|
||
})
|
||
},
|
||
async submitForm() {
|
||
try {
|
||
let valid = await this.$refs.patientForm.validate()
|
||
if (!valid) return
|
||
this.formLoading = true
|
||
let res = await editPatientInfo(this.formData)
|
||
this.formLoading = false
|
||
if (res.IsSuccess) {
|
||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
||
this.cachePtClinicalDataToInstances()
|
||
this.refreshDicomAfterClinicalDataChanged()
|
||
}
|
||
} catch (e) {
|
||
this.formLoading = false
|
||
console.log(e)
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.dicom-wrapper {
|
||
display: flex;
|
||
height: 100%;
|
||
}
|
||
|
||
.dicom-wrapper .case-dialog-class {
|
||
position: fixed;
|
||
left: 25%;
|
||
pointer-events: auto;
|
||
display: block;
|
||
overflow: auto;
|
||
}
|
||
|
||
.dicom-wrapper .case-dialog-div {
|
||
pointer-events: none;
|
||
}
|
||
|
||
.dicom-wrapper .case-dialog-class .el-dialog__body {
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.dicom-wrapper .el-dialog__header {
|
||
padding: 15px;
|
||
}
|
||
|
||
.dicom-wrapper .el-dialog__body {
|
||
padding: 10px 20px;
|
||
}
|
||
|
||
.dicom-viewer {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
position: relative;
|
||
}
|
||
|
||
.Anonymous {
|
||
position: absolute;
|
||
border-radius: 5px;
|
||
bottom: 30px;
|
||
left: 5%;
|
||
right: 5%;
|
||
width: 90%;
|
||
height: 60px;
|
||
padding: 0 10px;
|
||
background-color: rgba(255, 255, 255, .2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
z-index: 9999;
|
||
|
||
.btn {
|
||
width: 15%;
|
||
text-align: center;
|
||
height: 40px;
|
||
line-height: 30px;
|
||
border-radius: 15px;
|
||
background-color: rgba(255, 255, 255, .3);
|
||
cursor: pointer;
|
||
padding: 5px 10px;
|
||
border: 1px solid rgba(255, 255, 255, .7);
|
||
|
||
&:hover {
|
||
background-color: rgba(255, 255, 255, .5);
|
||
}
|
||
}
|
||
|
||
.activeBtn {
|
||
background-color: rgba(255, 255, 255, .5);
|
||
}
|
||
|
||
.isNoted {
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
|
||
.Anonymous .btn {
|
||
width: 15%;
|
||
text-align: center;
|
||
height: 40px;
|
||
line-height: 30px;
|
||
border-radius: 15px;
|
||
background-color: rgba(255, 255, 255, .3);
|
||
cursor: pointer;
|
||
padding: 5px 10px;
|
||
border: 1px solid rgba(255, 255, 255, .7);
|
||
}
|
||
.Anonymous .btn:hover {
|
||
background-color: rgba(255, 255, 255, .5);
|
||
}
|
||
|
||
.Anonymous .activeBtn {
|
||
background-color: rgba(255, 255, 255, .5);
|
||
}
|
||
|
||
|
||
.btnBox {
|
||
display: inline-block;
|
||
width: 80px;
|
||
text-align: center;
|
||
height: 30px;
|
||
line-height: 20px;
|
||
border-radius: 15px;
|
||
background-color: rgba(255, 255, 255, .3);
|
||
cursor: pointer;
|
||
padding: 5px 10px;
|
||
border: 1px solid rgba(255, 255, 255, .7);
|
||
margin: 5px;
|
||
}
|
||
|
||
.dicom-wrapper .dicom-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex: 1;
|
||
width: 100%;
|
||
}
|
||
|
||
.dicom-wrapper .dicom-item {
|
||
position: relative;
|
||
width: 0;
|
||
flex: 1;
|
||
flex-shrink: 1;
|
||
box-sizing: border-box;
|
||
padding: 5px;
|
||
border: 1px solid #c8c8c8;
|
||
}
|
||
|
||
.dicom-wrapper .activeItem {
|
||
border: 2px solid chocolate;
|
||
}
|
||
|
||
.dicom-item-fullscreen {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 1;
|
||
}
|
||
|
||
.dicom-wrapper ::-webkit-scrollbar {
|
||
width: 7px;
|
||
height: 7px;
|
||
}
|
||
|
||
.dicom-wrapper ::-webkit-scrollbar-thumb {
|
||
border-radius: 10px;
|
||
background: gray;
|
||
}
|
||
|
||
.dicom-wrapper .dicom-tools {
|
||
/* display: flex;
|
||
flex-direction: column; */
|
||
width: 300px;
|
||
height: 100%;
|
||
background-color: #323232;
|
||
margin: 0;
|
||
padding: 0;
|
||
margin-left: 2px;
|
||
/* border: 1px solid blueviolet; */
|
||
color: #d0d0d0;
|
||
font-size: 13px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.dicom-wrapper .measure-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.measure-wrapper .measure-list {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.measure-list-header {
|
||
padding: 2px 0px;
|
||
border-bottom: 1px solid gray;
|
||
}
|
||
|
||
.measure-wrapper ul {
|
||
margin: 0;
|
||
padding: 0 5px;
|
||
list-style: none;
|
||
}
|
||
|
||
.measure-wrapper ul li {
|
||
height: 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 5px;
|
||
text-align: center;
|
||
}
|
||
|
||
.measure-wrapper select {
|
||
height: 20px;
|
||
background-color: rgba(0, 0, 0, 0);
|
||
/* color: #323232; */
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
color: #606626;
|
||
}
|
||
|
||
.measure-wrapper select>option {
|
||
color: #323232;
|
||
}
|
||
|
||
/* .measure-wrapper select {
|
||
height: 20px;
|
||
background-color: rgba(0, 0, 0, 0);
|
||
color: #d0d0d0;
|
||
border-radius: 4px;
|
||
}
|
||
.measure-wrapper select > option {
|
||
background-color: #323232;
|
||
} */
|
||
.dicom-canvas {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 1px solid #333333;
|
||
}
|
||
|
||
.dicom-canvas.active {
|
||
border: 1px solid #337ab7;
|
||
}
|
||
|
||
.sideTool-title {
|
||
padding: 5px 8px;
|
||
background-color: #525252;
|
||
color: #bebdbd;
|
||
font-size: 14px;
|
||
margin: 4px;
|
||
}
|
||
|
||
.sideTool-wrapper {
|
||
border-bottom: 1px solid gray;
|
||
margin: 0px 4px;
|
||
padding: 4px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.sideTool-wrapper .btn {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.sidetool-select {
|
||
height: 30px;
|
||
background-color: rgba(0, 0, 0, 0);
|
||
color: #d0d0d0;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.sidetool-select>option {
|
||
background-color: #323232;
|
||
}
|
||
|
||
.sideTool-wrapper button.btn-link {
|
||
height: 40px;
|
||
padding: 8px !important;
|
||
border: 1px solid rgba(37, 37, 37, 1);
|
||
margin: 1px 1px;
|
||
}
|
||
|
||
.sideTool-wrapper .btn-link {
|
||
display: inline-block;
|
||
height: 40px;
|
||
padding: 8px !important;
|
||
border: 1px solid rgba(37, 37, 37, 1);
|
||
margin: 1px 1px;
|
||
}
|
||
|
||
.dicom-wrapper .btn-group {
|
||
position: relative;
|
||
display: inline-block;
|
||
height: 30px;
|
||
padding: 6px 3px;
|
||
vertical-align: middle;
|
||
margin: 1px 1px;
|
||
border-radius: 3px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: silver;
|
||
line-height: 20px !important;
|
||
background: rgba(50, 50, 50, 1);
|
||
border: 1px solid rgba(37, 37, 37, 1);
|
||
min-height: 30px;
|
||
}
|
||
|
||
.dicom-wrapper .btn-group .btn-left {
|
||
position: relative;
|
||
float: left;
|
||
padding: 1px !important;
|
||
margin-left: 0;
|
||
border-bottom-right-radius: 0;
|
||
border-top-right-radius: 0;
|
||
}
|
||
|
||
.dicom-wrapper .activeTool {
|
||
background: #16477b90 !important;
|
||
border: none;
|
||
}
|
||
|
||
.dicom-wrapper .btn-link {
|
||
color: #cccccc;
|
||
background-color: #ffffff00;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.dicom-wrapper .btn-submit {
|
||
cursor: pointer;
|
||
/* background-color: #eeeeee; */
|
||
background: #eeeeee;
|
||
border: 1px solid #dcdfe6;
|
||
border-radius: 4px;
|
||
color: #606266;
|
||
font-size: 13px;
|
||
}
|
||
|
||
/* .dicom-wrapper .btn-link:hover,
|
||
.dicom-wrapper .btn-link:focus,
|
||
.dicom-wrapper .btn-link:active {
|
||
border-color: transparent;
|
||
background-color: transparent;
|
||
} */
|
||
.dicom-wrapper .iconHover:hover {
|
||
color: red;
|
||
}
|
||
|
||
.dicom-wrapper .dropdown {
|
||
position: relative;
|
||
display: inline-block;
|
||
}
|
||
|
||
.dicom-wrapper .dropdown-content {
|
||
display: none;
|
||
position: absolute;
|
||
left: -20px;
|
||
top: 40px;
|
||
color: #d0d0d0;
|
||
background-color: #323232;
|
||
min-width: 100px;
|
||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||
border: 1px solid #4e4e4e;
|
||
padding: 5px;
|
||
}
|
||
|
||
.dicom-wrapper .dropdown:hover .dropdown-content {
|
||
display: block;
|
||
}
|
||
|
||
.dicom-wrapper .dropdown-content div {
|
||
height: 25px;
|
||
line-height: 25px;
|
||
cursor: point;
|
||
}
|
||
|
||
.dicom-wrapper .dropdown-content div:hover {
|
||
background-color: #16477b90;
|
||
}
|
||
|
||
.patient-form .el-form-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.patient-form .el-form-item__label {
|
||
color: #d0d0d0;
|
||
flex: 0 0 150px;
|
||
text-align: left;
|
||
}
|
||
|
||
.patient-form .el-form-item__content {
|
||
flex: 1;
|
||
margin-left: 0;
|
||
}
|
||
|
||
.patient-form .el-input.is-disabled .el-input__inner {
|
||
background-color: #424244;
|
||
}
|
||
|
||
.patient-form .el-input .el-input__inner {
|
||
background-color: #323232;
|
||
color: #d0d0d0;
|
||
}
|
||
</style>
|