Compare commits

..

17 Commits

Author SHA1 Message Date
wangxiaoshuang cbd860a35e 先下载再上传的逻辑指定IVUS和OCT标准
continuous-integration/drone/push Build is passing Details
2026-07-31 15:01:02 +08:00
wangxiaoshuang 7e9eb1f3a5 待阅列表屏蔽上传下载按钮
continuous-integration/drone/push Build is passing Details
2026-07-31 11:00:34 +08:00
wangxiaoshuang 254e43ac3d 分割球体工具bug修复
continuous-integration/drone/push Build is passing Details
2026-07-30 10:47:35 +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 Details
2026-07-29 15:53:17 +08:00
caiyiling 78f4a78994 测量数据预览按钮控制更改
continuous-integration/drone/push Build is passing Details
2026-07-29 09:53:43 +08:00
caiyiling 4e1911223d 1
continuous-integration/drone/push Build is passing Details
2026-07-28 09:17:40 +08:00
caiyiling 7dd02e84aa 样式调整
continuous-integration/drone/push Build is passing Details
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 Details
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 Details
2026-07-27 14:28:55 +08:00
caiyiling 6ecc0e58e9 重置标记更改;标记定位更改
continuous-integration/drone/push Build is passing Details
2026-07-16 15:40:28 +08:00
wangxiaoshuang 84a81e1851 临床数据输入框禁用样式修改
continuous-integration/drone/push Build is passing Details
2026-07-16 14:12:04 +08:00
wangxiaoshuang b09209af31 完全随机分割问题绑定弹框不显示检查编号显示检查模态
continuous-integration/drone/push Build is passing Details
2026-07-16 13:30:48 +08:00
caiyiling 9892070ccc 播放功能修改
continuous-integration/drone/push Build is passing Details
2026-07-16 11:43:10 +08:00
wangxiaoshuang 63cbc8746f 完全随机融合弹框不显示检查号显示检查模态
continuous-integration/drone/push Build is passing Details
2026-07-16 10:54:55 +08:00
26 changed files with 509 additions and 92 deletions

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

View File

@ -480,3 +480,11 @@ export function getNoneDicomMarkList(data) {
data data
}) })
} }
export function getFilterTableQuestion(data) {
return request({
url: `/ReadingImageTask/getFilterTableQuestion`,
method: 'post',
data
})
}

View File

@ -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)

View File

@ -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"
@ -70,7 +71,7 @@
</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 +380,10 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
isReading: {
type: Boolean,
default: false,
}
}, },
components: { components: {
'study-view': studyView, 'study-view': studyView,
@ -428,7 +433,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 +477,10 @@ 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 = this.Criterion && this.Criterion.CriterionType && (
this.Criterion.CriterionType == 19 ||
this.Criterion.CriterionType == 20
) ? res.OtherInfo.IsIRImageDownloaded : true
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 = []

View File

@ -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" />
</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,10 @@ export default {
IsImageSegment: { IsImageSegment: {
type: Boolean, type: Boolean,
default: false, default: false,
},
isReading: {
type: Boolean,
default: false,
} }
}, },
data() { data() {

View File

@ -111,7 +111,7 @@
: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 :disabled="scope.row.UploadedFileCount <= 0 ||
scope.row.ReadingTaskState === 2 scope.row.ReadingTaskState === 2
@ -270,6 +270,10 @@ export default {
type: Number, type: Number,
default: 0, default: 0,
}, },
isReading: {
type: Boolean,
default: false,
}
}, },
data() { data() {
return { return {
@ -291,7 +295,8 @@ export default {
BodyPart: {}, BodyPart: {},
relationInfo: { relationInfo: {
ImageFormatList: [] ImageFormatList: []
} },
isDownloaded: false
} }
}, },
watch: { watch: {
@ -348,6 +353,10 @@ 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 = this.Criterion && this.Criterion.CriterionType && (
this.Criterion.CriterionType == 19 ||
this.Criterion.CriterionType == 20
) ? res.OtherInfo.IsIRImageDownloaded : true
this.faccept = [] this.faccept = []
this.relationInfo.ImageFormatList.forEach((item) => { this.relationInfo.ImageFormatList.forEach((item) => {
this.faccept.push(`.${item}`) this.faccept.push(`.${item}`)

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',

View File

@ -11,10 +11,10 @@ function zipFiles(zipName, files) {
store.dispatch('trials/setUnLock', true) store.dispatch('trials/setUnLock', true)
files = formatFiles(files) files = formatFiles(files)
// 创建压缩文件输出流 // 创建压缩文件输出流
const zipFileOutputStream = streamSaver.createWriteStream(zipName); let zipFileOutputStream = streamSaver.createWriteStream(zipName);
// 创建下载文件流 // 创建下载文件流
const fileIterator = files.values(); const fileIterator = files.values();
const readableZipStream = new ZIP({ let readableZipStream = new ZIP({
async pull(ctrl) { async pull(ctrl) {
const fileInfo = fileIterator.next(); const fileInfo = fileIterator.next();
if (fileInfo.done) {//迭代终止 if (fileInfo.done) {//迭代终止
@ -41,9 +41,13 @@ function zipFiles(zipName, files) {
.then(() => { .then(() => {
console.log("同步下载打包结束时间:" + new Date()); console.log("同步下载打包结束时间:" + new Date());
store.dispatch('trials/setUnLock', false) store.dispatch('trials/setUnLock', false)
readableZipStream = null;
zipFileOutputStream = null;
resolve(true) resolve(true)
}).catch(err => { }).catch(err => {
console.log(err); console.log(err);
readableZipStream = null;
zipFileOutputStream = null;
resolve(false) resolve(false)
}); });
} else { } else {
@ -61,14 +65,16 @@ async function updateFile(file, name) {
return new Promise(async resolve => { return new Promise(async resolve => {
try { try {
store.dispatch('trials/setUnLock', true) store.dispatch('trials/setUnLock', true)
const fileOutputStream = streamSaver.createWriteStream(name); let fileOutputStream = streamSaver.createWriteStream(name);
// file = decodeUtf8(file); // file = decodeUtf8(file);
let res = await fetch(file); let res = await fetch(file);
res.body.pipeTo(fileOutputStream).then(() => { res.body.pipeTo(fileOutputStream).then(() => {
store.dispatch('trials/setUnLock', true) store.dispatch('trials/setUnLock', true)
fileOutputStream = null;
resolve(true) resolve(true)
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)
fileOutputStream = null;
resolve(false) resolve(false)
}); });
} catch (err) { } catch (err) {

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

View File

@ -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: {

View File

@ -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: '',

View File

@ -218,9 +218,9 @@
<div class="text">{{ $t('trials:reading:button:screenShot') }}</div> <div class="text">{{ $t('trials:reading:button:screenShot') }}</div>
</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">
@ -563,7 +563,7 @@
</el-button> </el-button>
</div> </div>
</div> </div>
</transition> </transition>
@ -596,7 +596,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 +708,10 @@
</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" />
<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">
@ -1129,7 +1129,7 @@ export default {
DicomEvent.$on('imageLocation', async (measuredData) => { DicomEvent.$on('imageLocation', async (measuredData) => {
return new Promise(async resolve => { return new Promise(async resolve => {
if (!measuredData) return if (!measuredData) return
await this.imageLocation(measuredData) await this.imageLocation(measuredData)
resolve() resolve()
}) })
@ -1197,7 +1197,7 @@ export default {
} else if (this.CriterionType === 22) { } else if (this.CriterionType === 22) {
this.setToolActive('Probe', true, null, 'tableQuestion') this.setToolActive('Probe', true, null, 'tableQuestion')
} }
}) })
window.addEventListener('beforeunload', () => { window.addEventListener('beforeunload', () => {
if (this.petctWindow) { if (this.petctWindow) {
@ -1451,7 +1451,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 +2496,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 +2609,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) {

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -411,7 +411,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 +448,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" :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>

View File

@ -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 {

View File

@ -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()

View File

@ -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)
} }

View File

@ -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>
@ -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 {
} }
} }
} }
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series, true) if (!this.isActiveViewportAtMeasureLocation(obj.annotation)) {
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex) this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series, true)
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) {
await this.imageLocation(measureData) if (!this.isActiveViewportAtMeasureLocation(measureData.annotation)) {
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}"]`
) )

View File

@ -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

View File

@ -804,12 +804,16 @@ export default {
toolGroup.setToolPassive(this.activeTool) toolGroup.setToolPassive(this.activeTool)
} }
this.$emit('update:activeTool', '') this.$emit('update:activeTool', '')
} else if (this.activeTool === name) {
toolGroup.setToolPassive(this.ThresholdTools[1])
this.$emit('update:activeTool', '')
} else { } else {
if (this.activeTool) { if (this.activeTool) {
if (toolName === CrosshairsTool.toolName) { if (toolName === CrosshairsTool.toolName) {
toolGroup.setToolDisabled(this.activeTool) toolGroup.setToolDisabled(this.activeTool)
} else { } else {
toolGroup.setToolPassive(this.activeTool) toolGroup.setToolPassive(this.activeTool)
toolGroup.setToolPassive(this.ThresholdTools[1])
} }
} }
toolGroup.setToolActive(toolName, { toolGroup.setToolActive(toolName, {

View File

@ -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) {

View File

@ -88,21 +88,21 @@
: $t('trials:pendingReadingTasks:button:review') : $t('trials:pendingReadingTasks:button:review')
" icon="el-icon-edit-outline" @click="handleReadImage(scope.row)" /> " icon="el-icon-edit-outline" @click="handleReadImage(scope.row)" />
<!-- 上传 --> <!-- 上传 -->
<el-button v-if=" <!-- <el-button v-if="
item.ImageUploadEnum > 0 && item.ImageUploadEnum > 0 &&
item.IsReadingTaskViewInOrder > 1 && item.IsReadingTaskViewInOrder > 1 &&
!scope.row.IsSubjectJudge !scope.row.IsSubjectJudge
" v-hasPermi="['role:ir']" circle icon="el-icon-upload2" " v-hasPermi="['role:ir']" circle icon="el-icon-upload2"
:title="$t('trials:pendingReadingTasks:button:upload')" :title="$t('trials:pendingReadingTasks:button:upload')"
@click="openUploadImage(scope.row, item, 'upload')" /> @click="openUploadImage(scope.row, item, 'upload')" /> -->
<!-- 下载 --> <!-- 下载 -->
<el-button v-if=" <!-- <el-button v-if="
item.ImageDownloadEnum > 0 && item.ImageDownloadEnum > 0 &&
item.IsReadingTaskViewInOrder > 1 && item.IsReadingTaskViewInOrder > 1 &&
!scope.row.IsSubjectJudge !scope.row.IsSubjectJudge
" v-hasPermi="['role:ir']" circle icon="el-icon-download" " v-hasPermi="['role:ir']" circle icon="el-icon-download"
:title="$t('trials:pendingReadingTasks:button:download')" :title="$t('trials:pendingReadingTasks:button:download')"
@click="openUploadImage(scope.row, item, 'download')" /> @click="openUploadImage(scope.row, item, 'download')" /> -->
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>

View File

@ -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) {

View File

@ -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++
}
})
}, },
} }
} }