54 Commits

Author SHA1 Message Date
wangxiaoshuang 9ecaf5e267 1 2026-08-24 09:45:13 +08:00
wangxiaoshuang 1ca4fb7dd4 影像替换与补充 2026-08-21 11:34:52 +08:00
caiyiling cb2fed1ad2 1 2026-08-17 14:29:26 +08:00
caiyiling 4c642cd814 上传更改 2026-07-31 17:20:20 +08:00
caiyiling 9dcbad7616 数据处理更改 2026-07-31 16:40:37 +08:00
caiyiling 197b3213e7 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main 2026-07-29 16:06:05 +08:00
caiyiling 3bfda749d8 质控预览非DICOM文件处理zip类型的数据 2026-07-29 16:05:06 +08:00
wangxiaoshuang 6f911d0d74 锁定蒙版添加白名单
continuous-integration/drone/push Build is passing
2026-07-29 15:53:17 +08:00
caiyiling 78f4a78994 测量数据预览按钮控制更改
continuous-integration/drone/push Build is passing
2026-07-29 09:53:43 +08:00
caiyiling 4e1911223d 1
continuous-integration/drone/push Build is passing
2026-07-28 09:17:40 +08:00
caiyiling 7dd02e84aa 样式调整
continuous-integration/drone/push Build is passing
2026-07-27 15:38:03 +08:00
caiyiling 7f260799d8 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2026-07-27 15:22:13 +08:00
caiyiling 06c7a8126b IVUS & OCT测量表格添加新开页签预览功能 2026-07-27 15:21:30 +08:00
wangxiaoshuang 78624d9cce 阅片中上传影像需要在下载影像后才能上传
continuous-integration/drone/push Build is passing
2026-07-27 14:28:55 +08:00
caiyiling 6ecc0e58e9 重置标记更改;标记定位更改
continuous-integration/drone/push Build is passing
2026-07-16 15:40:28 +08:00
wangxiaoshuang 84a81e1851 临床数据输入框禁用样式修改
continuous-integration/drone/push Build is passing
2026-07-16 14:12:04 +08:00
wangxiaoshuang b09209af31 完全随机分割问题绑定弹框不显示检查编号显示检查模态
continuous-integration/drone/push Build is passing
2026-07-16 13:30:48 +08:00
caiyiling 9892070ccc 播放功能修改
continuous-integration/drone/push Build is passing
2026-07-16 11:43:10 +08:00
wangxiaoshuang 63cbc8746f 完全随机融合弹框不显示检查号显示检查模态
continuous-integration/drone/push Build is passing
2026-07-16 10:54:55 +08:00
wangxiaoshuang fe64feac80 路由守卫判断条件修改
continuous-integration/drone/push Build was killed
2026-07-15 14:49:51 +08:00
wangxiaoshuang dce7fd9737 Merge branch 'uat'
continuous-integration/drone/push Build is passing
2026-07-14 17:54:02 +08:00
wangxiaoshuang 6187c585ad 接口频繁请求不提示
continuous-integration/drone/push Build is passing
2026-07-14 17:53:39 +08:00
wangxiaoshuang 22ac496d7b 用户编辑页添加国际化
continuous-integration/drone/push Build is passing
2026-07-14 11:50:46 +08:00
caiyiling 45c0e5030b Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2026-07-10 17:54:12 +08:00
caiyiling 36e23b8135 通知列表添加默认排序 2026-07-10 17:53:50 +08:00
wangxiaoshuang b530f68446 1
continuous-integration/drone/push Build is passing
2026-07-10 17:11:39 +08:00
wangxiaoshuang 383bfabae8 1
continuous-integration/drone/push Build is passing
2026-07-10 16:59:18 +08:00
wangxiaoshuang 196ed7f993 完全随机ir上传下载页面及预览页屏蔽受试者编号和项目编号
continuous-integration/drone/push Build is passing
2026-07-10 16:53:41 +08:00
caiyiling 70cc0027cd 受试者状态更改弹窗移除结束访视日期
continuous-integration/drone/push Build is passing
2026-07-10 16:21:44 +08:00
caiyiling 975b7d39a7 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2026-07-10 15:06:24 +08:00
caiyiling 21bcd1fddb 只有顺序阅片的时显示是否复制后续任务阅片表单 2026-07-10 15:05:39 +08:00
wangxiaoshuang 04bbde8583 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web 2026-07-10 13:55:06 +08:00
wangxiaoshuang f3c4bedbfd 趋势图提示红色改为蓝色 2026-07-10 13:54:54 +08:00
caiyiling 835aedc800 1
continuous-integration/drone/push Build is passing
2026-07-10 13:40:57 +08:00
caiyiling 9026b1d7fd 自定义标准完全随机阅片更改
continuous-integration/drone/push Build is passing
2026-07-10 13:12:44 +08:00
caiyiling b8f96efa66 列表调整
continuous-integration/drone/push Build is passing
2026-07-10 11:18:05 +08:00
caiyiling 4c4f68dbd0 完全随机阅片规则更改
continuous-integration/drone/push Build is passing
2026-07-10 11:12:55 +08:00
caiyiling 11c56d2f7a 1
continuous-integration/drone/push Build is passing
2026-07-09 16:52:58 +08:00
caiyiling a72521304e Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2026-07-09 16:33:50 +08:00
caiyiling 8baf6cb88f IR阅片模块相关页面添加任务名称搜索条件 2026-07-09 16:33:14 +08:00
wangxiaoshuang 71e04c3bf1 中心调研移动端确认弹框样式修改
continuous-integration/drone/push Build is passing
2026-07-09 15:17:44 +08:00
caiyiling 1af9b48622 完全随机IR阅片列表及阅片页面更改
continuous-integration/drone/push Build is pending
2026-07-09 14:17:02 +08:00
wangxiaoshuang 447b74fa55 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web
continuous-integration/drone/push Build is passing
2026-07-09 13:29:51 +08:00
wangxiaoshuang c633fbcf87 外层问题配置为表格问题不显示时阅片时不展示表格 2026-07-09 13:29:45 +08:00
caiyiling 1cc57ad1d6 修复保存申请重阅表单异常问题
continuous-integration/drone/push Build is passing
2026-07-09 09:30:19 +08:00
caiyiling f282fdfabc 是否复制后续表单去除标准限制
continuous-integration/drone/push Build is passing
2026-07-08 16:46:50 +08:00
caiyiling d77c6ecf03 Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is passing
2026-07-08 16:04:27 +08:00
caiyiling 1db75cf4f7 MRI-PDFF报告页数据更改 2026-07-08 16:03:55 +08:00
wangxiaoshuang 7bc70bfd6c 【中心调研】中心调研表手机扫描二维码进入时,同pc一样也需要增加验证。上传文件功能也要能正常使用
continuous-integration/drone/push Build is passing
2026-07-07 17:35:33 +08:00
wangxiaoshuang 69ba30e5a9 【趋势图】趋势图访视结果后增加查看按钮,可以查看已阅结果
continuous-integration/drone/push Build is passing
2026-07-07 10:25:32 +08:00
wangxiaoshuang 18482ddaa9 【中心调研】选择项目方案号生成验证码,项目方案号中有特殊符号,生成的验证码中未显示特殊符号
continuous-integration/drone/push Build is passing
2026-07-07 09:38:14 +08:00
caiyiling 14e2afcfee Merge branch 'main' of https://gitea.frp.extimaging.com/XCKJ/irc_web into main
continuous-integration/drone/push Build is pending
2026-07-06 17:22:30 +08:00
caiyiling 39dadbbf47 受试者任务状态变更原因更改 2026-07-06 17:20:59 +08:00
wangxiaoshuang d0ba340fea 去除分割上传文件按钮
continuous-integration/drone/push Build is passing
2026-07-06 11:44:37 +08:00
58 changed files with 1923 additions and 803 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

+2 -1
View File
@@ -98,6 +98,7 @@ import timeTag from '@/components/timeTag'
import Vue from 'vue' import Vue from 'vue'
import i18n from './lang' import i18n from './lang'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent' import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
import WHITELIST from "./utils/whiteList"
export default { export default {
name: 'App', name: 'App',
components: { components: {
@@ -156,7 +157,7 @@ export default {
// }, // },
methods: { methods: {
getIsLock() { getIsLock() {
if (zzSessionStorage.getItem('isLock') === 'true') { if (zzSessionStorage.getItem('isLock') === 'true' && !WHITELIST.includes(this.$route.path)) {
this.isLock = true this.isLock = true
} else { } else {
this.isLock = false this.isLock = false
+29
View File
@@ -480,3 +480,32 @@ export function getNoneDicomMarkList(data) {
data data
}) })
} }
export function getFilterTableQuestion(data) {
return request({
url: `/ReadingImageTask/getFilterTableQuestion`,
method: 'post',
data
})
}
export function deleteImageCache(params) {
return request({
url: `/Study/deleteImageCache`,
method: 'delete',
params
})
}
export function getAnonymizeInfo(params) {
return request({
url: `/Study/getAnonymizeInfo`,
method: 'get',
params
})
}
export function updateModality(params) {
return request({
url: `/Study/updateModality`,
method: 'put',
params
})
}
+20
View File
@@ -4523,3 +4523,23 @@ export function addOrUpdateCommonUploadRecord(data) {
data, data,
}) })
} }
export function getInspectionById(data) {
return request({
url: `/Inspection/getInspectionById`,
method: 'post',
data,
})
}
export function setJsonDetail(data) {
return request({
url: `/Inspection/setJsonDetail`,
method: 'post',
data,
})
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

After

Width:  |  Height:  |  Size: 320 KiB

+4
View File
@@ -213,6 +213,10 @@ export default {
}, },
methods: { methods: {
getInfo() {
var image = cornerstone.getImage(this.canvas)
return image
},
loadImageStack(dicomSeries, text = '') { loadImageStack(dicomSeries, text = '') {
this.tip = text this.tip = text
this.$nextTick(() => { this.$nextTick(() => {
+32 -1
View File
@@ -296,6 +296,13 @@
<option v-for="(item, index) in colormapsList" :key="index" :value="item.id">{{ item.name }}</option> <option v-for="(item, index) in colormapsList" :key="index" :value="item.id">{{ item.name }}</option>
</select> </select>
</div> </div>
<div class="sideTool-wrapper">
<label for="Replacement" style="display: inline-block;color: #fff;line-height: 20px;cursor: pointer;">
替换
</label>
<input type="file" id="Replacement" @change="beginScanFiles($event, 'replace')" style="display: none;">
</div>
</div> </div>
<!-- 患者信息 --> <!-- 患者信息 -->
@@ -382,6 +389,7 @@ import {
editPatientInfo editPatientInfo
} from '@/api/trials' } from '@/api/trials'
import { setPTClinicalDataForInstance, clearPTClinicalDataCache } from '@/utils/ptClinicalDataCache' import { setPTClinicalDataForInstance, clearPTClinicalDataCache } from '@/utils/ptClinicalDataCache'
import { changeFile } from "@/views/trials/trials-panel/reading/dicoms/components/upload.js"
export default { export default {
name: 'DicomsViewer', name: 'DicomsViewer',
components: { components: {
@@ -400,6 +408,14 @@ export default {
modality: { modality: {
type: String, type: String,
default: '' default: ''
},
SeriesList: {
type: Array,
default: () => []
},
currentSeriesIndex: {
type: Number,
default: -1
} }
}, },
watch: { watch: {
@@ -483,7 +499,9 @@ export default {
}, },
formLoading: false, formLoading: false,
type: '', type: '',
isEdit: 0 isEdit: 0,
fileKey: null,
file: null,
} }
}, },
computed: { computed: {
@@ -511,6 +529,19 @@ export default {
}, },
methods: { methods: {
beginScanFiles(e, key) {
this.fileKey = key
this.file = e.target.files[0]
let image = this.$refs[this.activeItem].getInfo()
let study = {
seriesList: this.SeriesList,
currentSeriesIndex: this.currentSeriesIndex
}
this.$emit("update:loading", true)
changeFile('CRC', this.file, study, image)
// DicomEvent.$emit('getStudyFile')
},
setToolsPassive() { setToolsPassive() {
const elements = document.querySelectorAll('.dicom-item') const elements = document.querySelectorAll('.dicom-item')
const scope = this const scope = this
@@ -141,6 +141,10 @@ export default {
type: Number, type: Number,
default: 2, default: 2,
}, },
isReading: {
type: Boolean,
default: false,
}
}, },
data() { data() {
return { return {
@@ -442,6 +446,9 @@ export default {
let params = { let params = {
TrialImageDownloadId: this.downloadId, TrialImageDownloadId: this.downloadId,
} }
if (this.isReading) {
params.VisitTaskId = this.TaskId
}
await downloadImageSuccess(params) await downloadImageSuccess(params)
} catch (err) { } catch (err) {
console.log(err) console.log(err)
@@ -43,7 +43,8 @@
<template slot-scope="scope"> <template slot-scope="scope">
<div class="btnBox"> <div class="btnBox">
<!--上传---> <!--上传--->
<form id="inputForm" :ref="`uploadForm_${scope.row.Id}`" enctype="multipart/form-data" v-if="!forbid"> <form id="inputForm" :ref="`uploadForm_${scope.row.Id}`" enctype="multipart/form-data"
v-if="!forbid && (!isReading || isDownloaded)">
<div class="form-group" style="margin-right: 10px"> <div class="form-group" style="margin-right: 10px">
<div :id="`directoryInputWrapper_${scope.row.Id}`" class="btn btn-link file-input"> <div :id="`directoryInputWrapper_${scope.row.Id}`" class="btn btn-link file-input">
<el-button circle icon="el-icon-upload2" :disabled="btnLoading" :loading="btnLoading" <el-button circle icon="el-icon-upload2" :disabled="btnLoading" :loading="btnLoading"
@@ -60,17 +61,15 @@
scope.row.UploadStudyList.length <= 0 scope.row.UploadStudyList.length <= 0
" @click.stop="handleViewReadingImages(scope.row)" :title="$t('upload:dicom:button:preview')" /> " @click.stop="handleViewReadingImages(scope.row)" :title="$t('upload:dicom:button:preview')" />
<!--删除---> <!--删除--->
<el-button circle :disabled="!scope.row.UploadStudyList || <el-button circle icon="el-icon-delete" :title="$t('upload:dicom:button:delete')"
scope.row.UploadStudyList.length <= 0 || @click.stop="remove(scope.row)" />
scope.row.ReadingTaskState === 2
" icon="el-icon-delete" :title="$t('upload:dicom:button:delete')" @click.stop="remove(scope.row)" />
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div style="margin: 10px 0" class="top"> <div style="margin: 10px 0" class="top">
<span>{{ $t('upload:dicom:uploadTitle') }}</span> <span>{{ $t('upload:dicom:uploadTitle') }}</span>
<div class="btnBox" v-if="!forbid"> <div class="btnBox" v-if="!forbid && (!isReading || isDownloaded)">
<span style="margin-right: 10px">{{ $store.state.trials.uploadTip }}</span> <span style="margin-right: 10px">{{ $store.state.trials.uploadTip }}</span>
<form id="inputForm" ref="uploadForm" enctype="multipart/form-data"> <form id="inputForm" ref="uploadForm" enctype="multipart/form-data">
<div class="form-group"> <div class="form-group">
@@ -379,6 +378,14 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
isReading: {
type: Boolean,
default: false,
},
StudyInstanceUID: {
type: String,
default: '',
}
}, },
components: { components: {
'study-view': studyView, 'study-view': studyView,
@@ -428,7 +435,8 @@ export default {
openSubjectVisitId: null, openSubjectVisitId: null,
openVisitTaskId: null, openVisitTaskId: null,
TrialModality: [], TrialModality: [],
IsReadingTaskViewInOrder: 2 IsReadingTaskViewInOrder: 2,
isDownloaded: false
} }
}, },
async mounted() { async mounted() {
@@ -471,6 +479,7 @@ export default {
let res = await getSubjectImageUploadList(params) let res = await getSubjectImageUploadList(params)
this.loading = false this.loading = false
if (res.IsSuccess) { if (res.IsSuccess) {
this.isDownloaded = res.OtherInfo.IsIRImageDownloaded
this.TrialModality = res.OtherInfo.TrialModality.split('|') this.TrialModality = res.OtherInfo.TrialModality.split('|')
this.IsReadingTaskViewInOrder = res.OtherInfo.IsReadingTaskViewInOrder || res.OtherInfo.IsReadingTaskViewInOrder === 0 ? res.OtherInfo.IsReadingTaskViewInOrder : 2 this.IsReadingTaskViewInOrder = res.OtherInfo.IsReadingTaskViewInOrder || res.OtherInfo.IsReadingTaskViewInOrder === 0 ? res.OtherInfo.IsReadingTaskViewInOrder : 2
this.StudyInstanceUidList = [] this.StudyInstanceUidList = []
@@ -948,7 +957,7 @@ export default {
var instanceItem = instanceList.find(function (item) { var instanceItem = instanceList.find(function (item) {
return item.instanceUid === instanceUid return item.instanceUid === instanceUid
}) })
if (!instanceItem) { if (!false) {
var date = data.string('x00080023') var date = data.string('x00080023')
var time = data.string('x00080033') var time = data.string('x00080033')
var instanceTime = '' var instanceTime = ''
@@ -1077,11 +1086,12 @@ export default {
async verifyStudy() { async verifyStudy() {
this.btnLoading = true this.btnLoading = true
var studyList = [] var studyList = []
let scope = this
this.selectArr.forEach((item) => { this.selectArr.forEach((item) => {
item.dicomInfo.uploadFileSize = 0 item.dicomInfo.uploadFileSize = 0
if (!item.uploadState.selected) { if (!item.uploadState.selected) {
studyList.push({ studyList.push({
studyInstanceUid: item.dicomInfo.studyUid, studyInstanceUid: scope.isReading && scope.StudyInstanceUID ? scope.StudyInstanceUID : item.dicomInfo.studyUid,
studyDate: item.dicomInfo.studyTime, studyDate: item.dicomInfo.studyTime,
}) })
} }
@@ -1176,7 +1186,7 @@ export default {
let t = setInterval(() => { let t = setInterval(() => {
dicomUploadInProgress({ dicomUploadInProgress({
trialId: scope.trialId, trialId: scope.trialId,
studyInstanceUid: dicomInfo.studyUid, studyInstanceUid: scope.isReading && scope.StudyInstanceUID ? scope.StudyInstanceUID : dicomInfo.studyUid,
VisitTaskId: dicomInfo.visitTaskId, VisitTaskId: dicomInfo.visitTaskId,
}).then((res) => { }).then((res) => {
console.log(dicomInfo.visitTaskId) console.log(dicomInfo.visitTaskId)
@@ -1203,7 +1213,7 @@ export default {
dicomInfo.RadiopharmaceuticalStartTime, dicomInfo.RadiopharmaceuticalStartTime,
studyId: dicomInfo.studyId, studyId: dicomInfo.studyId,
studyInstanceUid: dicomInfo.studyUid, studyInstanceUid: scope.isReading && scope.StudyInstanceUID ? scope.StudyInstanceUID : dicomInfo.studyUid,
studyTime: dicomInfo.studyTime, studyTime: dicomInfo.studyTime,
description: dicomInfo.description, description: dicomInfo.description,
seriesCount: dicomInfo.seriesNum, seriesCount: dicomInfo.seriesNum,
@@ -1244,58 +1254,60 @@ export default {
try { try {
let o = v.instanceList[ii] let o = v.instanceList[ii]
let name = `${v.instanceList[ii].file.webkitRelativePath}_${v.instanceList[ii].instanceUid}` let name = `${v.instanceList[ii].file.webkitRelativePath}_${v.instanceList[ii].instanceUid}`
if (o.isReUpload) { // if (o.isReUpload) {
dicomInfo.failedFileCount++ // dicomInfo.failedFileCount++
dicomInfo.uploadFileSize += o.file.size // dicomInfo.uploadFileSize += o.file.size
Record.Existed.push(name) // Record.Existed.push(name)
Record.FileCount++ // Record.FileCount++
} else if (o.myPath) { // }
instanceList.push({ // else if (o.myPath) {
studyInstanceUid: dicomInfo.studyUid, // instanceList.push({
seriesInstanceUid: v.seriesUid, // studyInstanceUid: dicomInfo.studyUid,
SOPClassUID: o.SOPClassUID, // seriesInstanceUid: v.seriesUid,
TransferSytaxUID: o.TransferSytaxUID, // SOPClassUID: o.SOPClassUID,
MediaStorageSOPInstanceUID: // TransferSytaxUID: o.TransferSytaxUID,
o.MediaStorageSOPInstanceUID, // MediaStorageSOPInstanceUID:
MediaStorageSOPClassUID: // o.MediaStorageSOPInstanceUID,
o.MediaStorageSOPClassUID, // MediaStorageSOPClassUID:
sopInstanceUid: o.instanceUid, // o.MediaStorageSOPClassUID,
instanceNumber: o.instanceNumber, // sopInstanceUid: o.instanceUid,
instanceTime: o.instanceTime, // instanceNumber: o.instanceNumber,
imageRows: o.imageRows, // instanceTime: o.instanceTime,
imageColumns: o.imageColumns, // imageRows: o.imageRows,
sliceLocation: o.sliceLocation, // imageColumns: o.imageColumns,
sliceThickness: o.sliceThickness, // sliceLocation: o.sliceLocation,
numberOfFrames: o.numberOfFrames, // sliceThickness: o.sliceThickness,
pixelSpacing: o.pixelSpacing, // numberOfFrames: o.numberOfFrames,
imagerPixelSpacing: o.imagerPixelSpacing, // pixelSpacing: o.pixelSpacing,
frameOfReferenceUID: o.frameOfReferenceUID, // imagerPixelSpacing: o.imagerPixelSpacing,
windowCenter: o.windowCenter, // frameOfReferenceUID: o.frameOfReferenceUID,
windowWidth: o.windowWidth, // windowCenter: o.windowCenter,
path: o.myPath, // windowWidth: o.windowWidth,
FileSize: o.FileSize, // path: o.myPath,
// FileSize: o.FileSize,
PhotometricInterpretation: // PhotometricInterpretation:
o.PhotometricInterpretation, // o.PhotometricInterpretation,
BitsAllocated: o.BitsAllocated, // BitsAllocated: o.BitsAllocated,
PixelRepresentation: o.PixelRepresentation, // PixelRepresentation: o.PixelRepresentation,
RescaleIntercept: o.RescaleIntercept, // RescaleIntercept: o.RescaleIntercept,
RescaleSlope: o.RescaleSlope, // RescaleSlope: o.RescaleSlope,
ImagePositionPatient: o.ImagePositionPatient, // ImagePositionPatient: o.ImagePositionPatient,
ImageOrientationPatient: // ImageOrientationPatient:
o.ImageOrientationPatient, // o.ImageOrientationPatient,
SequenceOfUltrasoundRegions: // SequenceOfUltrasoundRegions:
o.SequenceOfUltrasoundRegions, // o.SequenceOfUltrasoundRegions,
FrameTime: o.FrameTime, // FrameTime: o.FrameTime,
CorrectedImage: o.CorrectedImage, // CorrectedImage: o.CorrectedImage,
Units: o.Units, // Units: o.Units,
DecayCorrection: o.DecayCorrection, // DecayCorrection: o.DecayCorrection,
EncapsulatedDocument: o.EncapsulatedDocument, // EncapsulatedDocument: o.EncapsulatedDocument,
}) // })
Record.Uploaded.push(name) // Record.Uploaded.push(name)
dicomInfo.failedFileCount++ // dicomInfo.failedFileCount++
Record.FileCount++ // Record.FileCount++
} else { // }
// else {
let path = `/${params.trialId}/Image/${params.subjectId let path = `/${params.trialId}/Image/${params.subjectId
}/${params.subjectVisitId}/${dicomInfo.visitTaskId }/${params.subjectVisitId}/${dicomInfo.visitTaskId
}/${scope.getGuid( }/${scope.getGuid(
@@ -1321,7 +1333,18 @@ export default {
file: o.file, file: o.file,
speed: true, speed: true,
}, },
null, scope.isReading && scope.StudyInstanceUID ? {
AnonymizeFixedList: [
{
Element: '000D',
Group: '0020',
ReplaceValue: scope.StudyInstanceUID,
Id: 'StudyInstanceUID'
}
],
AnonymizeNotFixedList: [],
DicomStoreInfo: {}
} : null,
(percentage, checkpoint, lastPer) => { (percentage, checkpoint, lastPer) => {
dicomInfo.uploadFileSize += dicomInfo.uploadFileSize +=
checkpoint.size * (percentage - lastPer) checkpoint.size * (percentage - lastPer)
@@ -1387,7 +1410,7 @@ export default {
} }
if (res && res.url) { if (res && res.url) {
instanceList.push({ instanceList.push({
studyInstanceUid: dicomInfo.studyUid, studyInstanceUid: scope.isReading && scope.StudyInstanceUID ? scope.StudyInstanceUID : dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid, seriesInstanceUid: v.seriesUid,
sopInstanceUid: o.instanceUid, sopInstanceUid: o.instanceUid,
SOPClassUID: o.SOPClassUID, SOPClassUID: o.SOPClassUID,
@@ -1436,7 +1459,7 @@ export default {
Record.Failed.push(name) Record.Failed.push(name)
Record.FileCount++ Record.FileCount++
} }
} // }
resolve1() resolve1()
} catch (e) { } catch (e) {
console.log(e) console.log(e)
@@ -1454,7 +1477,7 @@ export default {
} }
} }
params.study.seriesList.push({ params.study.seriesList.push({
studyInstanceUid: dicomInfo.studyUid, studyInstanceUid: scope.isReading && scope.StudyInstanceUID ? scope.StudyInstanceUID : dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid, seriesInstanceUid: v.seriesUid,
seriesNumber: v.seriesNumber, seriesNumber: v.seriesNumber,
seriesTime: v.seriesTime, seriesTime: v.seriesTime,
@@ -6,12 +6,13 @@
<el-tab-pane :label="$t('uploadDicomAndNonedicom:label:dicom')" name="dicom"> <el-tab-pane :label="$t('uploadDicomAndNonedicom:label:dicom')" name="dicom">
<dicomFile v-if="activeName === 'dicom'" :SubjectId="SubjectId" :SubjectCode="SubjectCode" <dicomFile v-if="activeName === 'dicom'" :SubjectId="SubjectId" :SubjectCode="SubjectCode"
:Criterion="Criterion" :TaskId="VisitTaskId" :isUpload.sync="isUpload" :Criterion="Criterion" :TaskId="VisitTaskId" :isUpload.sync="isUpload"
:isReadingTaskViewInOrder="isReadingTaskViewInOrder" :IsImageSegment="IsImageSegment" :forbid="forbid" /> :isReadingTaskViewInOrder="isReadingTaskViewInOrder" :IsImageSegment="IsImageSegment" :forbid="forbid"
:isReading="isReading" :StudyInstanceUID="StudyInstanceUID" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('uploadDicomAndNonedicom:label:nonedicom')" name="nonedicom"> <el-tab-pane :label="$t('uploadDicomAndNonedicom:label:nonedicom')" name="nonedicom">
<nonedicomFile v-if="activeName === 'nonedicom'" :SubjectId="SubjectId" :SubjectCode="SubjectCode" <nonedicomFile v-if="activeName === 'nonedicom'" :SubjectId="SubjectId" :SubjectCode="SubjectCode"
:Criterion="Criterion" :VisitTaskId="VisitTaskId" :isUpload.sync="isUpload" :IsImageSegment="IsImageSegment" :Criterion="Criterion" :VisitTaskId="VisitTaskId" :isUpload.sync="isUpload" :IsImageSegment="IsImageSegment"
:isReadingTaskViewInOrder="isReadingTaskViewInOrder" :forbid="forbid" /> :isReadingTaskViewInOrder="isReadingTaskViewInOrder" :forbid="forbid" :isReading="isReading" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-dialog> </el-dialog>
@@ -58,6 +59,14 @@ export default {
IsImageSegment: { IsImageSegment: {
type: Boolean, type: Boolean,
default: false, default: false,
},
isReading: {
type: Boolean,
default: false,
},
StudyInstanceUID: {
type: String,
default: '',
} }
}, },
data() { data() {
@@ -111,11 +111,9 @@
:title="$t('upload:nonedicom:button:preview')" @click.stop="handlePreviewNoneDicomFiles(scope.row)" /> :title="$t('upload:nonedicom:button:preview')" @click.stop="handlePreviewNoneDicomFiles(scope.row)" />
<!--上传---> <!--上传--->
<el-button circle icon="el-icon-upload2" :title="$t('upload:nonedicom:button:upload')" v-if="!forbid" <el-button circle icon="el-icon-upload2" :title="$t('upload:nonedicom:button:upload')" v-if="!forbid"
@click.native.prevent="handleUpload(scope.row)" /> @click.native.prevent="handleUpload(scope.row)" :disabled="isReading && !isDownloaded" />
<!--删除---> <!--删除--->
<el-button :disabled="scope.row.UploadedFileCount <= 0 || <el-button circle icon="el-icon-delete" :title="$t('upload:nonedicom:button:delete')"
scope.row.ReadingTaskState === 2
" circle icon="el-icon-delete" :title="$t('upload:nonedicom:button:delete')"
@click.stop="remove(scope.row)" /> @click.stop="remove(scope.row)" />
</template> </template>
</el-table-column> </el-table-column>
@@ -270,6 +268,10 @@ export default {
type: Number, type: Number,
default: 0, default: 0,
}, },
isReading: {
type: Boolean,
default: false,
}
}, },
data() { data() {
return { return {
@@ -291,7 +293,8 @@ export default {
BodyPart: {}, BodyPart: {},
relationInfo: { relationInfo: {
ImageFormatList: [] ImageFormatList: []
} },
isDownloaded: false
} }
}, },
watch: { watch: {
@@ -348,6 +351,7 @@ export default {
if (res.IsSuccess) { if (res.IsSuccess) {
this.list = res.Result this.list = res.Result
this.relationInfo = res.OtherInfo this.relationInfo = res.OtherInfo
this.isDownloaded = res.OtherInfo.IsIRImageDownloaded
this.faccept = [] this.faccept = []
this.relationInfo.ImageFormatList.forEach((item) => { this.relationInfo.ImageFormatList.forEach((item) => {
this.faccept.push(`.${item}`) this.faccept.push(`.${item}`)
+5
View File
@@ -92,8 +92,13 @@ router.beforeEach(async (to, from, next) => {
await store.dispatch('user/getUserInfo') await store.dispatch('user/getUserInfo')
const accessRoutes = await store.dispatch('permission/generateRoutes') const accessRoutes = await store.dispatch('permission/generateRoutes')
resetRouter() resetRouter()
if (accessRoutes.length > 0) {
router.addRoutes(accessRoutes) router.addRoutes(accessRoutes)
next({ ...to, replace: true }) next({ ...to, replace: true })
} else {
next()
}
} catch (error) { } catch (error) {
console.log(error) console.log(error)
// 删除token并进入登录页面以重新登录 // 删除token并进入登录页面以重新登录
+5
View File
@@ -149,6 +149,11 @@ export const constantRoutes = [
hidden: true, hidden: true,
component: () => import('@/views/trials/trials-panel/reading/dicoms/none-dicoms') component: () => import('@/views/trials/trials-panel/reading/dicoms/none-dicoms')
}, },
{
path: '/ecrfList',
hidden: true,
component: () => import('@/views/trials/trials-panel/reading/dicoms/components/TableList')
},
{ {
path: '/readingPage', path: '/readingPage',
name: 'readingPage', name: 'readingPage',
+1 -1
View File
@@ -1,7 +1,7 @@
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
module.exports = { module.exports = {
title: 'HASC lmaging Trial Management System', title: 'EIC lmaging Trial Management System',
/** /**
* @type {boolean} true | false * @type {boolean} true | false
+5 -2
View File
@@ -6,6 +6,7 @@ export const anonymization = function (file, config) {
try { try {
const reader = new FileReader() const reader = new FileReader()
let AnonymizeFixedList = config.AnonymizeFixedList let AnonymizeFixedList = config.AnonymizeFixedList
console.log(AnonymizeFixedList, 'AnonymizeFixedList')
let AnonymizeNotFixedList = config.AnonymizeNotFixedList let AnonymizeNotFixedList = config.AnonymizeNotFixedList
let DicomStoreInfo = config.DicomStoreInfo let DicomStoreInfo = config.DicomStoreInfo
reader.onload = async (event) => { reader.onload = async (event) => {
@@ -15,9 +16,11 @@ export const anonymization = function (file, config) {
let dataset = dcmjs.data.DicomMessage.readFile(buffer) let dataset = dcmjs.data.DicomMessage.readFile(buffer)
for (var i = 0; i < AnonymizeFixedList.length; i++) { for (var i = 0; i < AnonymizeFixedList.length; i++) {
let AnonymizeFixed = AnonymizeFixedList[i] let AnonymizeFixed = AnonymizeFixedList[i]
if (!dataset.dict.hasOwnProperty(`${AnonymizeFixed.Group + AnonymizeFixed.Element}`)) continue if (!dataset.dict.hasOwnProperty(`${AnonymizeFixed.Group + AnonymizeFixed.Element}`) && !dataset.meta.hasOwnProperty(`${AnonymizeFixed.Group + AnonymizeFixed.Element}`)) continue
if (dataset.dict[AnonymizeFixed.Group + AnonymizeFixed.Element]) { if (dataset.dict[AnonymizeFixed.Group + AnonymizeFixed.Element]) {
dataset.dict[AnonymizeFixed.Group + AnonymizeFixed.Element].Value[0] = AnonymizeFixed.ReplaceValue dataset.dict[AnonymizeFixed.Group + AnonymizeFixed.Element].Value[0] = AnonymizeFixed.ReplaceValue
} else if (dataset.meta[AnonymizeFixed.Group + AnonymizeFixed.Element]) {
dataset.meta[AnonymizeFixed.Group + AnonymizeFixed.Element].Value[0] = AnonymizeFixed.ReplaceValue
} else { } else {
dataset.dict[AnonymizeFixed.Group + AnonymizeFixed.Element] = { dataset.dict[AnonymizeFixed.Group + AnonymizeFixed.Element] = {
vr: AnonymizeFixed.ValueRepresentation, vr: AnonymizeFixed.ValueRepresentation,
@@ -60,7 +63,7 @@ export const anonymization = function (file, config) {
let newDicomFile = dataset.write() // fragmentMultiframe 原始数据是否进行分割 let newDicomFile = dataset.write() // fragmentMultiframe 原始数据是否进行分割
const bufferArray = new Uint8Array(newDicomFile) const bufferArray = new Uint8Array(newDicomFile)
const blob = new Blob([bufferArray], { type: 'application/octet-stream' }) const blob = new Blob([bufferArray], { type: 'application/octet-stream' })
resolve({ blob, pixelDataElement }) resolve({ blob, pixelDataElement, Modality: dataset.dict['00080060'].Value[0] })
} catch (err) { } catch (err) {
console.log(file, 'warning') console.log(file, 'warning')
console.log(err) console.log(err)
+2 -1
View File
@@ -12,7 +12,8 @@ export const dcmUpload = async function (data, config, progressFn, fileInfo) {
let res = await Vue.prototype.OSSclient.multipartUpload(Object.assign(data, { file: blob.blob }), progressFn, fileInfo) let res = await Vue.prototype.OSSclient.multipartUpload(Object.assign(data, { file: blob.blob }), progressFn, fileInfo)
resolve({ resolve({
...res, ...res,
image: blob.pixelDataElement image: blob.pixelDataElement,
Modality: blob.Modality
}) })
// let OSSclientA = await OSSclient // let OSSclientA = await OSSclient
// let blob = await encoder(file) // let blob = await encoder(file)
+1 -1
View File
@@ -1,6 +1,6 @@
import defaultSettings from '@/settings' import defaultSettings from '@/settings'
const title = defaultSettings.title || 'HASC lmaging Trial Management System' const title = defaultSettings.title || 'EIC lmaging Trial Management System'
export default function getPageTitle(pageTitle) { export default function getPageTitle(pageTitle) {
if (pageTitle) { if (pageTitle) {
+7 -7
View File
@@ -40,7 +40,7 @@ async function ossGenerateSTS() {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
let _vm = router.default.app let _vm = router.default.app
if (_vm._route.path !== '/trials/trials-panel/visit/crc-upload') { if (_vm._route.path !== '/trials/trials-panel/visit/crc-upload' && _vm._route.path !== '/readingDicoms' && _vm._route.path !== '/showdicom') {
var objectItem = objectName.split('/') var objectItem = objectName.split('/')
// objectItem[objectItem.length - 1] = new Date().getTime() + '_' + objectItem[objectItem.length - 1] // objectItem[objectItem.length - 1] = new Date().getTime() + '_' + objectItem[objectItem.length - 1]
objectItem[objectItem.length - 1] = `${objectItem[objectItem.length - 1].substring( objectItem[objectItem.length - 1] = `${objectItem[objectItem.length - 1].substring(
@@ -57,7 +57,7 @@ async function ossGenerateSTS() {
const trialId = urlParams.get('trialId') const trialId = urlParams.get('trialId')
if (Object.keys(fileInfo).length !== 0) { if (Object.keys(fileInfo).length !== 0) {
fileInfo.fileType = mimeTypeToExt(fileInfo.fileType) fileInfo.fileType = mimeTypeToExt(fileInfo.fileType)
let params = Object.assign({path: objectName}, fileInfo) let params = Object.assign({ path: objectName }, fileInfo)
addOrUpdateFileUploadRecord(params) addOrUpdateFileUploadRecord(params)
} else if (trialId) { } else if (trialId) {
const fileName = objectName.split('/').pop() const fileName = objectName.split('/').pop()
@@ -97,7 +97,7 @@ async function ossGenerateSTS() {
OSSclient = new OSS(Vue.prototype.OSSclientConfig); OSSclient = new OSS(Vue.prototype.OSSclientConfig);
} }
let _vm = router.default.app let _vm = router.default.app
if (_vm._route.path !== '/trials/trials-panel/visit/crc-upload') { if (_vm._route.path !== '/trials/trials-panel/visit/crc-upload' && _vm._route.path !== '/readingDicoms' && _vm._route.path !== '/showdicom') {
var objectItem = data.path.split('/') var objectItem = data.path.split('/')
// objectItem[objectItem.length - 1] = new Date().getTime() + '_' + objectItem[objectItem.length - 1] // objectItem[objectItem.length - 1] = new Date().getTime() + '_' + objectItem[objectItem.length - 1]
objectItem[objectItem.length - 1] = `${objectItem[objectItem.length - 1].substring( objectItem[objectItem.length - 1] = `${objectItem[objectItem.length - 1].substring(
@@ -114,7 +114,7 @@ async function ossGenerateSTS() {
const trialId = urlParams.get('trialId') const trialId = urlParams.get('trialId')
if (Object.keys(fileInfo).length !== 0) { if (Object.keys(fileInfo).length !== 0) {
fileInfo.fileType = mimeTypeToExt(fileInfo.fileType) fileInfo.fileType = mimeTypeToExt(fileInfo.fileType)
let params = Object.assign({path: data.path}, fileInfo) let params = Object.assign({ path: data.path }, fileInfo)
addOrUpdateFileUploadRecord(params) addOrUpdateFileUploadRecord(params)
} else if (trialId) { } else if (trialId) {
const fileName = data.path.split('/').pop() const fileName = data.path.split('/').pop()
@@ -151,7 +151,7 @@ async function ossGenerateSTS() {
try { try {
var name = objectName.split('/')[objectName.split('/').length - 1] var name = objectName.split('/')[objectName.split('/').length - 1]
let _vm = router.default.app let _vm = router.default.app
if (_vm._route.path !== '/trials/trials-panel/visit/crc-upload') { if (_vm._route.path !== '/trials/trials-panel/visit/crc-upload' && _vm._route.path !== '/readingDicoms' && _vm._route.path !== '/showdicom') {
var objectItem = objectName.split('/') var objectItem = objectName.split('/')
objectItem[objectItem.length - 1] = new Date().getTime() + '_' + objectItem[objectItem.length - 1] objectItem[objectItem.length - 1] = new Date().getTime() + '_' + objectItem[objectItem.length - 1]
objectName = objectItem.join('/') objectName = objectItem.join('/')
@@ -225,7 +225,7 @@ function uploadAWS(aws, data, progress, fileInfo) {
const { file, path } = data; const { file, path } = data;
if (!file || !path) return reject('file and path be required'); if (!file || !path) return reject('file and path be required');
let _vm = router.default.app let _vm = router.default.app
if (_vm._route.path !== '/trials/trials-panel/visit/crc-upload') { if (_vm._route.path !== '/trials/trials-panel/visit/crc-upload' && _vm._route.path !== '/readingDicoms' && _vm._route.path !== '/showdicom') {
var objectItem = data.path.split('/') var objectItem = data.path.split('/')
// objectItem[objectItem.length - 1] = new Date().getTime() + '_' + objectItem[objectItem.length - 1] // objectItem[objectItem.length - 1] = new Date().getTime() + '_' + objectItem[objectItem.length - 1]
objectItem[objectItem.length - 1] = `${objectItem[objectItem.length - 1].substring( objectItem[objectItem.length - 1] = `${objectItem[objectItem.length - 1].substring(
@@ -246,7 +246,7 @@ function uploadAWS(aws, data, progress, fileInfo) {
const trialId = urlParams.get('trialId') const trialId = urlParams.get('trialId')
if (Object.keys(fileInfo).length !== 0) { if (Object.keys(fileInfo).length !== 0) {
fileInfo.fileType = mimeTypeToExt(fileInfo.fileType) fileInfo.fileType = mimeTypeToExt(fileInfo.fileType)
let params = Object.assign({path: decodeUtf8(curPath)}, fileInfo) let params = Object.assign({ path: decodeUtf8(curPath) }, fileInfo)
addOrUpdateFileUploadRecord(params) addOrUpdateFileUploadRecord(params)
} else if (trialId) { } else if (trialId) {
const fileName = decodeUtf8(curPath).split('/').pop() const fileName = decodeUtf8(curPath).split('/').pop()
+3
View File
@@ -118,6 +118,9 @@ service.interceptors.response.use(
if (res.IsSuccess) { if (res.IsSuccess) {
return Promise.resolve(res) return Promise.resolve(res)
} else if (res.IsSuccess === false) { } else if (res.IsSuccess === false) {
if (res.Code === -4) {
return Promise.resolve(res)
}
if (res.Code !== 5) { if (res.Code !== 5) {
MessageBox.confirm(res.ErrorMessage, { MessageBox.confirm(res.ErrorMessage, {
type: 'warning', type: 'warning',
+1 -1
View File
@@ -1,2 +1,2 @@
const WHITELIST = ['/', "/curriculumVitae", 'researchDetail_m', '/researchForm', '/ReviewersResearch', '/login', '/link_expired', '/error', '/resetpassword', '/recompose', '/email-recompose', '/trialStats', '/showdicom', '/imagesShare', '/audit', '/preview', '/researchLogin', '/researchLogin_m', '/blindResumeInfo', '/trialsResume', '/joinVerify', '/showNoneDicoms', '/noneDicomReading', '/clinicalData', '/readingDicoms', '/readingPage', '/visitDicomReview', '/visitNondicomReview', '/globalReview', '/adReview', '/oncologyReview', '/nonedicoms'] const WHITELIST = ['/', "/curriculumVitae", 'researchDetail_m', '/researchForm', '/ReviewersResearch', '/login', '/link_expired', '/error', '/resetpassword', '/recompose', '/email-recompose', '/trialStats', '/showdicom', '/imagesShare', '/audit', '/preview', '/researchLogin', '/researchLogin_m', '/blindResumeInfo', '/trialsResume', '/joinVerify', '/showNoneDicoms', '/noneDicomReading', '/clinicalData', '/readingDicoms', '/readingPage', '/visitDicomReview', '/visitNondicomReview', '/globalReview', '/adReview', '/oncologyReview', '/nonedicoms', '/ecrfList']
export default WHITELIST export default WHITELIST
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="dashboard"> <div class="dashboard">
<div class="dashboard-header"> <div class="dashboard-header">
<h1> <h1>
<a href="#" @click="handleTitleClick">GRR Real-Time HASC Information</a> <a href="#" @click="handleTitleClick">GRR Real-Time IRC Information</a>
</h1> </h1>
<div> <div>
+1 -1
View File
@@ -1,7 +1,7 @@
<template> <template>
<div class="dashboard"> <div class="dashboard">
<div class="dashboard-header"> <div class="dashboard-header">
<!-- <h1>Extimaging Real-Time HASC Information</h1> --> <h1>Extimaging Real-Time IRC Information</h1>
<div> <div>
<span>{{ currentTime }}</span> <span>{{ currentTime }}</span>
</div> </div>
+13 -4
View File
@@ -125,7 +125,8 @@
</div> </div>
<div class="viewerContent"> <div class="viewerContent">
<dicom-viewer id="dicomViewer" ref="dicomViewer" style="height:100%" :loading.sync="loading" <dicom-viewer id="dicomViewer" ref="dicomViewer" style="height:100%" :loading.sync="loading"
:modality="modality" :Comparison.sync="isComparison" @loadStudy="loadStudy" /> :modality="modality" :Comparison.sync="isComparison" :SeriesList="seriesList"
:currentSeriesIndex="currentSeriesIndex" @loadStudy="loadStudy" />
</div> </div>
<!-- <div class="viewerRightSidePanel"> <!-- <div class="viewerRightSidePanel">
<dicom-tools /> <dicom-tools />
@@ -179,7 +180,7 @@ export default {
description: '', description: '',
seriesCount: 0, seriesCount: 0,
seriesList: [], seriesList: [],
currentSeriesIndex: -1, currentSeriesIndex: 0,
arr: [], arr: [],
activeName: 'first', activeName: 'first',
tpList: [], tpList: [],
@@ -253,6 +254,11 @@ export default {
}) })
workSpeedclose(true) workSpeedclose(true)
}, },
watch: {
currentSeriesIndex() {
console.log(this.currentSeriesIndex, 'currentSeriesIndex')
}
},
methods: { methods: {
async updateImageResizePath(data) { async updateImageResizePath(data) {
try { try {
@@ -353,13 +359,14 @@ export default {
i.ImageId = imageId i.ImageId = imageId
} }
}) })
var subjectVisitId = this.$router.currentRoute.query.subjectVisitId var subjectVisitId = this.$router.currentRoute.query.subjectVisitId ? this.$router.currentRoute.query.subjectVisitId : item.SubjectVisitId
var studyId = this.$router.currentRoute.query.studyId var studyId = this.$router.currentRoute.query.studyId
var trialId = this.$router.currentRoute.query.trialId var trialId = this.$router.currentRoute.query.trialId
seriesList.push({ seriesList.push({
trialId, trialId,
subjectVisitId, subjectVisitId,
studyId, studyId,
subjectId: item.SubjectId,
imageIds: imageIds, imageIds: imageIds,
instanceInfoList: item.InstanceInfoList, instanceInfoList: item.InstanceInfoList,
seriesId: item.Id, seriesId: item.Id,
@@ -415,12 +422,13 @@ export default {
i.ImageId = imageId i.ImageId = imageId
} }
}) })
var subjectVisitId = this.$router.currentRoute.query.subjectVisitId var subjectVisitId = this.$router.currentRoute.query.subjectVisitId ? this.$router.currentRoute.query.subjectVisitId : item.SubjectVisitId
var studyId = this.$router.currentRoute.query.studyId var studyId = this.$router.currentRoute.query.studyId
var trialId = this.$router.currentRoute.query.trialId var trialId = this.$router.currentRoute.query.trialId
seriesList.push({ seriesList.push({
trialId, trialId,
subjectVisitId, subjectVisitId,
subjectId: item.SubjectId,
studyId, studyId,
imageIds: imageIds, imageIds: imageIds,
instanceInfoList: item.InstanceInfoList, instanceInfoList: item.InstanceInfoList,
@@ -1197,6 +1205,7 @@ export default {
border-color: #213a54 !important; border-color: #213a54 !important;
background-color: #213a54; background-color: #213a54;
} }
.frame_content_active { .frame_content_active {
border-color: #213a54 !important; border-color: #213a54 !important;
background-color: #213a54; background-color: #213a54;
+14 -13
View File
@@ -5,27 +5,28 @@
</div> </div>
<div class="login-body"> <div class="login-body">
<div class="login-l"> <div class="login-l">
<!-- <div class="login-logo"> <div class="login-logo">
<img v-if="language === 'zh'" src="@/assets/zzlogo2.png" alt="" /> <img v-if="language === 'zh'" src="@/assets/zzlogo2.png" alt="" />
<img v-else-if="NODE_ENV === 'usa'" src="@/assets/zzlogo-usa.png" alt="" class="usa-logo" /> <img v-else-if="NODE_ENV === 'usa'" src="@/assets/zzlogo-usa.png" alt="" class="usa-logo" />
<img v-else src="@/assets/zzlogo2.png" alt="" /> <img v-else src="@/assets/zzlogo4.png" alt="" />
</div> --> </div>
<div :class="{ <div :class="{
'login-image': true, 'login-image': true,
'login-image-usa': true, 'login-image-usa': true,
}"> }">
<!-- <svg-icon icon-class="login-bg" style="width: 90%; height: 90%" /> --> <svg-icon icon-class="login-bg" style="width: 90%; height: 90%" />
<img src="@/assets/login-bg.png" style=" height: 80%" /> <!-- <img src="@/assets/login-bg.png" v-else /> -->
</div> </div>
</div> </div>
<div class="login-r"> <div class="login-r">
<div class="title-container"> <div class="title-container">
<!-- HASC Management System --> <!-- IRC Management System -->
<div v-if="NODE_ENV === 'usa'"> <div v-if="NODE_ENV === 'usa'">
<svg-icon icon-class="login-logo" style="width: 300px; height: 94px" /> <svg-icon icon-class="login-logo" style="width: 300px; height: 94px" />
</div> </div>
<div class="title" v-else> <div class="title" v-else>
<img src="@/assets/system.png" alt="" :style="{ height: isEN ? '50px' : '55px' }" /> <img src="@/assets/system.png" alt=""
:style="{ width: isEN ? '180px' : '200px', height: isEN ? '60px' : '65px' }" />
<div :style="`font-size:${isEN ? '28px' : '35px'}`">{{ $t('login:title:system') }}</div> <div :style="`font-size:${isEN ? '28px' : '35px'}`">{{ $t('login:title:system') }}</div>
</div> </div>
</div> </div>
@@ -110,17 +111,17 @@
</div> </div>
</div> </div>
<div v-if="language === 'zh'" class="login-footer"> <div v-if="language === 'zh'" class="login-footer">
<span>Copyright © {{ new Date().getFullYear() }} 上海禾安枢信息科技有限公司 <span>Copyright © {{ new Date().getFullYear() }} 上海展影医疗科技有限公司
版权所有</span> 版权所有</span>
<span> | </span> <span> | </span>
<!-- <a target="_blank" href="https://beian.miit.gov.cn/"> <a target="_blank" href="https://beian.miit.gov.cn/">
<span> 沪ICP备2021037850-2 </span> <span> 沪ICP备2021037850-2 </span>
</a> </a>
<span> | </span> <span> | </span>
<a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=31011002005859"> <a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=31011002005859">
<img src="@/assets/filing.png" /> <img src="@/assets/filing.png" />
<span>沪公网安备 31011002005859号</span> <span>沪公网安备 31011002005859号</span>
</a> --> </a>
<a @click="openAbout"> <a @click="openAbout">
<span style="color: #428bca">关于</span> <span style="color: #428bca">关于</span>
</a> </a>
@@ -161,16 +162,16 @@
V{{ $version.IsEnv_US ? $version.Version_US : $version.Version }} V{{ $version.IsEnv_US ? $version.Version_US : $version.Version }}
</p> </p>
<p style="margin-bottom: 20px" v-if="language === 'zh'"> <p style="margin-bottom: 20px" v-if="language === 'zh'">
Copyright © {{ new Date().getFullYear() }} 上海禾安枢信息科技有限公司 Copyright © {{ new Date().getFullYear() }} 上海展影医疗科技有限公司
版权所有 版权所有
</p> </p>
<p style="margin-bottom: 20px" v-else-if="NODE_ENV === 'usa'"> <p style="margin-bottom: 20px" v-else-if="NODE_ENV === 'usa'">
© {{ new Date().getFullYear() }} Elevate Imaging Inc. © {{ new Date().getFullYear() }} Elevate Imaging Inc.
</p> </p>
<!-- <p style="margin-bottom: 20px" v-else> <p style="margin-bottom: 20px" v-else>
Copyright © {{ new Date().getFullYear() }} Shanghai Extensive Imaging Copyright © {{ new Date().getFullYear() }} Shanghai Extensive Imaging
Inc. Inc.
</p> --> </p>
<!-- <div style="margin-bottom: 20px" v-if="NODE_ENV === 'usa'"> <!-- <div style="margin-bottom: 20px" v-if="NODE_ENV === 'usa'">
<img style="width: 180px" src="@/assets/zzlogo-usa.png" alt="" /> <img style="width: 180px" src="@/assets/zzlogo-usa.png" alt="" />
</div> </div>
@@ -50,6 +50,9 @@
</div> </div>
<div v-if="v.fileType.includes('zip')" class="content flex_col">
<img :title="v.FileName" crossorigin="anonymous" :src="zipImg" height="100%"/>
</div>
</div> </div>
</div> </div>
<div class="Anonymous" v-if="isAnonymous"> <div class="Anonymous" v-if="isAnonymous">
@@ -158,6 +161,7 @@ import hardcodedMetaDataProvider from '@/views/trials/trials-panel/reading/visit
import registerWebImageLoader from '@/views/trials/trials-panel/reading/visit-review/js/registerWebImageLoader' import registerWebImageLoader from '@/views/trials/trials-panel/reading/visit-review/js/registerWebImageLoader'
import Note_RectangleRoiTool from '@/views/trials/trials-panel/reading/dicoms3D/components/tools/Note_RectangleRoiTool' import Note_RectangleRoiTool from '@/views/trials/trials-panel/reading/dicoms3D/components/tools/Note_RectangleRoiTool'
import { noneDicomStudyMaskImage, noneDicomStudyUndoMaskImage } from "@/api/reading" import { noneDicomStudyMaskImage, noneDicomStudyUndoMaskImage } from "@/api/reading"
import zipImg from '@/assets/zip.png'
const { ViewportType } = Enums const { ViewportType } = Enums
const renderingEngineId = 'myRenderingEngine' const renderingEngineId = 'myRenderingEngine'
const { const {
@@ -221,7 +225,7 @@ export default {
activeTool: '', activeTool: '',
imageType: ['image/jpeg', 'image/jpg', 'image/bmp', 'image/png'], imageType: ['image/jpeg', 'image/jpg', 'image/bmp', 'image/png'],
loading: false, loading: false,
isFitToWindowMode: false, isFitToWindowMode: true,
trialId: null, trialId: null,
activeName: '1', activeName: '1',
tools: [], tools: [],
@@ -231,7 +235,8 @@ export default {
tip: [ tip: [
this.$t('DicomViewer:anonymous:after'), this.$t('DicomViewer:anonymous:after'),
this.$t('DicomViewer:anonymous:before') this.$t('DicomViewer:anonymous:before')
] ],
zipImg
} }
}, },
computed: { computed: {
@@ -42,6 +42,9 @@
<el-image v-else-if=" <el-image v-else-if="
!!~k.FileType.indexOf('mp4') !!~k.FileType.indexOf('mp4')
" style="width: 100%; height: 100%" :src="mp4" fit="contain" crossorigin="anonymous" /> " style="width: 100%; height: 100%" :src="mp4" fit="contain" crossorigin="anonymous" />
<el-image v-else-if="
!!~k.FileType.indexOf('zip')
" style="width: 100%; height: 100%" :src="zipImg" fit="contain" crossorigin="anonymous" />
</div> </div>
<div class="file-text" :title="k.FileName"> <div class="file-text" :title="k.FileName">
{{ k.FileName }} {{ k.FileName }}
@@ -73,6 +76,7 @@ import { getNoneDicomStudyList, setNodicomStudyState } from '@/api/trials'
import FileNameSorter from "@/utils/customSort" import FileNameSorter from "@/utils/customSort"
import pdf from '@/assets/pdf.png' import pdf from '@/assets/pdf.png'
import mp4 from '@/assets/mp4.png' import mp4 from '@/assets/mp4.png'
import zipImg from '@/assets/zip.jpg'
export default { export default {
name: 'StudyList', name: 'StudyList',
props: { props: {
@@ -98,6 +102,7 @@ export default {
studyList: [], studyList: [],
pdf, pdf,
mp4, mp4,
zipImg,
BodyPart: {}, BodyPart: {},
subjectVisitId: '', subjectVisitId: '',
studyId: '', studyId: '',
+1 -1
View File
@@ -1,7 +1,7 @@
<template> <template>
<div class="share-image-container"> <div class="share-image-container">
<div class="share-image-banner"> <div class="share-image-banner">
<h2 style="text-align:center;">HASC Management System</h2> <h2 style="text-align:center;">IRC Management System</h2>
<el-card shadow="hover"> <el-card shadow="hover">
<h4>请输入提取码</h4> <h4>请输入提取码</h4>
<el-form ref="validateForm" :inline="true" :rules="rules" :model="form"> <el-form ref="validateForm" :inline="true" :rules="rules" :model="form">
+2 -2
View File
@@ -302,13 +302,13 @@ export default {
// 发件人邮箱地址 // 发件人邮箱地址
"FromEmail": "test@extimaging.com", "FromEmail": "test@extimaging.com",
// 发件人显示名称 // 发件人显示名称
"FromName": "Test HASC Imaging System", "FromName": "Test IRC Imaging System",
//SMTP授权码 //SMTP授权码
"AuthorizationCode": "SHzyyl2021", "AuthorizationCode": "SHzyyl2021",
//系统对外访问地址 //系统对外访问地址
"SiteUrl": "http://irc.test.extimaging.com/login", "SiteUrl": "http://irc.test.extimaging.com/login",
//系统简称-致性核查使用 //系统简称-致性核查使用
"SystemShortName": "HASC", "SystemShortName": "IRC",
// 组织英文名称-添加用户默认值 // 组织英文名称-添加用户默认值
"OrganizationName": "ExtImaging", "OrganizationName": "ExtImaging",
// 组织中文名称 // 组织中文名称
+18 -18
View File
@@ -178,7 +178,7 @@ export default {
updateUser(this.user) updateUser(this.user)
.then((res) => { .then((res) => {
this.isDisabled = false this.isDisabled = false
this.$message.success('Updated successfully') this.$message.success(this.$t("common:message:updatedSuccessfully"))
}) })
.catch(() => { .catch(() => {
this.isDisabled = false this.isDisabled = false
@@ -190,7 +190,7 @@ export default {
this.user.Id = res.Result.Id this.user.Id = res.Result.Id
this.user.UserCode = res.Result.UserCode this.user.UserCode = res.Result.UserCode
this.$emit('getUserId', res.Result.Id) this.$emit('getUserId', res.Result.Id)
this.$message.success('Added successfully') this.$message.success(this.$t("common:message:addedSuccessfully"))
this.$router.push({ path: '/system/user/list' }) this.$router.push({ path: '/system/user/list' })
}) })
.catch(() => { .catch(() => {
@@ -308,63 +308,63 @@ export default {
UserWorkLanguage: [ UserWorkLanguage: [
{ {
required: true, required: true,
message: 'Please Select', message: this.$t("common:ruleMessage:select"),
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
}, },
], ],
Roles: [ Roles: [
{ {
required: true, required: true,
message: 'Please Select', message: this.$t("common:ruleMessage:select"),
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
}, },
], ],
IsZhiZhun: [ IsZhiZhun: [
{ {
required: true, required: true,
message: 'Please Select', message: this.$t("common:ruleMessage:select"),
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
}, },
], ],
OrganizationName: [ OrganizationName: [
{ required: true, message: 'Please specify', trigger: 'blur' }, { required: true, message: this.$t("common:ruleMessage:specify"), trigger: 'blur' },
], ],
LastName: [ LastName: [
{ required: true, message: 'Please specify', trigger: 'blur' }, { required: true, message: this.$t("common:ruleMessage:specify"), trigger: 'blur' },
{ max: 50, message: 'The maximum length is 50' }, { max: 50, message: this.$t("trials:researchForm:formRule:maxLength") },
], ],
FirstName: [ FirstName: [
{ required: true, message: 'Please specify', trigger: 'blur' }, { required: true, message: this.$t("common:ruleMessage:specify"), trigger: 'blur' },
{ max: 50, message: 'The maximum length is 50' }, { max: 50, message: this.$t("trials:researchForm:formRule:maxLength") },
], ],
Phone: [ Phone: [
{ {
max: 20, max: 20,
min: 7, min: 7,
message: 'The length is 7 to 20', message: this.$t("system:userInfo:ruleMessage:length7to20"),
trigger: ['blur'], trigger: ['blur'],
}, },
], ],
EMail: [ EMail: [
{ {
required: true, required: true,
message: 'Please input the email address', message: this.$t("common:ruleMessage:specify"),
trigger: 'blur', trigger: 'blur',
}, },
{ {
// type: 'email', // type: 'email',
pattern: new RegExp(this.$reg().EmailRegexStr), pattern: new RegExp(this.$reg().EmailRegexStr),
message: 'Please input the correct email address', message: this.$t("trials:site:ruleMessage:correctEmail"),
trigger: ['blur'], trigger: ['blur'],
}, },
{ max: 50, message: 'The maximum length is 50' }, { max: 50, message: this.$t("trials:researchForm:formRule:maxLength") },
], ],
Sex: [{ required: true, message: 'Please specify', trigger: 'blur' }], Sex: [{ required: true, message: this.$t("common:ruleMessage:specify"), trigger: 'blur' }],
Status: [ Status: [
{ required: true, message: 'Please specify', trigger: 'blur' }, { required: true, message: this.$t("common:ruleMessage:specify"), trigger: 'blur' },
], ],
DepartmentName: [{ max: 50, message: 'The maximum length is 50' }], DepartmentName: [{ max: 50, message: this.$t("trials:researchForm:formRule:maxLength") }],
PositionName: [{ max: 50, message: 'The maximum length is 50' }], PositionName: [{ max: 50, message: this.$t("trials:researchForm:formRule:maxLength") }],
}, },
userTypeOptions: [], userTypeOptions: [],
isDisabled: false, isDisabled: false,
@@ -1675,7 +1675,10 @@ export default {
cornerstone.getDefaultViewportForImage(this.canvas, image) cornerstone.getDefaultViewportForImage(this.canvas, image)
) )
}, },
getInfo() {
var image = cornerstone.getImage(this.canvas)
return image
},
toggleDicomInfo() { toggleDicomInfo() {
this.toolState.dicomInfoVisible = !this.toolState.dicomInfoVisible this.toolState.dicomInfoVisible = !this.toolState.dicomInfoVisible
if (this.toolState.dicomInfoVisible) { if (this.toolState.dicomInfoVisible) {
@@ -219,8 +219,8 @@
</div> </div>
</el-tooltip> </el-tooltip>
<el-popover v-model="keySeriesPopoverVisible" placement="bottom" trigger="click" popper-class="key-series-popper" <el-popover v-model="keySeriesPopoverVisible" placement="bottom" trigger="click"
@show="showKeySeriesPanel"> popper-class="key-series-popper" @show="showKeySeriesPanel">
<ul class="key-series-list"> <ul class="key-series-list">
<li v-if="keyStack && keyStack.taskBlindName" class="key-series-header"> <li v-if="keyStack && keyStack.taskBlindName" class="key-series-header">
<div class="key-series-header-top"> <div class="key-series-header-top">
@@ -422,7 +422,7 @@
</div> </div>
</div> </div>
</div> </div>
<el-tooltip v-if="trialCriterion.ImageUploadEnum > 0 && currentReadingTaskState < 2" v-hasPermi="['role:ir']" <el-tooltip v-if="trialCriterion.ImageUploadEnum > 0"
class="item" effect="dark" :content="$t('trials:reading:button:upload')" placement="bottom"> class="item" effect="dark" :content="$t('trials:reading:button:upload')" placement="bottom">
<div class="tool-wrapper"> <div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('upload')"> <div class="icon" @click.prevent="openUploadImage('upload')">
@@ -431,15 +431,7 @@
<div class="text">{{ $t('trials:reading:button:upload') }}</div> <div class="text">{{ $t('trials:reading:button:upload') }}</div>
</div> </div>
</el-tooltip> </el-tooltip>
<el-tooltip v-if="trialCriterion.ImageDownloadEnum > 0" v-hasPermi="[ <el-tooltip v-if="trialCriterion.ImageDownloadEnum > 0" class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom">
'role:ir',
'role:mim',
'role:mc',
'role:pm',
'role:apm',
'role:ea',
'role:qa',
]" class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom">
<div class="tool-wrapper"> <div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('download')"> <div class="icon" @click.prevent="openUploadImage('download')">
<i class="el-icon-download svg-icon" /> <i class="el-icon-download svg-icon" />
@@ -447,6 +439,10 @@
<div class="text">{{ $t('trials:reading:button:download') }}</div> <div class="text">{{ $t('trials:reading:button:download') }}</div>
</div> </div>
</el-tooltip> </el-tooltip>
<label for="Replacement" style="color: #fff;cursor: pointer;">
替换
</label>
<input type="file" id="Replacement" @change="beginScanFiles($event, 'replace')" style="display: none;">
<div style="margin-left:auto;"> <div style="margin-left:auto;">
<div style="padding:5px;display: flex;"> <div style="padding:5px;display: flex;">
<!-- 手册 --> <!-- 手册 -->
@@ -596,7 +592,7 @@
</el-tab-pane> </el-tab-pane>
<!-- 其他 --> <!-- 其他 -->
<el-tab-pane :label="$t('trials:reading:tab:others')" name="3"> <el-tab-pane :label="$t('trials:reading:tab:others')" name="3">
<Others v-if="activeName === '3'" :imageToolType="1"/> <Others v-if="activeName === '3'" :imageToolType="1" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@@ -708,10 +704,11 @@
</el-dialog> </el-dialog>
<upload-dicom-and-nonedicom v-if="uploadImageVisible" :subject-id="uploadSubjectId" <upload-dicom-and-nonedicom v-if="uploadImageVisible" :subject-id="uploadSubjectId"
:subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible" :subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible"
:visit-task-id="taskId" :is-reading-task-view-in-order="isReadingTaskViewInOrder" /> :visit-task-id="taskId" :is-reading-task-view-in-order="isReadingTaskViewInOrder" :isReading="true"
:StudyInstanceUID="StudyInstanceUID" />
<download-dicom-and-nonedicom v-if="downloadImageVisible" :subject-id="uploadSubjectId" <download-dicom-and-nonedicom v-if="downloadImageVisible" :subject-id="uploadSubjectId"
:subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :task-id="taskId" :subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :task-id="taskId"
:visible.sync="downloadImageVisible" /> :visible.sync="downloadImageVisible" :isReading="true" />
<!-- 签名框 --> <!-- 签名框 -->
<el-dialog v-if="signVisible" :visible.sync="signVisible" :close-on-click-modal="false" width="600px" <el-dialog v-if="signVisible" :visible.sync="signVisible" :close-on-click-modal="false" width="600px"
custom-class="base-dialog-wrapper"> custom-class="base-dialog-wrapper">
@@ -767,6 +764,8 @@ import const_ from '@/const/sign-code'
import { changeURLStatic } from '@/utils/history.js' import { changeURLStatic } from '@/utils/history.js'
import SystemInfo from "@/utils/systemInfo"; import SystemInfo from "@/utils/systemInfo";
import md5 from 'js-md5' import md5 from 'js-md5'
import { changeFile } from "./upload.js"
import dcmjs from '@/utils/dcmUpload/dcmjs'
export default { export default {
name: 'DicomViewer', name: 'DicomViewer',
components: { components: {
@@ -845,6 +844,10 @@ export default {
default() { default() {
return true return true
} }
},
Loading: {
type: Boolean,
default: false
} }
}, },
data() { data() {
@@ -978,7 +981,10 @@ export default {
fullScreenWidth: window.innerWidth - 570 + 'px', fullScreenWidth: window.innerWidth - 570 + 'px',
fullScreenHeight: window.innerHeight - 130 + 'px', fullScreenHeight: window.innerHeight - 130 + 'px',
ManualsClose: false ManualsClose: false,
fileKey: null,
file: null,
StudyInstanceUID: null
} }
}, },
@@ -1213,6 +1219,12 @@ export default {
this.AspectRatio = windowWidth / windowHeight this.AspectRatio = windowWidth / windowHeight
}; };
this.getSystemInfoReading() this.getSystemInfoReading()
DicomEvent.$on('sendStudyFile', (study) => {
let image = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].getInfo()
study.visitTaskList = this.visitTaskList
this.$emit("update:Loading", true)
changeFile('IR', this.file, study, image)
})
}, },
beforeDestroy() { beforeDestroy() {
DicomEvent.$off('updateImage') DicomEvent.$off('updateImage')
@@ -1236,6 +1248,11 @@ export default {
}) })
}, },
methods: { methods: {
beginScanFiles(e, key) {
this.fileKey = key
this.file = e.target.files[0]
DicomEvent.$emit('getStudyFile')
},
handleReadingChart(e) { handleReadingChart(e) {
this.$emit('handleReadingChart', e) this.$emit('handleReadingChart', e)
}, },
@@ -1283,6 +1300,10 @@ export default {
if (idx > -1) { if (idx > -1) {
this.taskId = this.visitTaskList[idx].VisitTaskId this.taskId = this.visitTaskList[idx].VisitTaskId
} }
let image = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].getInfo()
let dataset = dcmjs.data.DicomMessage.readFile(image.data.byteArray.buffer)
this.StudyInstanceUID = dataset.dict['0020000D'].Value[0]
dataset = null
this.uploadSubjectCode = localStorage.getItem("subjectCode") this.uploadSubjectCode = localStorage.getItem("subjectCode")
this.uploadSubjectId = localStorage.getItem("subjectId") this.uploadSubjectId = localStorage.getItem("subjectId")
this.uploadTrialCriterion = this.trialCriterion this.uploadTrialCriterion = this.trialCriterion
@@ -1451,7 +1472,7 @@ export default {
// } catch (err) { // } catch (err) {
// console.error(err) // console.error(err)
// } // }
const series = this.getSeriesInfoByMark(visitTaskId, {lesionName: kf.MarkName}) const series = this.getSeriesInfoByMark(visitTaskId, { lesionName: kf.MarkName })
if (!series) return if (!series) return
this.loadImageStack(series) this.loadImageStack(series)
}, },
@@ -2496,7 +2517,7 @@ export default {
this.readingTaskState = 2 this.readingTaskState = 2
await store.dispatch('reading/setVisitTaskReadingTaskState', { visitTaskId: this.visitTaskId, readingTaskState: 2 }) await store.dispatch('reading/setVisitTaskReadingTaskState', { visitTaskId: this.visitTaskId, readingTaskState: 2 })
await store.dispatch('reading/setCurrentReadingTaskState', 2) await store.dispatch('reading/setCurrentReadingTaskState', 2)
const res = await getAutoCutNextTask({imageToolType: 1}) const res = await getAutoCutNextTask({ imageToolType: 1 })
var isAutoTask = res.Result.AutoCutNextTask var isAutoTask = res.Result.AutoCutNextTask
if (isAutoTask) { if (isAutoTask) {
window.location.reload() window.location.reload()
@@ -2609,7 +2630,7 @@ export default {
await this.$confirm(this.$t('trials:lugano:fusionDialog:message:checkSeries'), this.$t('trials:lugano:fusionDialog:warning'), { await this.$confirm(this.$t('trials:lugano:fusionDialog:message:checkSeries'), this.$t('trials:lugano:fusionDialog:warning'), {
showCancelButton: false, showCancelButton: false,
type: 'warning' type: 'warning'
}).catch(() => {}) }).catch(() => { })
return return
} }
if (this.ctSeriesInfo.instanceCount > 400) { if (this.ctSeriesInfo.instanceCount > 400) {
@@ -33,17 +33,21 @@
<div v-for="item in qs.Childrens" :key="item.Id"> <div v-for="item in qs.Childrens" :key="item.Id">
<div v-if="item.Type === 'basicTable'" class="flex-row" style="margin:3px 0;"> <div v-if="item.Type === 'basicTable'" class="flex-row" style="margin:3px 0;">
<div class="title">{{ item.QuestionName }}</div> <div class="title">{{ item.QuestionName }}</div>
<div v-if="item.LesionType === 104 && readingTaskState < 2"> <div v-if="item.LesionType === 104">
<div class="add-icon" @click.prevent="downloadTpl"> <div
class="add-icon" @click.prevent="downloadTpl" v-if="readingTaskState < 2">
<i class="el-icon-download" /> <i class="el-icon-download" />
</div> </div>
<div class="add-icon" style="margin: 0 5px;" <div class="add-icon" style="margin: 0 5px;"
@click.prevent="uploadTpl(item.LesionType, item.QuestionName)"> @click.prevent="uploadTpl(item.LesionType, item.QuestionName)" v-if="readingTaskState < 2">
<i class="el-icon-upload2" /> <i class="el-icon-upload2" />
</div> </div>
<div class="add-icon" @click.prevent="handleAddOrEdit('add', item)"> <div class="add-icon" @click.prevent="handleAddOrEdit('add', item)" v-if="readingTaskState < 2">
<i class="el-icon-plus" /> <i class="el-icon-plus" />
</div> </div>
<div class="add-icon" @click.prevent="handleView(item)">
<i class="el-icon-view" />
</div>
</div> </div>
</div> </div>
@@ -222,6 +226,7 @@
<script> <script>
import { saveTaskQuestion, submitTableQuestion, deleteReadingRowAnswer } from '@/api/trials' import { saveTaskQuestion, submitTableQuestion, deleteReadingRowAnswer } from '@/api/trials'
import { resetReadingTask, getIVUSTemplate } from '@/api/reading' import { resetReadingTask, getIVUSTemplate } from '@/api/reading'
import { getToken } from '@/utils/auth'
import DicomEvent from './../DicomEvent' import DicomEvent from './../DicomEvent'
import store from '@/store' import store from '@/store'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
@@ -284,7 +289,8 @@ export default {
sRoiEndDistanceId: '', sRoiEndDistanceId: '',
sRoiDistanceId: '', sRoiDistanceId: '',
rowSaveLoadingMap: {}, rowSaveLoadingMap: {},
dialogSaveLoading: false dialogSaveLoading: false,
childWindows: []
} }
}, },
computed: { computed: {
@@ -324,11 +330,13 @@ export default {
DicomEvent.$on('refreshQuestions', _ => { DicomEvent.$on('refreshQuestions', _ => {
this.refreshQuestions() this.refreshQuestions()
}) })
window.addEventListener('beforeunload', this.closeAllChildWindows)
}, },
beforeDestroy() { beforeDestroy() {
DicomEvent.$off('setCollapseActive') DicomEvent.$off('setCollapseActive')
DicomEvent.$off('getUnSaveTarget') DicomEvent.$off('getUnSaveTarget')
DicomEvent.$off('refreshQuestions') DicomEvent.$off('refreshQuestions')
window.removeEventListener('beforeunload', this.closeAllChildWindows)
}, },
methods: { methods: {
handleReadingChart(e) { handleReadingChart(e) {
@@ -739,6 +747,24 @@ export default {
this.addOrEdit.visible = true this.addOrEdit.visible = true
this.addOrEdit.lesionType = row.LesionType this.addOrEdit.lesionType = row.LesionType
}, },
handleView(item) {
localStorage.setItem('taskBlindName', this.taskBlindName)
localStorage.setItem('isReadingShowSubjectInfo', this.isReadingShowSubjectInfo)
let token = getToken()
let routeData = this.$router.resolve({ path: `/ecrfList?trialId=${this.$route.query.trialId}&visitTaskId=${this.visitTaskId}&questionId=${item.Id}&TokenKey=${token}` })
const win = window.open(routeData.href, '_blank')
if (win) {
this.childWindows.push(win)
}
},
closeAllChildWindows() {
this.childWindows.forEach(win => {
if (win && !win.closed) {
win.close()
}
})
this.childWindows = []
},
async saveFormData() { async saveFormData() {
if (this.dialogSaveLoading) return if (this.dialogSaveLoading) return
this.dialogSaveLoading = true this.dialogSaveLoading = true
@@ -33,17 +33,20 @@
<div v-for="item in qs.Childrens" :key="item.Id"> <div v-for="item in qs.Childrens" :key="item.Id">
<div v-if="item.Type === 'basicTable'" class="flex-row" style="margin:3px 0;"> <div v-if="item.Type === 'basicTable'" class="flex-row" style="margin:3px 0;">
<div class="title">{{ item.QuestionName }}</div> <div class="title">{{ item.QuestionName }}</div>
<div v-if="(item.LesionType === 104) && readingTaskState < 2"> <div v-if="(item.LesionType === 104)">
<div class="add-icon" @click.prevent="downloadTpl(item.LesionType)"> <div class="add-icon" @click.prevent="downloadTpl(item.LesionType)" v-if="readingTaskState < 2">
<i class="el-icon-download" /> <i class="el-icon-download" />
</div> </div>
<div class="add-icon" style="margin: 0 5px;" <div class="add-icon" style="margin: 0 5px;"
@click.prevent="uploadTpl(item.LesionType, item.QuestionName)"> @click.prevent="uploadTpl(item.LesionType, item.QuestionName)" v-if="readingTaskState < 2">
<i class="el-icon-upload2" /> <i class="el-icon-upload2" />
</div> </div>
<div class="add-icon" @click.prevent="handleAddOrEdit('add', item)"> <div class="add-icon" @click.prevent="handleAddOrEdit('add', item)" v-if="readingTaskState < 2">
<i class="el-icon-plus" /> <i class="el-icon-plus" />
</div> </div>
<div class="add-icon" @click.prevent="handleView(item)">
<i class="el-icon-view" />
</div>
</div> </div>
</div> </div>
@@ -224,6 +227,7 @@ import { saveTaskQuestion, submitTableQuestion, deleteReadingRowAnswer } from '@
import { resetReadingTask, getOCTFCTTemplate, getOCTLipidAngleTemplate } from '@/api/reading' import { resetReadingTask, getOCTFCTTemplate, getOCTLipidAngleTemplate } from '@/api/reading'
import DicomEvent from './../DicomEvent' import DicomEvent from './../DicomEvent'
import store from '@/store' import store from '@/store'
import { getToken } from '@/utils/auth'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import Questions from './../Questions' import Questions from './../Questions'
import QuestionTableFormItem from './QuestionTableFormItem' import QuestionTableFormItem from './QuestionTableFormItem'
@@ -285,7 +289,8 @@ export default {
sRoiEndDistanceId: '', sRoiEndDistanceId: '',
sRoiDistanceId: '', sRoiDistanceId: '',
rowSaveLoadingMap: {}, rowSaveLoadingMap: {},
dialogSaveLoading: false dialogSaveLoading: false,
childWindows: []
} }
}, },
computed: { computed: {
@@ -325,11 +330,13 @@ export default {
DicomEvent.$on('refreshQuestions', _ => { DicomEvent.$on('refreshQuestions', _ => {
this.refreshQuestions() this.refreshQuestions()
}) })
window.addEventListener('beforeunload', this.closeAllChildWindows)
}, },
beforeDestroy() { beforeDestroy() {
DicomEvent.$off('setCollapseActive') DicomEvent.$off('setCollapseActive')
DicomEvent.$off('getUnSaveTarget') DicomEvent.$off('getUnSaveTarget')
DicomEvent.$off('refreshQuestions') DicomEvent.$off('refreshQuestions')
window.removeEventListener('beforeunload', this.closeAllChildWindows)
}, },
methods: { methods: {
handleReadingChart(e) { handleReadingChart(e) {
@@ -755,6 +762,27 @@ export default {
this.addOrEdit.visible = true this.addOrEdit.visible = true
this.addOrEdit.lesionType = row.LesionType this.addOrEdit.lesionType = row.LesionType
}, },
handleView(item) {
if (this.openWindow) {
this.openWindow.close()
}
localStorage.setItem('taskBlindName', this.taskBlindName)
localStorage.setItem('isReadingShowSubjectInfo', this.isReadingShowSubjectInfo)
let token = getToken()
let routeData = this.$router.resolve({ path: `/ecrfList?trialId=${this.$route.query.trialId}&visitTaskId=${this.visitTaskId}&questionId=${item.Id}&TokenKey=${token}` })
const win = window.open(routeData.href, '_blank')
if (win) {
this.childWindows.push(win)
}
},
closeAllChildWindows() {
this.childWindows.forEach(win => {
if (win && !win.closed) {
win.close()
}
})
this.childWindows = []
},
async saveFormData() { async saveFormData() {
if (this.dialogSaveLoading) return if (this.dialogSaveLoading) return
this.dialogSaveLoading = true this.dialogSaveLoading = true
@@ -1,5 +1,5 @@
<template> <template>
<div class="dicom-container"> <div class="dicom-container" v-loading="loading">
<div :class="{ 'dicom-list': true, studyHide: !studyShow }"> <div :class="{ 'dicom-list': true, studyHide: !studyShow }">
<div class="container"> <div class="container">
<div class="related-study-wrapper"> <div class="related-study-wrapper">
@@ -27,7 +27,8 @@
class="study-wrapper"> class="study-wrapper">
<StudyList v-if="selectArr.includes(s.VisitTaskId)" :ref="s.VisitTaskId" :visit-task-id="s.VisitTaskId" <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" :trial-id="trialId" :subject-visit-id="s.VisitId" :task-blind-name="s.TaskBlindName"
:is-reading-show-subject-info="isReadingShowSubjectInfo" :is-reading-task-view-in-order="isReadingTaskViewInOrder" @loadImageStack="loadImageStack" :is-reading-show-subject-info="isReadingShowSubjectInfo"
:is-reading-task-view-in-order="isReadingTaskViewInOrder" @loadImageStack="loadImageStack"
@previewNoneDicoms="previewNoneDicoms" /> @previewNoneDicoms="previewNoneDicoms" />
</div> </div>
</div> </div>
@@ -38,7 +39,7 @@
</div> </div>
<div class="dicom-viewer"> <div class="dicom-viewer">
<div class="container"> <div class="container">
<DicomViewer v-if="activeTaskVisitId" ref="dicomViewer" :is-show="isShow" <DicomViewer v-if="activeTaskVisitId" ref="dicomViewer" :is-show="isShow" :Loading.sync="loading"
:question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum" :question-form-change-state="questionFormChangeState" :question-form-change-num="questionFormChangeNum"
:is-exists-clinical-data="isExistsClinicalData" :is-exists-no-dicom-file="isExistsNoDicomFile" :is-exists-clinical-data="isExistsClinicalData" :is-exists-no-dicom-file="isExistsNoDicomFile"
:is-reading-show-subject-info="isReadingShowSubjectInfo" :studyShow="studyShow" :is-reading-show-subject-info="isReadingShowSubjectInfo" :studyShow="studyShow"
@@ -1,9 +1,11 @@
<template> <template>
<div v-loading="loading" class="study-wrapper"> <div v-loading="loading" class="study-wrapper">
<h4 v-if="isReadingShowSubjectInfo" style="color: #ddd;padding: 5px 0px;margin: 0;text-align: center;background-color: #4c4c4c;"> <h4 v-if="isReadingShowSubjectInfo"
style="color: #ddd;padding: 5px 0px;margin: 0;text-align: center;background-color: #4c4c4c;">
{{ subjectCode }} {{ subjectCode }}
</h4> </h4>
<h4 v-if="isReadingShowSubjectInfo" style="color: #ddd;padding: 5px 0px;margin: 0;text-align: center;background-color: #4c4c4c;margin-bottom: 5px;"> <h4 v-if="isReadingShowSubjectInfo"
style="color: #ddd;padding: 5px 0px;margin: 0;text-align: center;background-color: #4c4c4c;margin-bottom: 5px;">
{{ taskBlindName }} {{ taskBlindName }}
</h4> </h4>
<div class="ps"> <div class="ps">
@@ -14,21 +16,18 @@
<!-- 关键序列 --> <!-- 关键序列 -->
{{ $t('trials:reading:title:keySeries') }} {{ $t('trials:reading:title:keySeries') }}
</div> </div>
<div <div v-else class="dicom-desc" style="white-space: normal;">
v-else
class="dicom-desc"
style="white-space: normal;"
>
<div> <div>
<div style="text-overflow: ellipsis;overflow: hidden;"> <div style="text-overflow: ellipsis;overflow: hidden;">
<span v-if="taskInfo && taskInfo.IsShowStudyName && study.StudyName" :title="study.StudyName"> <span v-if="taskInfo && taskInfo.IsShowStudyName && study.StudyName" :title="study.StudyName">
{{study.StudyName}} {{ study.StudyName }}
</span> </span>
</div> </div>
<div> <div>
<span class="study-meta-line" :title="study.Modalities"> <span class="study-meta-line" :title="study.Modalities">
<span v-if="isReadingTaskViewInOrder !== 0" class="study-code" :title="study.StudyCode">{{ study.StudyCode }}</span> <span v-if="isReadingTaskViewInOrder !== 0" class="study-code" :title="study.StudyCode">{{
study.StudyCode }}</span>
<span class="study-modality">{{ study.Modalities }}({{ study.SeriesCount }})</span> <span class="study-modality">{{ study.Modalities }}({{ study.SeriesCount }})</span>
<span class="patient-info" v-if="['PT、CT', 'CT、PT', 'PET-CT'].includes(study.Modalities)"> <span class="patient-info" v-if="['PT、CT', 'CT、PT', 'PET-CT'].includes(study.Modalities)">
<el-popover placement="right-start" trigger="hover" popper-class="patient-info-popper"> <el-popover placement="right-start" trigger="hover" popper-class="patient-info-popper">
@@ -63,67 +62,57 @@
</span> </span>
</span> </span>
</div> </div>
<div v-if="study.Description" class="study-desc-text" :title="study.Description">{{ study.Description }}</div> <div v-if="study.Description" class="study-desc-text" :title="study.Description">{{ study.Description }}
</div>
</div> </div>
</div> </div>
</template> </template>
<div class="series"> <div class="series">
<div <div v-for="(series, i) in study.SeriesList" :key="i" style="position:relative;margin-top:5px;"
v-for="(series, i) in study.SeriesList" series-type="current" @click="showSeriesImage(index, i, series)">
:key="i"
style="position:relative;margin-top:5px;"
series-type="current"
@click="showSeriesImage(index,i,series)"
>
<div <div :class="{ 'series-active': i == seriesIndex && index === studyIndex }" class="series-wrapper">
:class="{'series-active': i==seriesIndex && index === studyIndex}" <el-image class="image-preview" :src="series.previewImageUrl" fit="fill" crossorigin="anonymous" />
class="series-wrapper"
>
<el-image
class="image-preview"
:src="series.previewImageUrl"
fit="fill"
crossorigin="anonymous"
/>
<div class="image-desc"> <div class="image-desc">
<div class="flex-div"> <div class="flex-div">
<div style="width: 40px;display: flex;flex-direction: row;justify-content: space-between;"> <div style="width: 40px;display: flex;flex-direction: row;justify-content: space-between;">
<div v-if="!study.IsCriticalSequence">#{{ series.seriesNumber }}</div> <div v-if="!study.IsCriticalSequence">#{{ series.seriesNumber }}</div>
<div v-if="series.isDicom && series.prefetchInstanceCount<series.instanceCount * 100 && series.modality!== 'SR'"> <div
v-if="series.isDicom && series.prefetchInstanceCount < series.instanceCount * 100 && series.modality !== 'SR'">
<!-- 下载 --> <!-- 下载 -->
<el-tooltip v-if="!series.isLoading" class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom"> <el-tooltip v-if="!series.isLoading" class="item" effect="dark"
<i class="el-icon-video-play" style="font-size: 18px;margin-right: 5px;color: #ffeb3b;cursor: pointer;" @click.stop="loadSeries(series,index,i)" /> :content="$t('trials:reading:button:download')" placement="bottom">
<i class="el-icon-video-play"
style="font-size: 18px;margin-right: 5px;color: #ffeb3b;cursor: pointer;"
@click.stop="loadSeries(series, index, i)" />
</el-tooltip> </el-tooltip>
</div> </div>
<el-tooltip v-else-if="series.isDicom && series.prefetchInstanceCount === 0 &&series.modality!== 'SR'" class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom"> <el-tooltip
<i class="el-icon-video-play" style="font-size: 18px;margin-right: 5px;color: #ffeb3b;cursor: pointer;" @click.stop="loadSeries(series,index,i)" /> v-else-if="series.isDicom && series.prefetchInstanceCount === 0 && series.modality !== 'SR'"
class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom">
<i class="el-icon-video-play"
style="font-size: 18px;margin-right: 5px;color: #ffeb3b;cursor: pointer;"
@click.stop="loadSeries(series, index, i)" />
</el-tooltip> </el-tooltip>
</div> </div>
<div v-if="series.isExistMutiFrames && series.instanceCount > 1"> <div v-if="series.isExistMutiFrames && series.instanceCount > 1">
<el-popover <el-popover placement="right" trigger="hover" popper-class="instance_frame_wrapper">
placement="right"
trigger="hover"
popper-class="instance_frame_wrapper"
>
<div class="frame_list"> <div class="frame_list">
<div <div v-for="(instance, idx) in series.instanceInfoList" :key="instance.Id"
v-for="(instance, idx) in series.instanceInfoList" class="frame_content" :class="{ 'frame_content_active': activeInstanceId === instance.Id }"
:key="instance.Id" :style="{ 'margin-bottom': idx < series.instanceInfoList.length - 1 ? '5px' : '0px' }"
class="frame_content" @click.stop="showMultiFrames(index, series, i, instance)">
:class="{ 'frame_content_active': activeInstanceId === instance.Id }"
:style="{'margin-bottom':idx<series.instanceInfoList.length-1? '5px':'0px'}"
@click.stop="showMultiFrames(index,series, i, instance)"
>
<div> <div>
<div>{{ instance.InstanceNumber }}</div> <div>{{ instance.InstanceNumber }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ? instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div> <div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ?
instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div>
</div> </div>
</div> </div>
</div> </div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;color: #ffeb3b;" /> <i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;color: #ffeb3b;" />
</el-popover> </el-popover>
</div> </div>
</div> </div>
@@ -145,9 +134,11 @@
</p> </p>
<div class="flex-div"> <div class="flex-div">
<div v-if="measureData && measureData.findIndex(v=>v.SeriesId === series.seriesId && v.MeasureData) > -1"> <div
v-if="measureData && measureData.findIndex(v => v.SeriesId === series.seriesId && v.MeasureData) > -1">
<!-- 有标注 --> <!-- 有标注 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:marked')" placement="right"> <el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:marked')"
placement="right">
<i class="el-icon-star-on" style="font-size: 16px;color: #ff5722;" /> <i class="el-icon-star-on" style="font-size: 16px;color: #ff5722;" />
</el-tooltip> </el-tooltip>
@@ -155,10 +146,10 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="series.isDicom && series.prefetchInstanceCount>0 && series.prefetchInstanceCount<series.instanceCount * 100" style="width: 100%;"> <div
<el-progress v-if="series.isDicom && series.prefetchInstanceCount > 0 && series.prefetchInstanceCount < series.instanceCount * 100"
:percentage="parseInt((series.prefetchInstanceCount / series.instanceCount).toFixed(2))" style="width: 100%;">
/> <el-progress :percentage="parseInt((series.prefetchInstanceCount / series.instanceCount).toFixed(2))" />
</div> </div>
</div> </div>
@@ -168,24 +159,19 @@
</div> </div>
<div class="sr-wrapper"> <div class="sr-wrapper">
<el-dialog <el-dialog :visible.sync="srDialogVisible"
:visible.sync="srDialogVisible" :custom-class="isSrFullscreen ? 'sr-full-dialog-container' : 'sr-dialog-container'" :show-close="false"
:custom-class="isSrFullscreen?'sr-full-dialog-container':'sr-dialog-container'" :close-on-click-modal="false" :fullscreen="isSrFullscreen">
:show-close="false"
:close-on-click-modal="false"
:fullscreen="isSrFullscreen"
>
<span slot="title" class="dialog-footer"> <span slot="title" class="dialog-footer">
<div style="position: absolute;right: 20px;top: 10px;"> <div style="position: absolute;right: 20px;top: 10px;">
<svg-icon :icon-class="isSrFullscreen?'exit-fullscreen':'fullscreen'" style="cursor: pointer;font-size: 20px;color:#000;" @click="isSrFullscreen=!isSrFullscreen" /> <svg-icon :icon-class="isSrFullscreen ? 'exit-fullscreen' : 'fullscreen'"
<svg-icon icon-class="dClose" style="cursor: pointer;font-size: 25px;margin-left: 10px;" @click="srDialogVisible = false" /> style="cursor: pointer;font-size: 20px;color:#000;" @click="isSrFullscreen = !isSrFullscreen" />
<svg-icon icon-class="dClose" style="cursor: pointer;font-size: 25px;margin-left: 10px;"
@click="srDialogVisible = false" />
</div> </div>
</span> </span>
<div style="height: 100%;margin:0;"> <div style="height: 100%;margin:0;">
<SrList <SrList v-if="srDialogVisible" :sr-info="srInfo" />
v-if="srDialogVisible"
:sr-info="srInfo"
/>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
@@ -296,6 +282,9 @@ export default {
var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId) var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId)
this.measureData = this.visitTaskList[idx].MeasureData this.measureData = this.visitTaskList[idx].MeasureData
}) })
DicomEvent.$on("getStudyFile", () => {
DicomEvent.$emit('sendStudyFile', { studyList: this.studyList, studyIndex: this.studyIndex, seriesIndex: this.seriesIndex, visitTaskId: this.visitTaskId })
})
// DicomEvent.$on('setReadingState', readingTaskState => { // DicomEvent.$on('setReadingState', readingTaskState => {
// var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId) // var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId)
@@ -320,7 +309,7 @@ export default {
methods: { methods: {
debounce(fn, delay) { debounce(fn, delay) {
let timer = null let timer = null
return function() { return function () {
const context = this const context = this
const args = arguments const args = arguments
clearTimeout(timer) clearTimeout(timer)
@@ -334,7 +323,7 @@ export default {
// //
this.getInitSeries().then((res) => { this.getInitSeries().then((res) => {
requestPoolManager.startTaskTimer() requestPoolManager.startTaskTimer()
res.map(async(item) => { res.map(async (item) => {
// this.loadInitialImage(item) // this.loadInitialImage(item)
const imageId = item.imageIds[item.imageIdIndex] const imageId = item.imageIds[item.imageIdIndex]
const p = parseInt(new Date().getTime()) const p = parseInt(new Date().getTime())
@@ -456,8 +445,8 @@ export default {
this.studyIndex = obj.studyIndex this.studyIndex = obj.studyIndex
this.seriesIndex = obj.seriesIndex this.seriesIndex = obj.seriesIndex
seriesList.push(obj.series) seriesList.push(obj.series)
this.activeNames = [`${this.studyList[ this.studyIndex].StudyId}`] this.activeNames = [`${this.studyList[this.studyIndex].StudyId}`]
this.studyList[ obj.studyIndex].SeriesList[obj.seriesIndex].isFirstRender = true this.studyList[obj.studyIndex].SeriesList[obj.seriesIndex].isFirstRender = true
} else { } else {
if (this.studyList.length > 0) { if (this.studyList.length > 0) {
// //
@@ -494,7 +483,7 @@ export default {
this.studyIndex = secondObj.studyIndex this.studyIndex = secondObj.studyIndex
this.seriesIndex = secondObj.seriesIndex this.seriesIndex = secondObj.seriesIndex
seriesList.push(secondObj.series) seriesList.push(secondObj.series)
this.studyList[ secondObj.studyIndex].SeriesList[secondObj.seriesIndex].isFirstRender = true this.studyList[secondObj.studyIndex].SeriesList[secondObj.seriesIndex].isFirstRender = true
this.studyIndex = secondObj.studyIndex this.studyIndex = secondObj.studyIndex
this.seriesIndex = secondObj.seriesIndex this.seriesIndex = secondObj.seriesIndex
@@ -527,7 +516,7 @@ export default {
// const instanceList = seriesList[srIdx].instanceList // const instanceList = seriesList[srIdx].instanceList
const imageIds = seriesList[srIdx].imageIds const imageIds = seriesList[srIdx].imageIds
// const filterStr = seriesList[srIdx].isExistMutiFrames ? `frame=${measureDatas[i].MeasureData.frame}&instanceId=${measureDatas[i].InstanceId}` : `instanceId=${measureDatas[i].InstanceId}` // const filterStr = seriesList[srIdx].isExistMutiFrames ? `frame=${measureDatas[i].MeasureData.frame}&instanceId=${measureDatas[i].InstanceId}` : `instanceId=${measureDatas[i].InstanceId}`
let instanceIndex = seriesList[srIdx].instanceInfoList.findIndex(k=>k.Id === measureDatas[i].InstanceId) let instanceIndex = seriesList[srIdx].instanceInfoList.findIndex(k => k.Id === measureDatas[i].InstanceId)
let filterStr = '' let filterStr = ''
if (instanceIndex > -1 && seriesList[srIdx].isExistMutiFrames) { if (instanceIndex > -1 && seriesList[srIdx].isExistMutiFrames) {
if (seriesList[srIdx].instanceInfoList[instanceIndex].NumberOfFrames > 0) { if (seriesList[srIdx].instanceInfoList[instanceIndex].NumberOfFrames > 0) {
@@ -611,7 +600,7 @@ export default {
// const instanceList = seriesList[srIdx].imageIds // const instanceList = seriesList[srIdx].imageIds
const imageIds = seriesList[srIdx].imageIds const imageIds = seriesList[srIdx].imageIds
// const filterStr = seriesList[srIdx].isExistMutiFrames ? `frame=${measureDatas[mIdx].MeasureData.frame}&instanceId=${measureDatas[mIdx].InstanceId}` : `instanceId=${measureDatas[mIdx].InstanceId}` // const filterStr = seriesList[srIdx].isExistMutiFrames ? `frame=${measureDatas[mIdx].MeasureData.frame}&instanceId=${measureDatas[mIdx].InstanceId}` : `instanceId=${measureDatas[mIdx].InstanceId}`
let instanceIndex = seriesList[srIdx].instanceInfoList.findIndex(i=>i.Id === measureDatas[mIdx].InstanceId) let instanceIndex = seriesList[srIdx].instanceInfoList.findIndex(i => i.Id === measureDatas[mIdx].InstanceId)
let filterStr = '' let filterStr = ''
if (instanceIndex > -1 && seriesList[srIdx].isExistMutiFrames) { if (instanceIndex > -1 && seriesList[srIdx].isExistMutiFrames) {
if (seriesList[srIdx].instanceInfoList[instanceIndex].NumberOfFrames > 0) { if (seriesList[srIdx].instanceInfoList[instanceIndex].NumberOfFrames > 0) {
@@ -968,15 +957,17 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.study-wrapper{ .study-wrapper {
::v-deep .el-progress-bar__inner{ ::v-deep .el-progress-bar__inner {
transition: width 0s ease; transition: width 0s ease;
} }
width:100%; width:100%;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
.dicom-desc{
.dicom-desc {
font-weight: bold; font-weight: bold;
font-size: 13px; font-size: 13px;
text-align: left; text-align: left;
@@ -985,6 +976,7 @@ export default {
white-space: normal; white-space: normal;
overflow: visible; overflow: visible;
} }
.study-meta-line { .study-meta-line {
// display: grid; // display: grid;
// grid-template-columns: minmax(0, 1fr) auto; // grid-template-columns: minmax(0, 1fr) auto;
@@ -994,6 +986,7 @@ export default {
width: 100%; width: 100%;
min-width: 0; min-width: 0;
} }
.study-meta-main { .study-meta-main {
display: block; display: block;
min-width: 0; min-width: 0;
@@ -1001,11 +994,13 @@ export default {
overflow-wrap: anywhere; overflow-wrap: anywhere;
flex: 1; flex: 1;
} }
.study-code, .study-code,
.study-modality { .study-modality {
white-space: normal; white-space: normal;
margin: 0 2px; margin: 0 2px;
} }
.study-desc-text { .study-desc-text {
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
@@ -1017,19 +1012,23 @@ export default {
overflow-anchor: none; overflow-anchor: none;
touch-action: auto; touch-action: auto;
} }
.series-active { .series-active {
background-color: #607d8b!important; background-color: #607d8b !important;
border: 1px solid #607d8b!important; border: 1px solid #607d8b !important;
} }
::v-deep .el-progress__text{
::v-deep .el-progress__text {
color: #ccc; color: #ccc;
font-size: 12px; font-size: 12px;
} }
.series{
.series {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
.series-wrapper { .series-wrapper {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -1042,11 +1041,13 @@ export default {
border-radius: 2px; border-radius: 2px;
border: 1px solid #404040; border: 1px solid #404040;
background-color: #3a3a3a; background-color: #3a3a3a;
.el-progress__text{
.el-progress__text {
display: none; display: none;
} }
.el-progress-bar{
padding-right:0px; .el-progress-bar {
padding-right: 0px;
} }
.image-preview { .image-preview {
@@ -1055,9 +1056,11 @@ export default {
border: 2px solid #252525; border: 2px solid #252525;
cursor: pointer; cursor: pointer;
} }
.image-desc { .image-desc {
vertical-align: top; vertical-align: top;
p{
p {
width: 95px; width: 95px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -1066,7 +1069,8 @@ export default {
color: #ddd; color: #ddd;
margin: 0px; margin: 0px;
line-height: 1.5; line-height: 1.5;
div{
div {
width: 95px; width: 95px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -1074,7 +1078,8 @@ export default {
} }
} }
} }
.flex-div{
.flex-div {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
@@ -1091,21 +1096,25 @@ export default {
} }
} }
::v-deep .el-collapse{
::v-deep .el-collapse {
border: none; border: none;
.el-collapse-item{
background-color: #000!important; .el-collapse-item {
background-color: #000 !important;
color: #ddd; color: #ddd;
} }
.el-collapse-item__content{
padding-bottom:5px; .el-collapse-item__content {
background-color: #000!important; padding-bottom: 5px;
background-color: #000 !important;
} }
.el-collapse-item__header{
background-color: #000!important; .el-collapse-item__header {
background-color: #000 !important;
color: #ddd; color: #ddd;
border-bottom-color:#5a5a5a; border-bottom-color: #5a5a5a;
padding-left: 1px; padding-left: 1px;
min-height: 40px; min-height: 40px;
height: auto; height: auto;
@@ -1114,35 +1123,42 @@ export default {
padding-top: 6px; padding-top: 6px;
padding-bottom: 6px; padding-bottom: 6px;
} }
.el-collapse-item__arrow{
.el-collapse-item__arrow {
align-self: flex-start; align-self: flex-start;
margin-top: 2px; margin-top: 2px;
line-height: 20px; line-height: 20px;
} }
} }
.sr-wrapper{
::v-deep .el-dialog{ .sr-wrapper {
::v-deep .el-dialog {
background: #fff !important; background: #fff !important;
border: 1px solid #ddd; border: 1px solid #ddd;
// color: #ddd; // color: #ddd;
.el-dialog__title{ .el-dialog__title {
color:#fff; color: #fff;
} }
} }
::v-deep .sr-dialog-container{
::v-deep .sr-dialog-container {
margin-top: 50px !important; margin-top: 50px !important;
width:75%; width: 75%;
height:80%; height: 80%;
} }
::v-deep .el-dialog__body{
::v-deep .el-dialog__body {
padding: 10px; padding: 10px;
height: calc(100% - 50px); height: calc(100% - 50px);
} }
.el-dialog__header{
.el-dialog__header {
position: relative; position: relative;
} }
.sr-full-dialog-container{
::v-deep .is-fullscreen .el-dialog__body{ .sr-full-dialog-container {
::v-deep .is-fullscreen .el-dialog__body {
height: calc(100% - 50px); height: calc(100% - 50px);
} }
} }
@@ -1150,25 +1166,29 @@ export default {
} }
</style> </style>
<style> <style>
.instance_frame_wrapper{ .instance_frame_wrapper {
min-width: 120px; min-width: 120px;
background-color: #2c2c2c; background-color: #2c2c2c;
border: 1px solid #2c2c2c; border: 1px solid #2c2c2c;
padding: 5px; padding: 5px;
} }
.frame_list{
.frame_list {
max-height: 500px; max-height: 500px;
overflow-y: auto; overflow-y: auto;
} }
.instance_frame_wrapper ::-webkit-scrollbar { .instance_frame_wrapper ::-webkit-scrollbar {
width: 7px; width: 7px;
height: 7px; height: 7px;
} }
.instance_frame_wrapper ::-webkit-scrollbar-thumb { .instance_frame_wrapper ::-webkit-scrollbar-thumb {
border-radius: 10px; border-radius: 10px;
background: #d0d0d0; background: #d0d0d0;
} }
.frame_content{
.frame_content {
height: 50px; height: 50px;
padding: 5px; padding: 5px;
display: flex; display: flex;
@@ -1177,6 +1197,7 @@ export default {
font-size: 12px; font-size: 12px;
border: 1px solid #404040; border: 1px solid #404040;
} }
.frame_content:hover { .frame_content:hover {
/* font-weight: bold; */ /* font-weight: bold; */
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */ /* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
@@ -1185,6 +1206,7 @@ export default {
border-color: #213a54 !important; border-color: #213a54 !important;
background-color: #213a54; background-color: #213a54;
} }
.frame_content_active { .frame_content_active {
border-color: #213a54 !important; border-color: #213a54 !important;
background-color: #213a54; background-color: #213a54;
@@ -1197,6 +1219,7 @@ export default {
line-height: 1; line-height: 1;
flex: 0 0 auto; flex: 0 0 auto;
} }
.patient-info-popper { .patient-info-popper {
font-size: 12px; font-size: 12px;
color: #ddd; color: #ddd;
@@ -1221,7 +1244,7 @@ export default {
line-height: 18px; line-height: 18px;
} }
.patient-info-popper .patient-info-row + .patient-info-row { .patient-info-popper .patient-info-row+.patient-info-row {
margin-top: 4px; margin-top: 4px;
} }
@@ -0,0 +1,115 @@
<template>
<div class="table-list-container" v-loading="loading">
<h3 v-if="isReadingShowSubjectInfo">
<span v-if="subjectCode">{{ subjectCode }} </span>
<span style="margin-left:5px;">{{ taskBlindName }}</span>
</h3>
<h3 v-if="tableQuestion && tableQuestion.GroupName">
{{ language === 'en' ? tableQuestion.GroupEnName : tableQuestion.GroupName }}
</h3>
<h4 v-if="tableQuestion && tableQuestion.QuestionName">
{{ tableQuestion.QuestionName }}
</h4>
<el-table
v-if="tableQuestion && tableQuestion.Type === 'basicTable' && tableQuestion.TableQuestions"
:ref="tableQuestion.Id"
:data="tableQuestion.TableQuestions.Answers"
height="100%"
>
<el-table-column
v-for="q of tableQuestion.TableQuestions.Questions"
:key="q.Id"
:prop="q.Id"
:label="q.QuestionName"
show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="q.Unit > 0 && !isNaN(parseFloat(scope.row[q.Id]))">
{{ `${scope.row[q.Id]} ${$fd('ValueUnit', parseInt(q.Unit))}` }}
</span>
<span v-else-if="q.DictionaryCode">
{{`${scope.row[q.Id] instanceof Array ? scope.row[q.Id].map(item => $fd(q.DictionaryCode,
parseInt(item))).join(',') : $fd(q.DictionaryCode, parseInt(scope.row[q.Id]))}`}}
</span>
<span v-else-if="q.OptionTypeEnum === 1">
{{ `${scope.row[q.Id] instanceof Array ? scope.row[q.Id].join(',') : scope.row[q.Id]}` }}
</span>
<span v-else>
{{ `${scope.row[q.Id]}` }}
</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getFilterTableQuestion } from '@/api/reading'
import store from '@/store'
import { mapGetters } from 'vuex'
import { changeURLStatic } from '@/utils/history.js'
export default {
name: 'DicomTableList',
data() {
return {
isReadingShowSubjectInfo: false,
subjectCode: null,
taskBlindName: null,
questionId: null,
tableQuestion: null,
loading: false
}
},
computed: {
...mapGetters(['language'])
},
created() {
if (this.$route.query.TokenKey) {
store.dispatch('user/setToken', this.$route.query.TokenKey)
changeURLStatic('TokenKey', '')
}
this.subjectCode = localStorage.getItem('subjectCode')
this.taskBlindName = localStorage.getItem('taskBlindName')
this.isReadingShowSubjectInfo = localStorage.getItem('isReadingShowSubjectInfo')
this.getList()
},
methods: {
async getList() {
try {
this.loading = true
let params = {
trialId: this.$route.query.trialId,
visitTaskId: this.$route.query.visitTaskId,
questionId: this.$route.query.questionId
}
let res = await getFilterTableQuestion(params)
if (res.IsSuccess) {
this.tableQuestion = res.Result
}
} catch (e) {
console.log(e)
} finally {
this.loading = false
}
},
resetList() {
}
}
}
</script>
<style lang="scss" scoped>
.table-list-container {
height: 100%;
padding: 0 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
h3, h4 {
flex-shrink: 0;
margin: 10px 0 10px;
}
</style>
@@ -0,0 +1,205 @@
import { dcmUpload } from '@/utils/dcmUpload/dcmUpload'
import dcmjs from '@/utils/dcmUpload/dcmjs'
import { deleteImageCache, getAnonymizeInfo, updateModality } from '@/api/reading'
import * as dicomParser from 'dicom-parser'
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
var config = {
maxWebWorkers: 4,
startWebWorkersOnDemand: true,
taskConfiguration: {
decodeTask: {
initializeCodecsOnStartup: false,
},
},
}
cornerstoneWADOImageLoader.webWorkerManager.initialize(config)
cornerstoneWADOImageLoader.external.dicomParser = dicomParser
cornerstoneWADOImageLoader.external.cornerstone = cornerstone
import Vue from 'vue'
function dicomToPng(imageId, width, height) {
return new Promise((resolve) => {
cornerstone.loadImage(imageId).then(async (image) => {
let canvas = document.createElement('canvas')
canvas.width = (width * 60) / height
canvas.height = 60
if (image) {
cornerstone.renderToCanvas(canvas, image)
// 将 Canvas 图像对象转换为 PNG 格式
let blob = await canvasToBlob(canvas)
resolve(blob)
} else {
resolve()
}
})
}).catch((reason) => {
reason()
})
}
function canvasToBlob(canvas) {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob)
})
})
}
async function UpdateModality(params) {
try {
let res = await updateModality(params)
if (res.IsSuccess) {
return true
}
return false
} catch (err) {
console.log(err)
return false
}
}
async function getAnonymize(VisitTaskId, SubjectVisitId) {
try {
let params = {}
if (VisitTaskId) {
params.VisitTaskId = VisitTaskId
} else {
params.SubjectVisitId = SubjectVisitId
}
let res = await getAnonymizeInfo(params)
if (res.IsSuccess) {
return res.Result
}
return false
} catch (err) {
console.log(err)
return false
}
}
async function deleteCache(path) {
try {
let params = {
path
}
let res = await deleteImageCache(params)
if (res.IsSuccess) {
return true
}
return false
} catch (err) {
console.log(err)
return false
}
}
/**
* 0002,0003 MediaStorageSOPInstanceUID
* 0008,0018 SOPInstanceUID
* 0020,000D StudyInstanceUID
* 0020,000E SeriesInstanceUID
*
*/
export async function changeFile(key, file, study, image) {
// console.log(image, 'image')
// return console.log(study, 'study')
let dataset = dcmjs.data.DicomMessage.readFile(image.data.byteArray.buffer)
// console.log(dataset, 'dataset')
let MediaStorageSOPInstanceUID = dataset.meta['00020003'].Value[0]
let SOPInstanceUID = dataset.dict['00080018'].Value[0]
let StudyInstanceUID = dataset.dict['0020000D'].Value[0]
let SeriesInstanceUID = dataset.dict['0020000E'].Value[0]
// let Modality = dataset.dict['00080060'].Value[0]
let { visitTaskId, visitTaskList = [], studyList, studyIndex, seriesIndex, seriesList, currentSeriesIndex } = study
let series = key === 'IR' ? studyList[studyIndex].SeriesList[seriesIndex] : seriesList[currentSeriesIndex]
console.log(series, 'seriesId')
let index = visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
let config = null
if (key === 'IR') {
config = await getAnonymize(visitTaskId)
} else {
config = await getAnonymize(null, series.subjectVisitId)
}
if (!config) return false
config.AnonymizeFixedList.push({
Element: '0003',
Group: '0002',
ReplaceValue: MediaStorageSOPInstanceUID,
Id: 'MediaStorageSOPInstanceUID'
})
config.AnonymizeFixedList.push({
Element: '0018',
Group: '0008',
ReplaceValue: SOPInstanceUID,
Id: 'SOPInstanceUID'
})
config.AnonymizeFixedList.push({
Element: '000D',
Group: '0020',
ReplaceValue: StudyInstanceUID,
Id: 'StudyInstanceUID'
})
config.AnonymizeFixedList.push({
Element: '000E',
Group: '0020',
ReplaceValue: SeriesInstanceUID,
Id: 'SeriesInstanceUID'
})
let uploadBatchId = Vue.prototype.$guid()
let path = image.imageId.split(`wadouri:${Vue.prototype.OSSclientConfig.basePath}`)[1].split("?")[0]
let res = await dcmUpload(
{
path: path,
file: file,
speed: true,
},
config,
(percentage, checkpoint, lastPer) => {
},
{
fileName: file.name,
fileSize: file.size,
fileType: 'application/dicom',
uploadBatchId: uploadBatchId,
batchDataType: 1,
// trialId: params.trialId,
subjectId: key === 'IR' ? localStorage.getItem("subjectId") : series.subjectId,
subjectVisitId: key === 'IR' ? visitTaskList[index].VisitId : series.subjectVisitId,
}
)
if (!res || !res.url) return false
let Modality = res.Modality
let fileId =
cornerstoneWADOImageLoader.wadouri.fileManager.add(
file
)
let blob = await dicomToPng(
fileId,
image.columns,
image.rows
)
let thumbnailPath = series.previewImageUrl.split(Vue.prototype.OSSclientConfig.basePath)[1]
let OSSclient = Vue.prototype.OSSclient
try {
let seriesRes = await OSSclient.put(thumbnailPath, blob, {
fileName: thumbnailPath.split('/')[thumbnailPath.split('/').length - 1],
fileSize: blob.size,
fileType: 'image/jpeg',
uploadBatchId: uploadBatchId,
batchDataType: 2,
// trialId: params.trialId,
subjectId: key === 'IR' ? localStorage.getItem("subjectId") : series.subjectId,
subjectVisitId: key === 'IR' ? visitTaskList[index].VisitId : series.subjectVisitId,
})
console.log(seriesRes, 'seriesRes')
if (!seriesRes || !seriesRes.url) return false
res = await Promise.all([deleteCache(path), deleteCache(thumbnailPath)])
if (!res) return false
let params = {
Modality,
SeriesId: series.seriesId,
IsCRCReplace: key === 'IR' ? false : true
}
res = await UpdateModality(params)
if (!res) return false
window.location.reload(true)
} catch (e) {
console.log(e)
}
}
@@ -477,6 +477,10 @@ export default {
DicomEvent.$off('updateImage') DicomEvent.$off('updateImage')
}, },
methods: { methods: {
getInfo() {
var image = cornerstone.getImage(this.canvas)
return image
},
goViewer(e) { goViewer(e) {
console.log(this.$refs['sliderBox'].clientHeight) console.log(this.$refs['sliderBox'].clientHeight)
var height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight var height = e.offsetY * 100 / this.$refs['sliderBox'].clientHeight
@@ -309,10 +309,8 @@
<div class="text">{{ $t('trials:reading:button:reset') }}</div> <div class="text">{{ $t('trials:reading:button:reset') }}</div>
</div> </div>
</el-tooltip> </el-tooltip>
<el-tooltip v-if=" <el-tooltip v-if="trialCriterion.ImageUploadEnum > 0" class="item" effect="dark"
trialCriterion.ImageUploadEnum > 0 && currentReadingTaskState < 2 :content="$t('trials:reading:button:upload')" placement="bottom">
" v-hasPermi="['role:ir']" class="item" effect="dark" :content="$t('trials:reading:button:upload')"
placement="bottom">
<div class="tool-wrapper"> <div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('upload')"> <div class="icon" @click.prevent="openUploadImage('upload')">
<i class="el-icon-upload2 svg-icon" /> <i class="el-icon-upload2 svg-icon" />
@@ -320,15 +318,8 @@
<div class="text">{{ $t('trials:reading:button:upload') }}</div> <div class="text">{{ $t('trials:reading:button:upload') }}</div>
</div> </div>
</el-tooltip> </el-tooltip>
<el-tooltip v-if="trialCriterion.ImageDownloadEnum > 0" v-hasPermi="[ <el-tooltip v-if="trialCriterion.ImageDownloadEnum > 0" class="item" effect="dark"
'role:ir', :content="$t('trials:reading:button:download')" placement="bottom">
'role:mim',
'role:mc',
'role:pm',
'role:apm',
'role:ea',
'role:qa',
]" class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom">
<div class="tool-wrapper"> <div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('download')"> <div class="icon" @click.prevent="openUploadImage('download')">
<i class="el-icon-download svg-icon" /> <i class="el-icon-download svg-icon" />
@@ -336,7 +327,10 @@
<div class="text">{{ $t('trials:reading:button:download') }}</div> <div class="text">{{ $t('trials:reading:button:download') }}</div>
</div> </div>
</el-tooltip> </el-tooltip>
<label for="Replacement" style="color: #fff;cursor: pointer;">
替换
</label>
<input type="file" id="Replacement" @change="beginScanFiles($event, 'replace')" style="display: none;">
<div style="margin-left: auto"> <div style="margin-left: auto">
<div style="display: flex;"> <div style="display: flex;">
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:handbooks')" placement="bottom" <el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:handbooks')" placement="bottom"
@@ -411,7 +405,7 @@
<WL v-if="activeName === '2'" @getWwcTpl="getWwcTpl" /> <WL v-if="activeName === '2'" @getWwcTpl="getWwcTpl" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('trials:reading:tab:others')" name="3"> <el-tab-pane :label="$t('trials:reading:tab:others')" name="3">
<Others v-if="activeName === '3'" :imageToolType="1"/> <Others v-if="activeName === '3'" :imageToolType="1" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-dialog> </el-dialog>
@@ -448,10 +442,10 @@
/> --> /> -->
<upload-dicom-and-nonedicom v-if="uploadImageVisible" :SubjectId="uploadSubjectId" :SubjectCode="uploadSubjectCode" <upload-dicom-and-nonedicom v-if="uploadImageVisible" :SubjectId="uploadSubjectId" :SubjectCode="uploadSubjectCode"
:Criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible" :VisitTaskId="taskId" :Criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible" :VisitTaskId="taskId"
:isReadingTaskViewInOrder="isReadingTaskViewInOrder" /> :isReadingTaskViewInOrder="isReadingTaskViewInOrder" :StudyInstanceUID="StudyInstanceUID" :isReading="true" />
<download-dicom-and-nonedicom v-if="downloadImageVisible" :SubjectId="uploadSubjectId" <download-dicom-and-nonedicom v-if="downloadImageVisible" :SubjectId="uploadSubjectId"
:SubjectCode="uploadSubjectCode" :Criterion="uploadTrialCriterion" :TaskId="taskId" :SubjectCode="uploadSubjectCode" :Criterion="uploadTrialCriterion" :TaskId="taskId"
:visible.sync="downloadImageVisible" /> :visible.sync="downloadImageVisible" :isReading="true" />
</div> </div>
</template> </template>
<script> <script>
@@ -478,6 +472,8 @@ import { getDoctorShortcutKey, getUserWLTemplateList } from '@/api/user'
import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom' import uploadDicomAndNonedicom from '@/components/uploadDicomAndNonedicom'
import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom' import downloadDicomAndNonedicom from '@/components/downloadDicomAndNonedicom'
import { getCriterionReadingInfo, setReadKeyFile } from '@/api/trials' import { getCriterionReadingInfo, setReadKeyFile } from '@/api/trials'
import { changeFile } from "@/views/trials/trials-panel/reading/dicoms/components/upload.js"
import dcmjs from '@/utils/dcmUpload/dcmjs'
export default { export default {
name: 'DicomViewer', name: 'DicomViewer',
components: { components: {
@@ -683,7 +679,10 @@ export default {
fullScreenWidth: window.innerWidth - 570 + 'px', fullScreenWidth: window.innerWidth - 570 + 'px',
fullScreenHeight: window.innerHeight - 128 + 'px', fullScreenHeight: window.innerHeight - 128 + 'px',
ManualsClose: false ManualsClose: false,
fileKey: null,
file: null,
StudyInstanceUID: null
} }
}, },
@@ -856,6 +855,12 @@ export default {
this.loadLinkedImageStack(seriesInfo) this.loadLinkedImageStack(seriesInfo)
console.log('loadLinkedImageStack') console.log('loadLinkedImageStack')
}) })
DicomEvent.$on('sendStudyFile', (study) => {
let image = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].getInfo()
study.visitTaskList = this.visitTaskList
this.$emit("update:Loading", true)
changeFile('IR', this.file, study, image)
})
}, },
beforeDestroy() { beforeDestroy() {
DicomEvent.$off('updateImage') DicomEvent.$off('updateImage')
@@ -866,6 +871,11 @@ export default {
DicomEvent.$off('loadLinkedImageStack') DicomEvent.$off('loadLinkedImageStack')
}, },
methods: { methods: {
beginScanFiles(e, key) {
this.fileKey = key
this.file = e.target.files[0]
DicomEvent.$emit('getStudyFile')
},
handleReadingChart(e) { handleReadingChart(e) {
this.$emit('handleReadingChart', e) this.$emit('handleReadingChart', e)
}, },
@@ -898,6 +908,11 @@ export default {
if (idx > -1) { if (idx > -1) {
this.taskId = this.visitTaskList[idx].VisitTaskId this.taskId = this.visitTaskList[idx].VisitTaskId
} }
let image = this.$refs[`dicomCanvas${this.currentDicomCanvasIndex}`][0].getInfo()
let dataset = dcmjs.data.DicomMessage.readFile(image.data.byteArray.buffer)
this.StudyInstanceUID = dataset.dict['0020000D'].Value[0]
dataset = null
console.log(this.StudyInstanceUID, 'StudyInstanceUID')
this.uploadSubjectCode = localStorage.getItem("subjectCode") this.uploadSubjectCode = localStorage.getItem("subjectCode")
this.uploadSubjectId = localStorage.getItem("subjectId") this.uploadSubjectId = localStorage.getItem("subjectId")
this.uploadTrialCriterion = this.trialCriterion this.uploadTrialCriterion = this.trialCriterion
@@ -128,7 +128,7 @@ export default {
immediate: true, immediate: true,
handler(val) { handler(val) {
if (val) { if (val) {
this.readingTaskState = val this.readingTaskState = 1
} }
} }
} }
@@ -169,7 +169,7 @@ export default {
if (i > -1) { if (i > -1) {
this.visitTaskId = this.visitTaskList[i].VisitTaskId this.visitTaskId = this.visitTaskList[i].VisitTaskId
this.taskBlindName = this.visitTaskList[i].TaskBlindName this.taskBlindName = this.visitTaskList[i].TaskBlindName
this.readingTaskState = this.visitTaskList[i].ReadingTaskState this.readingTaskState = 1
console.log(this.visitTaskList[i].IsInit) console.log(this.visitTaskList[i].IsInit)
if (!this.visitTaskList[i].IsInit) { if (!this.visitTaskList[i].IsInit) {
var loading = this.$loading({ fullscreen: true }) var loading = this.$loading({ fullscreen: true })
@@ -306,9 +306,9 @@ export default {
} }
this.signVisible = false this.signVisible = false
// //
this.readingTaskState = 2 this.readingTaskState = 1
store.dispatch('reading/setVisitTaskReadingTaskState', { visitTaskId: this.visitTaskId, readingTaskState: 2 }) store.dispatch('reading/setVisitTaskReadingTaskState', { visitTaskId: this.visitTaskId, readingTaskState: 1 })
DicomEvent.$emit('setReadingState', 2) DicomEvent.$emit('setReadingState', 1)
window.opener.postMessage('refreshTaskList', window.location) window.opener.postMessage('refreshTaskList', window.location)
this.$confirm(this.$t('trials:oncologyReview:title:msg2'), { this.$confirm(this.$t('trials:oncologyReview:title:msg2'), {
type: 'warning', type: 'warning',
@@ -1,6 +1,6 @@
<template> <template>
<div v-loading="loading" style="min-height: 500px;"> <div v-loading="loading" style="min-height: 500px;">
<el-form v-if="isRender" ref="questions" size="small" :model="questionForm" :disabled="readingTaskState >= 2"> <el-form v-if="isRender" ref="questions" size="small" :model="questionForm">
<template> <template>
<QuestionFormItem v-for="question of questions" :key="question.Id" :visitTaskId="visitTaskId" <QuestionFormItem v-for="question of questions" :key="question.Id" :visitTaskId="visitTaskId"
:question="question" :question-form="questionForm" :reading-task-state="readingTaskState" :question="question" :question-form="questionForm" :reading-task-state="readingTaskState"
@@ -112,8 +112,8 @@ export default {
VisitTaskId: visitTaskId ? visitTaskId : this.visitTaskId VisitTaskId: visitTaskId ? visitTaskId : this.visitTaskId
}).then(res => { }).then(res => {
this.IsBaseline = res.OtherInfo.IsBaseline this.IsBaseline = res.OtherInfo.IsBaseline
this.readingTaskState = res.OtherInfo.ReadingTaskState this.readingTaskState = 1
DicomEvent.$emit('setReadingState', res.OtherInfo.ReadingTaskState) DicomEvent.$emit('setReadingState', 1)
res.Result.SinglePage.map((v) => { res.Result.SinglePage.map((v) => {
if (v.Type === 'group' && v.Childrens.length === 0) return if (v.Type === 'group' && v.Childrens.length === 0) return
if (!v.IsPage && v.Type !== 'group' && v.Type !== 'summary' && v.Type !== 'table' && v.Type !== 'basicTable' && v.Type !== 'number') { if (!v.IsPage && v.Type !== 'group' && v.Type !== 'summary' && v.Type !== 'table' && v.Type !== 'basicTable' && v.Type !== 'number') {
@@ -1,9 +1,11 @@
<template> <template>
<div v-loading="loading" class="study-wrapper"> <div v-loading="loading" class="study-wrapper">
<h4 v-if="isReadingShowSubjectInfo" style="color: #ddd;padding: 5px 0px;margin: 0;text-align: center;background-color: #4c4c4c;"> <h4 v-if="isReadingShowSubjectInfo"
style="color: #ddd;padding: 5px 0px;margin: 0;text-align: center;background-color: #4c4c4c;">
{{ subjectCode }} {{ subjectCode }}
</h4> </h4>
<h4 v-if="isReadingShowSubjectInfo" style="color: #ddd;padding: 5px 0px;margin: 0;text-align: center;background-color: #4c4c4c;margin-bottom: 5px;"> <h4 v-if="isReadingShowSubjectInfo"
style="color: #ddd;padding: 5px 0px;margin: 0;text-align: center;background-color: #4c4c4c;margin-bottom: 5px;">
{{ taskBlindName }} {{ taskBlindName }}
</h4> </h4>
<div class="ps"> <div class="ps">
@@ -14,11 +16,8 @@
<!-- 关键序列 --> <!-- 关键序列 -->
{{ $t('trials:reading:title:keySeries') }} {{ $t('trials:reading:title:keySeries') }}
</div> </div>
<div <div v-else class="dicom-desc"
v-else style="width: 150px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
class="dicom-desc"
style="width: 150px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;"
>
<div> <div>
<div style="text-overflow: ellipsis;overflow: hidden;" v-if="!study.StudyName"> <div style="text-overflow: ellipsis;overflow: hidden;" v-if="!study.StudyName">
<span :title="study.StudyCode">{{ study.StudyCode }}</span> <span :title="study.StudyCode">{{ study.StudyCode }}</span>
@@ -27,63 +26,51 @@
<div style="text-overflow: ellipsis;overflow: hidden;" v-else> <div style="text-overflow: ellipsis;overflow: hidden;" v-else>
<span :title="study.StudyCode">{{ study.StudyCode }}</span> <span :title="study.StudyCode">{{ study.StudyCode }}</span>
<span v-if="taskInfo && taskInfo.IsShowStudyName" :title="study.StudyName"> <span v-if="taskInfo && taskInfo.IsShowStudyName" :title="study.StudyName">
{{study.StudyName}} {{ study.StudyName }}
</span> </span>
<div>{{ study.Modalities }} ({{ study.SeriesCount }})</div> <div>{{ study.Modalities }} ({{ study.SeriesCount }})</div>
</div> </div>
<div style="text-overflow: ellipsis;overflow: hidden;" :title="study.Description">{{ study.Description }}</div> <div style="text-overflow: ellipsis;overflow: hidden;" :title="study.Description">{{ study.Description
}}</div>
</div> </div>
</div> </div>
</template> </template>
<div class="series"> <div class="series">
<div <div v-for="(series, i) in study.SeriesList" :key="i" style="position:relative;margin-top:5px;"
v-for="(series, i) in study.SeriesList" series-type="current" @click="showSeriesImage(index, i, series)">
:key="i"
style="position:relative;margin-top:5px;"
series-type="current"
@click="showSeriesImage(index,i,series)"
>
<div <div :class="{ 'series-active': i == seriesIndex && index === studyIndex }" class="series-wrapper">
:class="{'series-active': i==seriesIndex && index === studyIndex}" <el-image class="image-preview" :src="series.previewImageUrl" fit="fill" crossorigin="anonymous" />
class="series-wrapper"
>
<el-image
class="image-preview"
:src="series.previewImageUrl"
fit="fill"
crossorigin="anonymous"
/>
<div class="image-desc"> <div class="image-desc">
<div class="flex-div"> <div class="flex-div">
<div style="width: 40px;display: flex;flex-direction: row;justify-content: space-between;"> <div style="width: 40px;display: flex;flex-direction: row;justify-content: space-between;">
<div v-if="!study.IsCriticalSequence">#{{ series.seriesNumber }}</div> <div v-if="!study.IsCriticalSequence">#{{ series.seriesNumber }}</div>
<div v-if="series.isDicom && series.prefetchInstanceCount<series.instanceCount * 100 && series.modality!== 'SR'"> <div
v-if="series.isDicom && series.prefetchInstanceCount < series.instanceCount * 100 && series.modality !== 'SR'">
<!-- 下载 --> <!-- 下载 -->
<el-tooltip v-if="!series.isLoading" class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom"> <el-tooltip v-if="!series.isLoading" class="item" effect="dark"
<i class="el-icon-video-play" style="font-size: 18px;margin-right: 5px;color: #ffeb3b;cursor: pointer;" @click.stop="loadSeries(series,index,i)" /> :content="$t('trials:reading:button:download')" placement="bottom">
<i class="el-icon-video-play"
style="font-size: 18px;margin-right: 5px;color: #ffeb3b;cursor: pointer;"
@click.stop="loadSeries(series, index, i)" />
</el-tooltip> </el-tooltip>
</div> </div>
<el-tooltip v-else-if="series.isDicom && series.prefetchInstanceCount === 0 &&series.modality!== 'SR'" class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom"> <el-tooltip
<i class="el-icon-video-play" style="font-size: 18px;margin-right: 5px;color: #ffeb3b;cursor: pointer;" @click.stop="loadSeries(series,index,i)" /> v-else-if="series.isDicom && series.prefetchInstanceCount === 0 && series.modality !== 'SR'"
class="item" effect="dark" :content="$t('trials:reading:button:download')" placement="bottom">
<i class="el-icon-video-play"
style="font-size: 18px;margin-right: 5px;color: #ffeb3b;cursor: pointer;"
@click.stop="loadSeries(series, index, i)" />
</el-tooltip> </el-tooltip>
</div> </div>
<div v-if="series.isExistMutiFrames && series.instanceCount > 1"> <div v-if="series.isExistMutiFrames && series.instanceCount > 1">
<el-popover <el-popover placement="right" trigger="hover" popper-class="instance_frame_wrapper">
placement="right"
trigger="hover"
popper-class="instance_frame_wrapper"
>
<div class="frame_list"> <div class="frame_list">
<div <div v-for="(instance, idx) in series.instanceInfoList" :key="instance.Id"
v-for="(instance, idx) in series.instanceInfoList" class="frame_content" :class="{ 'frame_content_active': activeInstanceId === instance.Id }"
:key="instance.Id" :style="{ 'margin-bottom': idx < series.instanceInfoList.length - 1 ? '5px' : '0px' }"
class="frame_content" @click.stop="showMultiFrames(index, series, i, instance)">
:class="{ 'frame_content_active': activeInstanceId === instance.Id }"
:style="{'margin-bottom':idx<series.instanceInfoList.length-1? '5px':'0px'}"
@click.stop="showMultiFrames(index,series, i, instance)"
>
<!-- <div> <!-- <div>
<img <img
class="image-preview" class="image-preview"
@@ -96,12 +83,14 @@
</div> --> </div> -->
<div> <div>
<div>{{ instance.InstanceNumber }}</div> <div>{{ instance.InstanceNumber }}</div>
<div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ? instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div> <div>{{ `${instance.NumberOfFrames > 0 ? instance.KeyFramesList.length > 0 ?
instance.KeyFramesList.length : instance.NumberOfFrames : 1} frame` }}</div>
</div> </div>
</div> </div>
</div> </div>
<i slot="reference" class="el-icon-connection" style="font-size: 15px;cursor: pointer;color: #ffeb3b;" /> <i slot="reference" class="el-icon-connection"
style="font-size: 15px;cursor: pointer;color: #ffeb3b;" />
</el-popover> </el-popover>
</div> </div>
</div> </div>
@@ -121,9 +110,11 @@
{{ series.modality }}: {{ series.instanceCount }} image {{ series.modality }}: {{ series.instanceCount }} image
</p> </p>
<div class="flex-div"> <div class="flex-div">
<div v-if="measureData && measureData.findIndex(v=>v.SeriesId === series.seriesId && v.MeasureData) > -1"> <div
v-if="measureData && measureData.findIndex(v => v.SeriesId === series.seriesId && v.MeasureData) > -1">
<!-- 有标注 --> <!-- 有标注 -->
<el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:marked')" placement="right"> <el-tooltip class="item" effect="dark" :content="$t('trials:reading:button:marked')"
placement="right">
<i class="el-icon-star-on" style="font-size: 16px;color: #ff5722;" /> <i class="el-icon-star-on" style="font-size: 16px;color: #ff5722;" />
</el-tooltip> </el-tooltip>
@@ -131,10 +122,10 @@
</div> </div>
</div> </div>
</div> </div>
<div v-if="series.isDicom && series.prefetchInstanceCount>0 && series.prefetchInstanceCount<series.instanceCount * 100" style="width: 100%;"> <div
<el-progress v-if="series.isDicom && series.prefetchInstanceCount > 0 && series.prefetchInstanceCount < series.instanceCount * 100"
:percentage="parseInt((series.prefetchInstanceCount / series.instanceCount).toFixed(2))" style="width: 100%;">
/> <el-progress :percentage="parseInt((series.prefetchInstanceCount / series.instanceCount).toFixed(2))" />
</div> </div>
</div> </div>
@@ -144,24 +135,19 @@
</div> </div>
<div class="sr-wrapper"> <div class="sr-wrapper">
<el-dialog <el-dialog :visible.sync="srDialogVisible"
:visible.sync="srDialogVisible" :custom-class="isSrFullscreen ? 'sr-full-dialog-container' : 'sr-dialog-container'" :show-close="false"
:custom-class="isSrFullscreen?'sr-full-dialog-container':'sr-dialog-container'" :close-on-click-modal="false" :fullscreen="isSrFullscreen">
:show-close="false"
:close-on-click-modal="false"
:fullscreen="isSrFullscreen"
>
<span slot="title" class="dialog-footer"> <span slot="title" class="dialog-footer">
<div style="position: absolute;right: 20px;top: 10px;"> <div style="position: absolute;right: 20px;top: 10px;">
<svg-icon :icon-class="isSrFullscreen?'exit-fullscreen':'fullscreen'" style="cursor: pointer;font-size: 20px;color:#000;" @click="isSrFullscreen=!isSrFullscreen" /> <svg-icon :icon-class="isSrFullscreen ? 'exit-fullscreen' : 'fullscreen'"
<svg-icon icon-class="dClose" style="cursor: pointer;font-size: 25px;margin-left: 10px;" @click="srDialogVisible = false" /> style="cursor: pointer;font-size: 20px;color:#000;" @click="isSrFullscreen = !isSrFullscreen" />
<svg-icon icon-class="dClose" style="cursor: pointer;font-size: 25px;margin-left: 10px;"
@click="srDialogVisible = false" />
</div> </div>
</span> </span>
<div style="height: 100%;margin:0;"> <div style="height: 100%;margin:0;">
<SrList <SrList v-if="srDialogVisible" :sr-info="srInfo" />
v-if="srDialogVisible"
:sr-info="srInfo"
/>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
@@ -266,6 +252,9 @@ export default {
var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId) var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId)
this.measureData = this.visitTaskList[idx].MeasureData this.measureData = this.visitTaskList[idx].MeasureData
}) })
DicomEvent.$on("getStudyFile", () => {
DicomEvent.$emit('sendStudyFile', { studyList: this.studyList, studyIndex: this.studyIndex, seriesIndex: this.seriesIndex, visitTaskId: this.visitTaskId })
})
// DicomEvent.$on('setReadingState', readingTaskState => { // DicomEvent.$on('setReadingState', readingTaskState => {
// var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId) // var idx = this.visitTaskList.findIndex(i => i.VisitTaskId === this.visitTaskId)
// if (idx > -1) { // if (idx > -1) {
@@ -282,7 +271,7 @@ export default {
// //
this.getInitSeries().then((res) => { this.getInitSeries().then((res) => {
requestPoolManager.startTaskTimer() requestPoolManager.startTaskTimer()
res.map(async(item) => { res.map(async (item) => {
let imageId = item.imageIds[item.imageIdIndex] let imageId = item.imageIds[item.imageIdIndex]
const p = parseInt(new Date().getTime()) const p = parseInt(new Date().getTime())
// requestPoolManager.loadAndCacheImagePlus(imageId, item.seriesId, p * 100) // requestPoolManager.loadAndCacheImagePlus(imageId, item.seriesId, p * 100)
@@ -390,15 +379,15 @@ export default {
this.seriesIndex = seriesArr[1].seriesIndex this.seriesIndex = seriesArr[1].seriesIndex
this.activeNames = [`${seriesArr[1].studyId}`] this.activeNames = [`${seriesArr[1].studyId}`]
} }
} else if (this.visitTaskList[idx].IsBaseLineTask || isReadingTaskViewInOrder === 0){ } else if (this.visitTaskList[idx].IsBaseLineTask || isReadingTaskViewInOrder === 0) {
// 线 // 线
const obj = this.getFirstMarkedSeries(this.visitTaskList[idx].MeasureData, [...this.visitTaskList[idx].StudyList]) const obj = this.getFirstMarkedSeries(this.visitTaskList[idx].MeasureData, [...this.visitTaskList[idx].StudyList])
if (Object.keys(obj).length !== 0) { if (Object.keys(obj).length !== 0) {
this.studyIndex = obj.studyIndex this.studyIndex = obj.studyIndex
this.seriesIndex = obj.seriesIndex this.seriesIndex = obj.seriesIndex
seriesList.push(obj.series) seriesList.push(obj.series)
this.activeNames = [`${this.studyList[ this.studyIndex].StudyId}`] this.activeNames = [`${this.studyList[this.studyIndex].StudyId}`]
this.studyList[ obj.studyIndex].SeriesList[obj.seriesIndex].isFirstRender = true this.studyList[obj.studyIndex].SeriesList[obj.seriesIndex].isFirstRender = true
} else { } else {
// //
if (this.studyList.length > 0) { if (this.studyList.length > 0) {
@@ -430,7 +419,7 @@ export default {
this.studyIndex = secondObj.studyIndex this.studyIndex = secondObj.studyIndex
this.seriesIndex = secondObj.seriesIndex this.seriesIndex = secondObj.seriesIndex
seriesList.push(secondObj.series) seriesList.push(secondObj.series)
this.studyList[ secondObj.studyIndex].SeriesList[secondObj.seriesIndex].isFirstRender = true this.studyList[secondObj.studyIndex].SeriesList[secondObj.seriesIndex].isFirstRender = true
this.studyIndex = secondObj.studyIndex this.studyIndex = secondObj.studyIndex
this.seriesIndex = secondObj.seriesIndex this.seriesIndex = secondObj.seriesIndex
@@ -464,7 +453,7 @@ export default {
// const isIdx = instanceList.findIndex(is => is.includes(measureDatas[i].InstanceId)) // const isIdx = instanceList.findIndex(is => is.includes(measureDatas[i].InstanceId))
const imageIds = seriesList[srIdx].imageIds const imageIds = seriesList[srIdx].imageIds
// let filterStr = seriesList[srIdx].isExistMutiFrames ? `frame=${measureDatas[i].MeasureData.frame}&instanceId=${measureDatas[i].InstanceId}` : `instanceId=${measureDatas[i].InstanceId}` // let filterStr = seriesList[srIdx].isExistMutiFrames ? `frame=${measureDatas[i].MeasureData.frame}&instanceId=${measureDatas[i].InstanceId}` : `instanceId=${measureDatas[i].InstanceId}`
let instanceIndex = seriesList[srIdx].instanceInfoList.findIndex(k=>k.Id === measureDatas[i].InstanceId) let instanceIndex = seriesList[srIdx].instanceInfoList.findIndex(k => k.Id === measureDatas[i].InstanceId)
let filterStr = '' let filterStr = ''
if (instanceIndex > -1 && seriesList[srIdx].isExistMutiFrames) { if (instanceIndex > -1 && seriesList[srIdx].isExistMutiFrames) {
if (seriesList[srIdx].instanceInfoList[instanceIndex].NumberOfFrames > 0) { if (seriesList[srIdx].instanceInfoList[instanceIndex].NumberOfFrames > 0) {
@@ -548,7 +537,7 @@ export default {
// const instanceList = seriesList[srIdx].instanceList // const instanceList = seriesList[srIdx].instanceList
// const isIdx = instanceList.findIndex(is => is.includes(measureDatas[mIdx].InstanceId)) // const isIdx = instanceList.findIndex(is => is.includes(measureDatas[mIdx].InstanceId))
const imageIds = seriesList[srIdx].imageIds const imageIds = seriesList[srIdx].imageIds
let instanceIndex = seriesList[srIdx].instanceInfoList.findIndex(i=>i.Id === measureDatas[mIdx].InstanceId) let instanceIndex = seriesList[srIdx].instanceInfoList.findIndex(i => i.Id === measureDatas[mIdx].InstanceId)
let filterStr = '' let filterStr = ''
if (instanceIndex > -1 && seriesList[srIdx].isExistMutiFrames) { if (instanceIndex > -1 && seriesList[srIdx].isExistMutiFrames) {
if (seriesList[srIdx].instanceInfoList[instanceIndex].NumberOfFrames > 0) { if (seriesList[srIdx].instanceInfoList[instanceIndex].NumberOfFrames > 0) {
@@ -658,7 +647,7 @@ export default {
this.studyIndex = studyIndex this.studyIndex = studyIndex
this.seriesIndex = seriesIndex this.seriesIndex = seriesIndex
this.studyList[studyIndex].SeriesList[seriesIndex].measureData = this.measureData this.studyList[studyIndex].SeriesList[seriesIndex].measureData = this.measureData
var dicomStatck = Object.assign({},this.studyList[studyIndex].SeriesList[seriesIndex]) var dicomStatck = Object.assign({}, this.studyList[studyIndex].SeriesList[seriesIndex])
dicomStatck.imageIdIndex = 0 dicomStatck.imageIdIndex = 0
this.$emit('loadImageStack', dicomStatck) this.$emit('loadImageStack', dicomStatck)
@@ -707,7 +696,7 @@ export default {
this.studyIndex = studyIndex this.studyIndex = studyIndex
this.seriesIndex = seriesIndex this.seriesIndex = seriesIndex
this.studyList[studyIndex].SeriesList[seriesIndex].measureData = this.measureData this.studyList[studyIndex].SeriesList[seriesIndex].measureData = this.measureData
var dicomStatck = Object.assign({},this.studyList[studyIndex].SeriesList[seriesIndex]) var dicomStatck = Object.assign({}, this.studyList[studyIndex].SeriesList[seriesIndex])
const imageIds = [] const imageIds = []
if (instanceInfo.KeyFramesList.length > 0) { if (instanceInfo.KeyFramesList.length > 0) {
instanceInfo.KeyFramesList.map(i => { instanceInfo.KeyFramesList.map(i => {
@@ -867,15 +856,17 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.study-wrapper{ .study-wrapper {
::v-deep .el-progress-bar__inner{ ::v-deep .el-progress-bar__inner {
transition: width 0s ease; transition: width 0s ease;
} }
width:100%; width:100%;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
.dicom-desc{
.dicom-desc {
font-weight: bold; font-weight: bold;
font-size: 13px; font-size: 13px;
text-align: left; text-align: left;
@@ -887,19 +878,23 @@ export default {
overflow-anchor: none; overflow-anchor: none;
touch-action: auto; touch-action: auto;
} }
.series-active { .series-active {
background-color: #607d8b!important; background-color: #607d8b !important;
border: 1px solid #607d8b!important; border: 1px solid #607d8b !important;
} }
::v-deep .el-progress__text{
::v-deep .el-progress__text {
color: #ccc; color: #ccc;
font-size: 12px; font-size: 12px;
} }
.series{
.series {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
.series-wrapper { .series-wrapper {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -912,11 +907,13 @@ export default {
border-radius: 2px; border-radius: 2px;
border: 1px solid #404040; border: 1px solid #404040;
background-color: #3a3a3a; background-color: #3a3a3a;
.el-progress__text{
.el-progress__text {
display: none; display: none;
} }
.el-progress-bar{
padding-right:0px; .el-progress-bar {
padding-right: 0px;
} }
.image-preview { .image-preview {
@@ -925,9 +922,11 @@ export default {
border: 2px solid #252525; border: 2px solid #252525;
cursor: pointer; cursor: pointer;
} }
.image-desc { .image-desc {
vertical-align: top; vertical-align: top;
p{
p {
width: 95px; width: 95px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -936,7 +935,8 @@ export default {
color: #ddd; color: #ddd;
margin: 0px; margin: 0px;
line-height: 1.5; line-height: 1.5;
div{
div {
width: 95px; width: 95px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@@ -944,7 +944,8 @@ export default {
} }
} }
} }
.flex-div{
.flex-div {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
@@ -960,76 +961,89 @@ export default {
} }
} }
} }
::v-deep .el-collapse{
::v-deep .el-collapse {
border: none; border: none;
.el-collapse-item{
background-color: #000!important; .el-collapse-item {
background-color: #000 !important;
color: #ddd; color: #ddd;
} }
.el-collapse-item__content{
padding-bottom:5px; .el-collapse-item__content {
background-color: #000!important; padding-bottom: 5px;
background-color: #000 !important;
} }
.el-collapse-item__header{
background-color: #000!important; .el-collapse-item__header {
background-color: #000 !important;
color: #ddd; color: #ddd;
border-bottom-color:#5a5a5a; border-bottom-color: #5a5a5a;
padding-left: 5px; padding-left: 5px;
height: 40px; height: 40px;
line-height: 20px; line-height: 20px;
} }
} }
.sr-wrapper{
::v-deep .el-dialog{ .sr-wrapper {
::v-deep .el-dialog {
background: #fff !important; background: #fff !important;
border: 1px solid #ddd; border: 1px solid #ddd;
// color: #ddd; // color: #ddd;
.el-dialog__title{ .el-dialog__title {
color:#fff; color: #fff;
} }
} }
::v-deep .sr-dialog-container{
::v-deep .sr-dialog-container {
margin-top: 50px !important; margin-top: 50px !important;
width:75%; width: 75%;
height:80%; height: 80%;
} }
::v-deep .el-dialog__body{
::v-deep .el-dialog__body {
padding: 10px; padding: 10px;
height: calc(100% - 50px); height: calc(100% - 50px);
} }
.el-dialog__header{
.el-dialog__header {
position: relative; position: relative;
} }
.sr-full-dialog-container{
::v-deep .is-fullscreen .el-dialog__body{ .sr-full-dialog-container {
::v-deep .is-fullscreen .el-dialog__body {
height: calc(100% - 50px); height: calc(100% - 50px);
} }
} }
} }
} }
</style> </style>
<style> <style>
.instance_frame_wrapper{ .instance_frame_wrapper {
min-width: 120px; min-width: 120px;
background-color: #2c2c2c; background-color: #2c2c2c;
border: 1px solid #2c2c2c; border: 1px solid #2c2c2c;
padding: 5px; padding: 5px;
} }
.frame_list{
.frame_list {
max-height: 500px; max-height: 500px;
overflow-y: auto; overflow-y: auto;
} }
.instance_frame_wrapper ::-webkit-scrollbar { .instance_frame_wrapper ::-webkit-scrollbar {
width: 7px; width: 7px;
height: 7px; height: 7px;
} }
.instance_frame_wrapper ::-webkit-scrollbar-thumb { .instance_frame_wrapper ::-webkit-scrollbar-thumb {
border-radius: 10px; border-radius: 10px;
background: #d0d0d0; background: #d0d0d0;
} }
.frame_content{
.frame_content {
height: 50px; height: 50px;
padding: 5px; padding: 5px;
display: flex; display: flex;
@@ -1038,6 +1052,7 @@ export default {
font-size: 12px; font-size: 12px;
border: 1px solid #404040; border: 1px solid #404040;
} }
.frame_content:hover { .frame_content:hover {
/* font-weight: bold; */ /* font-weight: bold; */
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */ /* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
@@ -1046,6 +1061,7 @@ export default {
border-color: #213a54 !important; border-color: #213a54 !important;
background-color: #213a54; background-color: #213a54;
} }
.frame_content_active { .frame_content_active {
border-color: #213a54 !important; border-color: #213a54 !important;
background-color: #213a54; background-color: #213a54;
@@ -619,6 +619,14 @@ export default {
} }
} }
::v-deep .el-dialog.cd-dialog-container {
.el-input.is-disabled .el-input__inner {
background-color: rgb(245, 247, 250);
color: #666;
border: 1px solid #dcdfe6;
}
}
} }
::v-deep .dialog-container { ::v-deep .dialog-container {
@@ -12,7 +12,9 @@
{ required: true, message: $t('common:ruleMessage:select'), trigger: 'blur' } { required: true, message: $t('common:ruleMessage:select'), trigger: 'blur' }
]"> ]">
<el-select v-model="fusionForm.studyId" clearable @change="handleStudyIdChange"> <el-select v-model="fusionForm.studyId" clearable @change="handleStudyIdChange">
<el-option v-for="item in studyList" :key="item.StudyId" :label="item.StudyCode" :value="item.StudyId" /> <el-option v-for="item in studyList" :key="item.StudyId"
:label="taskInfo.IsReadingTaskViewInOrder !== 0 ? item.StudyCode : `${item.Modalities}(${item.SeriesCount})`"
:value="item.StudyId" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- 融合图像 --> <!-- 融合图像 -->
@@ -111,10 +113,12 @@ export default {
ctSeries: [], ctSeries: [],
petSeries: [], petSeries: [],
petctWindow: null, petctWindow: null,
digitPlaces: 2 digitPlaces: 2,
taskInfo: {}
} }
}, },
mounted() { mounted() {
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
var digitPlaces = Number(localStorage.getItem('digitPlaces')) var digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
this.initForm() this.initForm()
@@ -187,6 +187,7 @@ export default {
originalMarkers: [], originalMarkers: [],
markers: { top: '', right: '', bottom: '', left: '' }, markers: { top: '', right: '', bottom: '', left: '' },
playClipState: false, playClipState: false,
clipFramesPerSecond: null,
wwwcIdx: 2, wwwcIdx: 2,
presetName: '', presetName: '',
volumeId: null, volumeId: null,
@@ -625,6 +626,22 @@ export default {
} }
}, },
syncViewportStaticState() {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
if (!renderingEngine) return
const viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport) return
this.getOrientationMarker()
const toolGroup =
cornerstoneTools.ToolGroupManager.getToolGroupForViewport(
this.viewportId,
this.renderingEngineId
) || cornerstoneTools.ToolGroupManager.getToolGroup(this.viewportId)
if (toolGroup) {
toolGroup.setToolEnabled('ScaleOverlay')
}
},
setFullScreen(index) { setFullScreen(index) {
setTimeout(() => { setTimeout(() => {
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
@@ -727,14 +744,33 @@ export default {
} }
}, },
toggleClipPlay(isPlay, framesPerSecond) { toggleClipPlay(isPlay, framesPerSecond) {
this.playClipState = isPlay
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
if (!renderingEngine) return
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport?.element) return
const parsedFramesPerSecond = Number(framesPerSecond)
const nextFramesPerSecond = Number.isFinite(parsedFramesPerSecond)
? parsedFramesPerSecond
: (this.clipFramesPerSecond || 15)
const isSameSpeedWhilePlaying = this.playClipState &&
isPlay &&
this.clipFramesPerSecond === nextFramesPerSecond
if (isPlay) { if (isPlay) {
cornerstoneTools.utilities.cine.playClip(viewport.element, { framesPerSecond, loop: true }) if (isSameSpeedWhilePlaying) return
cornerstoneTools.utilities.cine.playClip(viewport.element, {
framesPerSecond: nextFramesPerSecond,
loop: true,
waitForRendered: 1
})
this.clipFramesPerSecond = nextFramesPerSecond
this.playClipState = true
} else { } else {
cornerstoneTools.utilities.cine.stopClip(viewport.element) cornerstoneTools.utilities.cine.stopClip(viewport.element)
this.clipFramesPerSecond = null
this.playClipState = false
} }
}, },
scrollPage(type) { scrollPage(type) {
@@ -1125,6 +1161,7 @@ export default {
} }
viewport.render() viewport.render()
this.syncViewportStaticState()
if (this.currentVoiUpper > 0) { if (this.currentVoiUpper > 0) {
this.voiChange(this.currentVoiUpper) this.voiChange(this.currentVoiUpper)
} }
@@ -284,7 +284,7 @@
<svg-icon icon-class="lastframe" class="svg-icon" /> <svg-icon icon-class="lastframe" class="svg-icon" />
</div> </div>
<select v-model="fps" :title="$t('trials:dicom-show:speed')" class="select-wrapper" <select v-model="fps" :title="$t('trials:dicom-show:speed')" class="select-wrapper"
:disabled="clipPlaying"> @change="handleClipFpsChange">
<!-- 默认值 --> <!-- 默认值 -->
<option :value="5">5</option> <option :value="5">5</option>
<option :value="10">10</option> <option :value="10">10</option>
@@ -320,7 +320,7 @@
</div> </div>
</div> </div>
<div class="tool-item" :title="$t('trials:reading:button:upload')" <div class="tool-item" :title="$t('trials:reading:button:upload')"
v-if="trialCriterion.ImageUploadEnum > 0 && readingTaskState < 2" v-hasPermi="['role:ir']"> v-if="trialCriterion.ImageUploadEnum > 0" v-hasPermi="['role:ir']">
<div class="tool-wrapper"> <div class="tool-wrapper">
<div class="icon" @click.prevent="openUploadImage('upload')"> <div class="icon" @click.prevent="openUploadImage('upload')">
<i class="el-icon-upload2 svg-icon" /> <i class="el-icon-upload2 svg-icon" />
@@ -582,10 +582,11 @@
:renderingEngineId="renderingEngineId" :visitInfo="taskInfo" /> --> :renderingEngineId="renderingEngineId" :visitInfo="taskInfo" /> -->
<upload-dicom-and-nonedicom v-if="uploadImageVisible" :subject-id="uploadSubjectId" <upload-dicom-and-nonedicom v-if="uploadImageVisible" :subject-id="uploadSubjectId"
:subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible" :subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible"
:visit-task-id="taskId" :is-reading-task-view-in-order="isReadingTaskViewInOrder" /> :visit-task-id="taskId" :is-reading-task-view-in-order="isReadingTaskViewInOrder" :isReading="true" />
<download-dicom-and-nonedicom v-if="downloadImageVisible" :subject-id="uploadSubjectId" <download-dicom-and-nonedicom v-if="downloadImageVisible" :subject-id="uploadSubjectId"
:subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :task-id="taskId" :subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :task-id="taskId"
:visible.sync="downloadImageVisible" :is-reading-task-view-in-order="isReadingTaskViewInOrder" /> :visible.sync="downloadImageVisible" :is-reading-task-view-in-order="isReadingTaskViewInOrder"
:isReading="true" />
<readingChart ref="readingChart" /> <readingChart ref="readingChart" />
</div> </div>
</template> </template>
@@ -1221,12 +1222,7 @@ export default {
this.selectArr.push(item.VisitTaskId) this.selectArr.push(item.VisitTaskId)
} }
if (item.IsCurrentTask) { if (item.IsCurrentTask) {
this.markedSeriesIds = [] this.syncMarkedSeriesIds(annotations)
annotations.map(i => {
if (i.MeasureData && i.MeasureData.seriesId) {
this.markedSeriesIds.push(i.MeasureData.seriesId)
}
})
} }
} }
@@ -2052,7 +2048,7 @@ export default {
// this.$refs['ecrf'].setAnnotation({ annotation, toolName: annotation.metadata.toolName }) // this.$refs['ecrf'].setAnnotation({ annotation, toolName: annotation.metadata.toolName })
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].setAnnotation({ annotation, toolName: annotation.metadata.toolName }) this.$refs[`ecrf_${this.lastViewportTaskId}`][0].setAnnotation({ annotation, toolName: annotation.metadata.toolName })
this.markedSeriesIds.push(series.Id) this.syncMarkedSeriesIdsFromState(series.TaskInfo.VisitTaskId)
} }
this.setToolsPassive() this.setToolsPassive()
@@ -2087,16 +2083,14 @@ export default {
throw errorMsg throw errorMsg
} }
if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) { if (annotation.visitTaskId === this.taskInfo.VisitTaskId && annotation.seriesId) {
const index = this.markedSeriesIds.indexOf(annotation.seriesId) this.syncMarkedSeriesIdsFromState(annotation.visitTaskId)
if (index !== -1) {
this.markedSeriesIds.splice(index, 1)
}
} else { } else {
const errorMsg = { message: 'annotation Not allowed to operate' } const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg throw errorMsg
} }
} catch (e) { } catch (e) {
cornerstoneTools.annotation.state.addAnnotation(annotation) cornerstoneTools.annotation.state.addAnnotation(annotation)
this.syncMarkedSeriesIdsFromState(annotation.visitTaskId)
const renderingEngine = getRenderingEngine(renderingEngineId) const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) { for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}` const viewportId = `${this.viewportKey}-${i}`
@@ -2133,7 +2127,7 @@ export default {
annotation.sliceThickness = series.SliceThickness annotation.sliceThickness = series.SliceThickness
annotation.numberOfFrames = isNaN(parseInt(params.frame)) ? null : parseInt(params.frame) annotation.numberOfFrames = isNaN(parseInt(params.frame)) ? null : parseInt(params.frame)
annotation.markTool = annotation.metadata.toolName annotation.markTool = annotation.metadata.toolName
this.markedSeriesIds.push(series.Id) this.syncMarkedSeriesIdsFromState(series.TaskInfo.VisitTaskId)
const operateStateEnum = this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].operateStateEnum const operateStateEnum = this.$refs[`ecrf_${this.taskInfo.VisitTaskId}`][0].operateStateEnum
const markName = await this.customPrompt(!this.isNumber(operateStateEnum)) const markName = await this.customPrompt(!this.isNumber(operateStateEnum))
@@ -2275,10 +2269,7 @@ export default {
if (!res.IsSuccess) throw '' if (!res.IsSuccess) throw ''
} }
} }
const index = this.markedSeriesIds.indexOf(annotation.seriesId) this.syncMarkedSeriesIdsFromState(annotation.visitTaskId)
if (index !== -1) {
this.markedSeriesIds.splice(index, 1)
}
const renderingEngine = getRenderingEngine(renderingEngineId) const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) { for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}` const viewportId = `${this.viewportKey}-${i}`
@@ -2291,6 +2282,7 @@ export default {
} }
} catch (e) { } catch (e) {
cornerstoneTools.annotation.state.addAnnotation(annotation) cornerstoneTools.annotation.state.addAnnotation(annotation)
this.syncMarkedSeriesIdsFromState(annotation.visitTaskId)
const renderingEngine = getRenderingEngine(renderingEngineId) const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) { for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}` const viewportId = `${this.viewportKey}-${i}`
@@ -2367,6 +2359,22 @@ export default {
this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations) this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations)
this.$set(this.visitTaskList[taskIdx], 'AnnotationUIDs', annotationUIDs) this.$set(this.visitTaskList[taskIdx], 'AnnotationUIDs', annotationUIDs)
}, },
collectMarkedSeriesIds(list = []) {
return [...new Set(
list
.map(item => item?.MeasureData?.seriesId || item?.seriesId)
.filter(Boolean)
)]
},
syncMarkedSeriesIds(list = []) {
this.markedSeriesIds = this.collectMarkedSeriesIds(list)
},
syncMarkedSeriesIdsFromState(visitTaskId = this.taskInfo?.VisitTaskId) {
const annotations = cornerstoneTools.annotation.state.getAllAnnotations().filter(item => {
return item.visitTaskId === visitTaskId && item.seriesId
})
this.syncMarkedSeriesIds(annotations)
},
async resetAnnotations(visitTaskId) { async resetAnnotations(visitTaskId) {
if (this.readingTaskState === 2) return if (this.readingTaskState === 2) return
const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId) const taskIdx = this.visitTaskList.findIndex(i => i.VisitTaskId === visitTaskId)
@@ -2401,9 +2409,9 @@ export default {
if (i.MeasureData) { if (i.MeasureData) {
const annotation = i.MeasureData const annotation = i.MeasureData
cornerstoneTools.annotation.state.addAnnotation(annotation) cornerstoneTools.annotation.state.addAnnotation(annotation)
this.markedSeriesIds.push(annotation.seriesId)
} }
}) })
this.syncMarkedSeriesIds(annotations)
const renderingEngine = getRenderingEngine(renderingEngineId) const renderingEngine = getRenderingEngine(renderingEngineId)
for (let i = 0; i < this.cells.length; i++) { for (let i = 0; i < this.cells.length; i++) {
const viewportId = `${this.viewportKey}-${i}` const viewportId = `${this.viewportKey}-${i}`
@@ -3415,6 +3423,15 @@ export default {
this.clipPlaying = !this.clipPlaying this.clipPlaying = !this.clipPlaying
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].toggleClipPlay(isPlay, this.fps) this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].toggleClipPlay(isPlay, this.fps)
}, },
handleClipFpsChange() {
if (!this.clipPlaying) return
const activeViewportRef = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`]
const activeViewport = activeViewportRef && activeViewportRef[0]
if (!activeViewport || typeof activeViewport.toggleClipPlay !== 'function') return
activeViewport.toggleClipPlay(true, this.fps)
},
// //
async getWwcTpl() { async getWwcTpl() {
try { try {
@@ -4037,8 +4054,10 @@ export default {
} }
} }
} }
if (!this.isActiveViewportAtMeasureLocation(obj.annotation)) {
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series, true) this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series, true)
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex) this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
}
setTimeout(async () => { setTimeout(async () => {
const divForDownloadViewport = document.querySelector( const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]` `div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]`
@@ -4049,9 +4068,48 @@ export default {
}, 200) }, 200)
} }
}, },
getMeasureImageLocation(measureData = {}) {
const referencedImageId = measureData?.metadata?.referencedImageId
const referencedImageParams = referencedImageId ? this.getInstanceInfo(referencedImageId) : {}
return {
visitTaskId: measureData.visitTaskId,
studyId: measureData.studyId || measureData.StudyId,
seriesId: measureData.seriesId || measureData.SeriesId,
instanceId: measureData.instanceId || measureData.InstanceId || referencedImageParams.instanceId || null,
frame: measureData.numberOfFrames ?? measureData.NumberOfFrames ?? referencedImageParams.frame
}
},
getActiveViewportImageLocation() {
const viewportRef = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`]?.[0]
const currentSeries = viewportRef?.series
if (!currentSeries) return null
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine?.getViewport(`${this.viewportKey}-${this.activeViewportIndex}`)
const currentImageId = viewport?.getCurrentImageId?.()
const currentImageParams = currentImageId ? this.getInstanceInfo(currentImageId) : {}
return {
visitTaskId: currentSeries.TaskInfo?.VisitTaskId,
studyId: currentSeries.StudyId,
seriesId: currentSeries.Id,
instanceId: currentImageParams.instanceId || null,
frame: currentImageParams.frame
}
},
isActiveViewportAtMeasureLocation(measureData) {
const targetLocation = this.getMeasureImageLocation(measureData)
const currentLocation = this.getActiveViewportImageLocation()
if (!currentLocation || !targetLocation.seriesId) return false
if (targetLocation.visitTaskId && currentLocation.visitTaskId !== targetLocation.visitTaskId) return false
if (targetLocation.studyId && currentLocation.studyId !== targetLocation.studyId) return false
if (currentLocation.seriesId !== targetLocation.seriesId) return false
if (targetLocation.instanceId && currentLocation.instanceId !== targetLocation.instanceId) return false
return currentLocation.frame === targetLocation.frame
},
async getScreenshots(measureData, callback) { async getScreenshots(measureData, callback) {
if (measureData) { if (measureData) {
if (!this.isActiveViewportAtMeasureLocation(measureData.annotation)) {
await this.imageLocation(measureData) await this.imageLocation(measureData)
}
const divForDownloadViewport = document.querySelector( const divForDownloadViewport = document.querySelector(
`div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]` `div[data-viewport-uid="${this.viewportKey}-${this.activeViewportIndex}"]`
) )
@@ -4,7 +4,8 @@
<el-form-item :label="$t('segment:form:label:studyName')" prop="taskBlindName"> <el-form-item :label="$t('segment:form:label:studyName')" prop="taskBlindName">
<el-select v-model="form.studyId" clearable @change="(e) => handleChange(e, 'study')" <el-select v-model="form.studyId" clearable @change="(e) => handleChange(e, 'study')"
@clear="(e) => handleClear(e, 'study')"> @clear="(e) => handleClear(e, 'study')">
<el-option v-for="item in studyList" :key="item.StudyId" :label="item.StudyCode" <el-option v-for="item in studyList" :key="item.StudyId"
:label="taskInfo.IsReadingTaskViewInOrder !== 0 ? item.StudyCode : `${item.Modalities}(${item.SeriesCount})`"
:value="item.StudyId" /> :value="item.StudyId" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -87,12 +88,13 @@ export default {
}, trigger: ['blur', 'change'] }, trigger: ['blur', 'change']
} }
] ]
} },
taskInfo: {}
} }
}, },
// mounted() { mounted() {
// this.init() this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
// }, },
methods: { methods: {
setSeries(series) { setSeries(series) {
this.series = series this.series = series
@@ -150,6 +150,8 @@ export default {
originalMarkers: [], originalMarkers: [],
markers: { top: '', right: '', bottom: '', left: '' }, markers: { top: '', right: '', bottom: '', left: '' },
playClipState: false, playClipState: false,
clipFramesPerSecond: null,
toggleClipPlayTimer: null,
wwwcIdx: 2, wwwcIdx: 2,
loading: false, loading: false,
forceFitToWindow: false, forceFitToWindow: false,
@@ -169,6 +171,10 @@ export default {
this.resizeObserver.unobserve(this.element) this.resizeObserver.unobserve(this.element)
this.resizeObserver.disconnect() this.resizeObserver.disconnect()
} }
if (this.toggleClipPlayTimer) {
clearInterval(this.toggleClipPlayTimer)
this.toggleClipPlayTimer = null
}
}, },
methods: { methods: {
initViewport() { initViewport() {
@@ -210,12 +216,11 @@ export default {
this.imageInfo.imageOrientationPatient = imagePlaneModule.imageOrientationPatient this.imageInfo.imageOrientationPatient = imagePlaneModule.imageOrientationPatient
this.imageInfo.imagePositionPatient = imagePlaneModule.imagePositionPatient this.imageInfo.imagePositionPatient = imagePlaneModule.imagePositionPatient
this.imageInfo.location = imagePlaneModule.sliceLocation this.imageInfo.location = imagePlaneModule.sliceLocation
// this.imageInfo.wwwc = `${Math.round(detail.image.windowWidth)}/${Math.round(detail.image.windowCenter)}`
this.getOrientationMarker() this.getOrientationMarker()
this.$emit('renderAnnotations', this.series) const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(this.viewportId)
const toolGroupId = this.viewportId if (toolGroup) {
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
toolGroup.setToolEnabled('ScaleOverlay') toolGroup.setToolEnabled('ScaleOverlay')
}
}, },
imageRendered(e) { imageRendered(e) {
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
@@ -312,14 +317,61 @@ export default {
} }
}, },
toggleClipPlay(isPlay, framesPerSecond) { toggleClipPlay(isPlay, framesPerSecond) {
this.playClipState = isPlay
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
if (!renderingEngine) return
const viewport = renderingEngine.getViewport(this.viewportId) const viewport = renderingEngine.getViewport(this.viewportId)
if (!viewport?.element) return
const parsedFramesPerSecond = Number(framesPerSecond)
const nextFramesPerSecond = Number.isFinite(parsedFramesPerSecond)
? parsedFramesPerSecond
: (this.clipFramesPerSecond || 15)
const isSameSpeedWhilePlaying = this.playClipState &&
isPlay &&
this.clipFramesPerSecond === nextFramesPerSecond
if (isPlay) { if (isPlay) {
cornerstoneTools.utilities.cine.playClip(viewport.element, { framesPerSecond, loop: true }) if (isSameSpeedWhilePlaying) return
cornerstoneTools.utilities.cine.stopClip(viewport.element)
if (this.toggleClipPlayTimer) {
clearInterval(this.toggleClipPlayTimer)
this.toggleClipPlayTimer = null
}
const frameInterval = Math.max(16, Math.round(1000 / nextFramesPerSecond))
this.toggleClipPlayTimer = setInterval(() => {
const imageIds = viewport.getImageIds()
if (!imageIds?.length) return
let index = viewport.getCurrentImageIdIndex() + 1
if (index > imageIds.length - 1) {
index = 0
}
csUtils.jumpToSlice(viewport.element, {
imageIndex: index,
debounceLoading: false
})
}, frameInterval)
this.clipFramesPerSecond = nextFramesPerSecond
this.playClipState = true
} else { } else {
cornerstoneTools.utilities.cine.stopClip(viewport.element) cornerstoneTools.utilities.cine.stopClip(viewport.element)
if (this.toggleClipPlayTimer) {
clearInterval(this.toggleClipPlayTimer)
this.toggleClipPlayTimer = null
}
this.clipFramesPerSecond = null
this.playClipState = false
}
},
syncViewportStaticState() {
this.getOrientationMarker()
this.$emit('renderAnnotations', this.series)
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(this.viewportId)
if (toolGroup) {
toolGroup.setToolEnabled('ScaleOverlay')
} }
}, },
scrollPage(type) { scrollPage(type) {
@@ -339,7 +391,13 @@ export default {
} }
// viewport.setImageIdIndex(newImageIdIndex) // viewport.setImageIdIndex(newImageIdIndex)
csUtils.jumpToSlice(viewport.element, { imageIndex: newImageIdIndex }) csUtils.jumpToSlice(viewport.element, { imageIndex: newImageIdIndex })
if (this.toggleClipPlayTimer) {
clearInterval(this.toggleClipPlayTimer)
this.toggleClipPlayTimer = null
}
cornerstoneTools.utilities.cine.stopClip(viewport.element) cornerstoneTools.utilities.cine.stopClip(viewport.element)
this.clipFramesPerSecond = null
this.playClipState = false
}, },
setZoom(ratio) { setZoom(ratio) {
const renderingEngine = getRenderingEngine(this.renderingEngineId) const renderingEngine = getRenderingEngine(this.renderingEngineId)
@@ -432,6 +490,7 @@ export default {
} }
this.prefetchMetadataInformation(obj.ImageIds, obj.Modality) this.prefetchMetadataInformation(obj.ImageIds, obj.Modality)
await viewport.setStack(this.series.Stack, obj.SliceIndex) await viewport.setStack(this.series.Stack, obj.SliceIndex)
this.syncViewportStaticState()
viewport.render() viewport.render()
} catch (e) { } catch (e) {
@@ -24,7 +24,7 @@
<template v-if="rerender"> <template v-if="rerender">
<QuestionFormItem v-for="question of questions" :key="question.Id" :visit-task-id="visitTaskId" <QuestionFormItem v-for="question of questions" :key="question.Id" :visit-task-id="visitTaskId"
:isNoneDicom="isNoneDicom" :question="question" :question-form="questionForm" :isNoneDicom="isNoneDicom" :question="question" :question-form="questionForm"
:reading-task-state="readingTaskState" :criterion-id="criterionId" :calculation-list="calculationList" :reading-task-state="1" :criterion-id="criterionId" :calculation-list="calculationList"
:question-mark-info-list="questionMarkInfoList" :questions-mark-status="questionsMarkStatus" :question-mark-info-list="questionMarkInfoList" :questions-mark-status="questionsMarkStatus"
:questionsSegmentMarkStatus="questionsSegmentMarkStatus" :is-baseline="isBaseLineTask" :questionsSegmentMarkStatus="questionsSegmentMarkStatus" :is-baseline="isBaseLineTask"
@resetFormItemData="resetFormItemData" @setFormItemData="setFormItemData" @getQuestions="getQuestions" @resetFormItemData="resetFormItemData" @setFormItemData="setFormItemData" @getQuestions="getQuestions"
@@ -32,7 +32,7 @@
@handleReadingChart="handleReadingChart" @saveSegmentBindingAndAnswer="saveSegmentBindingAndAnswer" /> @handleReadingChart="handleReadingChart" @saveSegmentBindingAndAnswer="saveSegmentBindingAndAnswer" />
</template> </template>
<el-form-item v-if="readingTaskState < 2"> <el-form-item>
<div class="action-bar"> <div class="action-bar">
<!-- <i class="el-icon-warning feedback-icon" @click="openFeedBackTable" <!-- <i class="el-icon-warning feedback-icon" @click="openFeedBackTable"
:style="{ color: taskInfo && taskInfo.IsExistUnprocessedFeedback ? '#ffeb3b' : '#fff' }" /> --> :style="{ color: taskInfo && taskInfo.IsExistUnprocessedFeedback ? '#ffeb3b' : '#fff' }" /> -->
@@ -251,7 +251,7 @@ export default {
return i return i
}) })
this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations) this.$set(this.visitTaskList[taskIdx], 'Annotations', annotations)
this.$refs[visitTaskId][0].initCurrentMaredFiles() this.$refs[visitTaskId][0].initCurrentMaredFiles(annotations)
this.$refs.fileViewer.resetAnnotations({ annotations, visitTaskId }) this.$refs.fileViewer.resetAnnotations({ annotations, visitTaskId })
}, },
setReadingTaskState(state) { setReadingTaskState(state) {
@@ -97,6 +97,7 @@ export default {
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo')) this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
this.BodyPart.Bodypart = await this.$getBodyPart(this.$route.query.trialId) this.BodyPart.Bodypart = await this.$getBodyPart(this.$route.query.trialId)
this.studyList = this.decorateStudyList(this.visitTaskInfo.StudyList || []) this.studyList = this.decorateStudyList(this.visitTaskInfo.StudyList || [])
this.initCurrentMaredFiles(this.visitTaskInfo.Annotations)
if (this.studyList.length === 0) return if (this.studyList.length === 0) return
this.$nextTick(() => { this.$nextTick(() => {
this.activeStudy(this.studyList[0].Id) this.activeStudy(this.studyList[0].Id)
@@ -110,8 +111,26 @@ export default {
}) })
} }
}, },
'visitTaskInfo.Annotations': {
handler(annotations) {
this.initCurrentMaredFiles(annotations)
},
deep: true
}
}, },
methods: { methods: {
buildCurrentMarkedFiles(annotations = []) {
return annotations.reduce((result, item) => {
const fileId = item?.NoneDicomFileId
if (!fileId) return result
if (!Object.hasOwn(result, fileId)) {
result[fileId] = { count: 1 }
} else {
result[fileId].count++
}
return result
}, {})
},
decorateStudyList(studyList = []) { decorateStudyList(studyList = []) {
return studyList.map(study => ({ return studyList.map(study => ({
...study, ...study,
@@ -204,6 +223,9 @@ export default {
if (type === 'remove') { if (type === 'remove') {
if (Object.hasOwn(this.currentMarkedFiles, fileId)) { if (Object.hasOwn(this.currentMarkedFiles, fileId)) {
this.currentMarkedFiles[fileId].count-- this.currentMarkedFiles[fileId].count--
if (this.currentMarkedFiles[fileId].count <= 0) {
this.$delete(this.currentMarkedFiles, fileId)
}
} }
} else if (type === 'add') { } else if (type === 'add') {
if (!Object.hasOwn(this.currentMarkedFiles, fileId)) { if (!Object.hasOwn(this.currentMarkedFiles, fileId)) {
@@ -214,15 +236,8 @@ export default {
} }
}, },
initCurrentMaredFiles() { initCurrentMaredFiles(annotations = this.visitTaskInfo.Annotations || []) {
this.currentMarkedFiles = {} this.currentMarkedFiles = this.buildCurrentMarkedFiles(annotations)
this.visitTaskInfo.Annotations.map(i => {
if (!Object.hasOwn(this.currentMarkedFiles, i.NoneDicomFileId)) {
this.$set(this.currentMarkedFiles, i.NoneDicomFileId, { count: 1 })
} else {
this.currentMarkedFiles[i.NoneDicomFileId].count++
}
})
}, },
} }
} }
@@ -0,0 +1,142 @@
<template>
<div class="json-editor" v-loading="loading">
<div class="header">
<!-- <span class="title">JSON 编辑</span> -->
<el-button type="primary" size="mini" icon="el-icon-check" @click="save">
{{ $t('common:button:save') }}
</el-button>
</div>
<div class="json">
<json-node
:node-key="'root'"
:node-value="jsonData"
:is-root="true"
@update="handleUpdate"
/>
</div>
</div>
</template>
<script>
import { getInspectionById, setJsonDetail } from '@/api/trials'
import JsonNode from './JsonNode.vue'
export default {
name: 'JsonEditor',
components: { JsonNode },
props: {
id: {
type: String,
required: true
}
},
data() {
return {
loading: false,
jsonData: {}
}
},
mounted() {
this.getJsonData()
},
methods: {
handleUpdate({ path, value }) {
const target = this.getTarget(path.slice(0, -1))
this.$set(target, path[path.length - 1], value)
},
getTarget(path) {
let t = this.jsonData
path.forEach(k => { t = t[k] })
return t
},
parseValue(val) {
if (typeof val === 'string') {
try {
const parsed = JSON.parse(val)
if (parsed && typeof parsed === 'object') {
return JSON.parse(JSON.stringify(parsed))
}
} catch (e) {
console.warn(e.message)
}
return {}
}
return val ? JSON.parse(JSON.stringify(val)) : {}
},
async getJsonData() {
try {
this.loading = true
const res = await getInspectionById({ id: this.id })
if (res.IsSuccess) {
this.jsonData = this.parseValue(res.Result.JsonDetail)
}
} catch (err) {
console.log(err)
} finally {
this.loading = false
}
},
isValidJsonObject(obj) {
if (obj === null || typeof obj !== 'object') return false
try {
JSON.stringify(obj)
return true
} catch (e) {
console.warn(e.message)
return false
}
},
safeClone(obj) {
if (this.isValidJsonObject(obj)) {
return JSON.parse(JSON.stringify(obj))
}
return {}
},
async save() {
try {
this.loading = true
const result = this.safeClone(this.jsonData)
const res = await setJsonDetail({ id: this.id, jsonDetail: JSON.stringify(result) })
if (res.IsSuccess) {
this.$emit('getList')
this.$emit('close')
this.$message.success(this.$t('common:message:savedSuccessfully'))
}
} catch (err) {
console.log(err)
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.json-editor {
font-family: Consolas, Monaco, monospace;
font-size: 13px;
line-height: 2;
padding: 12px;
background: #fafafa;
border-radius: 4px;
}
.header {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.title {
font-weight: 600;
font-size: 14px;
color: #303133;
}
.json {
max-height: 50vh;
overflow: auto;
}
</style>
@@ -0,0 +1,136 @@
<template>
<div class="node" :style="{ paddingLeft: depth * 20 + 'px' }">
<span v-if="!isRoot" class="key">
{{ isArray ? `[${nodeKey}]` : `"${nodeKey}"` }}:
</span>
<!-- 对象 -->
<template v-if="isObject">
<span class="bracket">{</span>
<div class="children">
<json-node
v-for="(v, k) in nodeValue"
:key="k"
:node-key="k"
:node-value="v"
:depth="depth + 1"
:parent-path="currentPath"
@update="$emit('update', $event)"
/>
</div>
<span class="bracket">}</span>
</template>
<!-- 数组 -->
<template v-else-if="isArray">
<span class="bracket">[</span>
<div class="children">
<json-node
v-for="(item, i) in nodeValue"
:key="i"
:node-key="i"
:node-value="item"
:depth="depth + 1"
:parent-path="currentPath"
@update="$emit('update', $event)"
/>
</div>
<span class="bracket">]</span>
</template>
<!-- 文本 -->
<el-input
v-else-if="valueType === 'string'"
v-model="editVal"
size="mini"
style="width: 300px"
@blur="emitUpdate"
@keyup.enter.native="emitUpdate"
/>
<!-- 数字 -->
<el-input-number
v-else-if="valueType === 'number'"
v-model="editVal"
size="mini"
style="width: 140px"
@blur="emitUpdate"
/>
<!-- 布尔值 -->
<el-switch
v-else-if="valueType === 'boolean'"
v-model="editVal"
size="small"
@change="emitUpdate"
/>
<!-- null -->
<el-tag v-else size="mini" type="info">null</el-tag>
</div>
</template>
<script>
export default {
name: 'JsonNode',
props: {
nodeKey: [String, Number],
nodeValue: [Object, Array, String, Number, Boolean],
depth: {
type: Number,
default: 0
},
isRoot: {
type: Boolean,
default: false
},
parentPath: {
type: Array,
default: () => []
}
},
data() {
return { editVal: this.nodeValue }
},
computed: {
isObject() {
return this.nodeValue && typeof this.nodeValue === 'object' && !Array.isArray(this.nodeValue)
},
isArray() {
return Array.isArray(this.nodeValue)
},
valueType() {
if (this.nodeValue === null) return 'null'
return typeof this.nodeValue
},
currentPath() {
return this.isRoot ? [] : [...this.parentPath, this.nodeKey]
}
},
watch: {
nodeValue(v) { this.editVal = v }
},
methods: {
emitUpdate() {
this.$emit('update', { path: this.currentPath, value: this.editVal })
}
}
}
</script>
<style scoped>
.node {
padding-bottom: 5px;
}
.key {
color: #c7254e;
margin-right: 4px;
}
.bracket {
color: #e6a23c;
font-weight: bold;
}
.children {
padding-left: 4px;
}
</style>
@@ -194,6 +194,9 @@
<el-button type="text" @click="lookDetails(scope.row)"> <el-button type="text" @click="lookDetails(scope.row)">
{{ $t('trials:auditRecord:action:detail') }} {{ $t('trials:auditRecord:action:detail') }}
</el-button> </el-button>
<el-button type="text" @click="edit(scope.row)">
{{ $t('common:button:edit') }}
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -622,6 +625,20 @@
<!-- :src="openImageUrl"--> <!-- :src="openImageUrl"-->
<!-- style="width: 50px;height: 50px; cursor:pointer">--> <!-- style="width: 50px;height: 50px; cursor:pointer">-->
<!-- </div>--> <!-- </div>-->
<!-- <json-editor v-model="data" /> -->
<el-dialog
v-if="editDialogVisible"
:visible.sync="editDialogVisible"
:close-on-click-modal="false"
:title="$t('common:button:edit') + `${$i18n.locale === 'zh' ? '' + editRow.DescriptionCN + '' : '( ' + editRow.Description + ' )'}`"
width="720px"
custom-class="base-dialog-wrapper">
<json-editor
:id="editRow.Id"
@close="editDialogVisible = false"
@getList="getList"
/>
</el-dialog>
</template> </template>
</BaseContainer> </BaseContainer>
</template> </template>
@@ -630,11 +647,12 @@
import { import {
getTrialSiteSelect, getTrialSiteSelect,
getTrialVisitStageSelect, getTrialVisitStageSelect,
getTrialCriterionList getTrialCriterionList,
} from '@/api/trials' } from '@/api/trials'
import { getInspectionList, getInspectionJsonDataList } from '@/api/trials/inspection' import { getInspectionList, getInspectionJsonDataList } from '@/api/trials/inspection'
import { getFrontAuditConfigList, getAuditConfigChildList, getModuleTypeDescriptionList, setInspectionEnumValue, getModuleTypeList } from '@/api/dictionary/checkConfig' import { getFrontAuditConfigList, getAuditConfigChildList, getModuleTypeDescriptionList, setInspectionEnumValue, getModuleTypeList } from '@/api/dictionary/checkConfig'
import Pagination from '@/components/Pagination' import Pagination from '@/components/Pagination'
import JsonEditor from './components/JsonEditor'
import BaseContainer from '@/components/BaseContainer' import BaseContainer from '@/components/BaseContainer'
import BaseModel from '@/components/BaseModel' import BaseModel from '@/components/BaseModel'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
@@ -670,7 +688,7 @@ const searchDataDefault = () => {
} }
} }
export default { export default {
components: { BaseContainer, Pagination, BaseModel }, components: { BaseContainer, Pagination, JsonEditor, BaseModel },
dicts: ['OptType', 'ModuleType', 'ChildrenType'], dicts: ['OptType', 'ModuleType', 'ChildrenType'],
data() { data() {
@@ -708,7 +726,10 @@ export default {
imagesList: [], imagesList: [],
moduleTypeList: [], moduleTypeList: [],
list2_total: 0, list2_total: 0,
list2_searchData: {} list2_searchData: {},
editDialogVisible: false,
jsonData: null,
editRow: null
} }
}, },
mounted() { mounted() {
@@ -1346,7 +1367,12 @@ export default {
}).catch(() => { }).catch(() => {
this.loading = false this.loading = false
}) })
} },
edit(row) {
this.editRow = Object.assign({}, row)
this.editDialogVisible = true
},
} }
} }
</script> </script>
@@ -105,6 +105,7 @@
<el-button icon="el-icon-delete" :title="$t('trials:uploadedDicoms:action:delete')" circle <el-button icon="el-icon-delete" :title="$t('trials:uploadedDicoms:action:delete')" circle
:disabled="!isAfresh && data.SubmitState === 2 && data.SubmitTime && moment(data.SubmitTime).isAfter(moment(scope.row.UploadedTime))" :disabled="!isAfresh && data.SubmitState === 2 && data.SubmitTime && moment(data.SubmitTime).isAfter(moment(scope.row.UploadedTime))"
@click="handleDeleteStudy(scope.row)" /> @click="handleDeleteStudy(scope.row)" />
<el-button icon="el-icon-upload2" title="上传" circle @click="upload(scope.row)" />
<!-- <el-button--> <!-- <el-button-->
<!-- icon="el-icon-toilet-paper"--> <!-- icon="el-icon-toilet-paper"-->
<!-- circle--> <!-- circle-->
@@ -423,7 +424,8 @@
<DicomPreview :uid="uid" :studyList="uploadQueues" /> <DicomPreview :uid="uid" :studyList="uploadQueues" />
</el-dialog> </el-dialog>
<!--pet-ct临床数据上传--> <!--pet-ct临床数据上传-->
<el-dialog v-if="petVisible" :show-close="true" :close-on-click-modal="false" :visible.sync="petVisible" append-to-body> <el-dialog v-if="petVisible" :show-close="true" :close-on-click-modal="false" :visible.sync="petVisible"
append-to-body>
<uploadPetClinicalData :subject-visit-id="data.Id" :data="data" :studyData="studyData" :allow-add-or-edit="true" <uploadPetClinicalData :subject-visit-id="data.Id" :data="data" :studyData="studyData" :allow-add-or-edit="true"
@getStudyInfo="getStudyInfo" /> @getStudyInfo="getStudyInfo" />
</el-dialog> </el-dialog>
@@ -650,6 +652,7 @@ export default {
BodyPart: {}, BodyPart: {},
isClose: false, isClose: false,
StudyInstanceUID: null
} }
}, },
computed: { computed: {
@@ -682,6 +685,10 @@ export default {
this.OSSclient.close() this.OSSclient.close()
}, },
methods: { methods: {
upload(row) {
this.StudyInstanceUID = row.StudyInstanceUid
this.$refs.pathClear.click()
},
handleDragover(e) { handleDragover(e) {
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
@@ -1144,7 +1151,7 @@ export default {
var instanceItem = instanceList.find(function (item) { var instanceItem = instanceList.find(function (item) {
return item.instanceUid === instanceUid return item.instanceUid === instanceUid
}) })
if (!instanceItem) { if (!false) {
var date = data.string('x00080023') var date = data.string('x00080023')
var time = data.string('x00080033') var time = data.string('x00080033')
var instanceTime = '' var instanceTime = ''
@@ -1274,11 +1281,12 @@ export default {
verifyStudy() { verifyStudy() {
this.btnLoading = true this.btnLoading = true
var studyList = [] var studyList = []
let scope = this
this.selectArr.forEach((item) => { this.selectArr.forEach((item) => {
item.dicomInfo.uploadFileSize = 0 item.dicomInfo.uploadFileSize = 0
if (!item.uploadState.selected) { if (!item.uploadState.selected) {
studyList.push({ studyList.push({
studyInstanceUid: item.dicomInfo.studyUid, studyInstanceUid: scope.StudyInstanceUID ? scope.StudyInstanceUID : item.dicomInfo.studyUid,
studyDate: item.dicomInfo.studyTime, studyDate: item.dicomInfo.studyTime,
}) })
} }
@@ -1293,6 +1301,8 @@ export default {
batchVerifyStudyAllowUpload(param) batchVerifyStudyAllowUpload(param)
.then(async (res) => { .then(async (res) => {
var messageArr = [] var messageArr = []
res.Result = []
console.log(res)
res.Result.forEach((item) => { res.Result.forEach((item) => {
const i = this.uploadQueues.findIndex( const i = this.uploadQueues.findIndex(
(value) => value.dicomInfo.studyUid === item.StudyInstanceUid (value) => value.dicomInfo.studyUid === item.StudyInstanceUid
@@ -1475,8 +1485,21 @@ export default {
}) })
}, },
// //
archiveStudy(index, config) { archiveStudy(index, config = {}) {
var scope = this var scope = this
try {
if (!config.AnonymizeFixedList) config.AnonymizeFixedList = []
config.AnonymizeFixedList.push(
{
Element: '000D',
Group: '0020',
ReplaceValue: scope.StudyInstanceUID,
Id: 'StudyInstanceUID'
}
)
} catch (err) {
console.log(err)
}
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
try { try {
preArchiveDicomStudy({ preArchiveDicomStudy({
@@ -1506,7 +1529,7 @@ export default {
let t = setInterval(() => { let t = setInterval(() => {
dicomUploadInProgress({ dicomUploadInProgress({
trialId: scope.trialId, trialId: scope.trialId,
studyInstanceUid: dicomInfo.studyUid, studyInstanceUid: scope.StudyInstanceUID ? scope.StudyInstanceUID : dicomInfo.studyUid,
}).then((res) => { }) }).then((res) => { })
}, 5000) }, 5000)
scope.myInterval.push(t) scope.myInterval.push(t)
@@ -1531,7 +1554,7 @@ export default {
dicomInfo.RadiopharmaceuticalStartTime, dicomInfo.RadiopharmaceuticalStartTime,
studyId: dicomInfo.studyId, studyId: dicomInfo.studyId,
studyInstanceUid: dicomInfo.studyUid, studyInstanceUid: scope.StudyInstanceUID ? scope.StudyInstanceUID : dicomInfo.studyUid,
studyTime: dicomInfo.studyTime, studyTime: dicomInfo.studyTime,
description: dicomInfo.description, description: dicomInfo.description,
seriesCount: dicomInfo.seriesNum, seriesCount: dicomInfo.seriesNum,
@@ -1572,58 +1595,60 @@ export default {
try { try {
let o = v.instanceList[ii] let o = v.instanceList[ii]
let name = `${v.instanceList[ii].file.webkitRelativePath}_${v.instanceList[ii].instanceUid}` let name = `${v.instanceList[ii].file.webkitRelativePath}_${v.instanceList[ii].instanceUid}`
if (o.isReUpload) { // if (o.isReUpload) {
dicomInfo.failedFileCount++ // dicomInfo.failedFileCount++
dicomInfo.uploadFileSize += o.file.size // dicomInfo.uploadFileSize += o.file.size
Record.Existed.push(name) // Record.Existed.push(name)
Record.FileCount++ // Record.FileCount++
} else if (o.myPath) { // }
instanceList.push({ // else if (o.myPath) {
studyInstanceUid: dicomInfo.studyUid, // instanceList.push({
seriesInstanceUid: v.seriesUid, // studyInstanceUid: dicomInfo.studyUid,
SOPClassUID: o.SOPClassUID, // seriesInstanceUid: v.seriesUid,
TransferSytaxUID: o.TransferSytaxUID, // SOPClassUID: o.SOPClassUID,
MediaStorageSOPInstanceUID: // TransferSytaxUID: o.TransferSytaxUID,
o.MediaStorageSOPInstanceUID, // MediaStorageSOPInstanceUID:
MediaStorageSOPClassUID: // o.MediaStorageSOPInstanceUID,
o.MediaStorageSOPClassUID, // MediaStorageSOPClassUID:
sopInstanceUid: o.instanceUid, // o.MediaStorageSOPClassUID,
instanceNumber: o.instanceNumber, // sopInstanceUid: o.instanceUid,
instanceTime: o.instanceTime, // instanceNumber: o.instanceNumber,
imageRows: o.imageRows, // instanceTime: o.instanceTime,
imageColumns: o.imageColumns, // imageRows: o.imageRows,
sliceLocation: o.sliceLocation, // imageColumns: o.imageColumns,
sliceThickness: o.sliceThickness, // sliceLocation: o.sliceLocation,
numberOfFrames: o.numberOfFrames, // sliceThickness: o.sliceThickness,
pixelSpacing: o.pixelSpacing, // numberOfFrames: o.numberOfFrames,
imagerPixelSpacing: o.imagerPixelSpacing, // pixelSpacing: o.pixelSpacing,
frameOfReferenceUID: o.frameOfReferenceUID, // imagerPixelSpacing: o.imagerPixelSpacing,
windowCenter: o.windowCenter, // frameOfReferenceUID: o.frameOfReferenceUID,
windowWidth: o.windowWidth, // windowCenter: o.windowCenter,
path: o.myPath, // windowWidth: o.windowWidth,
FileSize: o.FileSize, // path: o.myPath,
// FileSize: o.FileSize,
PhotometricInterpretation: // PhotometricInterpretation:
o.PhotometricInterpretation, // o.PhotometricInterpretation,
BitsAllocated: o.BitsAllocated, // BitsAllocated: o.BitsAllocated,
PixelRepresentation: o.PixelRepresentation, // PixelRepresentation: o.PixelRepresentation,
RescaleIntercept: o.RescaleIntercept, // RescaleIntercept: o.RescaleIntercept,
RescaleSlope: o.RescaleSlope, // RescaleSlope: o.RescaleSlope,
ImagePositionPatient: o.ImagePositionPatient, // ImagePositionPatient: o.ImagePositionPatient,
ImageOrientationPatient: // ImageOrientationPatient:
o.ImageOrientationPatient, // o.ImageOrientationPatient,
SequenceOfUltrasoundRegions: // SequenceOfUltrasoundRegions:
o.SequenceOfUltrasoundRegions, // o.SequenceOfUltrasoundRegions,
FrameTime: o.FrameTime, // FrameTime: o.FrameTime,
CorrectedImage: o.CorrectedImage, // CorrectedImage: o.CorrectedImage,
Units: o.Units, // Units: o.Units,
DecayCorrection: o.DecayCorrection, // DecayCorrection: o.DecayCorrection,
EncapsulatedDocument: o.EncapsulatedDocument, // EncapsulatedDocument: o.EncapsulatedDocument,
}) // })
Record.Uploaded.push(name) // Record.Uploaded.push(name)
dicomInfo.failedFileCount++ // dicomInfo.failedFileCount++
Record.FileCount++ // Record.FileCount++
} else { // }
// else {
let path = `/${params.trialId}/Image/${params.subjectId let path = `/${params.trialId}/Image/${params.subjectId
}/${params.subjectVisitId}/${dicomInfo.studyUid }/${params.subjectVisitId}/${dicomInfo.studyUid
}/${scope.getGuid( }/${scope.getGuid(
@@ -1712,7 +1737,7 @@ export default {
} }
if (res && res.url) { if (res && res.url) {
instanceList.push({ instanceList.push({
studyInstanceUid: dicomInfo.studyUid, studyInstanceUid: scope.StudyInstanceUID ? scope.StudyInstanceUID : dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid, seriesInstanceUid: v.seriesUid,
SOPClassUID: o.SOPClassUID, SOPClassUID: o.SOPClassUID,
TransferSytaxUID: o.TransferSytaxUID, TransferSytaxUID: o.TransferSytaxUID,
@@ -1761,7 +1786,7 @@ export default {
Record.Failed.push(name) Record.Failed.push(name)
Record.FileCount++ Record.FileCount++
} }
} // }
resolve1() resolve1()
} catch (e) { } catch (e) {
console.log(e) console.log(e)
@@ -1779,7 +1804,7 @@ export default {
} }
} }
params.study.seriesList.push({ params.study.seriesList.push({
studyInstanceUid: dicomInfo.studyUid, studyInstanceUid: scope.StudyInstanceUID ? scope.StudyInstanceUID : dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid, seriesInstanceUid: v.seriesUid,
seriesNumber: v.seriesNumber, seriesNumber: v.seriesNumber,
seriesTime: v.seriesTime, seriesTime: v.seriesTime,
@@ -300,19 +300,7 @@
<!-- 审核完成时间 --> <!-- 审核完成时间 -->
<el-table-column prop="AuditTime" :label="$t('trials:crcUpload:table:auditTime')" show-overflow-tooltip <el-table-column prop="AuditTime" :label="$t('trials:crcUpload:table:auditTime')" show-overflow-tooltip
width="170" /> width="170" />
<el-table-column v-if=" <el-table-column :label="$t('common:action:action')" width="250" fixed="right">
hasPermi(
[
'trials:trials-panel:visit:crc-upload:upload',
'trials:trials-panel:visit:crc-upload:submit',
'trials:trials-panel:visit:crc-upload:edit',
'trials:trials-panel:visit:crc-upload:delete',
'trials:trials-panel:visit:crc-upload:back',
'trials:trials-panel:visit:crc-upload:authBack'
],
'||'
)
" :label="$t('common:action:action')" width="250" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<!-- 上传 --> <!-- 上传 -->
<el-button v-hasPermi="['trials:trials-panel:visit:crc-upload:upload']" icon="el-icon-upload2" :disabled="!isOCTorIVUS && (scope.row.SubmitState * 1 === 2 || <el-button v-hasPermi="['trials:trials-panel:visit:crc-upload:upload']" icon="el-icon-upload2" :disabled="!isOCTorIVUS && (scope.row.SubmitState * 1 === 2 ||
@@ -320,10 +308,8 @@
scope.row.IsLostVisit || (scope.row.IsSubjectQuit && scope.row.SubmitState * 1 !== 2)) scope.row.IsLostVisit || (scope.row.IsSubjectQuit && scope.row.SubmitState * 1 !== 2))
" circle :title="$t('trials:crcUpload:action:upload')" @click="CRChandleUpload(scope.row)" /> " circle :title="$t('trials:crcUpload:action:upload')" @click="CRChandleUpload(scope.row)" />
<!-- 上传2 --> <!-- 上传2 -->
<el-button icon="el-icon-upload2" v-hasPermi="['trials:trials-panel:visit:crc-upload:upload2']" :disabled="!isOCTorIVUS && (scope.row.SubmitState * 1 === 2 || <el-button icon="el-icon-upload2"
scope.row.VisitExecuted === 2 || circle :title="$t('trials:crcUpload:action:upload')" @click="CRChandleUpload2(scope.row)" />
scope.row.IsLostVisit || (scope.row.IsSubjectQuit && scope.row.SubmitState * 1 !== 2))
" circle :title="$t('trials:crcUpload:action:upload')" @click="CRChandleUpload2(scope.row)" />
<!-- 提交 --> <!-- 提交 -->
<el-button v-hasPermi="['trials:trials-panel:visit:crc-upload:submit']" icon="el-icon-check" :disabled="scope.row.AuditState * 1 > 0 || <el-button v-hasPermi="['trials:trials-panel:visit:crc-upload:submit']" icon="el-icon-check" :disabled="scope.row.AuditState * 1 > 0 ||
scope.row.SubmitState * 1 !== 1 || scope.row.SubmitState * 1 !== 1 ||
+2 -2
View File
@@ -11,7 +11,7 @@ const webpack = require('webpack')
const defaultSettings = require('./src/settings.js') const defaultSettings = require('./src/settings.js')
const moment = require('moment') const moment = require('moment')
var distDate = moment(new Date()).format('YYYY-MM-DD') var distDate = moment(new Date()).format('YYYY-MM-DD')
const name = process.env.NODE_ENV === 'usa' ? 'Imaging Trial Management System' : defaultSettings.title || 'HASC lmaging Trial Management System' // page title const name = process.env.NODE_ENV === 'usa' ? 'Imaging Trial Management System' : defaultSettings.title || 'EIC lmaging Trial Management System' // page title
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
module.exports = defineConfig({ module.exports = defineConfig({
@@ -52,7 +52,7 @@ module.exports = defineConfig({
}, },
'/api': { '/api': {
target: 'http://106.14.89.110:30000', target: 'http://192.168.3.99:6100',
// target: 'http://101.132.253.119:7010', // uat // target: 'http://101.132.253.119:7010', // uat
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,