在影像上传、影像质控该过程的影像浏览页面,影像阅片任务页面,在影像为加载前的下载过程,需要展示下载过程数据,包括网速、文件大小等
continuous-integration/drone/push Build encountered an error Details

uat
wangxiaoshuang 2025-07-14 16:07:27 +08:00
parent 62a92ea7c9
commit b7f4cccbfa
14 changed files with 976 additions and 798 deletions

View File

@ -3,7 +3,7 @@
id="canvas"
ref="canvas"
v-loading="loading"
element-loading-text="Loading..."
:element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)"
style="width:100%;height:100%;position:relative;"
class="cornerstone-element"
@ -121,6 +121,11 @@ import DicomTags from './DicomTags'
export default {
name: 'DicomCanvas',
components: { DicomTags },
computed: {
NSTip() {
return `${this.$store.state.trials.uploadSize},NS: ${this.$store.state.trials.uploadTip}`
}
},
data() {
return {
loading: false,

View File

@ -12,6 +12,7 @@ import { getCustomTag } from '@/api/reading'
import requestPoolManager from '@/utils/request-pool'
import { getReadingVisitStudyList } from '@/api/trials'
import { getNetWorkSpeed, setNetWorkSpeedSize, workSpeedclose } from "@/utils"
const hangingAgreement = [
{ name: 'A', row: 1, col: 1 },
{ name: 'A|A', row: 1, col: 2 },
@ -1072,16 +1073,22 @@ const actions = {
}
}
}
let file = series.instanceInfoList.find(item => item.ImageId === obj.imageId)
if (file) {
getNetWorkSpeed()
setNetWorkSpeedSize(obj.percentComplete, file.FileSize, obj.imageId)
}
if (prefetchInstanceCount >= instanceCount * 100) {
series.prefetchInstanceCount = instanceCount * 100
// 设置当前序列状态为已下载完成
series.loadStatus = true
workSpeedclose()
}
if (prefetchInstanceCount2 !== null && instanceCount2 !== null && prefetchInstanceCount2 >= instanceCount2 * 100) {
pSeries.prefetchInstanceCount = instanceCount2 * 100
// 设置当前序列状态为已下载完成
pSeries.loadStatus = true
workSpeedclose()
}
} catch (e) {
console.log('error')

View File

@ -10,6 +10,7 @@ const getDefaultState = () => {
unlock: false,
config: {},
uploadTip: '0.00KB/s',
uploadSize: '',
timer: null,
whiteList: [],
checkTaskId: null

View File

@ -1,4 +1,5 @@
import store from "@/store";
/**
* Parse the time to string
* @param {(Object|string|number)} time
@ -92,3 +93,76 @@ export function deepClone(source, map = new WeakMap()) {
return target;
}
export function formatSize(size, fixed = 2) {
if (isNaN(parseFloat(size))) return ''
let kbSize = size / 1024
if (kbSize <= 1024) {
return `${kbSize.toFixed(fixed)}KB`
}
let mbSize = kbSize / 1024
return `${mbSize.toFixed(fixed)}MB`
}
let timer = null, // 网速定时器
lastPercentage = 0,
imageId = null,
bytesReceivedPerSecond = {}; // 时间节点上传文件总量
// 获取网速
export function getNetWorkSpeed() {
if (timer) return false;
if (lastPercentage < 100) return false;
imageId = null
timer = setInterval(() => {
let timeList = Object.keys(bytesReceivedPerSecond).sort((a, b) => a - b);
if (timeList.length > 0) {
let totalBytes = timeList.reduce((sum, bytes) => sum + bytesReceivedPerSecond[bytes], 0) / (5 * 1024);
let unit = 'KB/s';
if (totalBytes > 1024) {
totalBytes = totalBytes / 1024;
unit = "MB/s";
}
store.state.trials.uploadTip = totalBytes.toFixed(3) + unit;
}
if (timeList.length >= 5) {
delete bytesReceivedPerSecond[timeList[0]]
}
let time = new Date().getTime();
bytesReceivedPerSecond[time] = 0;
}, 1000)
}
export function setNetWorkSpeedSize(totalPercentage, total, Id) {
if (imageId && imageId !== Id) return false
imageId = Id
let percentage = totalPercentage - lastPercentage
lastPercentage = totalPercentage
console.log(percentage, totalPercentage, total)
let time = new Date().getTime();
let timeList = Object.keys(bytesReceivedPerSecond).sort((a, b) => a - b);
let bytesTime = timeList.find(item => time - item < 1000);
if (bytesTime) {
bytesReceivedPerSecond[bytesTime] += total * percentage;
} else {
// console.log("未查询到时间")
if (timeList.length > 0) {
bytesReceivedPerSecond[timeList[timeList.length - 1]] += total * percentage;
} else {
bytesReceivedPerSecond[time] = total * percentage;
}
}
store.state.trials.uploadSize = `${formatSize(totalPercentage / 100 * total)}/${formatSize(total)}`
}
export function workSpeedclose(isForce = false) {
if (!isForce && lastPercentage < 100) {
return false
}
if (timer) {
clearInterval(timer);
timer = null;
store.state.trials.uploadTip = '0KB/s'
store.state.trials.uploadSize = ''
}
bytesReceivedPerSecond = {};
lastPercentage = 0;
imageId = null
}

View File

@ -19,14 +19,9 @@
</div>
<div class="viewerSidethumbs ps" style="position: relative;">
<div class="viewerSidethumbinner">
<div
v-for="(item, index) in seriesList"
:key="index"
:class="{'viewerSideActive': index==0}"
<div v-for="(item, index) in seriesList" :key="index" :class="{ 'viewerSideActive': index == 0 }"
style="position: relative;margin-bottom:5px;border-radius: 2px;border: 1px solid #404040;"
series-type="current"
@click="showSeriesImage($event,index,item)"
>
series-type="current" @click="showSeriesImage($event, index, item)">
<div class="viewernavigatorwrapper">
<!-- <el-image
class="image-preview"
@ -34,14 +29,8 @@
:src="item.previewImageUrl"
fit="fill"
/> -->
<img
class="image-preview"
:src="item.previewImageUrl"
crossorigin="anonymous"
alt=""
style="width: 72px;height:72px;"
fit="fill"
>
<img class="image-preview" :src="item.previewImageUrl" crossorigin="anonymous" alt=""
style="width: 72px;height:72px;" fit="fill">
<div class="viewernavitextwrapper">
<div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div v-if="item.keySeries" style="color:red">
@ -51,23 +40,17 @@
#{{ item.seriesNumber }}
</div>
<div v-if="item.isExistMutiFrames && item.instanceCount > 1">
<el-popover
v-model="item.isShowPopper"
placement="right-start"
trigger="manual"
popper-class="instance_frame_wrapper"
>
<el-popover v-model="item.isShowPopper" placement="right-start" trigger="manual"
popper-class="instance_frame_wrapper">
<div style="text-align: right;">
<i class="el-icon-circle-close" style="font-size: 20px;cursor: pointer;color:#ddd;" @click="item.isShowPopper = false" />
<i class="el-icon-circle-close" style="font-size: 20px;cursor: pointer;color:#ddd;"
@click="item.isShowPopper = false" />
</div>
<div class="frame_list">
<div
v-for="(instance, idx) in item.instanceInfoList"
:key="instance.Id"
<div v-for="(instance, idx) in item.instanceInfoList" :key="instance.Id"
class="frame_content"
:style="{ 'margin-bottom': idx < item.instanceInfoList.length - 1 ? '5px' : '0px' }"
@click="showMultiFrames(item, index, instance)"
>
@click="showMultiFrames(item, index, instance)">
<div>
<div>{{ instance.InstanceNumber }}</div>
<div>
@ -75,22 +58,17 @@
</div>
<div v-if="showDelete">
<span>{{ $t('trials:audit:table:isDelete') }}</span>
<el-switch
v-model="instance.IsDeleted"
size="mini"
@change="changeInstanceDeleteStatus($event, item, instance)"
/>
<el-switch v-model="instance.IsDeleted" size="mini"
@change="changeInstanceDeleteStatus($event, item, instance)" />
<span style="margin-left:10px;">{{ $t('trials:audit:table:isReading') }}</span>
<el-switch
v-model="instance.IsReading"
size="mini"
@change="changeInstanceReadingStatus($event, item, instance)"
/>
<el-switch v-model="instance.IsReading" size="mini"
@change="changeInstanceReadingStatus($event, item, instance)" />
</div>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;" @click="popperClick(seriesList, item)" />
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;"
@click="popperClick(seriesList, item)" />
</el-popover>
</div>
</div>
@ -100,7 +78,8 @@
<div v-show="!item.keySeries && item.sliceThickness" style="padding: 1px;">
T: {{ parseFloat(item.sliceThickness).toFixed(2) }}
</div>
<div v-show="!item.keySeries &&item.description" style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
<div v-show="!item.keySeries && item.description"
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ item.description }}
</div>
<div v-if="!item.keySeries" style="padding: 1px;">
@ -109,27 +88,23 @@
</div>
</div>
<div v-if="showDelete" style="display: flex;flex-direction: row;justify-content: space-between;" @click.stop="">
<div v-if="showDelete" style="display: flex;flex-direction: row;justify-content: space-between;"
@click.stop="">
<div>
<span style="font-size: 12px;margin-right: 5px">{{ $t('trials:audit:table:isReading') }}</span>
<el-switch
v-model="item.isReading"
size="mini"
@change="changeReadingStatus($event, item)"
/>
<span style="font-size: 12px;margin-right: 5px">{{ $t('trials:audit:table:isReading')
}}</span>
<el-switch v-model="item.isReading" size="mini" @change="changeReadingStatus($event, item)" />
</div>
<div>
<span style="font-size: 12px;margin-right: 5px">{{ $t('trials:audit:table:isDelete') }}</span>
<el-switch
v-model="item.isDeleted"
size="mini"
@change="changeDeleteStatus($event, item)"
/>
<el-switch v-model="item.isDeleted" size="mini" @change="changeDeleteStatus($event, item)" />
</div>
</div>
<div v-if="item.prefetchInstanceCount>0 && item.prefetchInstanceCount<item.instanceCount * 100">
<el-progress :percentage="parseInt((item.prefetchInstanceCount/item.instanceCount).toFixed(2))" />
<div
v-if="item.prefetchInstanceCount > 0 && item.prefetchInstanceCount < item.instanceCount * 100">
<el-progress
:percentage="parseInt((item.prefetchInstanceCount / item.instanceCount).toFixed(2))" />
</div>
</div>
@ -164,6 +139,7 @@ import requestPoolManager from '@/utils/request-pool'
import store from '@/store'
import { changeURLStatic } from '@/utils/history.js'
import metaDataProvider from '@/utils/metaDataProvider'
import { getNetWorkSpeed, setNetWorkSpeedSize, workSpeedclose } from "@/utils"
cornerstone.metaData.addProvider(metaDataProvider, { priority: 10 })
var config = {
maxWebWorkers: 4,
@ -256,6 +232,7 @@ export default {
cornerstone.imageCache.purgeCache()
requestPoolManager.resetRequestPool()
})
workSpeedclose(true)
},
methods: {
async loadStudy() {
@ -870,10 +847,16 @@ export default {
this.seriesList[seriesIndex].imageloadedArr.push(imageId)
}
}
let file = this.seriesList[seriesIndex].instanceInfoList.find(item => item.ImageId === imageId)
if (file) {
getNetWorkSpeed()
setNetWorkSpeedSize(percentComplete, file.FileSize, imageId)
}
if (prefetchInstanceCount >= instanceCount * 100) {
this.seriesList[seriesIndex].prefetchInstanceCount = instanceCount * 100
//
this.seriesList[seriesIndex].loadStatus = true
workSpeedclose()
if (!this.isFromCRCUpload) {
this.loadAllImages()
}
@ -881,7 +864,6 @@ export default {
}
}
}
}
</script>
@ -890,25 +872,31 @@ export default {
background: none;
border: none;
}
.viewerContainer .el-tabs__item {
height: 35px;
line-height: 35px;
}
.viewerContainer .el-tabs--border-card>.el-tabs__content {
padding: 5px 0px 0px 0px;
}
.viewerContainer ::-webkit-scrollbar {
width: 7px;
height: 7px;
}
.viewerContainer ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.viewerContainer .relationVisit {
line-height: 20px;
cursor: pointer;
}
.viewerContainer {
display: block;
height: 100%;
@ -917,6 +905,7 @@ export default {
background-color: #444;
overflow: hidden;
}
.viewerContainer .viewerBanner {
background: linear-gradient(0, #444, #222);
min-height: 28px;
@ -928,6 +917,7 @@ export default {
padding-right: 5px;
font-weight: bold;
}
.viewerContainer .viewerContentWrapper {
display: flex;
flex-direction: row;
@ -939,10 +929,12 @@ export default {
text-overflow: clip;
white-space: nowrap;
}
.viewerContainer .viewerContentWrapper>div {
display: inline-block;
white-space: normal;
}
.viewerContainer .viewerLeftSidePanel {
width: 215px;
background-color: #323232;
@ -953,6 +945,7 @@ export default {
color: #D0D0D0;
overflow-y: auto;
}
.viewerContainer .viewerContentWrapper>div>.sidePanelBody {
background: rgba(50, 50, 50, 1);
word-break: break-all;
@ -960,10 +953,12 @@ export default {
width: 100%;
border: 1px solid #3e3f3a;
}
.viewerContainer .viewerContentWrapper>div>div.sidePanelBody>div {
width: 100%;
height: 100%;
}
.viewerContainer .studyDesc {
font-weight: bold;
font-size: 13px;
@ -972,6 +967,7 @@ export default {
color: #d0d0d0;
padding: 2px;
}
.viewerContainer .ps {
overflow: hidden !important;
overflow-anchor: none;
@ -979,6 +975,7 @@ export default {
touch-action: auto;
-ms-touch-action: auto;
}
.viewerContainer .viewerLeftSidePanel .viewernavigatorwrapper {
display: flex;
width: 200px;
@ -988,20 +985,25 @@ export default {
/* border-radius: 2px;
border: 1px solid #404040; */
}
.viewerContainer .viewernavigatorwrapper .el-progress__text {
display: none;
}
.viewerContainer .viewernavigatorwrapper .el-progress-bar {
padding-right: 0px;
}
.viewerContainer .ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
.viewerContainer .viewerLeftSidePanel .image-preview {
border: 2px solid #252525;
cursor: pointer;
}
.viewerContainer .viewerLeftSidePanel .viewernavitextwrapper {
/* width: 120px;
height: 80px; */
@ -1010,16 +1012,19 @@ export default {
vertical-align: top;
font-size: 12px;
}
.viewerContainer .viewerLeftSidePanel .viewerlabelwrapper {
width: 20px;
font-size: 12px;
}
.viewerContainer .viewerSideActive {
background: #16477b;
background: #16477b80;
border: 1px solid #23527b;
}
.viewerContainer .viewerContent {
flex: 1;
height: 100%;
@ -1028,24 +1033,29 @@ export default {
color: #D0D0D0;
font-size: 13px;
}
.instance_frame_wrapper {
min-width: 120px;
background-color: #2c2c2c;
border: 1px solid #2c2c2c;
padding: 5px;
}
.frame_list {
max-height: 500px;
overflow-y: auto;
}
.instance_frame_wrapper ::-webkit-scrollbar {
width: 7px;
height: 7px;
}
.instance_frame_wrapper ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.frame_content {
/* height: 50px; */
padding: 10px;
@ -1055,6 +1065,7 @@ export default {
font-size: 12px;
border: 1px solid #404040;
}
.frame_content:hover {
/* font-weight: bold; */
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
@ -1063,6 +1074,7 @@ export default {
border-color: #213a54 !important;
background-color: #213a54;
}
/* .viewerRightSidePanel {
width: 300px;
height: 100%;

View File

@ -16,7 +16,8 @@
:key="index"
> -->
<el-collapse v-model="activeNames">
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`"
:name="`${study.StudyId}`">
<template slot="title">
<div class="text-desc">
@ -25,8 +26,10 @@
<!-- <div v-show="study.Description" class="text-desc">
{{ study.Description }}
</div> -->
<el-tooltip v-show="study.Description" class="item" effect="dark" :content="study.Description" placement="bottom">
<div v-show="study.Description" style="width: 50px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
<el-tooltip v-show="study.Description" class="item" effect="dark" :content="study.Description"
placement="bottom">
<div v-show="study.Description"
style="width: 50px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ study.Description }}
</div>
</el-tooltip>
@ -37,44 +40,29 @@
<div v-show="study.Description" class="text-desc" style="background-color: #1f1f1f;">
{{ study.Description }}
</div>
<div
v-for="(series, i) in study.SeriesList"
:key="i"
<div v-for="(series, i) in study.SeriesList" :key="i"
:class="{ 'viewerSideActive': i == 0 && index === 0 }"
style="position: relative;margin-bottom:5px;border-radius: 2px;border: 1px solid #404040;"
series-type="current"
@click="showSeriesImage($event,index,i,series)"
>
series-type="current" @click="showSeriesImage($event, index, i, series)">
<div class="viewernavigatorwrapper">
<img
class="image-preview"
:src="series.previewImageUrl"
crossorigin="anonymous"
alt=""
style="width: 85px;height:85px;"
fit="fill"
>
<img class="image-preview" :src="series.previewImageUrl" crossorigin="anonymous" alt=""
style="width: 85px;height:85px;" fit="fill">
<div class="viewernavitextwrapper">
<div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
<div>#{{ series.seriesNumber }}</div>
<div v-if="series.isExistMutiFrames && series.instanceCount > 1">
<el-popover
v-model="series.isShowPopper"
placement="right-start"
trigger="manual"
popper-class="instance_frame_wrapper"
>
<el-popover v-model="series.isShowPopper" placement="right-start" trigger="manual"
popper-class="instance_frame_wrapper">
<div style="text-align: right;">
<i class="el-icon-circle-close" style="font-size: 20px;cursor: pointer;color:#ddd;" @click="series.isShowPopper = false" />
<i class="el-icon-circle-close"
style="font-size: 20px;cursor: pointer;color:#ddd;"
@click="series.isShowPopper = false" />
</div>
<div class="frame_list">
<div
v-for="(instance, idx) in series.instanceInfoList"
:key="instance.Id"
<div v-for="(instance, idx) in series.instanceInfoList" :key="instance.Id"
class="frame_content"
:style="{ 'margin-bottom': idx < series.instanceInfoList.length - 1 ? '5px' : '0px' }"
@click="showMultiFrames(index,series, i, instance)"
>
@click="showMultiFrames(index, series, i, instance)">
<!-- <div>
<img
class="image-preview"
@ -92,23 +80,20 @@
</div>
<div v-if="showDelete">
<span>{{ $t('trials:audit:table:isDelete') }}</span>
<el-switch
v-model="instance.IsDeleted"
size="mini"
@change="changeInstanceDeleteStatus($event, series, instance)"
/>
<span style="margin-left:10px;">{{ $t('trials:audit:table:isReading') }}</span>
<el-switch
v-model="instance.IsReading"
size="mini"
@change="changeInstanceReadingStatus($event, series, instance)"
/>
<el-switch v-model="instance.IsDeleted" size="mini"
@change="changeInstanceDeleteStatus($event, series, instance)" />
<span style="margin-left:10px;">{{ $t('trials:audit:table:isReading')
}}</span>
<el-switch v-model="instance.IsReading" size="mini"
@change="changeInstanceReadingStatus($event, series, instance)" />
</div>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;" @click="popperClick(studyList, series)" />
<i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;"
@click="popperClick(studyList, series)" />
</el-popover>
</div>
@ -120,8 +105,10 @@
T: {{ series.sliceThickness }}
</div>
<el-tooltip v-show="series.description" class="item" effect="dark" :content="series.description" placement="bottom">
<div v-show="series.description" style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
<el-tooltip v-show="series.description" class="item" effect="dark"
:content="series.description" placement="bottom">
<div v-show="series.description"
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ series.description }}
</div>
</el-tooltip>
@ -131,30 +118,27 @@
<div />
</div>
</div>
<div v-if="showDelete" style="display: flex;flex-direction: row;justify-content: space-between;" @click.stop="">
<div v-if="showDelete"
style="display: flex;flex-direction: row;justify-content: space-between;" @click.stop="">
<div>
<span style="font-size: 12px;">{{ $t('trials:audit:table:isReading') }}</span>
<el-switch
v-model="series.isReading"
size="mini"
@change="changeReadingStatus($event, series)"
/>
<el-switch v-model="series.isReading" size="mini"
@change="changeReadingStatus($event, series)" />
</div>
<div>
<span style="font-size: 12px;">{{ $t('trials:audit:table:isDelete') }}</span>
<el-switch
v-model="series.isDeleted"
size="mini"
@change="changeDeleteStatus($event, series)"
/>
<el-switch v-model="series.isDeleted" size="mini"
@change="changeDeleteStatus($event, series)" />
</div>
</div>
<!-- <div style="position: absolute;bottom: -10px;left: 0;width: 100%;">
<el-progress v-if="series.prefetchInstanceCount>0 && series.prefetchInstanceCount<series.instanceCount" :percentage="Number(series.prefetchInstanceCount/series.instanceCount)*100" />
</div> -->
<div v-if="series.prefetchInstanceCount>0 && series.prefetchInstanceCount<series.instanceCount * 100">
<el-progress :percentage="parseInt((series.prefetchInstanceCount/series.instanceCount).toFixed(2))" />
<div
v-if="series.prefetchInstanceCount > 0 && series.prefetchInstanceCount < series.instanceCount * 100">
<el-progress
:percentage="parseInt((series.prefetchInstanceCount / series.instanceCount).toFixed(2))" />
</div>
</div>
@ -165,10 +149,12 @@
</div>
</div>
</el-tab-pane>
<el-tab-pane v-if="!visitTaskId" :label="$t('trials:dicom-show:relatedVisit')" name="relation-study" class="pane-relation-wrapper">
<el-tab-pane v-if="!visitTaskId" :label="$t('trials:dicom-show:relatedVisit')" name="relation-study"
class="pane-relation-wrapper">
<div class="viewerSidethumbinner">
<el-collapse v-model="relationActiveName" @change="handelRelationActiveChange">
<el-collapse-item v-for="(study,studyIndex) in relationStudyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
<el-collapse-item v-for="(study, studyIndex) in relationStudyList" :key="`${study.StudyId}`"
:name="`${study.StudyId}`">
<template slot="title">
<div class="text-desc">
@ -187,28 +173,18 @@
</div>
<div v-if="study.seriesList" class="viewerSidethumbs ps" style="position: relative;">
<div class="viewerSidethumbinner">
<div
v-for="(seriesItem, index) in study.seriesList"
:key="index"
class="viewernavigatorwrapper"
style="position: relative;border:1px solid #434343;"
<div v-for="(seriesItem, index) in study.seriesList" :key="index"
class="viewernavigatorwrapper" style="position: relative;border:1px solid #434343;"
series-type="relation"
@click="showRelationSeriesImage($event,seriesItem,studyIndex,index)"
>
@click="showRelationSeriesImage($event, seriesItem, studyIndex, index)">
<!-- <el-image
class="image-preview"
style="height:72px;width:72px;"
:src="seriesItem.previewImageUrl"
fit="fill"
/> -->
<img
class="image-preview"
:src="seriesItem.previewImageUrl"
crossorigin="anonymous"
alt=""
style="width: 85px;height:85px;"
fit="fill"
>
<img class="image-preview" :src="seriesItem.previewImageUrl" crossorigin="anonymous" alt=""
style="width: 85px;height:85px;" fit="fill">
<div class="viewernavitextwrapper">
<div style="padding: 1px 5px 1px 1px;display: flex;justify-content: space-between;">
@ -217,27 +193,23 @@
</div>
<div v-else>#{{ seriesItem.seriesNumber }}</div>
<div v-if="seriesItem.isExistMutiFrames && seriesItem.instanceCount > 1">
<el-popover
placement="right-start"
trigger="click"
popper-class="instance_frame_wrapper"
>
<el-popover placement="right-start" trigger="click"
popper-class="instance_frame_wrapper">
<div class="frame_list">
<div
v-for="(instance, idx) in seriesItem.instanceInfoList"
:key="instance.Id"
<div v-for="(instance, idx) in seriesItem.instanceInfoList" :key="instance.Id"
class="frame_content"
:style="{ 'margin-bottom': idx < seriesItem.instanceInfoList.length - 1 ? '5px' : '0px' }"
@click="showMultiFrames(studyIndex,seriesItem, index, instance)"
>
@click="showMultiFrames(studyIndex, seriesItem, index, instance)">
<div>
<div>{{ instance.InstanceNumber }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.NumberOfFrames : 1} frame` }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.NumberOfFrames : 1} frame`
}}</div>
</div>
</div>
</div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;" />
<i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;" />
</el-popover>
</div>
@ -248,8 +220,10 @@
<div v-show="seriesItem.sliceThickness" style="padding: 1px;">
T: {{ seriesItem.sliceThickness }}
</div>
<el-tooltip v-show="seriesItem.description" class="item" effect="dark" :content="seriesItem.description" placement="bottom">
<div v-show="seriesItem.description" style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
<el-tooltip v-show="seriesItem.description" class="item" effect="dark"
:content="seriesItem.description" placement="bottom">
<div v-show="seriesItem.description"
style="width: 120px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding: 1x;">
{{ seriesItem.description }}
</div>
</el-tooltip>
@ -287,6 +261,7 @@ import { getTaskUploadedDicomStudyList } from '@/api/reading'
import requestPoolManager from '@/utils/request-pool'
import store from '@/store'
import { changeURLStatic } from '@/utils/history.js'
import { getNetWorkSpeed, setNetWorkSpeedSize, workSpeedclose } from "@/utils"
import metaDataProvider from '@/utils/metaDataProvider'
cornerstone.metaData.addProvider(metaDataProvider, { priority: 10 })
// import * as cornerstoneTools from 'cornerstone-tools'
@ -361,6 +336,7 @@ export default {
cornerstone.imageCache.purgeCache()
requestPoolManager.resetRequestPool()
})
workSpeedclose(true)
},
methods: {
// 访
@ -887,6 +863,11 @@ export default {
series.imageloadedArr.push(imageId)
}
}
let file = this.studyList[studyIndex].SeriesList[seriesIndex].instanceInfoList.find(item => item.ImageId === imageId)
if (file) {
getNetWorkSpeed()
setNetWorkSpeedSize(percentComplete, file.FileSize, imageId)
}
if (prefetchInstanceCount >= instanceCount * 100) {
series.prefetchInstanceCount = instanceCount * 100
//
@ -894,6 +875,7 @@ export default {
if (!this.isFromCRCUpload) {
this.loadAllImages()
}
workSpeedclose()
}
}
}
@ -905,6 +887,7 @@ export default {
background: none;
border: none;
}
.viewerContainer .el-tabs__item {
height: 35px;
line-height: 35px;
@ -913,21 +896,26 @@ export default {
text-align: center;
transform: scale(.8);
}
.viewerContainer .el-tabs--border-card>.el-tabs__content {
padding: 5px 0px 0px 0px;
}
.viewerContainer ::-webkit-scrollbar {
width: 7px;
height: 7px;
}
.viewerContainer ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.viewerContainer .relationVisit {
line-height: 20px;
cursor: pointer;
}
.viewerContainer {
display: block;
height: 100%;
@ -936,6 +924,7 @@ export default {
background-color: #444;
overflow: hidden;
}
.viewerContainer .viewerBanner {
background: linear-gradient(0, #444, #222);
min-height: 28px;
@ -947,6 +936,7 @@ export default {
padding-right: 5px;
font-weight: bold;
}
.viewerContainer .viewerContentWrapper {
display: flex;
flex-direction: row;
@ -958,10 +948,12 @@ export default {
text-overflow: clip; */
/* white-space: nowrap; */
}
.viewerContainer .viewerContentWrapper>div {
display: inline-block;
white-space: normal;
}
.viewerContainer .viewerLeftSidePanel {
width: 220px;
background-color: #323232;
@ -972,6 +964,7 @@ export default {
color: #D0D0D0;
overflow-y: auto;
}
.viewerContainer .viewerContentWrapper>div>.sidePanelBody {
background: rgba(50, 50, 50, 1);
word-break: break-all;
@ -979,10 +972,12 @@ export default {
width: 100%;
border: 1px solid #3e3f3a;
}
.viewerContainer .viewerContentWrapper>div>div.sidePanelBody>div {
width: 100%;
height: 100%;
}
.viewerContainer .text-desc {
font-weight: bold;
font-size: 13px;
@ -991,6 +986,7 @@ export default {
color: #d0d0d0;
padding: 2px;
}
.viewerContainer .ps {
overflow: hidden !important;
overflow-anchor: none;
@ -998,6 +994,7 @@ export default {
touch-action: auto;
-ms-touch-action: auto;
}
.viewerContainer .viewerLeftSidePanel .viewernavigatorwrapper {
display: flex;
width: 220px;
@ -1008,20 +1005,25 @@ export default {
border: 1px solid #404040; */
}
.viewerContainer .viewernavigatorwrapper .el-progress__text {
display: none;
}
.viewerContainer .viewernavigatorwrapper .el-progress-bar {
padding-right: 0px;
}
.viewerContainer .ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;
}
.viewerContainer .viewerLeftSidePanel .image-preview {
border: 2px solid #252525;
cursor: pointer;
}
.viewerContainer .viewerLeftSidePanel .viewernavitextwrapper {
/* width: 120px;
height: 80px; */
@ -1030,16 +1032,19 @@ export default {
vertical-align: top;
font-size: 12px;
}
.viewerContainer .viewerLeftSidePanel .viewerlabelwrapper {
width: 20px;
font-size: 12px;
}
.viewerContainer .viewerSideActive {
background: #16477b;
background: #16477b80;
border: 1px solid #23527b;
}
.viewerContainer .viewerContent {
flex: 1;
height: 100%;
@ -1048,9 +1053,11 @@ export default {
color: #D0D0D0;
font-size: 13px;
}
.viewerContainer .pane-relation-wrapper {
min-height: 500px;
}
.viewerContainer .el-collapse {
border: none;
}
@ -1060,11 +1067,13 @@ export default {
color: #ddd;
}
.viewerContainer .el-collapse-item__content {
padding-bottom: 5px;
background-color: #2c2c2c !important;
color: #ddd;
}
.viewerContainer .el-collapse-item__header {
background-color: #585453 !important;
color: #ddd;
@ -1073,24 +1082,29 @@ export default {
height: 40px;
line-height: 20px;
}
.instance_frame_wrapper {
min-width: 120px;
background-color: #2c2c2c;
border: 1px solid #2c2c2c;
padding: 5px;
}
.frame_list {
max-height: 500px;
overflow-y: auto;
}
.instance_frame_wrapper ::-webkit-scrollbar {
width: 7px;
height: 7px;
}
.instance_frame_wrapper ::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.frame_content {
/* height: 50px; */
padding: 10px;
@ -1100,6 +1114,7 @@ export default {
font-size: 12px;
border: 1px solid #404040;
}
.frame_content:hover {
/* font-weight: bold; */
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
@ -1121,4 +1136,3 @@ export default {
font-size: 13px;
} */
</style>

View File

@ -1,15 +1,7 @@
<template>
<div
id="canvas"
ref="canvas"
v-loading="loading"
element-loading-text="Loading..."
element-loading-background="rgba(0, 0, 0, 0.8)"
style="position:relative;"
class="cornerstone-element"
@mouseup="sliderMouseup"
@contextmenu.prevent="onContextmenu"
>
<div id="canvas" ref="canvas" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" style="position:relative;" class="cornerstone-element"
@mouseup="sliderMouseup" @contextmenu.prevent="onContextmenu">
<!-- 临床数据 -->
<div v-if="stack.isExistsClinicalData" class="info-cd" @click.stop="handleViewCD($event)">
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')" placement="bottom">
@ -18,33 +10,25 @@
</div>
<!-- 切换访视 -->
<div
v-if="stack.imageRendered && isReadingTaskViewInOrder === 1"
class="info-visit"
@dblclick.stop="preventDefault($event)"
>
<div
class="arrw_div_wrapper"
<div v-if="stack.imageRendered && isReadingTaskViewInOrder === 1" class="info-visit"
@dblclick.stop="preventDefault($event)">
<div class="arrw_div_wrapper"
:style="{ cursor: stack.visitTaskNum <= minVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum <= minVistNum ? '#888' : '#fff' }"
@click.stop.prevent="toggleSeries($event,-1)"
@dblclick.stop="preventDefault($event)"
>
@click.stop.prevent="toggleSeries($event, -1)" @dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-left" />
</div>
<div class="blind_name_wrapper">
{{ stack.taskBlindName }}
</div>
<div
class="arrw_div_wrapper"
<div class="arrw_div_wrapper"
:style="{ cursor: stack.visitTaskNum >= maxVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum >= maxVistNum ? '#888' : '#fff' }"
@click.stop.prevent="toggleSeries($event,1)"
@dblclick.stop="preventDefault($event)"
>
@click.stop.prevent="toggleSeries($event, 1)" @dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-right" />
</div>
</div>
<div class="info-series">
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{ stack.taskBlindName }}</h2>
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{
stack.taskBlindName }}</h2>
<div v-show="dicomInfo.series">Series: #{{ dicomInfo.series }}</div>
<div>Image: #{{ dicomInfo.frame }}</div>
<div>{{ dicomInfo.modality }}</div>
@ -53,7 +37,8 @@
<div v-show="mousePosition.mo">
Pos: {{ mousePosition.x ? mousePosition.x.toFixed(0) : '' }}, {{ mousePosition.y ? mousePosition.y.toFixed(0) : '' }}
</div>
<div v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
<div
v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
HU: {{ mousePosition.mo }}
</div>
<div v-else-if="(dicomInfo.modality === 'PT' && mousePosition.suv)">
@ -81,8 +66,12 @@
<!-- <div v-show="dicomInfo.acc">ACC {{ dicomInfo.acc }}</div> -->
<!-- <div>{{ dicomInfo.time }}</div> -->
</div>
<div ref="sliderBox" class="my_slider_box" style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer" @click.stop="goViewer($event)">
<div :style="{top: height + '%'}" style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" />
<div ref="sliderBox" class="my_slider_box"
style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer"
@click.stop="goViewer($event)">
<div :style="{ top: height + '%' }"
style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move"
@click.stop.prevent="() => { return }" @mousedown.stop="sliderMousedown($event)" />
</div>
<div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);">
{{ markers.top }}
@ -308,7 +297,10 @@ export default {
}
},
computed: {
...mapGetters(['visitTaskList', 'currentReadingTaskState'])
...mapGetters(['visitTaskList', 'currentReadingTaskState']),
NSTip() {
return `${this.$store.state.trials.uploadSize},NS: ${this.$store.state.trials.uploadTip}`
}
},
watch: {
currentReadingTaskState: {
@ -1256,8 +1248,7 @@ export default {
data.string('x00080030')
)
this.dicomInfo.series = data.string('x00200011')
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${
this.stack.imageIds.length
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${this.stack.imageIds.length
}`
this.dicomInfo.size = `${data.uint16('x00280011')}*${data.uint16(
'x00280010'
@ -2021,6 +2012,7 @@ export default {
<style lang="scss" scoped>
.context-menu-wrapper {
position: absolute;
ul {
list-style: none;
margin: 0px;
@ -2036,15 +2028,18 @@ export default {
width: 80px;
box-sizing: border-box;
}
.menu {
li {
padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666;
div {
padding: 5px;
}
}
.submenu {
position: absolute;
left: 77px;
@ -2052,9 +2047,11 @@ export default {
background: #343333;
color: #fff;
display: none;
li {
padding: 2px 5px;
border-bottom: 1px solid #666;
div {
padding: 5px;
}
@ -2062,20 +2059,25 @@ export default {
}
}
.menu li:hover {
background-color: #ff5722;
.submenu {
display: block;
}
}
.menu_active {
cursor: pointer;
}
.menu_disabled {
cursor: not-allowed;
}
}
.info-visit {
position: absolute;
left: 50%;
@ -2083,6 +2085,7 @@ export default {
transform: translateX(-50%);
display: flex;
flex-direction: row;
.arrw_div_wrapper {
width: 20px;
height: 20px;
@ -2091,6 +2094,7 @@ export default {
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper {
height: 20px;
line-height: 20px;
@ -2100,6 +2104,7 @@ export default {
font-size: 14px;
}
}
.info-cd {
position: absolute;
left: 10px;
@ -2109,6 +2114,7 @@ export default {
font-size: 18px;
cursor: pointer;
}
.info-series {
position: absolute;
left: 10px;
@ -2118,6 +2124,7 @@ export default {
font-size: 12px;
/* z-index: 1; */
}
.info-image {
position: absolute;
left: 10px;
@ -2137,6 +2144,7 @@ export default {
font-size: 12px;
/* z-index: 1; */
}
.info-instance {
position: absolute;
right: 15px;
@ -2169,6 +2177,7 @@ export default {
margin: 10px;
cursor: default;
}
.menu__item:hover {
color: #ff0000;
}
@ -2188,6 +2197,7 @@ li:hover {
background-color: #e0e0e2;
color: white;
}
.msg-div {
position: absolute;
z-index: 10;

View File

@ -7,14 +7,9 @@
<div class="visit-name-wrapper">
<div v-if="(visitTaskList.length > 0)" style="display: flex;flex-direction: row;">
<div
v-for="s in visitTaskList"
:key="s.VisitTaskId"
class="visit-item"
:class="{'visit-item-active': activeTaskVisitId==s.VisitTaskId}"
@click.prevent="handleClick(s)"
>{{ s.TaskBlindName }}</div>
<div v-for="s in visitTaskList" :key="s.VisitTaskId" class="visit-item"
:class="{ 'visit-item-active': activeTaskVisitId == s.VisitTaskId }" @click.prevent="handleClick(s)">{{
s.TaskBlindName }}</div>
</div>
@ -22,23 +17,12 @@
</div>
<div class="right">
<div
v-for="s in visitTaskList"
v-show="activeTaskVisitId === s.VisitTaskId"
:key="s.VisitTaskId"
class="study-wrapper"
>
<StudyList
v-if="selectArr.includes(s.VisitTaskId)"
:ref="s.VisitTaskId"
:visit-task-id="s.VisitTaskId"
:trial-id="trialId"
:subject-visit-id="s.VisitId"
:task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
@loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms"
/>
<div v-for="s in visitTaskList" v-show="activeTaskVisitId === s.VisitTaskId" :key="s.VisitTaskId"
class="study-wrapper">
<StudyList v-if="selectArr.includes(s.VisitTaskId)" :ref="s.VisitTaskId" :visit-task-id="s.VisitTaskId"
:trial-id="trialId" :subject-visit-id="s.VisitId" :task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo" @loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms" />
</div>
</div>
</div>
@ -46,20 +30,12 @@
</div>
<div class="dicom-viewer">
<div class="container">
<DicomViewer
v-if="activeTaskVisitId"
ref="dicomViewer"
:is-show="isShow"
:question-form-change-state="questionFormChangeState"
:question-form-change-num="questionFormChangeNum"
:is-exists-clinical-data="isExistsClinicalData"
:is-exists-no-dicom-file="isExistsNoDicomFile"
<DicomViewer v-if="activeTaskVisitId" ref="dicomViewer" :is-show="isShow"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-exists-clinical-data="isExistsClinicalData" :is-exists-no-dicom-file="isExistsNoDicomFile"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:is-reading-task-view-in-order="isReadingTaskViewInOrder"
:is-exists-manual="isExistsManual"
:iseCRFShowInDicomReading="iseCRFShowInDicomReading"
@previewCD="previewCD"
/>
:is-reading-task-view-in-order="isReadingTaskViewInOrder" :is-exists-manual="isExistsManual"
:iseCRFShowInDicomReading="iseCRFShowInDicomReading" @previewCD="previewCD" />
</div>
</div>
@ -75,6 +51,7 @@ import { getToken } from '@/utils/auth'
import { mapGetters } from 'vuex'
import * as dicomParser from 'dicom-parser'
import * as cornerstone from 'cornerstone-core'
import { workSpeedclose } from "@/utils"
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
// import metaDataProvider from '@/utils/metaDataProvider'
// cornerstone.metaData.addProvider(metaDataProvider, { priority: 10 });
@ -248,6 +225,7 @@ export default {
DicomEvent.$off('addNoneDicomMeasureData')
DicomEvent.$off('selectSeries')
window.removeEventListener('beforeunload', e => { cornerstone.imageCache.purgeCache() })
workSpeedclose(true)
},
methods: {
async getVisitInfo() {
@ -532,12 +510,14 @@ export default {
},
cornerstoneimageloadprogress(e) {
const imageId = e.detail.imageId
console.log(imageId,'imageId')
const params = {}
const searchParams = new URLSearchParams(imageId.split('?')[1])
for (const [key, value] of searchParams.entries()) {
params[key] = value
}
params.percentComplete = e.detail.percentComplete
params.imageId = imageId
store.dispatch('reading/setImageLoadedProgress', params)
}
}
@ -553,10 +533,12 @@ export default {
background-color: #000;
box-sizing: border-box;
user-select: none;
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
@ -573,6 +555,7 @@ export default {
box-sizing: border-box;
}
}
.dicom-list {
width: 200px;
padding: 5px 0px;
@ -586,6 +569,7 @@ export default {
// border: 1px solid #ccc;
}
::v-deep.el-tabs {
box-sizing: border-box;
padding: 0 5px;
@ -594,25 +578,30 @@ export default {
flex-direction: column;
// justify-content: flex-start;
border: 1px solid #727272;
.el-tabs__item {
color: #fff;
}
.el-tabs__header {
height: 55px;
margin: 0px;
box-sizing: border-box;
}
.el-tabs__content {
flex: 1;
margin: 0px;
overflow-y: auto;
box-sizing: border-box;
}
.el-tabs__item {
color: #fff;
}
}
.dicom-desc {
font-weight: bold;
font-size: 13px;
@ -621,23 +610,28 @@ export default {
color: #d0d0d0;
padding: 2px;
}
.ps {
overflow-anchor: none;
touch-action: auto;
}
.series-active {
background-color: #607d8b !important;
border: 1px solid #607d8b !important;
}
::v-deep.el-progress__text {
color: #ccc;
font-size: 12px;
}
.series {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
.series-wrapper {
display: flex;
flex-direction: row;
@ -650,9 +644,11 @@ export default {
border-radius: 2px;
border: 1px solid #404040;
background-color: #3a3a3a;
.el-progress__text {
display: none;
}
.el-progress-bar {
padding-right: 0px;
}
@ -663,8 +659,10 @@ export default {
border: 2px solid #252525;
cursor: pointer;
}
.image-desc {
vertical-align: top;
p {
width: 100px;
white-space: nowrap;
@ -678,6 +676,7 @@ export default {
}
}
}
.related-study-wrapper {
box-sizing: border-box;
height: 100%;
@ -693,6 +692,7 @@ export default {
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
.visit-name-wrapper {
position: absolute;
top: 5px;
@ -708,6 +708,7 @@ export default {
// flex-direction: row;
// align-content: flex-start;
}
.visit-item {
margin-left: 10px;
white-space: nowrap;
@ -720,17 +721,20 @@ export default {
cursor: pointer;
// margin-left: 10px;
}
.visit-item-active {
background-color: #607d8b;
border: 1px solid #607d8b;
}
}
.right {
width: 170px;
flex: 1;
height: 100%;
border-left: 1px solid #4a4a4a;
color: #d5d5d5;
.study-wrapper {
width: 100%;
height: 100%;

View File

@ -1,15 +1,7 @@
<template>
<div
id="canvas"
ref="canvas"
v-loading="loading"
element-loading-text="Loading..."
element-loading-background="rgba(0, 0, 0, 0.8)"
style="position:relative;"
class="cornerstone-element"
@mouseup="sliderMouseup"
@contextmenu.prevent="onContextmenu"
>
<div id="canvas" ref="canvas" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" style="position:relative;" class="cornerstone-element"
@mouseup="sliderMouseup" @contextmenu.prevent="onContextmenu">
<!-- 临床数据 -->
<div v-if="stack.isExistsClinicalData" class="info-cd" @click.stop="handleViewCD($event)">
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:clinicalData')" placement="bottom">
@ -18,33 +10,25 @@
</div>
<!-- 切换访视 -->
<div
v-if="stack.imageRendered && isReadingTaskViewInOrder === 1"
class="info-visit"
@dblclick.stop="preventDefault($event)"
>
<div
class="arrw_div_wrapper"
<div v-if="stack.imageRendered && isReadingTaskViewInOrder === 1" class="info-visit"
@dblclick.stop="preventDefault($event)">
<div class="arrw_div_wrapper"
:style="{ cursor: stack.visitTaskNum <= minVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum <= minVistNum ? '#888' : '#fff' }"
@click.stop.prevent="toggleSeries($event,-1)"
@dblclick.stop="preventDefault($event)"
>
@click.stop.prevent="toggleSeries($event, -1)" @dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-left" />
</div>
<div class="blind_name_wrapper">
{{ stack.taskBlindName }}
</div>
<div
class="arrw_div_wrapper"
<div class="arrw_div_wrapper"
:style="{ cursor: stack.visitTaskNum >= maxVistNum ? 'not-allowed' : 'pointer', color: stack.visitTaskNum >= maxVistNum ? '#888' : '#fff' }"
@click.stop.prevent="toggleSeries($event,1)"
@dblclick.stop="preventDefault($event)"
>
@click.stop.prevent="toggleSeries($event, 1)" @dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-right" />
</div>
</div>
<div class="info-series">
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{ stack.taskBlindName }}</h2>
<h2 v-if="isReadingShowSubjectInfo" style="color:#f44336;padding: 5px 0px;margin: 0;">{{ subjectCode }} {{
stack.taskBlindName }}</h2>
<div v-show="dicomInfo.series">Series: #{{ dicomInfo.series }}</div>
<div>Image: #{{ dicomInfo.frame }}</div>
<div>{{ dicomInfo.modality }}</div>
@ -53,7 +37,8 @@
<div v-show="mousePosition.mo">
Pos: {{ mousePosition.x ? mousePosition.x.toFixed(0) : '' }}, {{ mousePosition.y ? mousePosition.y.toFixed(0) : '' }}
</div>
<div v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
<div
v-if="(dicomInfo.modality === 'CT' || dicomInfo.modality === 'DR' || dicomInfo.modality === 'CR') && mousePosition.mo">
HU: {{ mousePosition.mo }}
</div>
<div v-else-if="(dicomInfo.modality === 'PT' && mousePosition.suv)">
@ -81,8 +66,12 @@
<!-- <div v-show="dicomInfo.acc">ACC {{ dicomInfo.acc }}</div> -->
<!-- <div>{{ dicomInfo.time }}</div> -->
</div>
<div ref="sliderBox" class="my_slider_box" style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer" @click.stop="goViewer($event)">
<div :style="{top: height + '%'}" style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move" @click.stop.prevent="() => {return}" @mousedown.stop="sliderMousedown($event)" />
<div ref="sliderBox" class="my_slider_box"
style="position: absolute;right: 1px;height: calc(100% - 140px);transform: translateY(-50%);top: calc(50% - 30px);width: 10px;background: #333;cursor: pointer"
@click.stop="goViewer($event)">
<div :style="{ top: height + '%' }"
style="z-index:10;background: #9e9e9e;height: 20px;width: 100%;position: absolute;top: 0;cursor: move"
@click.stop.prevent="() => { return }" @mousedown.stop="sliderMousedown($event)" />
</div>
<div style="position: absolute;left: 50%;top: 30px;color: #f44336;transform: translateX(-50%);">
{{ markers.top }}
@ -306,7 +295,10 @@ export default {
}
},
computed: {
...mapGetters(['visitTaskList', 'currentReadingTaskState'])
...mapGetters(['visitTaskList', 'currentReadingTaskState']),
NSTip() {
return `${this.$store.state.trials.uploadSize},NS: ${this.$store.state.trials.uploadTip}`
}
},
watch: {
currentReadingTaskState: {
@ -1209,8 +1201,7 @@ export default {
data.string('x00080030')
)
this.dicomInfo.series = data.string('x00200011')
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${
this.stack.imageIds.length
this.dicomInfo.frame = `${this.stack.currentImageIdIndex + 1}/${this.stack.imageIds.length
}`
this.dicomInfo.size = `${data.uint16('x00280011')}*${data.uint16(
'x00280010'
@ -1944,6 +1935,7 @@ export default {
<style lang="scss" scoped>
.context-menu-wrapper {
position: absolute;
ul {
list-style: none;
margin: 0px;
@ -1959,15 +1951,18 @@ export default {
width: 80px;
box-sizing: border-box;
}
.menu {
li {
padding: 2px 5px;
position: relative;
border-bottom: 1px solid #666;
div {
padding: 5px;
}
}
.submenu {
position: absolute;
left: 77px;
@ -1975,9 +1970,11 @@ export default {
background: #343333;
color: #fff;
display: none;
li {
padding: 2px 5px;
border-bottom: 1px solid #666;
div {
padding: 5px;
}
@ -1985,20 +1982,25 @@ export default {
}
}
.menu li:hover {
background-color: #ff5722;
.submenu {
display: block;
}
}
.menu_active {
cursor: pointer;
}
.menu_disabled {
cursor: not-allowed;
}
}
.info-visit {
position: absolute;
left: 50%;
@ -2006,6 +2008,7 @@ export default {
transform: translateX(-50%);
display: flex;
flex-direction: row;
.arrw_div_wrapper {
width: 20px;
height: 20px;
@ -2014,6 +2017,7 @@ export default {
line-height: 20px;
border-radius: 10%;
}
.blind_name_wrapper {
height: 20px;
line-height: 20px;
@ -2023,6 +2027,7 @@ export default {
font-size: 14px;
}
}
.info-cd {
position: absolute;
left: 10px;
@ -2032,6 +2037,7 @@ export default {
font-size: 18px;
cursor: pointer;
}
.info-series {
position: absolute;
left: 10px;
@ -2041,6 +2047,7 @@ export default {
font-size: 12px;
/* z-index: 1; */
}
.info-image {
position: absolute;
left: 10px;
@ -2060,6 +2067,7 @@ export default {
font-size: 12px;
/* z-index: 1; */
}
.info-instance {
position: absolute;
right: 15px;
@ -2092,6 +2100,7 @@ export default {
margin: 10px;
cursor: default;
}
.menu__item:hover {
color: #ff0000;
}
@ -2111,6 +2120,7 @@ li:hover {
background-color: #e0e0e2;
color: white;
}
.msg-div {
position: absolute;
z-index: 10;

View File

@ -5,42 +5,22 @@
<div class="related-study-wrapper">
<div class="left">
<div class="visit-name-wrapper">
<div
v-if="visitTaskList.length > 0"
style="display: flex; flex-direction: row"
>
<div
v-for="s in visitTaskList"
:key="s.VisitTaskId"
class="visit-item"
:class="{
<div v-if="visitTaskList.length > 0" style="display: flex; flex-direction: row">
<div v-for="s in visitTaskList" :key="s.VisitTaskId" class="visit-item" :class="{
'visit-item-active': activeTaskVisitId == s.VisitTaskId,
}"
@click.prevent="handleClick(s)"
>
}" @click.prevent="handleClick(s)">
{{ s.TaskBlindName }}
</div>
</div>
</div>
</div>
<div class="right">
<div
v-for="s in visitTaskList"
v-show="activeTaskVisitId === s.VisitTaskId"
:key="s.VisitTaskId"
class="study-wrapper"
>
<StudyList
v-if="selectArr.includes(s.VisitTaskId)"
:ref="s.VisitTaskId"
:visit-task-id="s.VisitTaskId"
:trial-id="trialId"
:subject-visit-id="s.VisitId"
:task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
@loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms"
/>
<div v-for="s in visitTaskList" v-show="activeTaskVisitId === s.VisitTaskId" :key="s.VisitTaskId"
class="study-wrapper">
<StudyList v-if="selectArr.includes(s.VisitTaskId)" :ref="s.VisitTaskId" :visit-task-id="s.VisitTaskId"
:trial-id="trialId" :subject-visit-id="s.VisitId" :task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo" @loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms" />
</div>
</div>
</div>
@ -48,21 +28,12 @@
</div>
<div class="dicom-viewer">
<div class="container">
<DicomViewer
v-if="activeTaskVisitId"
ref="dicomViewer"
:trial-id="trialId"
:trial-reading-criterion-id="trialReadingCriterionId"
:subject-id="subjectId"
:is-show="isShow"
:is-exists-clinical-data="isExistsClinicalData"
:is-exists-no-dicom-file="isExistsNoDicomFile"
<DicomViewer v-if="activeTaskVisitId" ref="dicomViewer" :trial-id="trialId"
:trial-reading-criterion-id="trialReadingCriterionId" :subject-id="subjectId" :is-show="isShow"
:is-exists-clinical-data="isExistsClinicalData" :is-exists-no-dicom-file="isExistsNoDicomFile"
:is-reading-show-subject-info="isReadingShowSubjectInfo"
:is-reading-task-view-in-order="isReadingTaskViewInOrder"
:isExistsManual="isExistsManual"
:IseCRFShowInDicomReading="IseCRFShowInDicomReading"
@previewCD="previewCD"
/>
:is-reading-task-view-in-order="isReadingTaskViewInOrder" :isExistsManual="isExistsManual"
:IseCRFShowInDicomReading="IseCRFShowInDicomReading" @previewCD="previewCD" />
</div>
</div>
</div>
@ -77,6 +48,7 @@ import { mapGetters } from "vuex";
import * as dicomParser from 'dicom-parser'
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
import { workSpeedclose } from "@/utils"
// import metaDataProvider from '@/utils/metaDataProvider'
// cornerstone.metaData.addProvider(metaDataProvider, { priority: 10 });
var config = {
@ -261,6 +233,7 @@ export default {
DicomEvent.$off("addNoneDicomMeasureData");
DicomEvent.$off("selectSeries");
window.removeEventListener('beforeunload', e => { cornerstone.imageCache.purgeCache() })
workSpeedclose(true)
},
methods: {
async getVisitInfo() {
@ -683,12 +656,14 @@ export default {
},
cornerstoneimageloadprogress(e) {
const imageId = e.detail.imageId
console.log(imageId,'imageId')
const params = {}
const searchParams = new URLSearchParams(imageId.split('?')[1])
for (const [key, value] of searchParams.entries()) {
params[key] = value
}
params.percentComplete = e.detail.percentComplete
params.imageId = imageId
store.dispatch('reading/setImageLoadedProgress', params)
}
},
@ -699,22 +674,27 @@ export default {
height: 100%;
// background-color: #fff;
background-color: #000;
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
}
.report-header {
display: flex;
}
.el-card {
background-color: #000;
color: #ffffff;
border: none;
}
::v-deep .el-table,
.el-table__expanded-cell {
background-color: #000;
@ -728,19 +708,23 @@ export default {
color: #fff;
border-color: #444444;
}
::v-deep .el-table__body tr>td {
background-color: #000 !important;
color: #fff;
border-color: #444444;
}
::v-deep .el-table__body tr:hover>td {
background-color: #858282 !important;
color: #fff;
border-color: #444444;
}
::v-deep .el-table--border th.gutter:last-of-type {
border: none;
}
::v-deep .el-card__header {
border: none;
padding: 10px;
@ -753,10 +737,12 @@ export default {
justify-content: flex-start;
background-color: #000;
box-sizing: border-box;
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #d0d0d0;
@ -773,6 +759,7 @@ export default {
box-sizing: border-box;
}
}
.dicom-list {
width: 200px;
padding: 5px 0px;
@ -785,6 +772,7 @@ export default {
border: 1px solid #727272;
// border: 1px solid #ccc;
}
.el-tabs {
box-sizing: border-box;
padding: 0 5px;
@ -793,24 +781,29 @@ export default {
flex-direction: column;
// justify-content: flex-start;
border: 1px solid #727272;
.el-tabs__item {
color: #fff;
}
::v-deep .el-tabs__header {
height: 55px;
margin: 0px;
box-sizing: border-box;
}
::v-deep .el-tabs__content {
flex: 1;
margin: 0px;
overflow-y: auto;
box-sizing: border-box;
}
::v-deep .el-tabs__item {
color: #fff;
}
}
.dicom-desc {
font-weight: bold;
font-size: 13px;
@ -819,23 +812,28 @@ export default {
color: #d0d0d0;
padding: 2px;
}
.ps {
overflow-anchor: none;
touch-action: auto;
}
.series-active {
background-color: #607d8b !important;
border: 1px solid #607d8b !important;
}
::v-deep .el-progress__text {
color: #ccc;
font-size: 12px;
}
.series {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
.series-wrapper {
display: flex;
flex-direction: row;
@ -848,9 +846,11 @@ export default {
border-radius: 2px;
border: 1px solid #404040;
background-color: #3a3a3a;
.el-progress__text {
display: none;
}
.el-progress-bar {
padding-right: 0px;
}
@ -861,8 +861,10 @@ export default {
border: 2px solid #252525;
cursor: pointer;
}
.image-desc {
vertical-align: top;
p {
width: 100px;
white-space: nowrap;
@ -876,6 +878,7 @@ export default {
}
}
}
.related-study-wrapper {
box-sizing: border-box;
height: 100%;
@ -891,6 +894,7 @@ export default {
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
.visit-name-wrapper {
position: absolute;
top: 5px;
@ -906,6 +910,7 @@ export default {
// flex-direction: row;
// align-content: flex-start;
}
.visit-item {
margin-left: 10px;
white-space: nowrap;
@ -918,17 +923,20 @@ export default {
cursor: pointer;
// margin-left: 10px;
}
.visit-item-active {
background-color: #607d8b;
border: 1px solid #607d8b;
}
}
.right {
width: 170px;
flex: 1;
height: 100%;
border-left: 1px solid #4a4a4a;
color: #d5d5d5;
.study-wrapper {
width: 100%;
height: 100%;

View File

@ -1,5 +1,6 @@
<template>
<div ref="viewport-fusion" class="viewport-wrapper" @mouseup="sliderMouseup" @mousemove="sliderMousemove"
<div ref="viewport-fusion" class="viewport-wrapper" v-loading="loading" :element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)" @mouseup="sliderMouseup" @mousemove="sliderMousemove"
@mouseleave="sliderMouseleave" :style="{ color: series.Modality === 'PT' || isMip ? '#666' : '#ddd' }">
<div v-if="series && taskInfo" class="left-top-text">
<div v-if="taskInfo.IsExistsClinicalData && !isMip && !isFusion" class="cd-info"
@ -185,6 +186,7 @@ export default {
isMove: false
},
ptVolumeId: null,
loading: false
}
},
mounted() {
@ -501,10 +503,12 @@ export default {
}
},
async createImageIdsAndCacheMetaData(obj) {
this.loading = true
await createImageIdsAndCacheMetaData({
modality: obj.Modality,
imageIds: obj.ImageIds
})
this.loading = false
},
async setSeriesInfo(obj, option = {}) {
try {
@ -782,7 +786,12 @@ export default {
e.stopPropagation()
e.preventDefault()
}
},
computed: {
NSTip() {
return `${this.$store.state.trials.uploadSize},NS: ${this.$store.state.trials.uploadTip}`
}
},
}
</script>
<style lang="scss" scoped>

View File

@ -617,6 +617,7 @@ import colorMap from './colorMap.vue'
import RectangleROITool from './tools/RectangleROITool'
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
import { getNetWorkSpeed, setNetWorkSpeedSize, workSpeedclose } from "@/utils"
const { visibility } = annotation
const { ViewportType, Events } = Enums
const renderingEngineId = 'myRenderingEngine'
@ -1322,6 +1323,14 @@ export default {
const isCriticalSequence = this.visitTaskList[taskIndex].StudyList[studyIndex].IsCriticalSequence
const keyImages = this.visitTaskList[taskIndex].KeyImages
const series = this.visitTaskList[taskIndex].StudyList[studyIndex].SeriesList[seriesIndex]
let file = series.find(item => item.ImageId === imageId)
if (file) {
getNetWorkSpeed()
setNetWorkSpeedSize(percentComplete, file.FileSize, imageId)
}
if (percentComplete === 100) {
workSpeedclose()
}
this.setImageLoadedProgress(series, percentComplete, instanceId)
if (!isCriticalSequence && series.IsBeMark && keyImages.length > 0) {
const i = keyImages.findIndex(i => i.Id === params.instanceId)
@ -2940,6 +2949,9 @@ export default {
this.uploadStatus = status
this[`${status}ImageVisible`] = true
},
},
beforeDestroy() {
workSpeedclose(true)
}
}
</script>

View File

@ -2,6 +2,9 @@
<div
ref="viewport"
class="viewport-wrapper"
v-loading="loading"
:element-loading-text="NSTip"
element-loading-background="rgba(0, 0, 0, 0.8)"
@mouseup="sliderMouseup"
@mousemove="sliderMousemove"
@mouseleave="sliderMouseleave"
@ -166,7 +169,8 @@ export default {
originalMarkers: [],
markers: { top: '', right: '', bottom: '', left: '' },
playClipState: false,
wwwcIdx: 2
wwwcIdx: 2,
loading: false
}
},
mounted() {
@ -403,7 +407,9 @@ export default {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
let imageId = obj.ImageIds[obj.SliceIndex] ? obj.ImageIds[obj.SliceIndex] : obj.ImageIds[0]
this.loading = true
const image = await cornerstoneDICOMImageLoader.wadouri.loadImage(imageId).promise
this.loading = false
if (obj.Modality === 'PT') {
this.cachePTMetadata([image])
}
@ -531,7 +537,12 @@ export default {
e.stopPropagation()
e.preventDefault()
}
},
computed: {
NSTip() {
return `${this.$store.state.trials.uploadSize},NS: ${this.$store.state.trials.uploadTip}`
}
},
}
</script>
<style lang="scss" scoped>

View File

@ -50,6 +50,7 @@ module.exports = defineConfig({
pathRewrite: {
}
},
// uat http://101.132.253.119:7010
'/api': {
target: 'http://106.14.89.110:30000',
changeOrigin: true,