Merge branch 'main' into uat_us

uat_us
wangxiaoshuang 2026-04-17 10:01:16 +08:00
commit a3e79db023
70 changed files with 6646 additions and 2145 deletions

1442
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,11 +15,12 @@
"i18n:en": "node i18nGenerate.js lang=en keyCol=5 valCol=7"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.726.1",
"@cornerstonejs/adapters": "4.19.2",
"@aws-sdk/client-s3": "3.726.1",
"@cornerstonejs/adapters": "^4.19.2",
"@cornerstonejs/calculate-suv": "^1.1.0",
"@cornerstonejs/core": "^4.19.2",
"@cornerstonejs/dicom-image-loader": "^4.19.2",
"@cornerstonejs/polymorphic-segmentation": "^4.19.2",
"@cornerstonejs/tools": "^4.19.2",
"@fingerprintjs/fingerprintjs": "^4.6.2",
"@icr/polyseg-wasm": "^0.4.0",
@ -38,7 +39,7 @@
"dcmjs": "^0.29.8",
"dicom-parser": "^1.8.9",
"dicomedit": "^0.1.0",
"echarts": "^4.8.0",
"echarts": "^6.0.0",
"element-ui": "^2.15.14",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",

41
src/api/file.js Normal file
View File

@ -0,0 +1,41 @@
import request from '@/utils/request'
export function addOrUpdateFileUploadRecord(params) {
return request({
url: `/FileUploadRecord/addOrUpdateFileUploadRecord`,
method: 'post',
data: params
})
}
export function batchAddSyncFileTask(params) {
return request({
url: `/FileUploadRecord/batchAddSyncFileTask`,
method: 'post',
data: params
})
}
export function getSubjectUploadRecordList(params) {
return request({
url: `/FileUploadRecord/getSubjectUploadRecordList`,
method: 'post',
data: params
})
}
export function getFileUploadRecordList(params) {
return request({
url: `/FileUploadRecord/getFileUploadRecordList`,
method: 'post',
data: params
})
}
export function getUploadFileSyncRecordList(params) {
return request({
url: `/FileUploadRecord/getUploadFileSyncRecordList`,
method: 'post',
data: params
})
}

View File

@ -8,20 +8,49 @@
<script>
import { getReportsChartData } from "@/api/reading"
let echarts = require('echarts/lib/echarts');
import * as echarts from 'echarts/core';
import { LineChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
DataZoomComponent,
LegendComponent,
DatasetComponent,
// (filter, sort)
TransformComponent
} from 'echarts/components';
//
import { LabelLayout, UniversalTransition } from 'echarts/features';
// Canvas CanvasRenderer SVGRenderer
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
DataZoomComponent,
LegendComponent,
LineChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
// let echarts = require('echarts/lib/echarts');
//
// require('echarts/lib/chart/bar');
require('echarts/lib/chart/line');
// require('echarts/lib/chart/line');
// require('echarts/lib/chart/pie');
// require('echarts/lib/chart/scatter');
//
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
require('echarts/lib/component/legend');
require('echarts/lib/component/grid');
require('echarts/lib/component/dataZoom');
// require('echarts/lib/component/tooltip');
// require('echarts/lib/component/title');
// require('echarts/lib/component/legend');
// require('echarts/lib/component/grid');
// require('echarts/lib/component/dataZoom');
export default {
name: "readingChart",
props: {
@ -151,7 +180,13 @@ export default {
text: obj.title,
textStyle: {
color: "#fff"
}
},
left: 0,
top: 0
},
grid: {
left: 50,
bottom: 30
},
tooltip: {
trigger: 'axis',
@ -193,6 +228,7 @@ export default {
}
},
axisLine: {
show: true,
lineStyle: {
color: '#fff',
}

View File

@ -1174,6 +1174,7 @@ export default {
},
}
let arr = []
let uploadBatchId = scope.$guid()
for (let i = 0; i < seriesList.length; i++) {
let v = seriesList[i]
let instanceList = []
@ -1266,6 +1267,16 @@ export default {
) {
dicomInfo.uploadFileSize = dicomInfo.fileSize
}
},
{
fileName: o.file.name,
fileSize: o.file.size,
fileType: 'application/dicom',
uploadBatchId: uploadBatchId,
batchDataType: 5,
trialId: params.trialId,
subjectId: params.subjectId,
subjectVisitId: params.subjectVisitId
}
)
if (!res || !res.url) {
@ -1289,7 +1300,17 @@ export default {
let OSSclient = scope.OSSclient
let seriesRes = await OSSclient.put(
thumbnailPath,
blob
blob,
{
fileName: `${v.seriesUid}.jpg`,
fileSize: blob.size,
fileType: 'image/jpeg',
uploadBatchId: uploadBatchId,
batchDataType: 6,
trialId: params.trialId,
subjectId: params.subjectId,
subjectVisitId: params.subjectVisitId
}
)
if (seriesRes && seriesRes.url) {
ImageResizePath = scope.$getObjectName(
@ -1430,7 +1451,20 @@ export default {
}
let OSSclient = scope.OSSclient
try {
let seriesRes = await OSSclient.put(thumbnailPath, blob)
let seriesRes = await OSSclient.put(
thumbnailPath,
blob,
{
fileName: `${v.seriesUid}.jpg`,
fileSize: blob.size,
fileType: 'image/jpeg',
uploadBatchId: uploadBatchId,
batchDataType: 6,
trialId: params.trialId,
subjectId: params.subjectId,
subjectVisitId: params.subjectVisitId
}
)
if (seriesRes && seriesRes.url) {
o.ImageResizePath = scope.$getObjectName(seriesRes.url)
}
@ -1448,6 +1482,7 @@ export default {
if (scope.IsImageSegment) {
params.IsImageSegmentLabel = true
}
params.UploadBatchId = uploadBatchId
addOrUpdateArchiveTaskStudy(params)
.then((res) => {
if (dicomInfo.failedFileCount === dicomInfo.fileCount) {

View File

@ -548,8 +548,9 @@ export default {
})
if (res.IsSuccess) {
this.studyMonitorId = res.Result
let uploadBatchId = this.$guid()
for (let i = 0; i < num; i++) {
funArr.push(this.handleUploadTask(this.selectArr, i))
funArr.push(this.handleUploadTask(this.selectArr, i, uploadBatchId))
}
if (funArr.length > 0) {
let res = await Promise.all(funArr)
@ -560,7 +561,7 @@ export default {
}
},
//
async handleUploadTask(arr, index) {
async handleUploadTask(arr, index, uploadBatchId) {
if (!this.uploadVisible) return
let file = this.fileList.filter((item) => item.id === arr[index].id)[0]
file.status = 1
@ -576,7 +577,7 @@ export default {
}
file.curPath = path
const fileData = await this.fileToBlob(file.file)
let res = await this.fileToOss(path, fileData, file)
let res = await this.fileToOss(path, fileData, file, uploadBatchId)
if (res) {
file.status = 2
this.successFileList.push({
@ -610,13 +611,13 @@ export default {
}
let ind = arr.findIndex((item) => item.status === 0)
if (ind >= 0) {
return this.handleUploadTask(arr, ind)
return this.handleUploadTask(arr, ind, uploadBatchId)
} else {
return false
}
},
// fileoss
async fileToOss(path, file, item) {
async fileToOss(path, file, item, uploadBatchId) {
try {
let res = await this.OSSclient.multipartUpload(
{
@ -629,6 +630,17 @@ export default {
if (item.uploadFileSize > file.fileSize) {
item.uploadFileSize = file.fileSize > 0 ? file.fileSize : 1
}
},
{
fileName: item.name,
fileSize: item.size,
fileType: item.fileType,
uploadBatchId: uploadBatchId,
batchDataType: 7,
trialId: this.$route.query.trialId,
subjectId: this.currentRow.SubjectId,
subjectVisitId: this.currentRow.SourceSubjectVisitId,
studyCode: this.SubjectCode
}
)
if (res) {

View File

@ -0,0 +1,16 @@
import resize from './resize'
const install = function (Vue) {
// 绑定v-adaptive指令
Vue.directive('resize', resize)
}
// if (window.Vue) {
// window['adaptive'] = adaptive
// // eslint-disable-next-line no-undef
// Vue.use(install)
// }
resize.install = install
export default resize

View File

@ -0,0 +1,19 @@
const map = new WeakMap()
const ob = new ResizeObserver(entries => {
for (const entry of entries) {
const handler = map.get(entry.target)
if (handler) {
const { inlineSize, blockSize } = entry.contentBoxSize[0]
handler({ width: inlineSize, height: blockSize })
}
}
})
export default {
bind(el, binding) {
map.set(el, binding.value)
ob.observe(el)
},
unbind(el) {
ob.unobserve(el)
}
}

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1775115523115" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5347" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M458.105263 970.105263h-215.578947V264.084211h215.578947V970.105263z m-161.68421-53.894737h107.789473V317.978947h-107.789473V916.210526z" fill="#ffffff" p-id="5348"></path><path d="M296.421053 970.105263h-215.578948V479.663158h215.578948V970.105263z m-161.684211-53.894737h107.789474V533.557895h-107.789474V916.210526zM943.157895 970.105263h-215.578948V479.663158h215.578948V970.105263z m-161.684211-53.894737h107.789474V533.557895h-107.789474V916.210526zM619.789474 970.105263h-215.578948V53.894737h215.578948v916.210526z m-161.684211-53.894737h107.789474V107.789474h-107.789474v808.421052z" fill="#ffffff" p-id="5349"></path><path d="M781.473684 970.105263h-215.578947V264.084211h215.578947V970.105263z m-161.68421-53.894737h107.789473V317.978947h-107.789473V916.210526z" fill="#ffffff" p-id="5350"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -70,6 +70,9 @@ import FB from '@/components/feedBack/index'
Vue.use(FB)
import FBT from '@/components/feedBackTable/index'
Vue.use(FBT)
import resize from '@/directive/resize/index'
// 表格自适应指令
Vue.use(resize)
import adaptive from '@/directive/adaptive/index'
// 表格自适应指令
Vue.use(adaptive)

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { anonymization } from './anonymization'
export const dcmUpload = async function (data, config, progressFn) {
export const dcmUpload = async function (data, config, progressFn, fileInfo) {
return new Promise(async resolve => {
try {
// let blob = await encoder(file, config)
@ -8,7 +8,8 @@ export const dcmUpload = async function (data, config, progressFn) {
if (config) {
blob = await anonymization(data.file, config)
}
let res = await Vue.prototype.OSSclient.multipartUpload(Object.assign(data, { file: blob.blob }), progressFn)
let res = await Vue.prototype.OSSclient.multipartUpload(Object.assign(data, { file: blob.blob }), progressFn, fileInfo)
resolve({
...res,
image: blob.pixelDataElement

View File

@ -5,6 +5,7 @@ const stream = require('stream')
import Vue from 'vue'
import { customerHttp, OSSclose } from "@/utils/multipartUpload/oss"
import { exist, AWSclose } from "@/utils/multipartUpload/aws"
import { addOrUpdateFileUploadRecord } from '@/api/file'
const { GetObjectStoreToken } = require('../api/user.js')
const {
S3Client,
@ -22,7 +23,6 @@ async function ossGenerateSTS() {
res = await GetObjectStoreToken()
localStorage.setItem('stsToken', JSON.stringify(res))
}
// res.Result.ObjectStoreUse = 'AWS';
Vue.prototype.OSSclientConfig = { ...res.Result[res.Result.ObjectStoreUse] }
Vue.prototype.OSSclientConfig.ObjectStoreUse = res.Result.ObjectStoreUse;
@ -34,7 +34,8 @@ async function ossGenerateSTS() {
Vue.prototype.OSSclientConfig.timeout = 10 * 60 * 1000
let OSSclient = new OSS(Vue.prototype.OSSclientConfig)
Vue.prototype.OSSclient = {
put: async function (objectName, object) {
put: async function (objectName, object, fileInfo = {}) {
OSSclient = await RefreshClient(OSSclient)
return new Promise(async (resolve, reject) => {
try {
@ -52,6 +53,15 @@ async function ossGenerateSTS() {
}
let res = await OSSclient.put(objectName, object)
if (res && res.url) {
const urlParams = new URLSearchParams(window.location.search)
const trialId = urlParams.get('trialId')
if (Object.keys(fileInfo).length !== 0) {
let params = Object.assign({path: objectName}, fileInfo)
addOrUpdateFileUploadRecord(params)
} else if (trialId) {
let params = { trialId }
addOrUpdateFileUploadRecord(params)
}
resolve({
name: objectName,
url: res.url
@ -65,7 +75,7 @@ async function ossGenerateSTS() {
}
})
},
multipartUpload: async (data, progress) => {
multipartUpload: async (data, progress, fileInfo = {}) => {
OSSclient = await RefreshClient(OSSclient)
return new Promise(async (resolve, reject) => {
try {
@ -95,6 +105,16 @@ async function ossGenerateSTS() {
}
let res = await customerHttp(OSSclient, data, progress);
if (res) {
const urlParams = new URLSearchParams(window.location.search)
const trialId = urlParams.get('trialId')
if (Object.keys(fileInfo).length !== 0) {
let params = Object.assign({path: data.path}, fileInfo)
addOrUpdateFileUploadRecord(params)
} else if (trialId) {
let params = { trialId }
addOrUpdateFileUploadRecord(params)
}
resolve({
name: data.path,
url: Vue.prototype.OSSclientConfig.viewEndpoint + decodeUtf8(res.name)
@ -169,17 +189,17 @@ async function ossGenerateSTS() {
}
});
Vue.prototype.OSSclient = {
put: async function (objectName, object) {
put: async function (objectName, object, fileInfo = {}) {
let data = {
file: object,
path: objectName
}
aws = await RefreshClient(aws);
return uploadAWS(aws, data, () => { });
return uploadAWS(aws, data, () => { }, fileInfo);
},
multipartUpload: async (data, progress) => {
multipartUpload: async (data, progress, fileInfo = {}) => {
aws = await RefreshClient(aws);
return uploadAWS(aws, data, progress);
return uploadAWS(aws, data, progress, fileInfo);
},
close: () => {
AWSclose();
@ -189,7 +209,7 @@ async function ossGenerateSTS() {
return
}
// AWS上传函数
function uploadAWS(aws, data, progress) {
function uploadAWS(aws, data, progress, fileInfo) {
return new Promise(async (resolve, reject) => {
try {
const { file, path } = data;
@ -211,6 +231,16 @@ function uploadAWS(aws, data, progress) {
data.path = data.path.replace(`/${bucketName}/`, '');
await exist(aws, bucketName, data, progress, (path, status) => {
if (status === 'success') {
const urlParams = new URLSearchParams(window.location.search)
const trialId = urlParams.get('trialId')
if (Object.keys(fileInfo).length !== 0) {
let params = Object.assign({path: decodeUtf8(curPath)}, fileInfo)
addOrUpdateFileUploadRecord(params)
} else if (trialId) {
let params = { trialId }
addOrUpdateFileUploadRecord(params)
}
resolve({
name: decodeUtf8(curPath),
url: Vue.prototype.OSSclientConfig.viewEndpoint + decodeUtf8(curPath)

View File

@ -2,8 +2,8 @@
<div ref="mapbox" class="map-wrapper" />
</template>
<script>
import echarts from 'echarts'
import 'modules/echarts/map/js/world.js'
import * as echarts from 'echarts'
// import 'modules/echarts/map/js/world.js'
import { fontSize } from 'utils/fontsize'
export default {
props: {

View File

@ -2,7 +2,7 @@
<div ref="trials_stats" style="width:100%;height:100%;" />
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
import { getTrialCountRank } from '@/api/dashboard'
import { fontSize } from 'utils/fontsize'
const Count = 5

View File

@ -2,7 +2,7 @@
<div ref="enroll_stats" style="width:100%;height:100%;" />
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
import { getEnrollDataByQuarter } from '@/api/dashboard'
import { fontSize } from 'utils/fontsize'
const Count = 6

View File

@ -2,7 +2,7 @@
<div ref="reviewers_stats" style="width:100%;height:100%;padding:5px;" />
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
import { getReviewersByRank } from '@/api/dashboard'
import { fontSize } from 'utils/fontsize'
export default {

View File

@ -2,7 +2,7 @@
<div ref="monthly_reading_stats" style="width:100%;height:100%;" />
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
import { getReadingDataByMonth } from '@/api/dashboard'
import { fontSize } from 'utils/fontsize'
const Count = 6

View File

@ -2,7 +2,7 @@
<div ref="reading_rank" class="reading-rank" />
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
import { getReadingDataRank } from '@/api/dashboard'
import { fontSize } from 'utils/fontsize'
const Count = 5

View File

@ -2,7 +2,7 @@
<div ref="reading_stats" style="width:100%;height:100%;padding-top:5px;" />
</template>
<script>
import echarts from 'echarts'
import * as echarts from 'echarts'
import { getReadingDataByType } from '@/api/dashboard'
import { fontSize } from 'utils/fontsize'
export default {

View File

@ -1,11 +1,7 @@
<template>
<div class="adReview_wrapper">
<el-card
v-if="isReadingShowSubjectInfo"
shadow="never"
:body-style="{ padding: '10px' }"
style="margin-bottom: 10px"
>
<el-card v-if="isReadingShowSubjectInfo" shadow="never" :body-style="{ padding: '10px' }"
style="margin-bottom: 10px">
<h4>
<!-- 受试者 -->
{{ $t("trials:adReview:title:subject") }}
@ -17,14 +13,11 @@
<!-- <div slot="header" class="clearfix">
<span style="font-weight: bold;">评估结果</span>
</div> -->
<div
slot="header"
style="
<div slot="header" style="
display: flex;
flex-direction: row;
justify-content: space-between;
"
>
">
<!-- 评估结果 -->
<div style="font-weight: bold">
{{ $t("trials:adReview:title:result") }}
@ -39,46 +32,33 @@
<el-table :data="adInfo.VisitInfoList" style="width: 100%">
<!-- 访视名称 -->
<el-table-column
prop="VisitName"
:label="$t('trials:adReview:table:visitName')"
show-overflow-tooltip
width="150"
/>
<el-table-column prop="VisitName" :label="$t('trials:adReview:table:visitName')" show-overflow-tooltip
width="150" />
<!-- 评估结果 -->
<el-table-column
v-for="j in judgeQuestion"
:key="j.armEnum"
:label="
j.armEnum === 1
<el-table-column v-for="j in judgeQuestion" :key="j.armEnum" :label="j.armEnum === 1
? $t('trials:adReview:table:viewR1')
: j.armEnum === 2
? $t('trials:adReview:table:viewR2')
: $fd('ArmEnum', j.armEnum)
"
align="center"
prop=""
>
" align="center" prop="">
<template>
<el-table-column
v-for="(qs, i) in j.judgeQuestionList"
:key="i"
prop=""
:label="qs"
show-overflow-tooltip
width="150"
>
<el-table-column v-for="(qs, i) in j.judgeQuestionList" :key="i" prop="" :label="qs" show-overflow-tooltip
width="150">
<template slot-scope="scope">
<div v-if="scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].QuestionType === 1">
<span v-if="scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].DictionaryCode">
{{ $fd(scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].DictionaryCode,parseInt(scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Answer))
{{
$fd(scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].DictionaryCode,
parseInt(scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Answer))
}}
</span>
<span v-else>
{{ scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Answer }}
</span>
<span v-if="scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Unit && !isNaN(parseFloat(scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Answer))">{{ $fd('ValueUnit', parseInt(scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Unit)) }}</span>
<span
v-if="scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Unit && !isNaN(parseFloat(scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Answer))">{{
$fd('ValueUnit', parseInt(scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Unit)) }}</span>
</div>
<div v-else-if="scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].QuestionType === 2">
<div v-if="scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Answer">
@ -86,15 +66,11 @@
{{ $fd("YesOrNo", scope.row.VisitTaskInfoList[j.index].JudgeQuestionList[i].Answer) }}
</span>
<!-- 查看详情 -->
<el-button
type="text"
style="margin-left: 5px"
@click="
<el-button type="text" style="margin-left: 5px" @click="
handleViewDetail(
scope.row.VisitTaskInfoList[j.index].GlobalVisitTaskId
)
"
>
">
{{ $t("trials:adReview:table:view") }}
</el-button>
</div>
@ -119,60 +95,34 @@
</el-table-column>
<!-- 查看详情 -->
<el-table-column
:label="
criterionType === 10
<el-table-column :label="criterionType === 10
? $t('trials:adReview:table:visitInfoview')
: $t('trials:adReview:table:view')
"
width="200"
:fixed="isFixed ? 'right' : false"
>
" width="200" :fixed="isFixed ? 'right' : false">
<template slot-scope="scope">
<!-- 查看R1详情 -->
<el-button
type="text"
:title="$t('trials:adReview:table:viewR1')"
@click="handleView(scope.row, 1)"
>
<el-button type="text" :title="$t('trials:adReview:table:viewR1')" @click="handleView(scope.row, 1)">
R1
</el-button>
<!-- 查看R2详情 -->
<el-button
type="text"
:title="$t('trials:adReview:table:viewR2')"
@click="handleView(scope.row, 2)"
>
<el-button type="text" :title="$t('trials:adReview:table:viewR2')" @click="handleView(scope.row, 2)">
R2
</el-button>
</template>
</el-table-column>
<!-- 全局详情 PCWG -->
<el-table-column
v-if="criterionType === 10"
prop="VisitName"
:label="$t('trials:adReview:table:glInfo')"
show-overflow-tooltip
width="150"
>
<el-table-column v-if="criterionType === 10" prop="VisitName" :label="$t('trials:adReview:table:glInfo')"
show-overflow-tooltip width="150">
<template slot-scope="scope">
<!-- 查看R1详情 -->
<el-button
v-if="scope.$index === adInfo.VisitInfoList.length - 1"
type="text"
:title="$t('trials:adReview:table:viewR1')"
@click="handleViewGl(scope.row, 1)"
>
<el-button v-if="scope.$index === adInfo.VisitInfoList.length - 1" type="text"
:title="$t('trials:adReview:table:viewR1')" @click="handleViewGl(scope.row, 1)">
R1
</el-button>
<!-- 查看R2详情 -->
<el-button
v-if="scope.$index === adInfo.VisitInfoList.length - 1"
type="text"
:title="$t('trials:adReview:table:viewR2')"
@click="handleViewGl(scope.row, 2)"
>
<el-button v-if="scope.$index === adInfo.VisitInfoList.length - 1" type="text"
:title="$t('trials:adReview:table:viewR2')" @click="handleViewGl(scope.row, 2)">
R2
</el-button>
</template>
@ -186,120 +136,64 @@
$t("trials:adReview:title:adResult")
}}</span>
</div>
<el-form
ref="adForm"
v-loading="loading"
:model="adForm"
style="width: 800px"
label-width="100"
>
<el-form ref="adForm" v-loading="loading" :model="adForm" style="width: 800px" label-width="100">
<!-- 选择阅片人 -->
<el-form-item
:label="$t('trials:adReview:title:choseReader')"
prop="judgeResultTaskId"
:rules="[
<el-form-item :label="$t('trials:adReview:title:choseReader')" prop="judgeResultTaskId" :rules="[
{ required: true, message: this.$t('common:ruleMessage:select') },
]"
>
<el-radio-group
v-model="adForm.judgeResultTaskId"
:disabled="adInfo.ReadingTaskState >= 2"
>
<el-radio
v-for="t in visitTaskArmList"
:key="t.VisitTaskId"
:label="t.VisitTaskId"
@change="handleVisitTaskArmChange"
>
]">
<el-radio-group v-model="adForm.judgeResultTaskId" :disabled="adInfo.ReadingTaskState >= 2">
<el-radio v-for="t in visitTaskArmList" :key="t.VisitTaskId" :label="t.VisitTaskId"
@change="handleVisitTaskArmChange">
{{ $fd("ArmEnum", t.ArmEnum) }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 裁判原因 -->
<el-form-item
:label="$t('trials:adReview:title:adReason')"
prop="judgeResultRemark"
:rules="[
<el-form-item :label="$t('trials:adReview:title:adReason')" prop="judgeResultRemark" :rules="[
{ required: true, message: this.$t('common:ruleMessage:specify') },
{
max: 500,
message: `${this.$t('common:ruleMessage:maxLength')} 500`,
trigger: ['blur', 'change'],
},
]"
>
]">
<div style="position: relative">
<div
style="
<div style="
position: absolute;
left: 0;
top: 30px;
color: #606266;
font-size: 13px;
"
>
">
{{ remark }}
</div>
<el-input
v-model="adForm.judgeResultRemark"
type="textarea"
:autosize="{ minRows: 4, maxRows: 6 }"
:disabled="adInfo.ReadingTaskState >= 2"
style="margin-top: 25px"
/>
<el-input v-model="adForm.judgeResultRemark" type="textarea" :autosize="{ minRows: 4, maxRows: 6 }"
:disabled="adInfo.ReadingTaskState >= 2" style="margin-top: 25px" />
</div>
</el-form-item>
<!-- 截图说明 -->
<el-form-item :label="$t('trials:adReview:title:screenShot')">
<el-upload
action
:accept="accept"
:on-preview="handlePictureCardPreview"
:before-upload="handleBeforeUpload"
:http-request="uploadScreenshot"
list-type="picture-card"
:on-remove="handleRemove"
:file-list="fileList"
:class="{ disabled: adInfo.ReadingTaskState >= 2 }"
:disabled="adInfo.ReadingTaskState >= 2"
>
<el-upload action :accept="accept" :on-preview="handlePictureCardPreview" :before-upload="handleBeforeUpload"
:http-request="uploadScreenshot" list-type="picture-card" :on-remove="handleRemove" :file-list="fileList"
:class="{ disabled: adInfo.ReadingTaskState >= 2 }" :disabled="adInfo.ReadingTaskState >= 2">
<i slot="default" class="el-icon-plus" />
<div
slot="file"
slot-scope="{ file }"
style="width: 100%; height: 100%"
>
<viewer
:ref="file.url"
:images="images"
style="
<div slot="file" slot-scope="{ file }" style="width: 100%; height: 100%">
<viewer :ref="file.url" :images="images" style="
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
"
>
<img
class="el-upload-list__item-thumbnail"
:src="OSSclientConfig.basePath + file.url"
alt=""
crossorigin="anonymous"
style="max-width: 100%; max-height: 100%"
>
">
<img class="el-upload-list__item-thumbnail" :src="OSSclientConfig.basePath + file.url" alt=""
crossorigin="anonymous" style="max-width: 100%; max-height: 100%">
<span class="el-upload-list__item-actions">
<span
class="el-upload-list__item-preview"
@click="handlePictureCardPreview(file)"
>
<span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
<i class="el-icon-zoom-in" />
</span>
<span
v-if="adInfo.ReadingTaskState < 2"
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<span v-if="adInfo.ReadingTaskState < 2" class="el-upload-list__item-delete"
@click="handleRemove(file)">
<i class="el-icon-delete" />
</span>
</span>
@ -324,10 +218,7 @@
</div>
</el-card>
<el-card
v-if="isReadingShowPreviousResults"
:body-style="{ padding: '10px' }"
>
<el-card v-if="isReadingShowPreviousResults" :body-style="{ padding: '10px' }">
<div slot="header" class="clearfix">
<!-- 既往裁判评估 -->
<span style="font-weight: bold">{{
@ -335,24 +226,12 @@
}}</span>
</div>
<el-table
v-loading="priorLoading"
:data="priorADList"
style="width: 100%"
>
<el-table v-loading="priorLoading" :data="priorADList" style="width: 100%">
<!-- 裁判阅片 -->
<el-table-column
prop="TaskBlindName"
:label="$t('trials:adReview:table:adReading')"
show-overflow-tooltip
width="200"
/>
<el-table-column
prop="JudgeResultArm"
:label="$t('trials:adReview:table:adResult')"
show-overflow-tooltip
width="200"
>
<el-table-column prop="TaskBlindName" :label="$t('trials:adReview:table:adReading')" show-overflow-tooltip
width="200" />
<el-table-column prop="JudgeResultArm" :label="$t('trials:adReview:table:adResult')" show-overflow-tooltip
width="200">
<template slot-scope="scope">
{{ $fd("ArmEnum", scope.row.JudgeResultArm) }}
</template>
@ -360,35 +239,22 @@
<el-table-column :label="$t('common:action:action')" width="200">
<template slot-scope="scope">
<!-- 查看详情 -->
<el-button
circle
:title="$t('trials:adReview:table:view')"
icon="el-icon-view"
@click="handleViewDetail(scope.row.VisitTaskId)"
/>
<el-button circle :title="$t('trials:adReview:table:view')" icon="el-icon-view"
@click="handleViewDetail(scope.row.VisitTaskId)" />
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 签名框 -->
<el-dialog
v-if="signVisible"
:visible.sync="signVisible"
:close-on-click-modal="false"
width="600px"
custom-class="base-dialog-wrapper"
>
<el-dialog v-if="signVisible" :visible.sync="signVisible" :close-on-click-modal="false" width="600px"
custom-class="base-dialog-wrapper">
<div slot="title">
<span style="font-size: 18px">{{ $t("common:dialogTitle:sign") }}</span>
<span style="font-size: 12px; margin-left: 5px">{{
`(${$t("common:label:sign")}${currentUser})`
}}</span>
</div>
<SignForm
ref="signForm"
:sign-code-enum="signCode"
@closeDialog="closeSignDialog"
/>
<SignForm ref="signForm" :sign-code-enum="signCode" @closeDialog="closeSignDialog" />
</el-dialog>
</div>
</template>
@ -729,17 +595,13 @@ export default {
this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
this.subjectId
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId
}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
this.subjectId
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId
}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
}
var routeData = this.$router.resolve({ path })
@ -766,21 +628,15 @@ export default {
this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
this.subjectId
}&visitTaskId=${
task.VisitTaskId
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId
}&visitTaskId=${task.VisitTaskId
}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
this.subjectId
}&visitTaskId=${
task.VisitTaskId
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId
}&visitTaskId=${task.VisitTaskId
}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
}
var routeData = this.$router.resolve({ path })
@ -801,21 +657,15 @@ export default {
this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
this.subjectId
}&visitTaskId=${
task.GlobalVisitTaskId
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId
}&visitTaskId=${task.GlobalVisitTaskId
}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${
this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${
this.subjectId
}&visitTaskId=${
task.GlobalVisitTaskId
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId
}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId
}&visitTaskId=${task.GlobalVisitTaskId
}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
}
var routeData = this.$router.resolve({ path })
@ -932,17 +782,21 @@ export default {
width: 100%;
height: 100%;
overflow-y: auto;
.box-mr {
margin: 10px 0;
}
.disabled {
::v-deep .el-upload--picture-card {
display: none;
}
}
::v-deep .el-upload-list__item {
transition: none !important;
}
::v-deep .el-upload-list__item-thumbnail {
/* 图片在方框内显示长边 */
object-fit: scale-down !important;

View File

@ -0,0 +1,116 @@
<template>
<div class="ContourViewport" ref="ContourViewport" id="ContourViewport"></div>
</template>
<script>
import {
getRenderingEngine,
CONSTANTS,
setVolumesForViewports,
eventTarget,
Enums,
utilities,
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
const {
Enums: csToolsEnums,
segmentation,
TrackballRotateTool,
ToolGroupManager
} = cornerstoneTools
const { MouseBindings, Events: toolsEvents } = csToolsEnums
export default {
name: "ContourViewport",
props: {
renderingEngineId: {
type: String,
required: true
},
viewportId: {
type: String,
required: true
},
visitInfo: {
type: Object,
default: () => {
return {}
}
},
// visible: {
// type: Boolean,
// default: false
// },
},
data() {
return {
volumeId: null,
}
},
mounted() {
eventTarget.addEventListener(Enums.Events.WEB_WORKER_PROGRESS, (evt) => {
const { progress } = evt.detail;
console.log(progress, 'countour_progress')
});
},
methods: {
async setSeriesInfo(obj) {
try {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
let { volumeId, segmentationId } = obj
this.volumeId = volumeId
await setVolumesForViewports(
renderingEngine,
[{ volumeId, callback: setCtTransferFunctionForVolumeActor }],
[this.viewportId]
);
// const volumeActor = viewport.getDefaultActor()
// .actor;
// utilities.applyPreset(
// volumeActor,
// CONSTANTS.VIEWPORT_PRESETS.find((preset) => preset.name === 'CT-Bone')
// );
// volumeActor.setVisibility(false);
viewport.render();
const toolGroup = ToolGroupManager.getToolGroup(this.viewportId)
toolGroup.setToolActive(TrackballRotateTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary,
},
],
});
let s = segmentation.getActiveSegmentation(this.viewportId)
if (s) {
await segmentation.removeSegmentationRepresentation(this.viewportId, {
segmentationId: s.segmentationId,
type: csToolsEnums.SegmentationRepresentations.Surface,
})
}
console.log("ContourLoading...")
await segmentation.addSegmentationRepresentations(this.viewportId, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Contour,
},
]);
} catch (e) {
console.log(e)
}
},
}
}
</script>
<style lang="scss" scoped>
#ContourViewport {
width: 450px;
height: 300px;
position: fixed;
top: 100px;
z-index: -9999;
left: 100px;
}
</style>

View File

@ -76,9 +76,24 @@ import * as cornerstoneTools from '@cornerstonejs/tools'
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setCtMappingRange } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
import {
setPetTransferFunctionForVolumeActor
} from './helpers/index.js'
import { vec3, mat4 } from 'gl-matrix'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
import {
renderSegmentation,
readingSegmentByConfig,
selectSegmentation,
selectSegment,
createSegmentationRepresentation,
viewSegmentation,
viewSegment,
jumpBidirectional,
viewBidirectional,
changeColor,
resetViewport
} from "./helpers/segmentations"
export default {
name: 'MPRViewport',
props: {
@ -99,6 +114,36 @@ export default {
default: () => {
return {}
}
},
histogramVisible: {
type: Boolean,
default: false
},
actionConfiguration: {
type: Object,
default: () => {
return {}
}
},
SegmentConfig: {
type: Object,
default: () => {
return {}
}
},
curSegSeries: {
type: Object,
default: () => {
return {}
}
},
segmentIndex: {
type: Number,
default: 0
},
segmentationId: {
type: String,
default: ''
}
},
data() {
@ -140,6 +185,7 @@ export default {
rotateAngle: 0,
rotateBarLeft: 0,
loading: false,
toggleClipPlayTimer: null
}
},
mounted() {
@ -149,6 +195,39 @@ export default {
this.$nextTick(() => {
this.initViewport()
})
DicomEvent.$on('createSegmentationRepresentation', (segmentationId) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
createSegmentationRepresentation(this.viewportId, segmentationId)
})
DicomEvent.$on('viewSegmentation', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
viewSegmentation(obj, this.viewportId)
})
DicomEvent.$on('viewSegment', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
viewSegment(obj, this.viewportId)
})
DicomEvent.$on('jumpBidirectional', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
jumpBidirectional(obj, this.viewportId, this.volumeId)
})
DicomEvent.$on('viewBidirectional', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
viewBidirectional(obj, this.viewportId)
})
DicomEvent.$on('changeColor', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
changeColor(obj, this.viewportId)
})
DicomEvent.$on('resetViewport', () => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
resetViewport(this.viewportId)
})
DicomEvent.$on('renderSegmentation', async (viewportId) => {
// if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.VisitTaskId !== this.series.VisitTaskId) return false
if (this.viewportId !== viewportId) return false
await renderSegmentation(this.series, this.series.TaskInfo, this.viewportId, this.SegmentConfig, this.renderingEngineId, null, this.actionConfiguration)
})
},
watch: {
MPRInfo: {
@ -167,6 +246,25 @@ export default {
}
},
deep: true
},
SegmentConfig: {
handler() {
if (!this.segmentationId) return false
if (!this.series.TaskInfo) return false
readingSegmentByConfig(this.series, this.series.TaskInfo, this.viewportId, this.segmentationId, this.SegmentConfig)
},
deep: true
},
segmentIndex() {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.VisitTaskId !== this.series.VisitTaskId) return false
if (this.segmentIndex <= 0) return false
selectSegment(this.viewportId, this.segmentationId, this.segmentIndex)
},
segmentationId() {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.VisitTaskId !== this.series.VisitTaskId) return false
if (!this.segmentationId) return false
selectSegmentation(this.viewportId, this.segmentationId)
readingSegmentByConfig(this.series, this.series.TaskInfo, this.viewportId, this.segmentationId, this.SegmentConfig)
}
},
methods: {
@ -183,6 +281,7 @@ export default {
this.element.addEventListener("CORNERSTONE_VOLUME_NEW_IMAGE", this.stackNewImage)
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
this.element.addEventListener('wheel', (e) => {
// if (this.histogramVisible) return false
// console.log('CORNERSTONE_STACK_VIEWPORT_SCROLL')
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
@ -285,6 +384,7 @@ export default {
this.imageInfo.sliceThickness = type === this.series.orientation ? spacing[2] : spacing[0]
this.imageInfo.total = detail.numberOfSlices
this.getOrientationMarker()
this.$emit("resetHistogram")
let properties = viewport.getProperties(this.volumeId)
if (properties && properties.voiRange) {
var { lower, upper } = properties.voiRange
@ -392,11 +492,20 @@ export default {
this.playClipState = isPlay
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
if (isPlay) {
cornerstoneTools.utilities.cine.playClip(viewport.element, { framesPerSecond, loop: true })
this.toggleClipPlayTimer = setInterval(() => {
let index = this.series.SliceIndex + 1;
if (index > this.imageInfo.total - 1) index = 0
csUtils.jumpToSlice(viewport.element, { imageIndex: index });
}, framesPerSecond)
// cornerstoneTools.utilities.cine.playClip(viewport.element, { framesPerSecond, loop: true })
} else {
cornerstoneTools.utilities.cine.stopClip(viewport.element)
if (this.toggleClipPlayTimer) {
clearInterval(this.toggleClipPlayTimer)
this.toggleClipPlayTimer = null
}
// cornerstoneTools.utilities.cine.stopClip(viewport.element)
}
},
scrollPage(type) {
@ -490,7 +599,7 @@ export default {
.setVolumes([{
volumeId: this.volumeId, callback: (r) => {
if (this.series.Modality === 'PT') {
setPetColorMapTransferFunctionForVolumeActor(r, true)
setPetTransferFunctionForVolumeActor(r)
} else {
let volume = cache.getVolume(this.volumeId)
const voi = metaData.get('voiLutModule', volume._imageIds[Math.ceil((volume._imageIds.length - 1) / 2)])
@ -498,7 +607,6 @@ export default {
setCtTransferFunctionForVolumeActor(r)
}
console.log("渲染成功")
DicomEvent.$emit("isloaded", { isChange: false })
}
}]).then(r => {
if (data.isLocation || !this.imageInfo.zoom) {
@ -506,6 +614,22 @@ export default {
}
})
viewport.render()
if (this.series.Modality === 'PT') {
setTimeout(() => {
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true })
viewport.resetProperties()
viewport.setProperties({ voiRange: { upper: 5, lower: 0 } })
viewport.render()
renderingEngine.render()
}, 100)
}
await renderSegmentation(this.series, this.series.TaskInfo, this.viewportId, this.SegmentConfig, this.renderingEngineId, null, this.actionConfiguration)
DicomEvent.$emit('SegmentationLoading', this.viewportId)
let volume = cache.getVolume(this.volumeId)
// console.log(volume, 'volume')
if (this.series.orientation === 'AXIAL' && this.series.curIndex) return this.setFullScreen(this.series.curIndex)
let index = this.series.orientation === 'AXIAL' ? Math.ceil((volume._imageIds.length - 1) / 2) - 1 : Math.ceil((volume.dimensions[0]) / 2) - 1
this.setFullScreen(index)
} catch (e) {
console.log(e)
}
@ -680,6 +804,12 @@ export default {
return `NS: ${this.$store.state.trials.downloadTip}`
}
},
destroyed() {
if (this.toggleClipPlayTimer) {
clearInterval(this.toggleClipPlayTimer)
this.toggleClipPlayTimer = null
}
}
}
</script>
<style lang="scss" scoped>

View File

@ -123,6 +123,10 @@ export default {
type: Number,
required: true
},
activeTool: {
type: String,
default: ''
},
},
data() {
return {
@ -254,10 +258,15 @@ export default {
this.defaultWindowLevel.windowCenter = windowCenter
this.imageInfo.wwwc = `${Math.round(windowWidth)}/${Math.round(windowCenter)}`
}
const toolGroupId = this.viewportId
const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroup(toolGroupId)
const toolGroup =
cornerstoneTools.ToolGroupManager.getToolGroupForViewport(
this.viewportId,
this.renderingEngineId
) || cornerstoneTools.ToolGroupManager.getToolGroup(this.viewportId)
if (toolGroup) {
toolGroup.setToolEnabled('ScaleOverlay')
}
}
},
setFullScreen(index) {
@ -829,6 +838,7 @@ export default {
this.sliderInfo.isMove = false
},
handletoolsMouseWheel(e) {
if (this.activeTool === 'Crosshairs') return
const { viewportId, wheel } = e.detail
if (this.isMip) {
const container = document.getElementById('rotateBar')
@ -852,6 +862,7 @@ export default {
this.rotateBarInfo.isMove = false
},
rotateBarMousemove(e) {
if (this.activeTool === 'Crosshairs') return
//
if (!this.rotateBarInfo.isMove) return
const container = document.getElementById('rotateBar')
@ -867,6 +878,7 @@ export default {
this.rotateBarLeft = x
},
rotateBarMousedown(e) {
if (this.activeTool === 'Crosshairs') return
this.rotateBarInfo.initLeft = e.srcElement.offsetLeft
this.rotateBarInfo.initX = e.clientX
this.rotateBarInfo.isMove = true
@ -905,6 +917,7 @@ export default {
viewport.render()
},
clickRotate(e) {
if (this.activeTool === 'Crosshairs') return
// console.log('clickRotate')
const container = document.getElementById('rotateBar')
const containerWidth = container.offsetWidth

View File

@ -14,8 +14,8 @@
<div v-for="s in visitTaskList" v-show="activeTaskId === s.VisitTaskId" :key="s.VisitTaskId"
style="height:100%;">
<study-list v-if="selectArr.includes(s.VisitTaskId) && s.StudyList.length > 0" :ref="s.VisitTaskId"
:visit-task-info="s" :marked-series-ids="markedSeriesIds" @activeSeries="activeSeries"
@showMultiFrame="showMultiFrame" />
:visit-task-info="s" :marked-series-ids="markedSeriesIds" :readingTool="readingTool"
@activeSeries="activeSeries" @showMultiFrame="showMultiFrame" />
</div>
</div>
@ -165,6 +165,11 @@
v-if="(criterionType === 0 && readingTool === 0) || this.readingTool === 3">
<svg-icon icon-class="mpr" class="svg-icon" style="transform: rotate(180deg);" />
</div>
<!-- 直方图 -->
<div class="tool-item" :title="`${$t('trials:reading:button:histogram')}`" @click.prevent="openHistogram"
v-if="this.readingTool === 3">
<svg-icon icon-class="histogram" class="svg-icon" />
</div>
<!-- 十字准星 -->
<div :class="['tool-item', activeTool === 'Crosshairs' ? 'tool-item-active' : '']" v-if="isMPR"
:title="$t('trials:reading:button:crosshairs')" @click.prevent="setToolActive('Crosshairs')">
@ -175,6 +180,11 @@
@click.prevent="openFusion">
<svg-icon icon-class="fusion" class="svg-icon" />
</div>
<div :class="['tool-item', activeTool === 'Crosshairs' ? 'tool-item-active' : '']"
v-if="readingTool === 2 && isFusion" :title="$t('trials:reading:button:crosshairs')"
@click.prevent="setToolActive('Crosshairs')">
<svg-icon icon-class="crosshairs" class="svg-icon" />
</div>
<div v-for="tool in tools" :key="tool.toolName"
:class="['tool-item', readingTaskState === 2 ? 'tool-disabled' : '', activeTool === tool.toolName ? 'tool-item-active' : '']"
:style="{ cursor: tool.isDisabled ? 'not-allowed' : 'pointer' }"
@ -308,8 +318,13 @@
@dblclick="toggleFullScreen($event, index)" @click="activeViewport(index)">
<VolumeViewport :ref="`viewport-${index}`" :data-viewport-uid="`viewport-${index}`"
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-${index}`" :viewport-index="index"
@activeViewport="activeViewport" @toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" @contentMouseup="contentMouseup" v-if="readingTool === 3" />
:histogramVisible="histogramVisible" :actionConfiguration="actionConfiguration"
:SegmentConfig="SegmentConfig" :segmentationId.sync="segId" :segmentIndex.sync="segIndex"
:curSegSeries.sync="curSegSeries" @activeViewport="activeViewport"
@toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" @contentMouseup="contentMouseup"
@resetViewport="resetViewport" @resetHistogram="resetHistogram" v-if="readingTool === 3"
v-resize="(e) => handleSizeChange(e, `viewport-${index}`)" />
<Viewport :ref="`viewport-${index}`" :data-viewport-uid="`viewport-${index}`"
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-${index}`" :viewport-index="index"
@activeViewport="activeViewport" @toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@ -325,9 +340,13 @@
@dblclick="toggleFullScreen($event, index)" @click="activeViewport(index)">
<MPRViewport :ref="`viewport-MPR-${index}`" :data-viewport-uid="`viewport-MPR-${index}`"
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-MPR-${index}`"
:viewport-index="index" :MPRInfo="MPRInfo" @activeViewport="activeViewport" @setMPRInfo="setMPRInfo"
:viewport-index="index" :histogramVisible="histogramVisible"
:actionConfiguration="actionConfiguration" :SegmentConfig="SegmentConfig"
:segmentationId.sync="segId" :segmentIndex.sync="segIndex" :curSegSeries.sync="curSegSeries"
:MPRInfo="MPRInfo" @activeViewport="activeViewport" @setMPRInfo="setMPRInfo"
@toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" @contentMouseup="contentMouseup" />
@renderAnnotations="renderAnnotations" @contentMouseup="contentMouseup"
@resetHistogram="resetHistogram" v-resize="(e) => handleSizeChange(e, `viewport-MPR-${index}`)" />
</div>
</div>
<div v-if="readingTool === 2"
@ -335,15 +354,19 @@
:style="gridStyle">
<div v-for="(v, index) in cellsMax" v-show="index < cells.length" :key="`viewport-fusion-${index}`"
:class="['grid-cell', index === activeViewportIndex ? 'cell_active' : '', index === fullScreenIndex ? 'cell-full-screen' : '']"
@dblclick="toggleFullScreen($event, index)" @click="activeViewport(index)">
@dblclick="toggleFullScreen($event, index)" @click="activeViewport(index)"
@mouseenter="hoverFusionViewport(index)" @mouseleave="hoverFusionViewport(-1)">
<PetCtViewport :ref="`viewport-fusion-${index}`" :data-viewport-uid="`viewport-fusion-${index}`"
:rendering-engine-id="renderingEngineId" :viewport-id="`viewport-fusion-${index}`"
:viewport-index="index" @activeViewport="activeViewport"
:viewport-index="index" :active-tool="activeTool" @activeViewport="activeViewport"
@toggleTaskByViewport="toggleTaskByViewport" @previewCD="previewCD"
@renderAnnotations="renderAnnotations" @upperRangeChange="upperRangeChange"
@contentMouseup="contentMouseup" />
</div>
</div>
<div v-if="readingTool === 2" class="fusion-hidden-viewports">
<div ref="viewport-fusion-hidden-sag" class="fusion-hidden-viewport" />
</div>
</div>
</div>
<!-- 表单 -->
@ -352,9 +375,11 @@
<el-tab-pane :label="$t('trials:reading:dicom3D:tabs:segment')" name="segment">
<Segmentations ref="Segmentations" :visitInfo="taskInfo" :isMPR="isMPR"
:volumeToolGroupId="volumeToolGroupId" :viewportKey="viewportKey" :global-loading.sync="loading"
:loadingText.sync="loadingText" :rendering-engine-id="renderingEngineId"
:activeViewportIndex="activeViewportIndex" :activeTool.sync="activeTool"
:actionConfiguration="actionConfiguration" @setToolsPassive="setToolsPassive"
:trialCriterion="trialCriterion" :loadingText.sync="loadingText"
:rendering-engine-id="renderingEngineId" :SegmentConfig="SegmentConfig" :segId.sync="segId"
:segIndex.sync="segIndex" :curSegSeries.sync="curSegSeries" :activeViewportIndex="activeViewportIndex"
:activeTool.sync="activeTool" :actionConfiguration="actionConfiguration"
:histogramVisible="histogramVisible" @setToolsPassive="setToolsPassive"
@resetQuestion="resetQuestion" />
</el-tab-pane>
<el-tab-pane :label="$t('trials:reading:dicom3D:tabs:ecrf')" name="ecrf">
@ -486,6 +511,14 @@
<SegmentForm ref="SegmentForm" v-if="segmentVisible" :visible.sync="segmentVisible" :visitInfo="segmentVisitInfo"
@handleSegmentSave="handleSegmentSave" />
</el-dialog>
<!--直方图-->
<histogram ref="histogram" v-if="readingTool === 3" :visible.sync="histogramVisible" :activeTool.sync="activeTool"
:viewportKey="viewportKey" :rendering-engine-id="renderingEngineId" :activeViewportIndex="activeViewportIndex" />
<!--分割可视化窗口-->
<!-- <SurfaceViewport ref="surfaceViewport" viewportId="surfaceViewport" v-if="readingTool === 3"
:visible.sync="surfaceVisible" :renderingEngineId="renderingEngineId" :visitInfo="taskInfo" />
<ContourViewport ref="contourViewport" viewportId="contourViewport" v-if="readingTool === 3"
:renderingEngineId="renderingEngineId" :visitInfo="taskInfo" /> -->
<upload-dicom-and-nonedicom v-if="uploadImageVisible" :subject-id="uploadSubjectId"
:subject-code="uploadSubjectCode" :criterion="uploadTrialCriterion" :visible.sync="uploadImageVisible"
:visit-task-id="taskId" :is-reading-task-view-in-order="isReadingTaskViewInOrder" />
@ -504,6 +537,7 @@ import {
RenderingEngine,
Enums,
// imageLoader,
// CONSTANTS,
metaData,
volumeLoader,
getRenderingEngine,
@ -524,6 +558,9 @@ import PetCtViewport from './PetCtViewport'
import MPRViewport from './MPRViewport'
import VolumeViewport from './VolumeViewport'
import Segmentations from './Segmentations'
import histogram from "./histogram"
// import SurfaceViewport from "./SurfaceViewport"
// import ContourViewport from "./ContourViewport"
import mRecisit from './mRecist/QuestionList'
import recisit from './Recist/QuestionList'
import customizeQuestionList from './customize/QuestionList'
@ -554,6 +591,9 @@ const {
ToolGroupManager,
Enums: csToolsEnums,
StackScrollTool,
TrackballRotateTool,
PlanarFreehandContourSegmentationTool,
SplineContourSegmentationTool,
// ScaleOverlayTool,
PanTool,
ZoomTool,
@ -615,6 +655,9 @@ export default {
MPRViewport,
VolumeViewport,
Segmentations,
histogram,
// SurfaceViewport,
// ContourViewport,
mRecisit,
recisit,
customizeQuestionList,
@ -648,7 +691,7 @@ export default {
activeTaskIndex: -1,
activeStudyIndex: -1,
activeSeriesIndex: -1,
currentVisitInfo: null,
currentVisitInfo: {},
layout: 1,
cellsMax: 4,
rows: 1,
@ -729,6 +772,7 @@ export default {
isMPR: false,
volumeToolGroupId: "share-viewport-volume",
fusionToolGroupId: "share-viewport-fusion",
MPRInfo: {
AXIAL: {
imageNum: 0
@ -758,12 +802,28 @@ export default {
},
},
},
SegmentConfig: {
renderOutline: true,
renderFill: true,
fillAlpha: 0.5,
outlineWidth: 1,
InactiveSegmentations: {
show: true,
fillAlpha: 0.3,
}
},
segId: null,
segIndex: null,
curSegSeries: {},
fusionOverlayModality: null,
lastUpper: null,
hasFusionUpperInitialized: false,
timer: null,
timer: {},
FullTimerOut: null,
isDelay: false
isDelay: false,
histogramVisible: false,
// surfaceVisible: false
}
},
computed: {
@ -856,7 +916,38 @@ export default {
this.setToolsPassive()
}
}
}
},
// histogramVisible: {
// handler() {
// if (this.readingTool !== 3) return false
// this.setToolsPassive()
// let viewportIds = ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3', this.volumeToolGroupId]
// // if (this.isMPR) {
// // viewportIds = [this.volumeToolGroupId]
// // }
// viewportIds.forEach(id => {
// const toolGroup = ToolGroupManager.getToolGroup(id)
// if (this.histogramVisible) {
// toolGroup.setToolEnabled(StackScrollTool.toolName)
// } else {
// toolGroup.setToolActive(StackScrollTool.toolName, {
// bindings: [{ mouseButton: MouseBindings.Wheel }]
// })
// let annotations = annotation.state.getAllAnnotations().filter(item => item.metadata.toolName.includes('histogram_'));
// annotations.forEach(item => {
// annotation.state.removeAnnotation(item.annotationUID)
// })
// for (let i = 0; i < this.cells.length; i++) {
// const viewportId = `${this.viewportKey}-${i}`
// let renderingEngine = getRenderingEngine(renderingEngineId)
// const viewport = renderingEngine.getViewport(viewportId)
// viewport.render()
// }
// }
// })
// }
// },
},
mounted() {
this.taskInfo = JSON.parse(sessionStorage.getItem('taskInfo'))
@ -904,6 +995,36 @@ export default {
this.getSystemInfoReading();
},
methods: {
resetHistogram() {
if (!this.histogramVisible) return false
if (this.timer['histogram']) {
clearTimeout(this.timer['histogram'])
this.timer['histogram'] = null
}
this.timer['histogram'] = setTimeout(() => {
if (this.$refs.histogram && this.histogramVisible) {
this.$refs.histogram.init()
}
clearTimeout(this.timer['histogram'])
this.timer['histogram'] = null
}, 500)
},
showSurface(obj) {
// this.surfaceVisible = true
// this.$refs.contourViewport.setSeriesInfo(obj)
// this.$refs.surfaceViewport.setSeriesInfo(obj)
},
async openHistogram() {
this.histogramVisible = true
this.setToolsPassive()
this.$refs.histogram.init()
},
handleSizeChange(e, viewportId) {
let index = this.$refs[viewportId][0].series.SliceIndex
this.resetRenderingEngine(viewportId, index)
},
resetQuestion() {
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].getQuestions(false)
this.$refs[`ecrf_${this.lastViewportTaskId}`][0].initSegmentBinding()
@ -1240,6 +1361,27 @@ export default {
}
}
]
// let element5 = this.$refs.surfaceViewport.$el
// let element6 = this.$refs.contourViewport.$el
// viewportInputArray.push({
// viewportId: 'surfaceViewport',
// type: ViewportType.VOLUME_3D,
// element: element5,
// defaultOptions: {
// orientation: Enums.OrientationAxis.CORONAL,
// background: [1, 1, 1]
// }
// })
// viewportInputArray.push({
// viewportId: 'contourViewport',
// type: ViewportType.ORTHOGRAPHIC,
// element: element6,
// defaultOptions: {
// orientation: Enums.OrientationAxis.AXIAL
// }
// })
// viewportIds.push('surfaceViewport')
// viewportIds.push('contourViewport')
}
if ((this.criterionType === 0 && this.readingTool === 0) || this.readingTool === 3) {
const volumeElement1 = this.$refs['viewport-MPR-0'][0].$el
@ -1279,6 +1421,7 @@ export default {
const fusionElement2 = this.$refs['viewport-fusion-1'][0].$el
const fusionElement3 = this.$refs['viewport-fusion-2'][0].$el
const fusionElement4 = this.$refs['viewport-fusion-3'][0].$el
const fusionHiddenSag = this.$refs['viewport-fusion-hidden-sag']
const arr = [
{
viewportId: 'viewport-fusion-0',
@ -1313,13 +1456,23 @@ export default {
orientation: Enums.OrientationAxis.CORONAL,
background: [1, 1, 1]
}
},
{
viewportId: 'viewport-fusion-hidden-sag',
type: ViewportType.ORTHOGRAPHIC,
element: fusionHiddenSag,
defaultOptions: {
orientation: Enums.OrientationAxis.SAGITTAL,
background: [1, 1, 1]
}
}
]
viewportInputArray = [...viewportInputArray, ...arr]
viewportIds = viewportIds.concat(fusionViewportIds)
viewportIds = viewportIds.concat(fusionViewportIds, ['viewport-fusion-hidden-sag'])
}
renderingEngine.setViewports(viewportInputArray)
this.addAnnotationListeners()
// cornerstoneTools.addTool(TrackballRotateTool)
cornerstoneTools.addTool(StackScrollTool)
cornerstoneTools.addTool(PanTool)
cornerstoneTools.addTool(ZoomTool)
@ -1344,24 +1497,63 @@ export default {
cornerstoneTools.addTool(LabelMapEditWithContourTool)
cornerstoneTools.addTool(BrushTool)
cornerstoneTools.addTool(SegmentBidirectionalTool)
// cornerstoneTools.addTool(PlanarFreehandContourSegmentationTool);
// cornerstoneTools.addTool(SplineContourSegmentationTool);
viewportIds.forEach((viewportId, i) => {
// const toolGroupId = `viewport-${i}`
let toolGroupId = viewportId
if (volumeViewportIds.includes(viewportId)) {
toolGroupId = this.volumeToolGroupId
} else if (viewportId.startsWith('viewport-fusion-')) {
toolGroupId = this.fusionToolGroupId
}
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId) ? ToolGroupManager.getToolGroup(toolGroupId) : ToolGroupManager.createToolGroup(toolGroupId)
toolGroup.addViewport(viewportId, renderingEngineId)
if (toolGroupId.includes('surface')) {
// toolGroup.addTool(TrackballRotateTool.toolName, {
// rotateSampleDistanceFactor: 0,
// });
// toolGroup.setToolActive(TrackballRotateTool.toolName, {
// bindings: [
// {
// mouseButton: MouseBindings.Primary,
// },
// ],
// });
} else if (toolGroupId.includes('contour')) {
// toolGroup.addTool(PlanarFreehandContourSegmentationTool.toolName);
// toolGroup.addTool(SplineContourSegmentationTool.toolName);
// toolGroup.setToolActive(PlanarFreehandContourSegmentationTool.toolName, {
// bindings: [
// {
// mouseButton: MouseBindings.Primary, // Middle Click
// },
// ],
// });
} else {
toolGroup.addTool(StackScrollTool.toolName, {
loop: true, //
})
toolGroup.addTool(ScaleOverlayTool.toolName)
toolGroup.addTool(PanTool.toolName)
toolGroup.addTool(ZoomTool.toolName)
toolGroup.addTool(BrushTool.toolName)
if (this.readingTool === 3 || toolGroupId === this.volumeToolGroupId) {
toolGroup.addToolInstance(
'histogram_RectangleROI',
RectangleROITool.toolName,
);
toolGroup.addToolInstance(
'histogram_CircleROI',
CircleROITool.toolName,
);
toolGroup.addToolInstance(
'histogram_PlanarFreehandROI',
PlanarFreehandROITool.toolName,
);
toolGroup.addToolInstance(
'CircularBrush',
BrushTool.toolName,
@ -1409,6 +1601,11 @@ export default {
toolGroup.addTool(CrosshairsTool.toolName, {
getReferenceLineColor: this.setCrosshairsToolLineColor
});
} else if (toolGroupId === this.fusionToolGroupId) {
toolGroup.addTool(CrosshairsTool.toolName, {
getReferenceLineColor: this.setCrosshairsToolLineColor,
getReferenceLineSlabThicknessControlsOn: (otherViewportId) => otherViewportId !== 'viewport-fusion-3'
});
} else {
toolGroup.addTool(WindowLevelTool.toolName)
}
@ -1450,26 +1647,20 @@ export default {
toolGroup.addTool(EllipticalROITool.toolName, {
getTextLines: this.getEllipticalROIToolTextLines
})
toolGroup.addTool(FixedRadiusCircleROITool.toolName), {
toolGroup.addTool(FixedRadiusCircleROITool.toolName, {
radius: Number.isFinite(this.taskInfo.CircleRadius) ? this.taskInfo.CircleRadius : 6,
getTextLines: this.getCircleROIToolTextLines
}
})
toolGroup.addTool(AngleTool.toolName, {
getTextLines: this.getAngleToolTextLines
})
toolGroup.addTool(CobbAngleTool.toolName, {
getTextLines: this.getCobbAngleToolTextLines
})
if (toolGroupId === 'viewport-fusion-3') {
if (viewportId === 'viewport-fusion-3') {
toolGroup.addTool(VolumeRotateTool.toolName)
toolGroup.setToolActive(VolumeRotateTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Wheel // mouse wheel
}
]
})
toolGroup.addTool(MIPJumpToClickTool.toolName, {
targetViewportIds: fusionViewportIds.filter((id) => id !== toolGroupId)
targetViewportIds: fusionViewportIds.filter((id) => id !== viewportId)
})
// Set the initial state of the tools, here we set one tool active on left click.
@ -1482,6 +1673,7 @@ export default {
]
})
}
toolGroup.setToolConfiguration(
ScaleOverlayTool.toolName,
{
@ -1501,7 +1693,6 @@ export default {
toolGroup.setToolPassive(WindowLevelTool.toolName)
toolGroup.setToolPassive(WindowLevelRegionTool.toolName)
toolGroup.setToolPassive(PlanarRotateTool.toolName)
if (this.readingTaskState < 2) {
toolGroup.setToolPassive(ArrowAnnotateTool.toolName)
toolGroup.setToolPassive(RectangleROITool.toolName)
@ -1528,9 +1719,14 @@ export default {
toolGroup.setToolEnabled(FixedRadiusCircleROITool.toolName)
toolGroup.setToolEnabled(AngleTool.toolName)
toolGroup.setToolEnabled(CobbAngleTool.toolName)
if (this.readingTool === 3) toolGroup.setToolEnabled(LabelMapEditWithContourTool.toolName)
if (this.readingTool === 3) {
toolGroup.setToolEnabled(LabelMapEditWithContourTool.toolName)
toolGroup.setToolEnabled(SegmentBidirectionalTool.toolName);
}
}
toolGroup.setToolPassive(EraserTool.toolName)
}
})
eventTarget.addEventListener('cornerstoneimageloadprogress', this.imageLoadProgress)
// console.log(Events, toolsEvents)
@ -1570,6 +1766,10 @@ export default {
setNetWorkSpeedSizeAll(percentComplete, detail.total, imageId)
}
}
if (this.readingTool === 3) {
getNetWorkSpeed()
setNetWorkSpeedSizeAll(percentComplete, detail.total, imageId)
}
if (percentComplete === 100) {
workSpeedclose()
}
@ -1674,9 +1874,10 @@ export default {
annotationCompletedListener(e) {
console.log('Completed')
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName.includes('histogram_')) return this.$refs.histogram.initToolValue(annotation)
if (this.readingTaskState === 2) return
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
@ -1699,10 +1900,11 @@ export default {
},
annotationModifiedListener(e) {
console.log('Modified')
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation.highlighted) return
if (!annotation) return
if (annotation.metadata.toolName.includes('histogram_')) return this.$refs.histogram.initToolValue(annotation)
if (this.readingTaskState === 2) return
if (!annotation.highlighted) return
if (annotation.metadata.toolName === 'PlanarFreehandROI' && !annotation.data.contour.closed) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
@ -1742,9 +1944,10 @@ export default {
}
},
async customAnnotationCompletedListener(e) {
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation) return
if (annotation.metadata.toolName.includes('histogram_')) return this.$refs.histogram.initToolValue(annotation)
if (this.readingTaskState === 2) return
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1) {
if (annotation.metadata.toolName !== LabelMapEditWithContourTool.toolName) this.setToolsPassive()
@ -1829,10 +2032,11 @@ export default {
}
},
customAnnotationModifiedListener(e) {
if (this.readingTaskState === 2) return
const { annotation } = e.detail
if (!annotation.highlighted) return
if (!annotation) return
if (annotation.metadata.toolName.includes('histogram_')) return this.$refs.histogram.initToolValue(annotation)
if (this.readingTaskState === 2) return
if (!annotation.highlighted) return
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1) {
if (annotation.metadata.toolName !== LabelMapEditWithContourTool.toolName) this.setToolsPassive()
@ -1864,8 +2068,14 @@ export default {
const errorMsg = { message: 'annotation Not allowed to operate' }
throw errorMsg
}
if (this.activeTool !== 'Eraser') return false
const i = this.tools.findIndex(i => i.toolName === annotation.metadata.toolName)
if (i === -1) {
if (annotation.metadata.toolName === SegmentBidirectionalTool.toolName) {
this.setToolsPassive()
const errorMsg = { message: 'SegmentBidirectionalTool Not remove' }
throw errorMsg
}
//
return
}
@ -2315,21 +2525,61 @@ export default {
const factor = 10 ** precision
return (Math.round(num * factor + 0.0000001) / factor).toFixed(precision)
},
getActiveToolGroupId() {
if (this.isMPR) return this.volumeToolGroupId
if (this.isFusion) return this.fusionToolGroupId
return `${this.viewportKey}-${this.activeViewportIndex}`
},
getCurrentToolGroupIds() {
if (this.isMPR) return [this.volumeToolGroupId]
if (this.isFusion) return [this.fusionToolGroupId]
return [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
},
setFusionMipJumpEnabled(enabled) {
if (!this.isFusion) return
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
if (!toolGroup || !toolGroup.hasTool(MIPJumpToClickTool.toolName)) return
if (enabled) {
toolGroup.setToolActive(MIPJumpToClickTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
} else {
toolGroup.setToolDisabled(MIPJumpToClickTool.toolName)
}
},
setFusionMipRotateEnabled(enabled) {
if (!this.isFusion) return
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
if (!toolGroup || !toolGroup.hasTool(VolumeRotateTool.toolName)) return
if (enabled) {
toolGroup.setToolActive(VolumeRotateTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
} else {
toolGroup.setToolDisabled(VolumeRotateTool.toolName)
}
},
//
setToolActive(toolName) {
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
if (this.histogramVisible) return false
const toolGroupId = this.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool === toolName) {
if (toolName === CrosshairsTool.toolName) {
toolGroup.setToolDisabled(this.activeTool)
this.setFusionMipJumpEnabled(true)
this.setFusionMipRotateEnabled(true)
} else {
toolGroup.setToolPassive(this.activeTool)
}
this.activeTool = ''
} else {
if (this.activeTool) {
if (toolName === CrosshairsTool.toolName) {
if (this.activeTool === CrosshairsTool.toolName) {
toolGroup.setToolDisabled(this.activeTool)
this.setFusionMipJumpEnabled(true)
this.setFusionMipRotateEnabled(true)
} else {
toolGroup.setToolPassive(this.activeTool)
}
@ -2337,18 +2587,67 @@ export default {
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
if (toolName === CrosshairsTool.toolName) {
if (this.isFusion) {
const instance = toolGroup.getToolInstance?.(CrosshairsTool.toolName)
if (instance && !instance.__fusionSameForPatched) {
instance.__fusionSameForPatched = true
const original = instance._checkIfViewportsRenderingSameScene?.bind(instance)
instance._checkIfViewportsRenderingSameScene = (viewport, otherViewport) => {
try {
const a = viewport?.getFrameOfReferenceUID?.()
const b = otherViewport?.getFrameOfReferenceUID?.()
if (a && b && a === b) return true
} catch (e) { }
return original ? original(viewport, otherViewport) : true
}
}
}
this.setFusionMipJumpEnabled(false)
this.setFusionMipRotateEnabled(false)
}
this.activeTool = toolName
}
},
hoverFusionViewport(index) {
if (!this.isFusion) return
if (this.activeTool === CrosshairsTool.toolName) return
const toolGroup = ToolGroupManager.getToolGroup(this.fusionToolGroupId)
if (!toolGroup) return
const isMip = index === 3
this.setFusionMipJumpEnabled(isMip)
if (isMip) {
if (toolGroup.hasTool(StackScrollTool.toolName)) {
toolGroup.setToolDisabled(StackScrollTool.toolName)
}
if (toolGroup.hasTool(VolumeRotateTool.toolName)) {
toolGroup.setToolActive(VolumeRotateTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
}
} else {
if (toolGroup.hasTool(VolumeRotateTool.toolName)) {
toolGroup.setToolDisabled(VolumeRotateTool.toolName)
}
if (toolGroup.hasTool(StackScrollTool.toolName)) {
toolGroup.setToolActive(StackScrollTool.toolName, {
bindings: [{ mouseButton: MouseBindings.Wheel }]
})
}
}
},
//
setAnnotateToolActive(toolName) {
// if (this.readingTaskState === 2) return
if (this.histogramVisible) return false
const toolObj = this.tools.find(i => i.toolName === toolName)
if (!toolObj || toolObj.isDisabled) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroupId = this.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool === toolName) {
if (toolName === CrosshairsTool.toolName) {
toolGroup.setToolDisabled(this.activeTool)
@ -2379,8 +2678,9 @@ export default {
if (this.activeTool === toolName) return
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroupId = this.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool) {
if (this.activeTool === CrosshairsTool.toolName) {
toolGroup.setToolDisabled(this.activeTool)
@ -2399,8 +2699,9 @@ export default {
if (this.activeTool && this.toolNames.includes(this.activeTool)) {
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroupId = this.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool === CrosshairsTool.toolName) {
toolGroup.setToolDisabled(this.activeTool)
} else {
@ -2412,11 +2713,13 @@ export default {
},
setMoreToolActive(toolName) {
// if (this.readingTaskState === 2) return
if (this.histogramVisible) return false
this.setToolsPassive()
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
if (series && series.TaskInfo.VisitTaskId && series.TaskInfo.VisitTaskId === this.taskInfo.VisitTaskId) {
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroupId = this.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
@ -2425,13 +2728,10 @@ export default {
},
setToolsPassive() {
if (!this.activeTool) return
let toolGroupIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
if (this.isMPR) {
// toolGroupIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`]
toolGroupIds = [this.volumeToolGroupId]
}
const toolGroupIds = this.getCurrentToolGroupIds()
toolGroupIds.forEach(toolGroupId => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
if (this.activeTool === CrosshairsTool.toolName) {
toolGroup.setToolDisabled(this.activeTool)
} else {
@ -2442,13 +2742,10 @@ export default {
},
setToolEnabled() {
if (!this.activeTool) return
let toolGroupIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`, `${this.viewportKey}-3`]
if (this.isMPR) {
// toolGroupIds = [`${this.viewportKey}-0`, `${this.viewportKey}-1`, `${this.viewportKey}-2`]
toolGroupIds = [this.volumeToolGroupId]
}
const toolGroupIds = this.getCurrentToolGroupIds()
toolGroupIds.forEach(toolGroupId => {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
toolGroup.setToolEnabled(this.activeTool)
})
this.activeTool = ''
@ -2531,20 +2828,28 @@ export default {
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resetOrientationMarkers()
let index = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series.SliceIndex
if (this.readingTool !== 3) {
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true })
}
viewport.resetProperties()
if (this.isMPR) {
let volume = cache.getVolume(this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].volumeId)
const voi = metaData.get('voiLutModule', volume._imageIds[Math.ceil((volume._imageIds.length - 1) / 2)])
const lower = voi.windowCenter[0] - voi.windowWidth[0] / 2
const upper = voi.windowCenter[0] + voi.windowWidth[0] / 2 - 1
console.log(lower, upper)
viewport.setProperties({ voiRange: { upper: upper, lower: lower } })
}
if (this.readingTool === 3 && this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series.Modality === 'PT') {
viewport.setProperties({ voiRange: { upper: 5, lower: 0 } })
}
viewport.render()
renderingEngine.render()
if (this.readingTool === 3) {
DicomEvent.$emit('isloaded', { isChange: false })
DicomEvent.$emit('isloaded', { isChange: false, viewportId })
}
if (this.readingTool === 3 || this.isMPR) {
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setFullScreen(index)
}
},
//
@ -2553,6 +2858,7 @@ export default {
this.fullScreenIndex = null
this.layout = v
const series = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series
let index = series.SliceIndex
const seriesArr = []
if (v === 1) {
this.rows = 1
@ -2565,6 +2871,7 @@ export default {
this.rows = 1
this.cols = 2
this.activeViewportIndex = 1
series.curIndex = index
if (typeof series === 'object') {
seriesArr.push(series)
seriesArr.push(series)
@ -2614,6 +2921,7 @@ export default {
} else if (v === 4) {
this.rows = 2
this.cols = 2
series.curIndex = index
if (typeof series === 'object') {
seriesArr.push(series)
seriesArr.push(series)
@ -2676,19 +2984,22 @@ export default {
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(`${this.viewportKey}-${this.activeViewportIndex}`)
const { invert } = viewport.getProperties()
if (this.isFusion) {
if (this.isFusion || this.readingTool === 3) {
viewport.setProperties({ invert: !invert }, this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].volumeId)
}
} else {
viewport.setProperties({ invert: !invert })
}
viewport.render()
},
//
scrollPage(type) {
// if (this.histogramVisible) return false
this.clipPlaying = false
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].scrollPage(type)
},
//
toggleClipPlay(isPlay) {
// if (this.histogramVisible) return false
this.clipPlaying = !this.clipPlaying
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].toggleClipPlay(isPlay, this.fps)
},
@ -2821,20 +3132,26 @@ export default {
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].resize(forceFitToWindow)
},
//
resetRenderingEngine() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
resetRenderingEngine(viewportId = null, i) {
if (this.timer[viewportId]) {
clearInterval(this.timer[viewportId])
this.timer[viewportId] = null
}
let index = null
this.timer = setTimeout(() => {
index = index || index === 0 ? index : this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series.SliceIndex
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(viewportId)
if (!viewport) return false
if (viewport.volumeIds.size <= 0) return false
let index = null
this.timer[viewportId] = setTimeout(() => {
index = i || i === 0 ? i : this.$refs[viewportId ? viewportId : `${this.viewportKey}-${this.activeViewportIndex}`][0].series.SliceIndex
renderingEngine.resize(true, false)
renderingEngine.render()
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setFullScreen(index)
clearTimeout(this.timer)
this.timer = null
this.$refs[viewportId ? viewportId : `${this.viewportKey}-${this.activeViewportIndex}`][0].setFullScreen(index)
clearTimeout(this.timer[viewportId])
this.timer[viewportId] = null
if (this.readingTool === 3) {
DicomEvent.$emit('isloaded', { isChange: false, viewportId })
}
}, 100)
},
setDelay(time) {
@ -2854,12 +3171,12 @@ export default {
if (this.readingTool === 3 || this.isMPR) {
// this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series)
this.$nextTick(() => {
this.resetRenderingEngine()
// this.resetRenderingEngine(`${this.viewportKey}-${index}`)
this.isDelay = true
this.setDelay(2000)
if (this.readingTool === 3) {
DicomEvent.$emit('isloaded', { isChange: false })
}
// if (this.readingTool === 3) {
// DicomEvent.$emit('isloaded', { isChange: false })
// }
})
}
@ -2958,6 +3275,7 @@ export default {
const confirm = await this.$confirm(this.$t('trials:reading:confirm:changeStack'))
if (!confirm) return false
}
if (this.histogramVisible) this.$refs.histogram.close()
this.isFusion = false
this.setToolsPassive()
this.rows = 1
@ -2966,8 +3284,10 @@ export default {
this.fullScreenIndex = null
this.isMPR = false
obj.isChange = false
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
return this.$nextTick(() => {
DicomEvent.$emit('activeSeries', obj)
DicomEvent.$emit('changeMPR')
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(obj)
const renderingEngine = getRenderingEngine(renderingEngineId)
renderingEngine.resize(true, false)
renderingEngine.render()
@ -2977,6 +3297,7 @@ export default {
})
}
if (this.histogramVisible) this.$refs.histogram.close()
if (!obj.IsDicom) {
return this.previewNoneDicoms(obj)
}
@ -3015,7 +3336,14 @@ export default {
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
}
}
if (this.readingTool === 3) {
this.$nextTick(() => {
DicomEvent.$emit('SegmentationLoading', `${this.viewportKey}-${this.activeViewportIndex}`)
})
}
if (this.activeTool !== CrosshairsTool.toolName) {
this.setToolsPassive()
}
},
getRelatedSeries(visitTaskInfo, baselineSeries) {
let obj = {}
@ -3139,13 +3467,15 @@ export default {
if (i === -1) return
const studyList = this.visitTaskList[i].StudyList
let series = null
let curSeriesId = this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].series.Id
if (obj.segment) {
let study = studyList.find(item => item.StudyId === obj.segmentGroup.StudyId)
series = study.SeriesList.find(item => item.Id === obj.segmentGroup.SeriesId)
series.SliceIndex = 0
series.segment = obj.segment
let Series = study.SeriesList.find(item => item.Id === obj.segmentGroup.SeriesId)
Series.SliceIndex = 0
series = Object.assign(Series, { segment: obj.segment })
} else {
series = this.getMarkedSeries(studyList, obj.annotation, true)
delete series.segment
}
if (series) {
@ -3188,7 +3518,7 @@ export default {
}
}
}
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series, obj.segment ? false : true)
this.$refs[`${this.viewportKey}-${this.activeViewportIndex}`][0].setSeriesInfo(series, series.segment || curSeriesId !== series.Id ? false : true)
this.$refs[series.TaskInfo.VisitTaskId][0].setSeriesActive(series.StudyIndex, series.SeriesIndex)
}
},
@ -3380,8 +3710,9 @@ export default {
this.setToolsPassive()
}
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroupId = this.getActiveToolGroupId()
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (!toolGroup) return
toolGroup.setToolActive(toolName, {
bindings: [{ mouseButton: MouseBindings.Primary }]
})
@ -3656,6 +3987,8 @@ export default {
if (series.ImageIds.length <= 5) return this.$confirm(this.$t('trials:reading:confirm:smallNumberOfimage'), this.$t('system:menu:confirm:title:warning'), {
type: 'warning'
})
series.curIndex = series.SliceIndex
DicomEvent.$emit('changeMPR')
if (series.ImageIds.length > 500) {
let res = await this.getSystemInfo()
if (!res) return false
@ -3740,6 +4073,7 @@ export default {
this.$refs[`viewport-fusion-1`][0].setSeriesInfo(ptData, false, { colorMap: false })
this.$refs[`viewport-fusion-2`][0].setSeriesInfo(fusionData, false, { isFusion: true, colorMap: true })
this.$refs[`viewport-fusion-3`][0].setSeriesInfo(ptData, false, { isMip: true, colorMap: false })
await this.initFusionHiddenSagViewport(pt)
// this.resetAnnotation = false
this.$nextTick(() => {
this.$refs[`colorMap`].init()
@ -3770,6 +4104,18 @@ export default {
}
return false
},
async initFusionHiddenSagViewport(pt) {
const ptVolumeId = pt?.SeriesInstanceUid
if (!ptVolumeId || !cache.getVolume(ptVolumeId)) return
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const sagViewport = renderingEngine?.getViewport?.('viewport-fusion-hidden-sag')
if (!sagViewport) return
await sagViewport.setVolumes([{ volumeId: ptVolumeId }])
const midIndex = Math.max(0, Math.floor((pt.ImageIds?.length || 1) / 2))
await csUtils.jumpToSlice(sagViewport.element, { imageIndex: midIndex })
sagViewport.render()
},
async getVolume(serie, isFusion = false) {
return new Promise(async res => {
let volumeId = `${isFusion ? 'fusion_' : ''}` + serie.SeriesInstanceUid;
@ -3954,8 +4300,14 @@ export default {
this.saveCustomAnnotationTimer = null
}
if (this.timer) {
clearInterval(this.timer)
this.timer = null
Object.keys(this.timer).forEach(key => {
if (this.timer[key]) {
clearInterval(this.timer[key])
clearTimeout(this.timer[key])
this.timer[key] = null
}
})
this.timer = {}
}
if (this.FullTimerOut) {
clearTimeout(this.FullTimerOut)
@ -4255,6 +4607,22 @@ export default {
position: relative;
}
.fusion-hidden-viewports {
position: absolute;
left: -100000px;
top: -100000px;
width: 512px;
height: 512px;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
.fusion-hidden-viewport {
width: 512px;
height: 512px;
}
.viewports-box {
display: grid;
position: absolute;

View File

@ -2,21 +2,24 @@
<el-form ref="segmentForm" :model="form" label-width="120px" label-position="left" :rules="rules">
<!-- 检查名称 -->
<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')">
<el-option v-for="item in studyList" :key="item.StudyId" :label="item.StudyCode"
:value="item.StudyId" />
</el-select>
</el-form-item>
<!-- 序列名称 -->
<el-form-item :label="$t('segment:form:label:seriesName')" prop="taskBlindName">
<el-select v-model="form.seriesId" clearable @change="(e) => handleChange(e, 'series')">
<el-select v-model="form.seriesId" clearable @change="(e) => handleChange(e, 'series')"
@clear="(e) => handleClear(e, 'series')">
<el-option v-for="item in seriesList" :key="item.Id"
:label="`#${item.SeriesNumber}/${item.Modality}/${item.Description}`" :value="item.Id" />
</el-select>
</el-form-item>
<!-- 分割组名称 -->
<el-form-item :label="$t('segment:form:label:segmentGroupName')" prop="segmentGroupId">
<el-select v-model="form.segmentGroupId" clearable @change="(e) => handleChange(e, 'segmentGroup')">
<el-select v-model="form.segmentGroupId" clearable @change="(e) => handleChange(e, 'segmentGroup')"
@clear="(e) => handleClear(e, 'segmentGroup')">
<el-option v-for="item in segmentGroupList" :key="item.Id" :label="item.SegmentationName"
:value="item.Id" />
</el-select>
@ -106,11 +109,22 @@ export default {
})
this.studyList = studyList
if (this.visitInfo.operateStateEnum === 21) {
let find = studyList.some(item => item.StudyId === this.series.StudyId)
if (find) {
this.form.studyId = this.series.StudyId
this.handleChange(null, 'study')
if (find.SeriesArr && find.SeriesArr.length > 0) {
let findSeries = find.SeriesArr.some(item => item.Id === this.form.seriesId)
if (findSeries) {
this.form.seriesId = this.series.Id
this.handleChange(null, 'series')
}
}
}
}
if (this.visitInfo.operateStateEnum === 22) {
let o = {}
if (this.isTableQuestion) {
@ -132,6 +146,7 @@ export default {
},
async handleChange(e, key) {
if (key === 'study') {
console.log(this.studyList, 'this.studyList')
this.seriesList = this.studyList.find(item => item.StudyId === this.form.studyId).SeriesArr
}
if (key === 'series') {
@ -142,6 +157,20 @@ export default {
this.segmentList = list.filter(item => item.SegmentJson)
}
},
handleClear(e, key) {
if (key === 'study') {
this.form.seriesId = null
this.form.segmentGroupId = null
this.form.segmentId = null
}
if (key === 'series') {
this.form.segmentGroupId = null
this.form.segmentId = null
}
if (key === 'segmentGroup') {
this.form.segmentId = null
}
},
handleCancel() {
this.$emit("update:visible", false)
},

View File

@ -8,25 +8,26 @@
<div class="tool-frame">
<div :title="$t('trials:Segmentations:tools:contour')"
:class="['tool-item', activeTool === 'LabelMapEditWithContour' && segmentList.length > 0 ? 'tool-item-active' : '']"
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) ? 'not-allowed' : 'pointer' }"
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) || ['viewport-MPR-1', 'viewport-MPR-2'].includes(`${viewportKey}-${activeViewportIndex}`) ? 'not-allowed' : 'pointer' }"
@click.prevent="setToolActive('LabelMapEditWithContour')">
<svg-icon icon-class="contour" class="svg-icon" />
</div>
<div :title="$t('trials:Segmentations:tools:thresholecircle')"
:class="['tool-item', ThresholdTools.includes(activeTool) && segmentList.length > 0 ? 'tool-item-active' : '']"
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) ? 'not-allowed' : 'pointer' }"
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) || ['viewport-MPR-1', 'viewport-MPR-2'].includes(`${viewportKey}-${activeViewportIndex}`) ? 'not-allowed' : 'pointer' }"
@click.prevent="initThreshold">
<svg-icon icon-class="thresholecircle" class="svg-icon" />
</div>
<div :title="$t('trials:Segmentations:tools:circularbrush')"
:class="['tool-item', activeTool === 'CircularBrush' && segmentList.length > 0 ? 'tool-item-active' : '']"
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) ? 'not-allowed' : 'pointer' }"
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) || ['viewport-MPR-1', 'viewport-MPR-2'].includes(`${viewportKey}-${activeViewportIndex}`) ? 'not-allowed' : 'pointer' }"
@click.prevent="setToolActive('CircularBrush')">
<svg-icon icon-class="circularbrush" class="svg-icon" />
</div>
<div :class="['tool-item', activeTool === 'CircularEraser' && segmentList.length > 0 ? 'tool-item-active' : '']"
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) ? 'not-allowed' : 'pointer' }"
:title="$t('trials:Segmentations:tools:Eraser')" @click.prevent="setToolActive('CircularEraser')">
:style="{ cursor: segmentList.length <= 0 || (curSegment && curSegment.lock) || ['viewport-MPR-1', 'viewport-MPR-2'].includes(`${viewportKey}-${activeViewportIndex}`) ? 'not-allowed' : 'pointer' }"
:title="$t('trials:Segmentations:tools:Eraser')"
@click.prevent="setToolActive('CircularEraser')">
<svg-icon icon-class="clear" class="svg-icon" />
</div>
<!-- <div :class="['tool-item']">
@ -79,7 +80,8 @@
@click.stop="changeShowSegmentConfig" />
</div>
</template>
<div class="addSegmentBox viewHover" @click.stop="addSegment" v-if="segmentList.length <= 0">
<div class="addSegmentBox viewHover" @click.stop="addSegment"
v-if="segmentList.length <= 0 && readingTaskState < 2">
<span><i class="el-icon-plus"></i>
{{ $t('trials:reading:Segmentations:button:addSegmention') }}
</span>
@ -123,16 +125,11 @@
$t('trials:reading:Segmentations:title:InactiveSegmentationsShow')
}}</span>
</div>
<!-- <div class="SegmentConfig" v-if="SegmentConfig.InactiveSegmentations.show">
<span>{{ $t('trials:reading:Segmentations:title:Opacity') }}</span>
<el-slider v-model="SegmentConfig.InactiveSegmentations.fillAlpha" show-input :step="0.1"
:max="1" input-size="mini" :show-input-controls="false" />
</div> -->
</div>
<template v-if="segmentList.length > 0">
<div class="SegmentGroupBox">
<div style="display: flex;align-items: center;">
<el-popover placement="left" width="40" trigger="click">
<el-popover placement="left" width="40" trigger="click" v-if="readingTaskState < 2">
<div class="SegmentGroupBtnBox">
<div class="SegmentGroupBtn" @click.stop="addSegmentGroup">
{{ $t('trials:reading:Segmentations:button:addSegmentGroup') }}
@ -149,13 +146,14 @@
</div>
<i slot="reference" class="el-icon-more" style="cursor: pointer;color:#fff" />
</el-popover>
<el-select v-model="segmentationId" placeholder="" @change="selectSegmentGroup()">
<el-select v-model="segmentationId" placeholder="" @change="selectSegmentGroup()"
:disabled="saveLoading">
<el-option v-for="item in segmentList" :key="`${item.segmentationId}`"
:label="item.name" :value="item.segmentationId">
</el-option>
</el-select>
</div>
<div style="display: flex;align-items: center;">
<div style="display: flex;align-items: center;" v-if="readingTaskState < 2">
<i class="el-icon-warning-outline" style="color:red;margin-right: 5px;"
:title="$t('trials:reading:Segmentations:tip:segmentationIsNotSave')"
v-if="!curSegmentGroup.isSaved"></i>
@ -166,7 +164,8 @@
</div>
</div>
<div class="addSegmentBox" @click.stop="addSegment"
style="display: flex;align-items: center;justify-content: space-between;">
style="display: flex;align-items: center;justify-content: space-between;"
v-if="readingTaskState < 2">
<span><i class="el-icon-plus"></i>
{{ $t('trials:reading:Segmentations:button:addSegment') }}
</span>
@ -219,7 +218,7 @@
<i class="el-icon-lock" v-if="item.lock" @click.stop="lockSegment(item, false)"></i>
<el-popover placement="bottom" width="40" trigger="click" class="docShow"
:value="popoverId === `popover-${item.segmentationId}_${item.segmentIndex}`"
@show="handleClickPopover(item)">
@show="handleClickPopover(item)" v-if="readingTaskState < 2">
<div class="SegmentGroupBtnBox">
<div class="SegmentGroupBtn" @click.stop="rename('segment', item)">
{{ $t('trials:reading:Segmentations:button:renameSegmentGroup') }}
@ -244,7 +243,7 @@
</template>
</el-collapse-item>
</el-collapse>
<div class="saveBtnBox">
<div class="saveBtnBox" v-if="readingTaskState < 2">
<el-button type="success" size="small" :disabled="saveLoading" @click="saveSegmentGroup()">
{{ $t("trials:reading:Segmentations:button:saveAll") }}
</el-button>
@ -260,6 +259,8 @@ import * as cornerstoneAdapters from "@cornerstonejs/adapters";
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
import { getCustomizeStandardsSegmentDicomTools } from './toolConfig'
import * as polySeg from '@cornerstonejs/polymorphic-segmentation'
cornerstoneTools.init({ addons: { polySeg } })
const {
ToolGroupManager,
Enums: csToolsEnums,
@ -270,6 +271,7 @@ const {
CrosshairsTool,
utilities: CStUtils,
} = cornerstoneTools;
const { MouseBindings, Events: toolsEvents } = csToolsEnums
const { segmentation: segmentationUtils } = CStUtils;
const { cache, getRenderingEngine, imageLoader, eventTarget, metaData, utilities: csUtils, volumeLoader } = cornerstone;
@ -304,12 +306,38 @@ export default {
return {}
}
},
SegmentConfig: {
type: Object,
default: () => {
return {}
}
},
actionConfiguration: {
type: Object,
default: () => {
return {}
}
},
curSegSeries: {
type: Object,
default: () => {
return {}
}
},
trialCriterion: {
type: Object,
default: () => {
return {}
}
},
segId: {
type: String,
default: ''
},
segIndex: {
type: Number,
default: 0
},
renderingEngineId: {
type: String,
required: true
@ -321,6 +349,10 @@ export default {
globalLoading: {
type: Boolean,
default: false
},
histogramVisible: {
type: Boolean,
default: false
}
},
data() {
@ -338,16 +370,6 @@ export default {
ThresholdTools: ['ThresholdCircle', 'ThresholdSphere'],
thresholdType: null,
showSegmentConfig: false,
SegmentConfig: {
renderOutline: true,
renderFill: true,
fillAlpha: 0.5,
outlineWidth: 1,
InactiveSegmentations: {
show: true,
fillAlpha: 0.3,
}
},
segmentList: [],
segmentationId: "",
segmentIndex: null,
@ -363,7 +385,7 @@ export default {
'#ff994d',
'#fb628b',
],
viewprotIds: ['viewport-0', 'viewport-1', 'viewport-2', 'viewport-3', 'viewport-MPR-0', 'viewport-MPR-1', 'viewport-MPR-2'], //
viewportIds: [], //
statsKey: [],
drawing: false, //
// isDel: false,
@ -383,14 +405,30 @@ export default {
this.segmentationModifiedCallback
);
DicomEvent.$on('activeSeries', (series) => {
console.log(series, 'series')
let { TaskInfo = {}, Id } = series
if (this.isMPR) return false
if (Id === this.series.Id && TaskInfo.VisitTaskId === this.visitInfo.VisitTaskId) return false
this.series = series
this.$emit("update:curSegSeries", Object.assign(series, {}))
this.getSegmentationList()
})
DicomEvent.$on('isloaded', (data) => {
if (this.isloaded) return false
this.isloaded = true
let { segment, isChange = true } = data
this.delAllSegment(isChange)
this.getSegmentationList(segment)
let { segment, isChange = true, viewportId, series } = data
DicomEvent.$emit('renderSegmentation', viewportId)
})
DicomEvent.$on('SegmentationLoading', (viewportId) => {
if (viewportId !== `${this.viewportKey}-${this.activeViewportIndex}`) return false
this.loading = false
})
DicomEvent.$on('changeMPR', () => {
if (this.loading) return false
// if (viewportId !== `${this.viewportKey}-${this.activeViewportIndex}`) return false
if (this.segmentList && this.segmentList.length > 0) {
this.segmentationId = this.segmentList[0].segmentationId
this.segmentIndex = this.segmentList[0] ? this.segmentList[0].segments[0].segmentIndex : null
}
})
const digitPlaces = Number(localStorage.getItem('digitPlaces'))
this.digitPlaces = digitPlaces === -1 ? this.digitPlaces : digitPlaces
@ -409,12 +447,15 @@ export default {
s = this.curSegmentGroup.segments.find(item => item.segmentIndex === this.segmentIndex)
}
return s
},
readingTaskState() {
return this.series.TaskInfo ? this.series.TaskInfo.ReadingTaskState : 0
}
},
watch: {
SegmentConfig: {
handler() {
this.readingSegmentByConfig()
// this.readingSegmentByConfig()
},
deep: true
},
@ -429,9 +470,23 @@ export default {
this.setBrushThreshold()
},
deep: true
},
segmentIndex() {
this.$emit('update:segIndex', this.segmentIndex)
},
segmentationId() {
this.$emit('update:segId', this.segmentationId)
}
},
methods: {
showSurface(item) {
this.$emit("showSurface", {
segmentationId: item.segmentationId,
segmentIndex: item.segmentIndex,
volumeId: this.series.SeriesInstanceUid,
segmentations: this.curSegmentGroup
})
},
handleClickPopover(item) {
this.popoverId = `popover-${item.segmentationId}_${item.segmentIndex}`
},
@ -499,7 +554,6 @@ export default {
segmentationId: list[0].segmentationId,
segmentIndices: list.map(item => item.segmentIndex),
});
console.log(bidirectionalData, list[0].segmentationId, 'bidirectionalData')
if (bidirectionalData.length <= 0) {
list.forEach(item => {
let annotations = annotation.state.getAllAnnotations().filter(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex);
@ -558,8 +612,11 @@ export default {
},
setToolActive(toolName) {
// if (!this.series.TaskInfo || this.series.TaskInfo.VisitTaskId !== this.visitInfo.VisitTaskId) return false
if (this.segmentList.length <= 0) return false
if (this.curSegment.lock) return false
if (this.histogramVisible && !this.ThresholdTools.includes(toolName)) return false
if (['viewport-MPR-1', 'viewport-MPR-2'].includes(`${this.viewportKey}-${this.activeViewportIndex}`)) return false
const toolGroupId = this.isMPR ? this.volumeToolGroupId : `${this.viewportKey}-${this.activeViewportIndex}`
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId)
if (this.activeTool === toolName) {
@ -596,71 +653,33 @@ export default {
viewBidirectional(arr, view) {
for (let j = 0; j < arr.length; j++) {
let item = arr[j]
let bidirectional = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
item.bidirectionalView = view
if (!bidirectional) continue
annotation.visibility.setAnnotationVisibility(bidirectional.annotationUID, view)
// let bidirectional = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
// item.bidirectionalView = view
// if (!bidirectional) continue
// annotation.visibility.setAnnotationVisibility(bidirectional.annotationUID, view)
}
this.resetViewport()
DicomEvent.$emit('viewBidirectional', arr)
// this.resetViewport()
},
async jumpBidirectional(item) {
if (item.bidirectional) {
let an = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
console.log(an, 'an')
if (!an) return false
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId)
let key = Object.keys(an.data.cachedStats)[0]; // referencedImageId
if (key) {
let sliceIndex = key.split("?")[1].split("&")[0].split("=")[1]
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIndex });
} else {
const points = an.data.handles.points;
const worldPoint = points[0]; //
let volume = cache.getVolume(this.series.SeriesInstanceUid)
let { imageData, numFrames } = volume
const ijk = imageData.worldToIndex(worldPoint);
const sliceIndex = Math.abs(Math.round(ijk[2]));
// console.log(sliceIndex, 'sliceIndex')
csUtils.jumpToSlice(viewport.element, { imageIndex: numFrames - sliceIndex - 1 });
}
}
DicomEvent.$emit('jumpBidirectional', item)
},
viewSegmentGroup(item) {
let view = !item.view
this.viewprotIds.forEach(id => {
segmentation.config.visibility.setSegmentationRepresentationVisibility(
id,
{
segmentationId: item.segmentationId,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
view
);
})
item.view = view
item.view = !item.view
item.segments.forEach(i => {
i.view = view
// this.viewBidirectional(i, view)
i.view = item.view
})
this.viewBidirectional(item.segments, view)
DicomEvent.$emit('viewSegmentation', item)
// this.viewBidirectional(item.segments, view)
},
viewSegment(item, view) {
this.viewprotIds.forEach(id => {
segmentation.config.visibility.setSegmentIndexVisibility(id, {
segmentationId: item.segmentationId,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
}, item.segmentIndex, view)
})
item.view = view
this.viewBidirectional([item], view)
this.$emit('setToolsPassive')
DicomEvent.$emit('viewSegment', item)
},
lockSegment(item, lock) {
if (this.readingTaskState >= 2) return false
segmentation.segmentLocking.setSegmentIndexLocked(item.segmentationId, item.segmentIndex, lock)
item.lock = lock
if (!lock) this.changeSegmentationSavedStatus(item.segmentationId, lock)
@ -670,7 +689,7 @@ export default {
this.segmentationId = item.segmentationId;
this.segmentIndex = item.segmentIndex;
}
segmentation.segmentIndex.setActiveSegmentIndex(item.segmentationId, item.segmentIndex);
// segmentation.segmentIndex.setActiveSegmentIndex(item.segmentationId, item.segmentIndex);
if (isChange) { this.jumpBidirectional(item) }
if (item.lock) {
@ -679,30 +698,57 @@ export default {
// this.resetViewport()
},
selectSegmentGroup(s) {
this.viewprotIds.forEach(id => {
segmentation.activeSegmentation.setActiveSegmentation(id, this.segmentationId)
})
// segmentation.activeSegmentation.setActiveSegmentation(`${this.viewportKey}-${this.activeViewportIndex}`, this.segmentationId)
this.$emit('setToolsPassive')
this.segmentIndex = null;
let segment = s ? s : this.segmentList.find(item => item.segmentationId === this.segmentationId).segments[0]
this.selectSegment(segment, s ? false : true)
this.readingSegmentByConfig()
this.$nextTick(() => {
this.selectSegment(segment)
})
// this.segmentIndex = segment.segmentIndex
// this.selectSegment(segment, s ? false : true)
// this.readingSegmentByConfig()
},
getSegmentationName(num = 1) {
let defaultSegmentationName = this.trialCriterion.DefaultSegmentName.SegmentationName
let name = defaultSegmentationName
let has = this.segmentList.find(item => item.name === name)
if (has) {
name = defaultSegmentationName + num
has = this.segmentList.find(item => item.name === name)
num++
if (has) name = this.getSegmentationName(num)
}
return name
},
getSegmentName(arr, num = 1) {
let defaultSegmentName = arr[0].SegmentLabel
let name = defaultSegmentName + num
let has = arr.find(item => item.SegmentLabel === name)
num++
if (has) name = this.getSegmentName(arr, num)
return name
},
async addSegmentGroup() {
let viewprotIds = this.viewprotIds
// let segmentationId = this.$guid();
let obj = {
name: this.$t('trials:reading:Segmentations:name:SegmentGroup') + (this.segmentList.length + 1),
name: this.getSegmentationName(),
view: true,
segmentationId: null,
isSaved: false,
segments: []
}
this.segmentIndex = null
let segmentationId = await this.addOrUpdateSegmentation({ name: obj.name })
obj.segmentationId = segmentationId
await this.createSegmentation(obj.segmentationId)
this.createSegmentationRepresentation(obj.segmentationId)
this.trialCriterion.DefaultSegmentName.SegmentNameList.forEach(async (SegmentName, index) => {
let o = {
segmentIndex: 1,
segmentIndex: index + 1,
segmentationId,
SegmentLabel: 'Segment 1',
color: this.colors[0],
SegmentLabel: SegmentName,
color: this.colors[index],
stats: null,
bidirectional: null,
bidirectionalView: true,
@ -711,33 +757,42 @@ export default {
}
let id = await this.addOrUpdateSegment({ name: o.SegmentLabel, color: o.color, segmentIndex: o.segmentIndex, segmentationId: o.segmentationId })
o.id = id
obj.segments.push(o);
obj.segments.splice(index, 0, o);
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, index + 1);
this.changeColor(this.colors[index], { segmentationId: obj.segmentationId, segmentIndex: index + 1, color: this.colors[index] })
if (index === this.trialCriterion.DefaultSegmentName.SegmentNameList.length - 1) {
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, 1);
}
})
this.segmentList.push(obj);
this.segmentationId = obj.segmentationId;
await this.createSegmentation(obj.segmentationId)
this.createSegmentationRepresentation(obj.segmentationId)
this.segmentIndex = 1
viewprotIds.forEach(id => {
segmentation.config.color.setSegmentIndexColor(id, obj.segmentationId, 1, this.hex2Rgb(this.colors[0]))
})
this.selectSegmentGroup()
// segmentation.segmentIndex.setActiveSegmentIndex(this.segmentationId, 1);
// viewportIds.forEach(id => {
// segmentation.config.color.setSegmentIndexColor(id, obj.segmentationId, 1, this.hex2Rgb(this.colors[0]))
// })
// this.selectSegmentGroup()
},
async addSegment() {
let viewprotIds = this.viewprotIds;
if (this.saveLoading) return false
if (this.segmentList.length <= 0) {
let obj = {
name: this.$t('trials:reading:Segmentations:name:SegmentGroup') + 1,
name: this.getSegmentationName(),
view: true,
isSaved: false,
segments: []
}
this.segmentIndex = null
let segmentationId = await this.addOrUpdateSegmentation({ name: obj.name })
obj.segmentationId = segmentationId
await this.createSegmentation(segmentationId)
this.createSegmentationRepresentation(segmentationId)
this.trialCriterion.DefaultSegmentName.SegmentNameList.forEach(async (SegmentName, index) => {
let o = {
segmentIndex: 1,
segmentIndex: index + 1,
segmentationId,
SegmentLabel: 'Segment 1',
color: this.colors[0],
SegmentLabel: SegmentName,
color: this.colors[index],
stats: null,
bidirectional: null,
bidirectionalView: true,
@ -746,17 +801,22 @@ export default {
}
let id = await this.addOrUpdateSegment({ name: o.SegmentLabel, color: o.color, segmentIndex: o.segmentIndex, segmentationId: o.segmentationId })
o.id = id
obj.segments.push(o);
this.segmentList.push(obj);
this.segmentationId = this.segmentList[0].segmentationId;
await this.createSegmentation(this.segmentationId)
this.createSegmentationRepresentation(this.segmentationId)
this.segmentIndex = 1
segmentation.segmentIndex.setActiveSegmentIndex(this.segmentList[0].segmentationId, 1);
viewprotIds.forEach(id => {
segmentation.config.color.setSegmentIndexColor(id, this.segmentList[0].segmentationId, 1, this.hex2Rgb(this.colors[0]))
obj.segments.splice(index, 0, o);
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, index + 1);
this.changeColor(this.colors[index], { segmentationId: obj.segmentationId, segmentIndex: index + 1, color: this.colors[index] })
if (index === this.trialCriterion.DefaultSegmentName.SegmentNameList.length - 1) {
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, 1);
}
})
this.readingSegmentByConfig()
this.segmentList.push(obj);
this.segmentationId = segmentationId;
this.segmentIndex = 1
// segmentation.segmentIndex.setActiveSegmentIndex(this.segmentList[0].segmentationId, 1);
// this.changeColor(this.colors[0], { segmentationId: this.segmentList[0].segmentationId, segmentIndex: 1, color: this.colors[0] })
// viewportIds.forEach(id => {
// segmentation.config.color.setSegmentIndexColor(id, this.segmentList[0].segmentationId, 1, this.hex2Rgb(this.colors[0]))
// })
// this.readingSegmentByConfig()
} else {
let item = this.segmentList.find(i => i.segmentationId === this.segmentationId)
@ -765,7 +825,7 @@ export default {
let obj = {
segmentIndex: segmentIndex,
segmentationId: this.segmentationId,
SegmentLabel: `Segment ${segmentIndex}`,
SegmentLabel: this.getSegmentName(item.segments),
color: item.segments.length >= this.colors.length ? this.colors[0] : this.colors[item.segments.length],
stats: null,
bidirectional: null,
@ -781,19 +841,22 @@ export default {
let id = await this.addOrUpdateSegment({ name: obj.SegmentLabel, color: obj.color, segmentIndex: obj.segmentIndex, segmentationId: obj.segmentationId })
obj.id = id
item.segments.push(obj)
segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, obj.segmentIndex);
viewprotIds.forEach(id => {
segmentation.config.color.setSegmentIndexColor(id, obj.segmentationId, obj.segmentIndex, this.hex2Rgb(obj.color))
})
this.segmentIndex = obj.segmentIndex
// segmentation.segmentIndex.setActiveSegmentIndex(obj.segmentationId, obj.segmentIndex);
this.changeColor(obj.color, { segmentationId: obj.segmentationId, segmentIndex: obj.segmentIndex, color: obj.color })
// viewportIds.forEach(id => {
// segmentation.config.color.setSegmentIndexColor(id, obj.segmentationId, obj.segmentIndex, this.hex2Rgb(obj.color))
// })
}
},
changeColor(e, item) {
this.viewprotIds.forEach(id => {
segmentation.config.color.setSegmentIndexColor(id, item.segmentationId, item.segmentIndex, this.hex2Rgb(e))
})
DicomEvent.$emit('changeColor', item)
// this.viewportIds.forEach(id => {
// segmentation.config.color.setSegmentIndexColor(id, item.segmentationId, item.segmentIndex, this.hex2Rgb(e))
// })
},
//
delAllSegment(isChange) {
@ -830,12 +893,13 @@ export default {
} else {
this.segmentationId = ''
}
this.readingSegmentByConfig()
// this.readingSegmentByConfig()
this.resetViewport()
this.$emit('resetQuestion')
},
//
async delSegment(data) {
this.popoverId = null;
let segmentIndex = data.segmentIndex
let confirm = await this.$confirm(this.$t('trials:reading:Segmentations:confirm:delSegment'))
if (!confirm) return false
@ -862,15 +926,18 @@ export default {
},
resetViewport(passive = true) {
let renderingEngine = getRenderingEngine(this.renderingEngineId)
this.viewprotIds.forEach(id => {
const viewport = renderingEngine.getViewport(id)
viewport.render()
})
DicomEvent.$emit('resetViewport')
if (passive) this.$emit('setToolsPassive')
},
async rename(key, item) {
let name = await this.customPrompt()
let value = null
if (key === 'segmentGroup') {
let group = this.segmentList.find(i => i.segmentationId === this.segmentationId)
value = group.name
} else {
value = item.SegmentLabel
}
let name = await this.customPrompt(value)
if (!name) return false
if (key === 'segmentGroup') {
let group = this.segmentList.find(i => i.segmentationId === this.segmentationId)
@ -881,7 +948,7 @@ export default {
this.addOrUpdateSegment({ name: item.SegmentLabel, color: item.color, segmentIndex: item.segmentIndex, segmentationId: item.segmentationId, segmentJson: JSON.stringify({ stats: item.stats, bidirectional: item.bidirectional }), id: item.id })
}
},
async customPrompt() {
async customPrompt(name) {
try {
const that = this
//
@ -893,6 +960,7 @@ export default {
showCancelButton: true,
closeOnClickModal: false,
closeOnPressEscape: false,
inputValue: name,
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
// const value = instance.inputValue
@ -911,7 +979,7 @@ export default {
//
changeInactiveSegmentShow() {
let segmentList = this.segmentList.filter(item => item.segmentationId !== this.segmentationId)
this.viewprotIds.forEach(id => {
this.viewportIds.forEach(id => {
segmentation.config.visibility.setSegmentationRepresentationVisibility(
id,
{
@ -953,6 +1021,7 @@ export default {
this.exportSegmentation(this.segmentationId, group, true)
},
exportSegmentation(segmentationId, group, isFile = false) {
try {
const segmentationIds = segmentation.state
.getSegmentations()
.map(x => x.segmentationId);
@ -1060,6 +1129,10 @@ export default {
} else {
this.downloadDICOMData(generatedSegmentation.dataset, `${group.name}.dcm`);
}
} catch (err) {
console.log(err)
}
},
downloadDICOMData(bufferOrDataset, filename) {
let blob;
@ -1109,7 +1182,7 @@ export default {
const arrayBuffer = image.data.byteArray.buffer;
await this.loadSegmentation(arrayBuffer, obj.segmentationId);
this.createSegmentationRepresentation(obj.segmentationId);
// this.createSegmentationRepresentation(obj.segmentationId);
},
async loadSegmentation(arrayBuffer, segmentationId) {
const generateToolState =
@ -1292,17 +1365,11 @@ export default {
}
},
createSegmentationRepresentation(segmentationId) {
this.viewprotIds.forEach(id => {
segmentation.addSegmentationRepresentations(id, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
])
})
DicomEvent.$emit('createSegmentationRepresentation', segmentationId)
this.$emit('setToolsPassive')
},
contentMouseup() {
try {
// console.log("segment contentMouseup")
if (!this.drawing) return false
if (this.timeoutId) {
@ -1331,12 +1398,16 @@ export default {
}
}, 500);
} catch (err) {
console.log(err)
}
},
//
async getSegmentBindingList(param = {}) {
try {
let data = {
VisitTaskId: this.visitInfo.VisitTaskId,
VisitTaskId: this.series.TaskInfo.VisitTaskId,
PageSize: 9999,
PageIndex: 1,
}
@ -1353,7 +1424,7 @@ export default {
async saveSegmentBindingAndAnswer(list) {
try {
let data = {
VisitTaskId: this.visitInfo.VisitTaskId,
VisitTaskId: this.series.TaskInfo.VisitTaskId,
BindingList: list
}
let res = await saveSegmentBindingAndAnswer(data)
@ -1406,6 +1477,8 @@ export default {
}/${this.series.Id}/${segmentGroup.name}.dcm`
const result = await this.OSSclient.put(path, blob)
segmentGroup.segUrl = this.$getObjectName(result.url)
} else {
segmentGroup.segUrl = null
}
this.addOrUpdateSegmentation({ name: segmentGroup.name, id: segmentGroup.segmentationId, url: segmentGroup.segUrl })
@ -1443,7 +1516,6 @@ export default {
list.forEach(item => {
this.createSegmentConfiguration(item.segmentIndex, item.segmentationId);
})
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewportId = `${this.viewportKey}-${this.activeViewportIndex}`
const viewport = renderingEngine.getViewport(viewportId);
@ -1468,7 +1540,7 @@ export default {
let bidirectional = bidirectionalData[0]
const { segmentIndex } = bidirectional;
const { majorAxis, minorAxis, maxMajor, maxMinor } = bidirectional;
const { majorAxis, minorAxis } = bidirectional;
let item = list.find(i => i.segmentIndex === segmentIndex)
SegmentBidirectionalTool.hydrate(viewportId, [majorAxis, minorAxis], {
segmentIndex,
@ -1477,7 +1549,7 @@ export default {
let an = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === bidirectional.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
if (an) {
annotation.locking.setAnnotationLocked(an.annotationUID, true)
annotation.visibility.setAnnotationVisibility(an.annotationUID, item.bidirectionalView)
annotation.visibility.setAnnotationVisibility(an.annotationUID, true)
}
item.bidirectional = bidirectional
reslove(true)
@ -1516,13 +1588,13 @@ export default {
answer = segment.stats && segment.stats[imageToolAttribute] ? Number((segment.stats[imageToolAttribute]).value).toFixed(this.digitPlaces) : ''
}
let o = {
Answer: answer,
Answer: answer === '-Infinity' || answer === 'NaN' ? null : answer,
QuestionId: item.QuestionId,
RowId: item.RowId,
SegmentId: item.SegmentId,
SegmentationId: item.SegmentationId,
TableQuestionId: item.TableQuestionId,
VisitTaskId: this.visitInfo.VisitTaskId,
VisitTaskId: this.series.TaskInfo.VisitTaskId,
}
bindingList.push(o)
}
@ -1540,7 +1612,7 @@ export default {
SubjectId: this.visitInfo.SubjectId,
SubjectVisitId: this.visitInfo.VisistId,
TrialId: this.$route.query.trialId,
VisitTaskId: this.visitInfo.VisitTaskId,
VisitTaskId: this.series.TaskInfo.VisitTaskId,
}
if (url) data.SegUrl = url;
if (id) data.Id = id;
@ -1558,17 +1630,20 @@ export default {
//
async getSegmentationList(SEGMENT = null) {
try {
this.$emit('setToolsPassive')
let data = {
SeriesId: this.series.Id,
VisitTaskId: this.visitInfo.VisitTaskId,
VisitTaskId: this.series.TaskInfo.VisitTaskId,
PageSize: 9999,
PageIndex: 1,
}
this.loading = true;
let res = await getSegmentationList(data);
this.loading = false;
// this.loading = false;
if (res.IsSuccess) {
this.segmentList = []
this.segmentationId = null;
this.segmentIndex = null;
let list = res.Result.CurrentPageData;
for (let i = 0; i < list.length; i++) {
let item = list[i]
@ -1583,12 +1658,6 @@ export default {
segments: []
}
this.segmentList.push(obj)
if (item.SEGUrl) {
await this.readSegmentation(obj)
} else {
await this.createSegmentation(obj.segmentationId)
this.createSegmentationRepresentation(obj.segmentationId)
}
}
if (!this.segmentationId) {
this.segmentationId = obj.segmentationId
@ -1607,48 +1676,20 @@ export default {
bidirectional: SegmentJson.bidirectional,
bidirectionalView: true,
view: true,
lock: item.locked,
lock: true,
id: s.Id
}
obj.segments.push(o)
this.selectSegment(o, false)
this.changeColor(s.ColorRgb, o)
this.lockSegment(o, true)
}
if (!this.segmentIndex) {
this.segmentIndex = s.SegmentNumber
}
})
this.$nextTick(() => {
if (SEGMENT) {
// console.log(SEGMENT, 'SEGMENT')
return this.getBidirectional(obj.segments, SEGMENT)
}
this.getBidirectional(obj.segments, null, false)
})
}
if (this.segmentationId && this.segmentIndex && this.segmentList && this.segmentList.length > 0) {
let o = this.segmentList.find(item => item.segmentationId === this.segmentationId)
if (o) {
let s = o.segments.find(item => item.segmentIndex === this.segmentIndex)
this.selectSegmentGroup(s)
} else {
this.segmentationId = this.segmentList[0].segmentationId
this.segmentIndex = this.segmentationId ? this.segmentList[0].segments[0].segmentIndex : null
if (this.segmentationId && this.segmentIndex) {
this.selectSegmentGroup(this.segmentList[0].segments[0])
}
}
// console.log(segment, 'segment')
// this.selectSegment(segment)
}
this.isloaded = false
this.readingSegmentByConfig()
}
} catch (err) {
this.loading = false
// this.loading = false
console.log(err)
}
},
@ -1674,7 +1715,7 @@ export default {
SegmentName: name,
SegmentNumber: segmentIndex,
SegmentationId: segmentationId,
VisitTaskId: this.visitInfo.VisitTaskId,
VisitTaskId: this.series.TaskInfo.VisitTaskId,
SegmentJson: segmentJson
}
if (id) data.Id = id
@ -1697,14 +1738,14 @@ export default {
PageSize: 9999,
PageIndex: 1,
}
this.loading = true;
// this.loading = true;
let res = await getSegmentList(data)
this.loading = false;
// this.loading = false;
if (res.IsSuccess) {
return res.Result.CurrentPageData
}
} catch (err) {
this.loading = false
// this.loading = false
console.log(err)
}
},

View File

@ -10,9 +10,10 @@
</div>
<div class="ps">
<el-collapse v-model="activeNames">
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.StudyId}`" :name="`${study.StudyId}`">
<template v-for="(study, index) in studyList">
<el-collapse-item :key="`${study.StudyId}`" :name="`${study.StudyId}`" v-if="!study.IsCriticalSequence">
<template slot="title">
<div v-if="!study.IsCriticalSequence" class="dicom-desc">
<div v-if="readingTool !== 3 || !study.IsCriticalSequence" class="dicom-desc">
<template v-if="taskInfo && taskInfo.IsShowStudyName">
<div style="text-overflow: ellipsis;overflow: hidden;">
<span :title="study.StudyCode">{{ study.StudyCode }}</span>
@ -53,7 +54,8 @@
style="position: absolute;right: 0;top: 0;">
<el-popover placement="right" trigger="hover" popper-class="instance_frame_wrapper">
<div class="frame_list">
<div v-for="(instance, idx) in series.InstanceInfoList" :key="instance.Id" class="frame_content"
<div v-for="(instance, idx) in series.InstanceInfoList" :key="instance.Id"
class="frame_content"
:style="{ 'margin-bottom': idx < series.InstanceInfoList.length - 1 ? '5px' : '0px' }"
@click.stop="showMultiFrames(index, series, i, instance)">
<div>
@ -99,6 +101,8 @@
</div>
</div>
</el-collapse-item>
</template>
</el-collapse>
</div>
</div>
@ -119,6 +123,10 @@ export default {
default() {
return []
}
},
readingTool: {
type: Number,
default: 2
}
},
data() {
@ -248,6 +256,7 @@ export default {
background-color: #607d8b !important;
border: 1px solid #607d8b !important;
}
::v-deep .el-progress__text {
color: #ccc;
font-size: 12px;
@ -299,6 +308,7 @@ export default {
}
}
::v-deep .el-collapse {
border: none;

View File

@ -0,0 +1,144 @@
<template>
<div class="SurfaceViewport" ref="SurfaceViewport" v-show="visible" id="SurfaceViewport"></div>
</template>
<script>
import {
getRenderingEngine,
CONSTANTS,
setVolumesForViewports,
eventTarget,
Enums,
utilities,
} from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
const {
Enums: csToolsEnums,
segmentation,
TrackballRotateTool,
ToolGroupManager
} = cornerstoneTools
const { MouseBindings, Events: toolsEvents } = csToolsEnums
export default {
name: "SurfaceViewport",
props: {
renderingEngineId: {
type: String,
required: true
},
viewportId: {
type: String,
required: true
},
visitInfo: {
type: Object,
default: () => {
return {}
}
},
visible: {
type: Boolean,
default: false
},
},
data() {
return {
volumeId: null,
info: null
}
},
mounted() {
eventTarget.addEventListener(Enums.Events.WEB_WORKER_PROGRESS, (evt) => {
const { progress } = evt.detail;
console.log(progress, 'progress')
});
},
methods: {
async setSeriesInfo(obj) {
try {
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
let { volumeId } = obj
this.info = obj
this.volumeId = volumeId
await setVolumesForViewports(
renderingEngine,
[{ volumeId, callback: setCtTransferFunctionForVolumeActor }],
[this.viewportId]
);
const volumeActor = viewport.getDefaultActor()
.actor;
utilities.applyPreset(
volumeActor,
CONSTANTS.VIEWPORT_PRESETS.find((preset) => preset.name === 'CT-Bone')
);
volumeActor.setVisibility(false);
viewport.render();
} catch (e) {
console.log(e)
}
},
async setSurface() {
let { segmentationId, segmentations } = this.info
const toolGroup = ToolGroupManager.getToolGroup(this.viewportId)
toolGroup.setToolActive(TrackballRotateTool.toolName, {
bindings: [
{
mouseButton: MouseBindings.Primary,
},
],
});
let s = segmentation.getActiveSegmentation(this.viewportId)
console.log(s)
if (s) {
await segmentation.removeSegmentationRepresentation(this.viewportId, {
segmentationId: s.segmentationId,
type: csToolsEnums.SegmentationRepresentations.Surface,
})
}
await segmentation.addSegmentationRepresentations(this.viewportId, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Contour,
},
]);
console.log("loading...")
this.$nextTick(async () => {
await segmentation.addSegmentationRepresentations(this.viewportId, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Surface,
},
]);
segmentations.segments.forEach(item => {
segmentation.config.color.setSegmentIndexColor(this.viewportId, item.segmentationId, item.segmentIndex, this.hex2Rgb(item.color))
})
})
},
hex2Rgb(hexValue, alpha = 1) {
const rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const hex = hexValue.replace(rgx, (m, r, g, b) => r + r + g + g + b + b);
const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!rgb) {
return hexValue;
}
const r = parseInt(rgb[1], 16),
g = parseInt(rgb[2], 16),
b = parseInt(rgb[3], 16);
return [r, g, b, alpha * 255];
},
}
}
</script>
<style lang="scss" scoped>
#SurfaceViewport {
width: 450px;
height: 300px;
position: fixed;
top: 100px;
z-index: 9999;
left: 100px;
}
</style>

View File

@ -14,6 +14,25 @@
<div v-if="imageInfo.total">Image: #{{ `${series.SliceIndex + 1}/${imageInfo.total}` }}</div>
<div>{{ series.Modality }}</div>
</div>
<div v-if="series && taskInfo && taskInfo.IsReadingTaskViewInOrder === 1" class="top-center-tool">
<div class="toggle-visit-container">
<div class="arrw_icon"
:style="{ cursor: series.TaskInfo && series.TaskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: series.TaskInfo && series.TaskInfo.VisitTaskNum !== 0 ? '#fff' : '#6b6b6b' }"
@click.stop.prevent="toggleTask($event, series.TaskInfo.VisitTaskNum, -1)"
@dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-left" />
</div>
<div class="arrow_text">
{{ series.TaskInfo ? series.TaskInfo.TaskBlindName : '' }}
</div>
<div class="arrw_icon"
:style="{ cursor: series.TaskInfo && series.TaskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: series.TaskInfo && series.TaskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff' : '#6b6b6b' }"
@click.stop.prevent="toggleTask($event, series.TaskInfo.VisitTaskNum, 1)"
@dblclick.stop="preventDefault($event)">
<i class="el-icon-caret-right" />
</div>
</div>
</div>
<div v-if="series" class="right-top-text">
<div>{{ series.Description }}</div>
</div>
@ -78,9 +97,24 @@ import * as cornerstoneTools from '@cornerstonejs/tools'
import { createImageIdsAndCacheMetaData } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/createImageIdsAndCacheMetaData'
import setCtTransferFunctionForVolumeActor from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setCtMappingRange } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setCtTransferFunctionForVolumeActor'
import { setPetColorMapTransferFunctionForVolumeActor } from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/setPetColorMapTransferFunctionForVolumeActor'
import { vec3, mat4 } from 'gl-matrix'
import {
setPetTransferFunctionForVolumeActor
} from './helpers/index.js'
import DicomEvent from '@/views/trials/trials-panel/reading/dicoms/components/DicomEvent'
import {
renderSegmentation,
readingSegmentByConfig,
selectSegmentation,
selectSegment,
createSegmentationRepresentation,
viewSegmentation,
viewSegment,
jumpBidirectional,
viewBidirectional,
changeColor,
resetViewport
} from "./helpers/segmentations"
export default {
name: 'MPRViewport',
props: {
@ -96,6 +130,36 @@ export default {
type: Number,
required: true
},
histogramVisible: {
type: Boolean,
default: false
},
actionConfiguration: {
type: Object,
default: () => {
return {}
}
},
SegmentConfig: {
type: Object,
default: () => {
return {}
}
},
curSegSeries: {
type: Object,
default: () => {
return {}
}
},
segmentIndex: {
type: Number,
default: 0
},
segmentationId: {
type: String,
default: ''
}
},
data() {
return {
@ -139,6 +203,7 @@ export default {
rotateAngle: 0,
rotateBarLeft: 0,
loading: false,
toggleClipPlayTimer: null
}
},
mounted() {
@ -148,6 +213,39 @@ export default {
this.$nextTick(() => {
this.initViewport()
})
DicomEvent.$on('createSegmentationRepresentation', (segmentationId) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
createSegmentationRepresentation(this.viewportId, segmentationId)
})
DicomEvent.$on('viewSegmentation', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
viewSegmentation(obj, this.viewportId)
})
DicomEvent.$on('viewSegment', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
viewSegment(obj, this.viewportId)
})
DicomEvent.$on('jumpBidirectional', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
jumpBidirectional(obj, this.viewportId, this.volumeId)
})
DicomEvent.$on('viewBidirectional', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
viewBidirectional(obj, this.viewportId)
})
DicomEvent.$on('changeColor', (obj) => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
changeColor(obj, this.viewportId)
})
DicomEvent.$on('resetViewport', () => {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.TaskInfo.VisitTaskId !== this.series.TaskInfo.VisitTaskId) return false
resetViewport(this.viewportId)
})
DicomEvent.$on('renderSegmentation', async (viewportId) => {
// if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.VisitTaskId !== this.series.VisitTaskId) return false
if (this.viewportId !== viewportId) return false
await renderSegmentation(this.series, this.series.TaskInfo, this.viewportId, this.SegmentConfig, this.renderingEngineId, null, this.actionConfiguration)
})
},
watch: {
MPRInfo: {
@ -166,6 +264,25 @@ export default {
}
},
deep: true
},
SegmentConfig: {
handler() {
if (!this.segmentationId) return false
if (!this.series.TaskInfo) return false
readingSegmentByConfig(this.series, this.series.TaskInfo, this.viewportId, this.segmentationId, this.SegmentConfig)
},
deep: true
},
segmentIndex() {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.VisitTaskId !== this.series.VisitTaskId) return false
if (this.segmentIndex <= 0) return false
selectSegment(this.viewportId, this.segmentationId, this.segmentIndex)
},
segmentationId() {
if (this.curSegSeries.Id !== this.series.Id || this.curSegSeries.VisitTaskId !== this.series.VisitTaskId) return false
if (!this.segmentationId) return false
selectSegmentation(this.viewportId, this.segmentationId)
readingSegmentByConfig(this.series, this.series.TaskInfo, this.viewportId, this.segmentationId, this.SegmentConfig)
}
},
methods: {
@ -183,6 +300,7 @@ export default {
this.element.addEventListener('CORNERSTONE_VOI_MODIFIED', this.voiModified)
this.element.addEventListener('CORNERSTONE_IMAGE_RENDERED', this.imageRendered)
this.element.addEventListener('wheel', (e) => {
// if (this.histogramVisible) return false
console.log('CORNERSTONE_STACK_VIEWPORT_SCROLL')
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
@ -266,6 +384,7 @@ export default {
},
stackNewImage(e) {
const { detail } = e
this.series.SliceIndex = detail.imageIndex
this.sliderInfo.height = detail.imageIndex * 100 / detail.numberOfSlices
const renderingEngine = getRenderingEngine(this.renderingEngineId)
@ -286,13 +405,14 @@ export default {
let spacing = volume ? volume.spacing : []
this.imageInfo.sliceThickness = type === 'AXIAL' ? spacing[2] : spacing[0]
this.getOrientationMarker()
if (this.series && this.series.Id) {
let annotations = cornerstoneTools.annotation.state.getAllAnnotations().filter(item => item.metadata.toolName !== 'ScaleOverlay' && item.metadata.volumeId !== this.volumeId && !item.metadata.segmentationId && item.seriesId !== this.series.Id)
annotations.forEach(item => {
cornerstoneTools.annotation.state.removeAnnotation(item.annotationUID)
})
}
// if (this.series && this.series.Id) {
// let annotations = cornerstoneTools.annotation.state.getAllAnnotations().filter(item => item.metadata.toolName !== 'ScaleOverlay' && item.metadata.volumeId !== this.volumeId && !item.metadata.segmentationId && item.seriesId !== this.series.Id)
// annotations.forEach(item => {
// cornerstoneTools.annotation.state.removeAnnotation(item.annotationUID)
// })
// }
this.$emit('renderAnnotations', this.series)
this.$emit("resetHistogram")
let properties = viewport.getProperties()
if (this.isFusion) {
properties = viewport.getProperties(this.ptVolumeId)
@ -418,11 +538,20 @@ export default {
this.playClipState = isPlay
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
if (isPlay) {
cornerstoneTools.utilities.cine.playClip(viewport.element, { framesPerSecond, loop: true })
this.toggleClipPlayTimer = setInterval(() => {
let index = this.series.SliceIndex + 1;
if (index > this.imageInfo.total - 1) index = 0
csUtils.jumpToSlice(viewport.element, { imageIndex: index });
}, framesPerSecond)
// cornerstoneTools.utilities.cine.playClip(viewport.element, { framesPerSecond, loop: true })
} else {
cornerstoneTools.utilities.cine.stopClip(viewport.element)
if (this.toggleClipPlayTimer) {
clearInterval(this.toggleClipPlayTimer)
this.toggleClipPlayTimer = null
}
// cornerstoneTools.utilities.cine.stopClip(viewport.element)
}
},
scrollPage(type) {
@ -520,7 +649,6 @@ export default {
if (this.series && data.Id === this.series.Id && data.Description === this.series.Description && !isLocate && !data.isLocation) {
data.SliceIndex = this.series.SliceIndex
}
// console.log(data)
const renderingEngine = getRenderingEngine(this.renderingEngineId)
const viewport = renderingEngine.getViewport(this.viewportId)
if (isLocate) return csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex });
@ -532,7 +660,8 @@ export default {
.setVolumes([{
volumeId: this.volumeId, callback: (r) => {
if (this.series.Modality === 'PT' || this.series.Modality === 'NM') {
setPetColorMapTransferFunctionForVolumeActor(r, true)
setPetTransferFunctionForVolumeActor(r)
// viewport.setProperties({ voiRange: { upper: 5, lower: 0 } })
} else {
const voi = metaData.get('voiLutModule', res.volume._imageIds[Math.ceil((res.volume._imageIds.length - 1) / 2)])
setCtMappingRange(voi.windowWidth[0], voi.windowCenter[0])
@ -541,19 +670,28 @@ export default {
console.log("渲染成功")
}
}]).then(r => {
if (data.segment) {
return DicomEvent.$emit("isloaded", { segment: data.segment, isChange: data.isChange })
}
if (data.isLocation) {
setTimeout(() => { csUtils.jumpToSlice(viewport.element, { imageIndex: data.SliceIndex }); })
}
DicomEvent.$emit("isloaded", { isChange: data.isChange })
})
res.volume.dimensionGroupNumber = 2;
viewport.render()
if (this.series.Modality === 'PT' || this.series.Modality === 'NM') {
setTimeout(() => {
viewport.resetCamera({ resetPan: true, resetZoom: true, resetToCenter: true })
viewport.resetProperties()
viewport.setProperties({ voiRange: { upper: 5, lower: 0 } })
viewport.render()
renderingEngine.render()
}, 100)
}
await renderSegmentation(this.series, this.series.TaskInfo, this.viewportId, this.SegmentConfig, this.renderingEngineId, data.segment, this.actionConfiguration)
DicomEvent.$emit('SegmentationLoading', this.viewportId)
if (this.series.hasOwnProperty('curIndex')) return this.setFullScreen(this.series.curIndex)
this.setFullScreen(Math.ceil((res.volume._imageIds.length - 1) / 2) - 1)
} catch (e) {
console.log(e)
}
},
cornerstoneToolsMouseMove(e) {
const { currentPoints } = e.detail
@ -723,6 +861,12 @@ export default {
return `NS: ${this.$store.state.trials.downloadTip}`
}
},
destroyed() {
if (this.toggleClipPlayTimer) {
clearInterval(this.toggleClipPlayTimer)
this.toggleClipPlayTimer = null
}
}
}
</script>
<style lang="scss" scoped>

View File

@ -1357,6 +1357,7 @@ export default {
await this.getQuestionCalculateRelation()
await this.getQuestions(true)
this.$emit('resetAnnotations', this.visitTaskId)
this.initSegmentBinding()
this.$nextTick(() => {
this.rerender = true
})

View File

@ -0,0 +1,436 @@
import * as cornerstoneTools from '@cornerstonejs/tools';
import * as cornerstone from "@cornerstonejs/core";
import dcmjs from '@/utils/dcmUpload/dcmjs'
import * as cornerstoneAdapters from "@cornerstonejs/adapters";
import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'
import * as polySeg from '@cornerstonejs/polymorphic-segmentation'
cornerstoneTools.init({ addons: { polySeg } })
import { getSegmentationList, getSegmentList } from '@/api/reading'
import Vue from 'vue'
const {
ToolGroupManager,
Enums: csToolsEnums,
segmentation,
annotation,
LabelMapEditWithContourTool,
SegmentBidirectionalTool,
CrosshairsTool,
utilities: CStUtils,
} = cornerstoneTools;
const { MouseBindings, Events: toolsEvents } = csToolsEnums
const { segmentation: segmentationUtils } = CStUtils;
const { cache, getRenderingEngine, imageLoader, eventTarget, metaData, utilities: csUtils, volumeLoader } = cornerstone;
const { Cornerstone3D } = cornerstoneAdapters.adaptersSEG;
let viewportInfo = {}
let renderingEngineId = null
async function createSegmentation(toolGroupId, volumeId, segmentationId) {
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId) || ToolGroupManager.getToolGroup('share-viewport-volume')
toolGroup.setToolActive(
LabelMapEditWithContourTool.toolName,
);
if (!cache.getVolume(segmentationId)) {
await volumeLoader.createAndCacheDerivedLabelmapVolume(
volumeId,
{
volumeId: segmentationId
}
)
}
if (!segmentation.state.getSegmentation(segmentationId)) {
segmentation.addSegmentations([
{
segmentationId,
representation: {
type: cornerstoneTools.Enums.SegmentationRepresentations
.Labelmap,
data: {
volumeId: segmentationId
}
}
}
]);
}
}
async function createSegmentationRepresentation(viewportId, segmentationId) {
segmentation.addSegmentationRepresentations(viewportId, [
{
segmentationId,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
])
}
async function readSegmentation(obj, series, segmentationId, isFile = false) {
let imageId = null
if (isFile) {
imageId = cornerstoneDICOMImageLoader.wadouri.fileManager.add(obj);
} else {
const imageIdObj = await cornerstoneDICOMImageLoader.wadouri.loadImage(`wadouri:${Vue.prototype.OSSclientConfig.basePath}${obj}`).promise
imageId = imageIdObj.imageId
}
const image = await imageLoader.loadAndCacheImage(imageId);
if (!image) {
return;
}
const instance = metaData.get("instance", imageId);
if (instance.Modality !== "SEG") {
console.error("This is not segmentation: " + file.name);
return;
}
const arrayBuffer = image.data.byteArray.buffer;
await loadSegmentation(arrayBuffer, series, segmentationId);
}
async function loadSegmentation(arrayBuffer, series, segmentationId) {
const generateToolState =
await Cornerstone3D.Segmentation.generateToolState(
series.ImageIds,
arrayBuffer,
metaData,
);
if (generateToolState.labelmapBufferArray.length !== 1) {
alert(
"Overlapping segments in your segmentation are not supported yet. You can turn on the skipOverlapping option but it will override the overlapping segments."
);
return;
}
// await createSegmentation(segmentationId);
let arr = []
generateToolState.segMetadata.data.forEach(item => {
if (item) {
let Target = JSON.parse(JSON.stringify(item))
arr.push(Target)
}
})
let mapping = {}
arr.forEach((item, index) => {
mapping[index + 1] = Number(item.SegmentNumber)
})
const megmentGroup =
segmentation.state.getSegmentation(segmentationId);
const { imageIds } = megmentGroup.representationData.Labelmap;
const derivedSegmentationImages = imageIds.map(imageId =>
cache.getImage(imageId)
);
const volumeScalarData = new Uint8Array(
generateToolState.labelmapBufferArray[0]
);
const remappedData = new Uint8Array(volumeScalarData.length);
for (let i = 0; i < volumeScalarData.length; i++) {
const value = volumeScalarData[i];
remappedData[i] = value === 0 ? 0 : (mapping[value] ? mapping[value] : value);
}
for (let i = 0; i < derivedSegmentationImages.length; i++) {
const voxelManager = derivedSegmentationImages[i].voxelManager;
const scalarData = voxelManager.getScalarData();
scalarData.set(
remappedData.slice(
i * scalarData.length,
(i + 1) * scalarData.length
)
);
voxelManager.setScalarData(scalarData);
}
}
function hex2Rgb(hexValue, alpha = 1) {
const rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const hex = hexValue.replace(rgx, (m, r, g, b) => r + r + g + g + b + b);
const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!rgb) {
return hexValue;
}
const r = parseInt(rgb[1], 16),
g = parseInt(rgb[2], 16),
b = parseInt(rgb[3], 16);
return [r, g, b, alpha * 255];
}
function removeSegmentFromViewport(viewportId) {
return new Promise(resolve => {
if (viewportInfo[viewportId] && viewportInfo[viewportId].length > 0) {
viewportInfo[viewportId].forEach(async segmentationId => {
segmentation.removeSegmentation(segmentationId)
segmentation.state.removeSegmentation(segmentationId)
let annotations = annotation.state.getAllAnnotations().filter(item => item.metadata.segmentationId && segmentationId === item.metadata.segmentationId && item.metadata.toolName === "SegmentBidirectional");
annotations.forEach(item => {
annotation.state.removeAnnotation(item.annotationUID)
})
})
resetViewport(viewportId)
}
viewportInfo[viewportId] = []
resolve(true)
})
}
function createSegmentConfiguration(segmentIndex, segmentationId, viewportId, actionConfiguration, otherSegments) {
const containedSegmentIndices = otherSegments
? { has: (segmentIndex) => otherSegments.indexOf(segmentIndex) !== -1 }
: undefined;
const colorConfig = segmentation.config.color.getSegmentIndexColor(
viewportId,
segmentationId,
segmentIndex
);
// Allow null style to skip style set
let color, activeColor;
if (colorConfig?.length) {
color = `rgb(${colorConfig.join(',')})`;
activeColor = color;
}
const style = {
color,
colorHighlightedActive: activeColor,
colorActive: activeColor,
textBoxColor: color,
textBoxColorActive: activeColor,
textBoxColorHighlightedActive: activeColor,
};
const label = otherSegments
? `Combined ${segmentIndex} with ${otherSegments.join(', ')}`
: `Segment ${segmentIndex}`;
actionConfiguration.contourBidirectional.data.segmentData.set(segmentIndex, {
containedSegmentIndices,
label,
style,
});
actionConfiguration.contourBidirectional.data.segmentationId = segmentationId
actionConfiguration.contourBidirectional.data.segmentIndex = segmentIndex
}
async function readingSegmentByConfig(series, visitInfo, viewportId, segmentationId, SegmentConfig) {
let data = {
SeriesId: series.Id,
VisitTaskId: visitInfo.VisitTaskId,
PageSize: 9999,
PageIndex: 1,
}
let res = await getSegmentationList(data);
if (res.IsSuccess) {
let list = res.Result.CurrentPageData;
changeInactiveSegmentShow(list, viewportId, segmentationId, SegmentConfig)
}
segmentation.config.style.setStyle(
{
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
{
renderFill: SegmentConfig.renderFill,
renderOutline: SegmentConfig.renderOutline,
outlineWidth: Number(SegmentConfig.outlineWidth),
fillAlpha: Number(SegmentConfig.fillAlpha),
}
)
}
function selectSegmentation(viewportId, segmentationId) {
segmentation.activeSegmentation.setActiveSegmentation(viewportId, segmentationId)
}
function selectSegment(viewportId, segmentationId, segmentIndex) {
selectSegmentation(viewportId, segmentationId)
segmentation.segmentIndex.setActiveSegmentIndex(segmentationId, segmentIndex);
}
async function changeInactiveSegmentShow(list, viewportId, segmentationId, SegmentConfig) {
let segmentList = list
segmentList.forEach(segment => {
segmentation.config.visibility.setSegmentationRepresentationVisibility(
viewportId,
{
segmentationId: segment.Id,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
SegmentConfig.InactiveSegmentations.show
);
})
segmentation.config.visibility.setSegmentationRepresentationVisibility(
viewportId,
{
segmentationId: segmentationId,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
true
);
let arr = []
for (let i = 0; i < segmentList.length; i++) {
let item = segmentList[i]
let params = {
SegmentationId: item.Id,
PageSize: 9999,
PageIndex: 1,
}
let r = await getSegmentList(params)
if (r.IsSuccess) {
let segments = r.Result.CurrentPageData
segments.forEach(s => {
let obj = {
segmentationId: item.Id,
segmentIndex: s.SegmentNumber,
view: SegmentConfig.InactiveSegmentations.show
}
if (item.Id === segmentationId) {
obj.view = true
}
arr.push(obj)
})
}
}
viewBidirectional(arr, viewportId)
}
function viewSegmentation(item, viewportId) {
segmentation.config.visibility.setSegmentationRepresentationVisibility(
viewportId,
{
segmentationId: item.segmentationId,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
},
item.view
);
viewBidirectional(item.segments, viewportId)
}
async function jumpBidirectional(item, viewportId, volumeId) {
// DicomEvent.$emit('jumpBidirectional', item)
if (item.bidirectional) {
let an = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
console.log(an, 'an')
if (!an) return false
if (['viewport-MPR-1', 'viewport-MPR-2'].includes(viewportId)) return false
const renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(viewportId)
let key = Object.keys(an.data.cachedStats)[0]; // referencedImageId
if (key) {
let sliceIndex = key.split("?")[1].split("&")[0].split("=")[1]
csUtils.jumpToSlice(viewport.element, { imageIndex: sliceIndex });
} else {
const points = an.data.handles.points;
const worldPoint = points[0]; // 取一个点
let volume = cache.getVolume(volumeId)
let { imageData, numFrames } = volume
const ijk = imageData.worldToIndex(worldPoint);
const sliceIndex = Math.abs(Math.round(ijk[2]));
// console.log(sliceIndex, 'sliceIndex')
csUtils.jumpToSlice(viewport.element, { imageIndex: numFrames - sliceIndex - 1 });
}
}
}
function viewSegment(item, viewportId) {
segmentation.config.visibility.setSegmentIndexVisibility(viewportId, {
segmentationId: item.segmentationId,
type: csToolsEnums.SegmentationRepresentations.Labelmap,
}, item.segmentIndex, item.view)
viewBidirectional([item], viewportId)
}
function viewBidirectional(arr, viewportId) {
for (let j = 0; j < arr.length; j++) {
let item = arr[j]
let bidirectional = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === item.segmentationId && i.metadata.segmentIndex === item.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
// item.bidirectionalView = view
if (!bidirectional) continue
let view = item.view
if (item.hasOwnProperty('bidirectionalView')) view = item.bidirectionalView
annotation.visibility.setAnnotationVisibility(bidirectional.annotationUID, view)
}
resetViewport(viewportId)
}
function resetViewport(viewportId) {
let renderingEngine = getRenderingEngine(renderingEngineId)
const viewport = renderingEngine.getViewport(viewportId)
viewport.render()
}
function changeColor(item, viewportId) {
segmentation.config.color.setSegmentIndexColor(viewportId, item.segmentationId, item.segmentIndex, hex2Rgb(item.color))
}
async function renderSegmentation(series, visitInfo, viewportId, SegmentConfig, RenderingEngineId, Segment = null, actionConfiguration) {
try {
// console.log(segmentation, 'segmentation')
renderingEngineId = RenderingEngineId
await removeSegmentFromViewport(viewportId)
let data = {
SeriesId: series.Id,
VisitTaskId: visitInfo.VisitTaskId,
PageSize: 9999,
PageIndex: 1,
}
let segmentationId = null;
let segmentIndex = null;
let res = await getSegmentationList(data);
if (res.IsSuccess) {
let list = res.Result.CurrentPageData;
for (let i = 0; i < list.length; i++) {
let item = list[i]
if (!segmentationId) segmentationId = item.Id
await createSegmentation(viewportId, series.SeriesInstanceUid, item.Id)
if (item.SEGUrl) await readSegmentation(item.SEGUrl, series, item.Id)
createSegmentationRepresentation(viewportId, item.Id)
if (!viewportInfo[viewportId]) {
viewportInfo[viewportId] = [item.Id]
} else {
viewportInfo[viewportId].push(item.Id)
}
let params = {
SegmentationId: item.Id,
PageSize: 9999,
PageIndex: 1,
}
let r = await getSegmentList(params)
if (r.IsSuccess) {
let segments = r.Result.CurrentPageData
segments.forEach(s => {
if (!segmentIndex) segmentIndex = s.SegmentNumber
let SegmentJson = s.SegmentJson ? JSON.parse(s.SegmentJson) : {};
segmentation.segmentIndex.setActiveSegmentIndex(s.SegmentationId, s.SegmentNumber);
segmentation.config.color.setSegmentIndexColor(viewportId, s.SegmentationId, s.SegmentNumber, hex2Rgb(s.ColorRgb))
segmentation.segmentLocking.setSegmentIndexLocked(s.SegmentationId, s.SegmentNumber, true)
if (SegmentJson.bidirectional) {
let an = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === s.SegmentationId && i.metadata.segmentIndex === SegmentJson.bidirectional.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
if (!an) {
let { majorAxis, minorAxis } = SegmentJson.bidirectional
// createSegmentConfiguration(s.SegmentNumber, s.SegmentationId, viewportId, actionConfiguration)
SegmentBidirectionalTool.hydrate(viewportId, [majorAxis, minorAxis], {
segmentIndex: s.SegmentNumber,
segmentationId: s.SegmentationId,
})
}
an = annotation.state.getAllAnnotations().find(i => i.metadata.segmentationId === s.SegmentationId && i.metadata.segmentIndex === SegmentJson.bidirectional.segmentIndex && i.metadata.toolName === "SegmentBidirectional");
if (an) {
annotation.locking.setAnnotationLocked(an.annotationUID, true)
annotation.visibility.setAnnotationVisibility(an.annotationUID, true)
}
console.log(an, 'an')
}
})
}
if (segmentationId && segmentIndex) {
selectSegment(viewportId, segmentationId, segmentIndex)
}
if (Segment) {
jumpBidirectional(Segment, viewportId, series.SeriesInstanceUid)
}
}
}
readingSegmentByConfig(series, visitInfo, viewportId, segmentationId, SegmentConfig)
} catch (err) {
console.log(err)
}
}
export {
createSegmentation,
createSegmentationRepresentation,
readSegmentation,
renderSegmentation,
readingSegmentByConfig,
selectSegment,
selectSegmentation,
viewSegmentation,
viewSegment,
jumpBidirectional,
viewBidirectional,
changeColor,
resetViewport
}

View File

@ -1,5 +1,6 @@
import vtkColorTransferFunction from "@kitware/vtk.js/Rendering/Core/ColorTransferFunction";
import vtkPiecewiseFunction from "@kitware/vtk.js/Common/DataModel/PiecewiseFunction";
import { Scale } from "@kitware/vtk.js/Rendering/Core/ColorTransferFunction/Constants";
import { cache, metaData, utilities } from "@cornerstonejs/core";
const { getColormap } = utilities.colormap;
@ -14,7 +15,7 @@ function getWindowCenterFromVolumeId(volumeId) {
? voiLutModule.windowCenter[0]
: voiLutModule?.windowCenter;
const center = Number(rawCenter);
return Number.isFinite(center) ? center : null;
return center;
}
export default function setPetColorMapTransferFunctionForVolumeActor({
@ -33,7 +34,25 @@ export default function setPetColorMapTransferFunctionForVolumeActor({
const center = getWindowCenterFromVolumeId(volumeId);
const upper = center > 1 ? center : 5;
cfun.setMappingRange(1, upper);
const safeUpper = Number.isFinite(upper) && upper > 0 ? upper : 5;
const rangeMin = 0;
const rangeMax = safeUpper;
cfun.setScale(Scale.LOG10);
cfun.setMappingRange(rangeMin, rangeMax);
volumeActor.getProperty().setRGBTransferFunction(0, cfun);
//低信号更明显,系数可以更小
const thresholdValue0 = Math.max(rangeMin, rangeMax * 0.002);
const thresholdValue1 = Math.max(thresholdValue0, rangeMax * 0.02);
const delta = Math.abs(rangeMax - rangeMin) * 0.001;
const threshold0MinusDelta = Math.max(rangeMin, thresholdValue0 - delta);
const ofun = vtkPiecewiseFunction.newInstance();
ofun.addPoint(rangeMin, 0.0);
ofun.addPoint(threshold0MinusDelta, 0.0);
//低信号更明显,系数可以更小
ofun.addPoint(thresholdValue0, 0.08);
ofun.addPoint(thresholdValue1, 0.9);
ofun.addPoint(rangeMax, 1.0);
volumeActor.getProperty().setScalarOpacity(0, ofun);
}

File diff suppressed because it is too large Load Diff

View File

@ -311,6 +311,15 @@ const config = {
'isDisabled': false,
'disabledReason': ''
},
{
'name': '定圆工具',
'icon': 'oval',
'toolName': 'FixedRadiusCircleROI',
'props': ['radius', 'area', 'mean', 'max', 'stdDev'],
'i18nKey': 'trials:reading:button:fixedCircle',
'isDisabled': false,
'disabledReason': ''
}
],
'customizeStandardsNoneDicom': [
{

View File

@ -28,10 +28,12 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
preventHandleOutsideImage: false,
storePointData: false,
centerPointRadius: 0,
radius: 10, // Default radius in mm
calculateStats: true,
radius: 6, // Default radius in mm
radiusUnit: 'mm',
statsCalculator: BasicStatsCalculator,
getTextLines: defaultGetTextLines,
simplified: true,
},
}
) {
@ -67,9 +69,12 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
);
const FrameOfReferenceUID = viewport.getFrameOfReferenceUID();
const targetId = this.getTargetId(viewport);
// Calculate end point based on fixed radius
const radius = this.configuration.radius || 10;
const radius = Number.isFinite(this.configuration.radius)
? this.configuration.radius
: 10;
// viewUp is a normalized vector.
// We want a point 'radius' distance away from center.
// We can use viewUp or any vector in the plane.
@ -106,7 +111,20 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
points: [[...worldPos], [...endPos]],
activeHandleIndex: null,
},
cachedStats: {},
cachedStats: {
[targetId]: {
Modality: null,
radius: null,
radiusUnit: null,
area: null,
mean: null,
stdDev: null,
max: null,
isEmptyArea: null,
areaUnit: null,
modalityUnit: null,
},
},
},
};
@ -180,9 +198,11 @@ class FixedRadiusCircleROITool extends cornerstoneTools.CircleROITool {
};
}
export default FixedRadiusCircleROITool;
function defaultGetTextLines(data, targetId) {
const cachedVolumeStats = data.cachedStats[targetId];
const cachedVolumeStats = data?.cachedStats?.[targetId];
if (!cachedVolumeStats) {
return [];
}
const {
radius,
radiusUnit,
@ -195,32 +215,33 @@ function defaultGetTextLines(data, targetId) {
modalityUnit,
} = cachedVolumeStats;
const textLines = [];
if (radius) {
if (csUtils.isNumber(radius)) {
const radiusLine = isEmptyArea
? `Radius: Oblique not supported`
: `Radius: ${csUtils.roundNumber(radius)} ${radiusUnit}`;
textLines.push(radiusLine);
}
if (area) {
if (csUtils.isNumber(area)) {
const areaLine = isEmptyArea
? `Area: Oblique not supported`
: `Area: ${csUtils.roundNumber(area)} ${areaUnit}`;
textLines.push(areaLine);
}
if (mean) {
if (csUtils.isNumber(mean)) {
textLines.push(`Mean: ${csUtils.roundNumber(mean)} ${modalityUnit}`);
}
if (max) {
if (csUtils.isNumber(max)) {
textLines.push(`Max: ${csUtils.roundNumber(max)} ${modalityUnit}`);
}
if (stdDev) {
if (csUtils.isNumber(stdDev)) {
textLines.push(`Std Dev: ${csUtils.roundNumber(stdDev)} ${modalityUnit}`);
}
return textLines;
}
export default FixedRadiusCircleROITool;

View File

@ -302,7 +302,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
if (readingTool === 0 || readingTool === 2|| readingTool === 3 ) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`

View File

@ -358,7 +358,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`

View File

@ -276,7 +276,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2|| readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`

View File

@ -290,7 +290,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`

View File

@ -1,31 +1,14 @@
<template>
<el-form v-if="globalForm.taskList.length > 0" ref="globalRuleForm" :model="globalForm" class="global-form">
<el-table
v-loading="loading"
:data="globalForm.taskList"
>
<el-table v-loading="loading" :data="globalForm.taskList">
<!-- 访视名称 -->
<el-table-column
prop="BlindName"
:label="$t('trials:globalReview:table:visitName')"
show-overflow-tooltip
width="150"
/>
<el-table-column prop="BlindName" :label="$t('trials:globalReview:table:visitName')" show-overflow-tooltip
width="150" />
<!-- 评估结果 -->
<el-table-column
:label="$t('trials:globalReview:table:evaluationRes')"
align="center"
prop=""
>
<el-table-column :label="$t('trials:globalReview:table:evaluationRes')" align="center" prop="">
<template>
<el-table-column
v-for="(qs,index) in globalInfo.evaluationQsList"
:key="index"
prop=""
:label="qs"
show-overflow-tooltip
width="150"
>
<el-table-column v-for="(qs, index) in globalInfo.evaluationQsList" :key="index" prop="" :label="qs"
show-overflow-tooltip width="150">
<!-- <template slot-scope="scope">
<div v-if="scope.row.BeforeQuestionList.length>index && scope.row.BeforeQuestionList[index].Answer" :style="{color: scope.row.BeforeQuestionList[index].IsGlobalAnswer ? '#f66' : null}">
<span v-if="scope.row.BeforeQuestionList[index].DictionaryCode">
@ -35,10 +18,14 @@
</div>
</template> -->
<template slot-scope="scope">
<template v-if="(scope.row.IsBaseLine && (scope.row.BeforeQuestionList[index].GlobalReadingShowType ===1 || scope.row.BeforeQuestionList[index].GlobalReadingShowType ===5)) || (!scope.row.IsBaseLine && (scope.row.BeforeQuestionList[index].GlobalReadingShowType ===2 || scope.row.BeforeQuestionList[index].GlobalReadingShowType ===6)) || (scope.row.BeforeQuestionList[index].GlobalReadingShowType ===0 || scope.row.BeforeQuestionList[index].GlobalReadingShowType ===4)">
<div v-if="scope.row.BeforeQuestionList.length>index && scope.row.BeforeQuestionList[index].Answer" :style="{color: scope.row.BeforeQuestionList[index].IsGlobalAnswer ? '#f66' : null}">
<template
v-if="(scope.row.IsBaseLine && (scope.row.BeforeQuestionList[index].GlobalReadingShowType === 1 || scope.row.BeforeQuestionList[index].GlobalReadingShowType === 5)) || (!scope.row.IsBaseLine && (scope.row.BeforeQuestionList[index].GlobalReadingShowType === 2 || scope.row.BeforeQuestionList[index].GlobalReadingShowType === 6)) || (scope.row.BeforeQuestionList[index].GlobalReadingShowType === 0 || scope.row.BeforeQuestionList[index].GlobalReadingShowType === 4)">
<div v-if="scope.row.BeforeQuestionList.length > index && scope.row.BeforeQuestionList[index].Answer"
:style="{ color: scope.row.BeforeQuestionList[index].IsGlobalAnswer ? '#f66' : null }">
<span v-if="scope.row.BeforeQuestionList[index].DictionaryCode">
{{ $fd(scope.row.BeforeQuestionList[index].DictionaryCode,parseInt(scope.row.BeforeQuestionList[index].Answer)) }}
{{
$fd(scope.row.BeforeQuestionList[index].DictionaryCode, parseInt(scope.row.BeforeQuestionList[index].Answer))
}}
</span>
<span v-else>{{ scope.row.BeforeQuestionList[index].Answer }}</span>
</div>
@ -49,211 +36,145 @@
</template>
</el-table-column>
<!-- 是否同意访视结果 -->
<el-table-column
prop=""
:label="$t('trials:globalReview:table:isAgreeEvaluationRes')"
show-overflow-tooltip
width="170"
>
<el-table-column prop="" :label="$t('trials:globalReview:table:isAgreeEvaluationRes')" show-overflow-tooltip
width="170">
<template slot-scope="scope">
<el-form-item
v-if="readingTaskState<2"
:prop="`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`"
label=""
:rules="[
<el-form-item v-if="readingTaskState < 2" :prop="`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`"
label="" :rules="[
{ required: true, message: $t('common:ruleMessage:select'), trigger: ['change', 'blur'] },
]"
class="form-item"
>
<el-radio-group
v-model="globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]"
@change="handleAgreeOrNotChange(scope.$index,scope.row.AgreeOrNot[0].GlobalAnswerType)"
>
<el-radio
v-for="item of $d.ReadingYesOrNo"
:key="'AgreeOrNot' + item.value"
:label="String(item.value)"
>
]" class="form-item">
<el-radio-group v-model="globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]"
@change="handleAgreeOrNotChange(scope.$index, scope.row.AgreeOrNot[0].GlobalAnswerType)">
<el-radio v-for="item of $d.ReadingYesOrNo" :key="'AgreeOrNot' + item.value" :label="String(item.value)">
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-tag v-else-if="scope.row.AgreeOrNot.length > 0 && parseInt(scope.row.AgreeOrNot[0].Answer) === 1" type="primary">
<el-tag v-else-if="scope.row.AgreeOrNot.length > 0 && parseInt(scope.row.AgreeOrNot[0].Answer) === 1"
type="primary">
{{ $fd('ReadingYesOrNo', parseInt(scope.row.AgreeOrNot[0].Answer)) }}
</el-tag>
<el-tag v-else-if="scope.row.AgreeOrNot.length > 0 && parseInt(scope.row.AgreeOrNot[0].Answer) === 0" type="danger"> {{ $fd('ReadingYesOrNo',parseInt(scope.row.AgreeOrNot[0].Answer)) }}</el-tag>
<el-tag v-else-if="scope.row.AgreeOrNot.length > 0 && parseInt(scope.row.AgreeOrNot[0].Answer) === 0"
type="danger"> {{ $fd('ReadingYesOrNo', parseInt(scope.row.AgreeOrNot[0].Answer)) }}</el-tag>
<span v-else />
</template>
</el-table-column>
<!-- 调整后结果 -->
<el-table-column
:label="$t('trials:globalReview:table:adjustedRes')"
align="center"
prop=""
>
<el-table-column :label="$t('trials:globalReview:table:adjustedRes')" align="center" prop="">
<template v-for="(qs, index) in globalInfo.adjustedQsList">
<el-table-column
v-if="qs.isShow"
:key="index"
prop=""
:label="qs.questionName"
show-overflow-tooltip
:min-width="index === 3 ? '200' : '200'"
>
<el-table-column v-if="qs.isShow" :key="index" prop="" :label="qs.questionName" show-overflow-tooltip
:min-width="index === 3 ? '200' : '200'">
<template slot-scope="scope">
<div v-if="readingTaskState<2 && (scope.row.AfterQuestionList[index].GlobalReadingShowType === 0 || (scope.row.IsBaseLine && scope.row.AfterQuestionList[index].GlobalReadingShowType === 1) || (!scope.row.IsBaseLine && scope.row.AfterQuestionList[index].GlobalReadingShowType === 2))">
<div
v-if="readingTaskState < 2 && (scope.row.AfterQuestionList[index].GlobalReadingShowType === 0 || (scope.row.IsBaseLine && scope.row.AfterQuestionList[index].GlobalReadingShowType === 1) || (!scope.row.IsBaseLine && scope.row.AfterQuestionList[index].GlobalReadingShowType === 2))">
<!-- <span v-if="(scope.row.IsBaseLine && scope.row.AfterQuestionList[index].LimitEdit === 2) || (!scope.row.IsBaseLine && scope.row.AfterQuestionList[index].LimitEdit === 1)">
{{ $fd(scope.row.AfterQuestionList[index].DictionaryCode, parseInt(scope.row.AfterQuestionList[index].VisitAnswer)) }}
</span> -->
<el-form-item
style="margin-bottom: 0;"
<el-form-item style="margin-bottom: 0;"
:prop="`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId ? scope.row.AfterQuestionList[index].QuestionId : scope.row.AfterQuestionList[index].GlobalAnswerType}`"
label=""
:rules="[
label="" :rules="[
{ required: parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) === 0, message: $t('common:ruleMessage:specify'), trigger: ['change', 'blur'] },
]"
>
<label v-if="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) === 0" />
]">
<label
v-if="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) === 0" />
<!-- 裁判问题 -->
<template v-if="scope.row.AfterQuestionList[index].GlobalAnswerType === 0">
<el-tooltip v-if="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId?scope.row.AfterQuestionList[index].QuestionId:String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`], scope.row.AfterQuestionList[index])" class="item" effect="dark" :content="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId?scope.row.AfterQuestionList[index].QuestionId:String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`], scope.row.AfterQuestionList[index])" placement="top-start">
<el-tooltip
v-if="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId ? scope.row.AfterQuestionList[index].QuestionId : String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`], scope.row.AfterQuestionList[index])"
class="item" effect="dark"
:content="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId ? scope.row.AfterQuestionList[index].QuestionId : String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`], scope.row.AfterQuestionList[index])"
placement="top-start">
<el-select
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId ? scope.row.AfterQuestionList[index].QuestionId : String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`]"
style="width:90%;"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
>
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0">
<template v-if="scope.row.AfterQuestionList[index].TypeValue">
<el-option
v-for="val in scope.row.AfterQuestionList[index].TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
<el-option v-for="val in scope.row.AfterQuestionList[index].TypeValue.split('|')" :key="val"
:label="val" :value="val" />
</template>
<template v-else-if="scope.row.AfterQuestionList[index].DictionaryCode && scope.row.AfterQuestionList[index].QuestionType === 13">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value!==-1"
:key="item.id"
:value="String(item.value)"
:label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
/>
<template
v-else-if="scope.row.AfterQuestionList[index].DictionaryCode && scope.row.AfterQuestionList[index].QuestionType === 13">
<el-option v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value !== -1" :key="item.id" :value="String(item.value)" :label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName, scope.row)) === String(item.value)" />
</template>
<template v-else-if="scope.row.AfterQuestionList[index].DictionaryCode">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
:key="item.id"
:value="String(item.value)"
<el-option v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
:key="item.id" :value="String(item.value)"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName, scope.row)) === String(item.value)"
:label="item.label"
/>
:label="item.label" />
</template>
</el-select>
</el-tooltip>
<el-select
v-else
<el-select v-else
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId ? scope.row.AfterQuestionList[index].QuestionId : String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`]"
style="width:90%;"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
>
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0">
<template v-if="scope.row.AfterQuestionList[index].TypeValue">
<el-option
v-for="val in scope.row.AfterQuestionList[index].TypeValue.split('|')"
:key="val"
:label="val"
:value="val"
/>
<el-option v-for="val in scope.row.AfterQuestionList[index].TypeValue.split('|')" :key="val"
:label="val" :value="val" />
</template>
<template v-else-if="scope.row.AfterQuestionList[index].DictionaryCode && scope.row.AfterQuestionList[index].QuestionType === 13">
<template
v-else-if="scope.row.AfterQuestionList[index].DictionaryCode && scope.row.AfterQuestionList[index].QuestionType === 13">
<template v-if="getLesionCount(scope.row.LesionCountList, 0)">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value!==-1 && item.value !== 1 && item.value !== 3"
:key="item.id"
:value="String(item.value)"
:label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
/>
<el-option v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value !== -1 && item.value !== 1 && item.value !== 3" :key="item.id"
:value="String(item.value)" :label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName, scope.row)) === String(item.value)" />
</template>
<template v-else-if="getLesionCount(scope.row.LesionCountList, 1)">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value!==-1 && item.value !== 1 && item.value !== 6"
:key="item.id"
:value="String(item.value)"
:label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
/>
<el-option v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value !== -1 && item.value !== 1 && item.value !== 6" :key="item.id"
:value="String(item.value)" :label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName, scope.row)) === String(item.value)" />
</template>
<template v-else>
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value === 1 || item.value === 2 || item.value === 4"
:key="item.id"
:value="String(item.value)"
:label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName,scope.row)) === String(item.value)"
/>
<el-option v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value === 1 || item.value === 2 || item.value === 4" :key="item.id"
:value="String(item.value)" :label="item.label"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName, scope.row)) === String(item.value)" />
</template>
</template>
<template v-else-if="scope.row.AfterQuestionList[index].DictionaryCode">
<el-option
v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value !== 6"
:key="item.id"
:value="String(item.value)"
<el-option v-for="item of $d[scope.row.AfterQuestionList[index].DictionaryCode]"
v-show="item.value !== 6" :key="item.id" :value="String(item.value)"
:disabled="String(getBeforeAnswer(scope.row.AfterQuestionList[index].QuestionName, scope.row)) === String(item.value)"
:label="item.label"
/>
:label="item.label" />
</template>
</el-select>
</template>
<!-- 评估更新类型 GlobalAnswerType:3 -->
<template v-if="scope.row.AfterQuestionList[index].GlobalAnswerType === 3">
<el-tooltip v-if="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`], scope.row.AfterQuestionList[index], scope.row.AfterQuestionList[index].GlobalAnswerType)" class="item" effect="dark" :content="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`], scope.row.AfterQuestionList[index], scope.row.AfterQuestionList[index].GlobalAnswerType)" placement="top-start">
<el-tooltip
v-if="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`], scope.row.AfterQuestionList[index], scope.row.AfterQuestionList[index].GlobalAnswerType)"
class="item" effect="dark"
:content="getText(globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`], scope.row.AfterQuestionList[index], scope.row.AfterQuestionList[index].GlobalAnswerType)"
placement="top-start">
<el-select
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`]"
style="width:90%;"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
>
<el-option
v-for="val in globalInfo.assessTypeList"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0">
<el-option v-for="val in globalInfo.assessTypeList"
v-show="(scope.row.IsBaseLine && val.IsBaseLineUse) || (!scope.row.IsBaseLine && val.IsFollowVisitUse)"
:key="val.Code"
:label="language === 'en'?val.Value:val.ValueCN"
:value="val.Code"
/>
:key="val.Code" :label="language === 'en' ? val.Value : val.ValueCN" :value="val.Code" />
</el-select>
</el-tooltip>
<el-select
v-else
<el-select v-else
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].GlobalAnswerType}`]"
style="width:90%;"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
>
<el-option
v-for="val in globalInfo.assessTypeList"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0">
<el-option v-for="val in globalInfo.assessTypeList"
v-show="(scope.row.IsBaseLine && val.IsBaseLineUse) || (!scope.row.IsBaseLine && val.IsFollowVisitUse)"
:key="val.Code"
:label="language === 'en'?val.Value:val.ValueCN"
:value="val.Code"
/>
:key="val.Code" :label="language === 'en' ? val.Value : val.ValueCN" :value="val.Code" />
</el-select>
</template>
<el-input
v-if="scope.row.AfterQuestionList[index].GlobalAnswerType === 1"
<el-input v-if="scope.row.AfterQuestionList[index].GlobalAnswerType === 1"
v-model="globalForm[`${scope.$index}${scope.row.AfterQuestionList[index].QuestionId ? scope.row.AfterQuestionList[index].QuestionId : String(scope.row.AfterQuestionList[index].GlobalAnswerType)}`]"
type="textarea"
maxlength="100"
show-word-limit
style="width:90%;"
:autosize="{ minRows: 2 }"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0"
/>
type="textarea" maxlength="100" show-word-limit style="width:90%;" :autosize="{ minRows: 2 }"
:disabled="parseInt(globalForm[`${scope.$index}${scope.row.AgreeOrNot[0].GlobalAnswerType}`]) !== 0" />
</el-form-item>
</div>
<div v-else>
@ -262,7 +183,9 @@
{{ getAssessType(scope.row.AfterQuestionList[index].Answer) }}
</span>
<span v-else-if="scope.row.AfterQuestionList[index].DictionaryCode">
{{ $fd(scope.row.AfterQuestionList[index].DictionaryCode,parseInt(scope.row.AfterQuestionList[index].Answer)) }}
{{
$fd(scope.row.AfterQuestionList[index].DictionaryCode, parseInt(scope.row.AfterQuestionList[index].Answer))
}}
</span>
<span v-else>{{ scope.row.AfterQuestionList[index].Answer }}</span>
</div>
@ -270,17 +193,10 @@
</el-table-column>
</template>
</el-table-column>
<el-table-column
:label="$t('common:action:action')"
width="100"
>
<el-table-column :label="$t('common:action:action')" width="100">
<template slot-scope="scope">
<el-button
circle
:title="$t('trials:globalReview:table:view')"
icon="el-icon-view"
@click="handleView(scope.row)"
/>
<el-button circle :title="$t('trials:globalReview:table:view')" icon="el-icon-view"
@click="handleView(scope.row)" />
</template>
</el-table-column>
</el-table>
@ -348,7 +264,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
@ -383,8 +299,10 @@ export default {
}
answerList.push(obj)
})
answerList.push({ questionId: '', globalAnswerType: item.AgreeOrNot[0].GlobalAnswerType,
answer: this.globalForm[ `${index}${item.AgreeOrNot[0].GlobalAnswerType}`] })
answerList.push({
questionId: '', globalAnswerType: item.AgreeOrNot[0].GlobalAnswerType,
answer: this.globalForm[`${index}${item.AgreeOrNot[0].GlobalAnswerType}`]
})
visitTaskAnswerList.push({ visitTaskId: item.VisitTaskId, answerList: answerList })
})
@ -462,17 +380,21 @@ export default {
color: #F56C6C;
margin-right: 4px;
}
::v-deep .el-textarea .el-input__count {
background: rgba(0, 0, 0, 0);
line-height: normal;
}
::v-deep .el-form-item {
margin-bottom: 0px;
}
.global-form {
::v-deep .el-form-item__content {
padding-bottom: 10px;
}
::v-deep .form-item .el-form-item__error {
top: 60%;
}

View File

@ -355,7 +355,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`

View File

@ -349,7 +349,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.globalInfo.trialId}&subjectCode=${this.globalInfo.subjectCode}&subjectId=${this.globalInfo.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`

View File

@ -404,7 +404,7 @@ export default {
readingTool = isNaN(parseInt(readingTool)) ? null : parseInt(readingTool)
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var path = ''
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2|| readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`

View File

@ -250,7 +250,7 @@ export default {
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId;
var path = "";
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2|| readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode
}&subjectId=${this.rowData.SubjectId
}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`;
@ -273,7 +273,7 @@ export default {
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId;
var path = "";
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2|| readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode
}&subjectId=${this.rowData.SubjectId}&visitTaskId=${task.VisitTaskId
}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`;

View File

@ -258,7 +258,7 @@ export default {
var isReadingTaskViewInOrder = this.rowData.IsReadingTaskViewInOrder
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2|| readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`

View File

@ -146,7 +146,7 @@ export default {
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2|| readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`

View File

@ -164,7 +164,7 @@ export default {
var trialReadingCriterionId = this.rowData.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${this.rowData.SubjectCode}&subjectId=${this.rowData.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`

View File

@ -795,7 +795,7 @@ export default {
var trialReadingCriterionId = row.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2 ) {
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}&key=${new Date().getTime()}`

View File

@ -452,7 +452,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var trialReadingCriterionId = this.$router.currentRoute.query.TrialReadingCriterionId
var path = ''
if (readingTool === 0 || readingTool === 2) {
if (readingTool === 0 || readingTool === 2 || readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${trialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${this.subjectCode}&subjectId=${this.subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${readingTool}&TokenKey=${token}`

View File

@ -953,7 +953,7 @@ export default {
lookReadingResults(row) {
var token = getToken()
var path
if (row.ReadingTool === 0 || row.ReadingTool === 2) {
if (row.ReadingTool === 0 || row.ReadingTool === 2|| row.ReadingTool === 3) {
path = `/readingDicoms?trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`

View File

@ -435,7 +435,7 @@ export default {
window.localStorage.setItem('TrialReadingCriterionId', this.TrialReadingCriterionId)
var token = getToken()
var path = ''
if (row.ReadingTool === 0 || row.ReadingTool === 2) {
if (row.ReadingTool === 0 || row.ReadingTool === 2 || row.ReadingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`

View File

@ -32,7 +32,7 @@ export default {
var isReadingTaskViewInOrder = this.$router.currentRoute.query.isReadingTaskViewInOrder
var criterionType = this.$router.currentRoute.query.criterionType
var path = ''
if (this.readingTool === 0 || this.readingTool === 2 ) {
if (this.readingTool === 0 || this.readingTool === 2 || this.readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${TrialReadingCriterionId}&trialId=${trialId}&subjectCode=${subjectCode}&subjectId=${subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${this.readingTool}&TokenKey=${this.token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${TrialReadingCriterionId}&trialId=${trialId}&subjectCode=${subjectCode}&subjectId=${subjectId}&visitTaskId=${visitTaskId}&isReadingTaskViewInOrder=${isReadingTaskViewInOrder}&criterionType=${criterionType}&readingTool=${this.readingTool}&TokenKey=${this.token}`

View File

@ -368,7 +368,7 @@ export default {
)
var token = getToken()
var path = ''
if (this.readingTool === 0 || this.readingTool === 2) {
if (this.readingTool === 0 || this.readingTool === 2|| this.readingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${this.TrialReadingCriterionId}&trialId=${this.trialId}&isReadingTaskViewInOrder=${this.isReadingTaskViewInOrder}&criterionType=${this.criterionType}&readingTool=${this.readingTool}&TokenKey=${token}`

View File

@ -614,7 +614,7 @@ export default {
}
var token = getToken()
var path
if (row.ReadingTool === 0 || row.ReadingTool === 2) {
if (row.ReadingTool === 0 || row.ReadingTool === 2|| row.ReadingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`

View File

@ -1202,7 +1202,7 @@ export default {
}
var token = getToken()
var path
if (row.ReadingTool === 0 || row.ReadingTool === 2) {
if (row.ReadingTool === 0 || row.ReadingTool === 2 || row.ReadingTool === 3) {
path = `/readingDicoms?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`
} else {
path = `/noneDicomReading?TrialReadingCriterionId=${row.TrialReadingCriterionId}&trialId=${this.trialId}&subjectCode=${row.SubjectCode}&subjectId=${row.SubjectId}&visitTaskId=${row.Id}&isReadingTaskViewInOrder=${row.IsReadingTaskViewInOrder}&criterionType=${row.CriterionType}&readingTool=${row.ReadingTool}&TokenKey=${token}`

View File

@ -4,123 +4,63 @@
<template>
<el-form v-loading="loading">
<!-- 仲裁对象 -->
<el-form-item
:label="$t('trials:adRules:title:arbitrationRule')"
:label-width="'180px'"
>
<el-radio-group
v-model="JudgyInfo.ArbitrationRule"
:disabled="OtherInfo.IsSign"
>
<el-radio
v-for="item of $d.ArbitrationRule"
:key="item.id"
:disabled="!JudgyInfo.IsReadingTaskViewInOrder || true"
:label="item.value"
@change="changeArbitrationRule"
>
<el-form-item :label="$t('trials:adRules:title:arbitrationRule')" :label-width="'180px'">
<el-radio-group v-model="JudgyInfo.ArbitrationRule" :disabled="OtherInfo.IsSign">
<el-radio v-for="item of $d.ArbitrationRule" :key="item.id"
:disabled="!JudgyInfo.IsReadingTaskViewInOrder || true" :label="item.value" @change="changeArbitrationRule">
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-collapse v-model="activeNames" style="border-top:none;">
<el-collapse-item
v-for="(item, index) of QuestionList"
:key="item.ReadingQuestionTrialId"
style="position: relative;padding:0 10px;"
:name="item.ReadingQuestionTrialId"
>
<el-collapse-item v-for="(item, index) of QuestionList" :key="item.ReadingQuestionTrialId"
style="position: relative;padding:0 10px;" :name="item.ReadingQuestionTrialId">
<div slot="title">
{{ item.PageName }}Q{{ index + 1 }}{{ item.QuestionName }}
</div>
<div style="position: relative">
<el-button
v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']"
style="position: absolute; right: 0; top: 0; z-index: 10"
size="mini"
type="primary"
:disabled="OtherInfo.IsSign"
@click="apply(item.ReadingQuestionTrialId, index)"
>{{ $t('common:button:save') }}</el-button
>
<el-button
v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']"
style="position: absolute; right: 60px; top: 0; z-index: 10"
size="mini"
type="primary"
:disabled="OtherInfo.IsSign"
@click="reset(item.ReadingQuestionTrialId, index)"
>{{ $t('common:button:reset') }}</el-button
>
<el-button v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']"
style="position: absolute; right: 0; top: 0; z-index: 10" size="mini" type="primary"
:disabled="OtherInfo.IsSign" @click="apply(item.ReadingQuestionTrialId, index)">{{ $t('common:button:save')
}}</el-button>
<el-button v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']"
style="position: absolute; right: 60px; top: 0; z-index: 10" size="mini" type="primary"
:disabled="OtherInfo.IsSign" @click="reset(item.ReadingQuestionTrialId, index)">{{ $t('common:button:reset')
}}</el-button>
<!-- 产生裁判阅片的条件 -->
<el-form-item
:label-width="'280px'"
:label="$t('trials:adRules:title:judgeTypeEnum')"
style="width: 95%"
>
<el-radio-group
v-model="QuestionList[index].JudgeType"
@input="(val) => JudgeTypeChange(val, index)"
>
<el-form-item :label-width="'280px'" :label="$t('trials:adRules:title:judgeTypeEnum')" style="width: 95%">
<el-radio-group v-model="QuestionList[index].JudgeType" @input="(val) => JudgeTypeChange(val, index)">
<template v-for="item of $d.JudgeTypeEnum">
<el-radio
style="margin-bottom: 5px"
:key="item.id"
:disabled="
OtherInfo.IsSign ||
<el-radio style="margin-bottom: 5px" :key="item.id" :disabled="OtherInfo.IsSign ||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
"
:label="item.value"
v-if="
" :label="item.value" v-if="
item.value < 4 ||
(item.value > 3 &&
(QuestionList[index].Type === 'number' ||
QuestionList[index].Type === 'calculation'))
"
>
">
{{ item.label }}
</el-radio>
</template>
</el-radio-group>
</el-form-item>
</div>
<div
v-if="QuestionList[index].JudgeType === 3"
style="text-align: right; margin: 10px 0"
>
<div v-if="QuestionList[index].JudgeType === 3" style="text-align: right; margin: 10px 0">
<!-- 添加规则 -->
<el-button
v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']"
size="mini"
type="primary"
:disabled="OtherInfo.IsSign"
@click="addGroup(index, null)"
>{{ $t('trials:adRules:button:addRule') }}</el-button
>
<el-button v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']" size="mini" type="primary"
:disabled="OtherInfo.IsSign" @click="addGroup(index, null)">{{ $t('trials:adRules:button:addRule')
}}</el-button>
</div>
<el-table
v-if="QuestionList[index].JudgeType === 3"
v-loading="loading"
:data="QuestionList[index].AnswerGroupList"
border
stripe
>
<el-table v-if="QuestionList[index].JudgeType === 3" v-loading="loading"
:data="QuestionList[index].AnswerGroupList" border stripe>
<!-- 序号 -->
<el-table-column
prop="AnswerGroupA"
:label="$t('trials:adRules:title:order')"
width="160"
>
<el-table-column prop="AnswerGroupA" :label="$t('trials:adRules:title:order')" width="160">
<template slot-scope="scope">
{{ scope.$index + 1 }}
</template>
</el-table-column>
<!-- 阅片人A -->
<el-table-column
prop="AnswerGroupA"
:label="$t('trials:adRules:title:answerGroupA')"
min-width="100"
>
<el-table-column prop="AnswerGroupA" :label="$t('trials:adRules:title:answerGroupA')" min-width="100">
<template slot-scope="scope">
{{
QuestionList[index].QuestionGenre === 3
@ -132,11 +72,7 @@
</template>
</el-table-column>
<!-- 阅片人B -->
<el-table-column
prop="AnswerGroupB"
:label="$t('trials:adRules:title:answerGroupB')"
min-width="100"
>
<el-table-column prop="AnswerGroupB" :label="$t('trials:adRules:title:answerGroupB')" min-width="100">
<template slot-scope="scope">
{{
QuestionList[index].QuestionGenre === 3
@ -147,83 +83,48 @@
}}
</template>
</el-table-column>
<el-table-column
prop="AnswerGroupB"
:label="$t('common:action:action')"
min-width="80"
>
<el-table-column prop="AnswerGroupB" :label="$t('common:action:action')" min-width="80">
<template slot-scope="scope">
<!-- 编辑 -->
<el-button
icon="el-icon-edit"
circle
:title="$t('common:button:edit')"
size="mini"
:disabled="OtherInfo.IsSign"
@click="addGroup(index, scope.$index)"
/>
<el-button icon="el-icon-edit" circle :title="$t('common:button:edit')" size="mini"
:disabled="OtherInfo.IsSign" @click="addGroup(index, scope.$index)" />
<!-- 删除 -->
<el-button
icon="el-icon-delete"
circle
:title="$t('common:button:delete')"
size="mini"
:disabled="OtherInfo.IsSign"
@click="tagClose(index, scope.$index)"
/>
<el-button icon="el-icon-delete" circle :title="$t('common:button:delete')" size="mini"
:disabled="OtherInfo.IsSign" @click="tagClose(index, scope.$index)" />
</template>
</el-table-column>
</el-table>
<div v-if="QuestionList[index].JudgeType === 2">
<div
style="
<div style="
background: #f6f6f6;
border-radius: 20px;
padding: 15px 20px;
margin-top: 10px;
"
>
<el-checkbox-group
v-if="QuestionList[index].QuestionGenre === 3"
v-model="QuestionList[index].grouping"
>
">
<el-checkbox-group v-if="QuestionList[index].QuestionGenre === 3" v-model="QuestionList[index].grouping">
<template v-for="item of $d[QuestionList[index].DictionaryCode]">
<el-checkbox
v-if="item.value !== -1"
:key="item.id"
:disabled="OtherInfo.IsSign"
:label="item.value"
>{{ item.label }}</el-checkbox
>
<el-checkbox v-if="item.value !== -1" :key="item.id" :disabled="OtherInfo.IsSign" :label="item.value">{{
item.label }}</el-checkbox>
</template>
</el-checkbox-group>
<el-checkbox-group v-else v-model="QuestionList[index].grouping">
<el-checkbox
v-for="item of item.TypeValue.split('|')"
:key="item"
:disabled="OtherInfo.IsSign"
:label="item"
>{{ item }}</el-checkbox
>
<el-checkbox v-for="item of item.TypeValue.split('|')" :key="item" :disabled="OtherInfo.IsSign"
:label="item">{{
item }}</el-checkbox>
</el-checkbox-group>
<div style="margin-top: 20px">
<!-- 当前选择答案分组: -->
{{ $t('trials:adRules:title:selectAnswerGroup') }}
<el-tag
v-if="
<el-tag v-if="
QuestionList[index].grouping.length > 0 &&
QuestionList[index].QuestionGenre !== 3
"
>{{
">{{
QuestionList[index].grouping.toString().replaceAll(',', '|')
}}</el-tag
>
<el-tag
v-if="
}}</el-tag>
<el-tag v-if="
QuestionList[index].grouping.length > 0 &&
QuestionList[index].QuestionGenre === 3
"
>{{
">{{
QuestionList[index].grouping
.map(
(v) =>
@ -233,54 +134,31 @@
)
.toString()
.replaceAll(',', '|')
}}</el-tag
>
<el-button
size="mini"
type="primary"
:disabled="OtherInfo.IsSign"
@click="addGroup2(index)"
>
}}</el-tag>
<el-button size="mini" type="primary" :disabled="OtherInfo.IsSign" @click="addGroup2(index)">
<!-- 添加分组 -->
{{ $t('trials:adRules:title:addGroup') }}
</el-button>
</div>
</div>
<div
v-if="QuestionList[index].QuestionGenre !== 3"
style="margin-top: 20px"
>
<div v-if="QuestionList[index].QuestionGenre !== 3" style="margin-top: 20px">
{{ $t('trials:adRules:title:group') }}
<el-tag
v-for="itemA of QuestionList[index].AnswerGroup2List"
:key="itemA"
closable
style="margin-right: 10px"
@close="
<el-tag v-for="itemA of QuestionList[index].AnswerGroup2List" :key="itemA" closable
style="margin-right: 10px" @close="
() => {
return tagClose2(index, indexA)
}
"
>{{ itemA }}</el-tag
>
">{{ itemA }}</el-tag>
</div>
<div
v-if="QuestionList[index].QuestionGenre === 3"
style="margin-top: 20px"
>
<div v-if="QuestionList[index].QuestionGenre === 3" style="margin-top: 20px">
<!-- 分组: -->
{{ $t('trials:adRules:title:group') }}
<el-tag
v-for="itemA of QuestionList[index].AnswerGroup2List"
:key="itemA"
closable
style="margin-right: 10px"
@close="
<el-tag v-for="itemA of QuestionList[index].AnswerGroup2List" :key="itemA" closable
style="margin-right: 10px" @close="
() => {
return tagClose2(index, indexA)
}
"
>
">
{{
itemA
.split('|')
@ -292,58 +170,31 @@
</el-tag>
</div>
</div>
<div
v-if="
<div v-if="
QuestionList[index].JudgeType === 4 ||
QuestionList[index].JudgeType === 5
"
>
<el-form
:inline="true"
:model="QuestionList[index]"
class="demo-form-inline"
:ref="
'JudgeDifferenceValue' + QuestionList[index].JudgeType + index
"
:rules="JudgeDifferenceValueQRules"
>
<el-form-item
:label="
$t(
">
<el-form :inline="true" :model="QuestionList[index]" class="demo-form-inline" :ref="'JudgeDifferenceValue' + QuestionList[index].JudgeType + index
" :rules="JudgeDifferenceValueQRules">
<el-form-item :label="$t(
`trials:trials-panel:setting:reading-unit:JudgeDifferenceType`
)
"
prop="JudgeDifferenceType"
>
<el-select
v-model="QuestionList[index].JudgeDifferenceType"
placeholder="请选择"
:disabled="OtherInfo.IsSign"
>
<el-option
v-for="item of $d.JudgeDifferenceType"
:key="item.id"
:label="item.label"
:value="item.value"
>
" prop="JudgeDifferenceType">
<el-select v-model="QuestionList[index].JudgeDifferenceType" placeholder="请选择"
:disabled="OtherInfo.IsSign">
<el-option v-for="item of $d.JudgeDifferenceType" :key="item.id" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item
:label="
$t(
<el-form-item :label="$t(
`trials:trials-panel:setting:reading-unit:JudgeDifferenceValue${QuestionList[index].JudgeType}`
)
"
prop="JudgeDifferenceValue"
>
" prop="JudgeDifferenceValue">
<div style="display: flex">
<el-input
v-model="QuestionList[index].JudgeDifferenceValue"
clearable
:disabled="OtherInfo.IsSign"
></el-input>
<span style="margin-left: 10px">{{
<el-input v-model="QuestionList[index].JudgeDifferenceValue" clearable
:disabled="OtherInfo.IsSign"></el-input>
<span style="margin-left: 10px" v-if="QuestionList[index].JudgeType !== 5">{{
$fd('ValueUnit', QuestionList[index].Unit)
}}</span>
</div>
@ -355,124 +206,62 @@
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<!-- 选择答案 -->
<el-dialog
v-if="QuestionVisible"
:title="$t('trials:adRules:title:selectAnswer')"
:visible.sync="QuestionVisible"
width="800px"
:close-on-click-modal="false"
custom-class="base-dialog-wrapper"
append-to-body
>
<el-dialog v-if="QuestionVisible" :title="$t('trials:adRules:title:selectAnswer')" :visible.sync="QuestionVisible"
width="800px" :close-on-click-modal="false" custom-class="base-dialog-wrapper" append-to-body>
<div class="base-dialog-body">
<!-- 阅片人A -->
<el-form-item
label-width="110px"
:label="$t('trials:adRules:title:answerGroupA')"
>
<el-form-item label-width="110px" :label="$t('trials:adRules:title:answerGroupA')">
<el-checkbox-group v-model="QuestionList[selectIndex].groupingA">
<template
v-for="item of QuestionList[selectIndex].TypeValue.split('|')"
>
<el-checkbox
v-if="QuestionList[selectIndex].QuestionGenre !== 3"
:key="item"
:label="item"
:disabled="
QuestionList[selectIndex].groupingB.length
<template v-for="item of QuestionList[selectIndex].TypeValue.split('|')">
<el-checkbox v-if="QuestionList[selectIndex].QuestionGenre !== 3" :key="item" :label="item" :disabled="QuestionList[selectIndex].groupingB.length
? ~QuestionList[selectIndex].groupingB.indexOf(item)
: false
"
>{{ item }}</el-checkbox
>
">{{ item }}</el-checkbox>
</template>
<template
v-for="item of $d[QuestionList[selectIndex].DictionaryCode]"
>
<el-checkbox
v-if="
<template v-for="item of $d[QuestionList[selectIndex].DictionaryCode]">
<el-checkbox v-if="
QuestionList[selectIndex].QuestionGenre === 3 &&
item.value !== -1
"
:key="item.id"
:label="item.value"
:disabled="
QuestionList[selectIndex].groupingB.length
" :key="item.id" :label="item.value" :disabled="QuestionList[selectIndex].groupingB.length
? ~QuestionList[selectIndex].groupingB.indexOf(item.value)
: false
"
>{{ item.label }}</el-checkbox
>
">{{ item.label }}</el-checkbox>
</template>
</el-checkbox-group>
</el-form-item>
<!-- 阅片人B -->
<el-form-item
label-width="110px"
:label="$t('trials:adRules:title:answerGroupB')"
>
<el-form-item label-width="110px" :label="$t('trials:adRules:title:answerGroupB')">
<el-checkbox-group v-model="QuestionList[selectIndex].groupingB">
<template
v-for="item of QuestionList[selectIndex].TypeValue.split('|')"
>
<el-checkbox
v-if="QuestionList[selectIndex].QuestionGenre !== 3"
:key="item"
:label="item"
:disabled="
QuestionList[selectIndex].groupingA.length
<template v-for="item of QuestionList[selectIndex].TypeValue.split('|')">
<el-checkbox v-if="QuestionList[selectIndex].QuestionGenre !== 3" :key="item" :label="item" :disabled="QuestionList[selectIndex].groupingA.length
? ~QuestionList[selectIndex].groupingA.indexOf(item)
: false
"
>{{ item }}</el-checkbox
>
">{{ item }}</el-checkbox>
</template>
<template
v-for="item of $d[QuestionList[selectIndex].DictionaryCode]"
>
<el-checkbox
v-if="
<template v-for="item of $d[QuestionList[selectIndex].DictionaryCode]">
<el-checkbox v-if="
QuestionList[selectIndex].QuestionGenre === 3 &&
item.value !== -1
"
:key="item.id"
:label="item.value"
:disabled="
QuestionList[selectIndex].groupingA.length
" :key="item.id" :label="item.value" :disabled="QuestionList[selectIndex].groupingA.length
? ~QuestionList[selectIndex].groupingA.indexOf(item.value)
: false
"
>{{ item.label }}</el-checkbox
>
">{{ item.label }}</el-checkbox>
</template>
</el-checkbox-group>
</el-form-item>
</div>
<div
class="base-dialog-footer"
style="text-align: right; margin-top: 10px"
>
<div class="base-dialog-footer" style="text-align: right; margin-top: 10px">
<!-- 取消 -->
<el-button
v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']"
:disabled="btnLoading"
size="small"
type="primary"
@click="
<el-button v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']" :disabled="btnLoading" size="small"
type="primary" @click="
QuestionVisible = false
$set(QuestionList[selectIndex], 'groupingA', [])
$set(QuestionList[selectIndex], 'groupingB', [])
"
>
">
{{ $t('common:button:cancel') }}
</el-button>
<el-button
v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']"
:disabled="btnLoading"
size="small"
type="primary"
@click="save"
>
<el-button v-hasPermi="['trials:trials-panel:setting:reading-unit:edit']" :disabled="btnLoading" size="small"
type="primary" @click="save">
{{ $t('common:button:save') }}
</el-button>
</div>

View File

@ -34,13 +34,22 @@
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1"
:label="$t('trials:readingUnit:readingRules:title:measureTool')">
<el-checkbox-group v-model="form.ReadingToolList" :disabled="isConfirm ||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
">
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])" @change="handleReadingToolListChange">
<el-checkbox v-for="tool in tools" :key="tool.toolName" :label="tool.toolName" name="ReadingToolList">
{{ $t(`${tool.i18nKey}`) }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 定圆工具半径 -->
<el-form-item v-if="form.ReadingToolList.includes('FixedRadiusCircleROI')"
:label="$t('trials:readingUnit:readingRules:title:circleRadius')" prop="CircleRadius">
<el-input v-model="form.CircleRadius" @input="(val) => handleNumberInput(val, 'CircleRadius')" clearable
:disabled="isConfirm ||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
">
<template slot="append">mm</template>
</el-input>
</el-form-item>
<!--分割工具 && (form.ReadingTool === 0 || form.ReadingTool === 1 || form.ReadingTool === 2)-->
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1 && form.ReadingTool === 3"
:label="$t('trials:readingUnit:readingRules:title:segmentTool')">
@ -52,6 +61,22 @@
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 分割组名 -->
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1 && form.ReadingTool === 3"
:label="$t('trials:readingUnit:readingRules:title:segmentations')" prop="DefaultSegmentName.SegmentationName">
<el-input v-model="form.DefaultSegmentName.SegmentationName" :disabled="isConfirm ||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])" style="width: 80%">
</el-input>
</el-form-item>
<!-- 分割片段 -->
<el-form-item v-if="CriterionType === 0 && form.ReadingVersionEnum === 1 && form.ReadingTool === 3"
:label="$t('trials:readingUnit:readingRules:title:segment')" prop="DefaultSegmentName.SegmentNameList">
<el-input v-model="form.SegmentNameListStr" style="width: 80%" disabled>
</el-input>
<el-button :disabled="isConfirm ||
!hasPermi(['trials:trials-panel:setting:reading-unit:edit'])
" icon="el-icon-plus" circle @click="segmentVisible = true" />
</el-form-item>
<!--检查类型筛选-->
<el-form-item :label="$t('trials:processCfg:form:IsImageFilter')" prop="IsImageFilter">
<el-radio-group v-model="form.IsImageFilter" @input="IsImageFilterChange" :disabled="isConfirm ||
@ -392,6 +417,35 @@
</el-dialog>
<attachmentPreview :visible.sync="perview_visible" :isView="true" :isExternal="true" :ExternalList="ExternalList"
v-if="perview_visible" />
<!-- 分割段编辑 -->
<el-dialog v-if="segmentVisible" :visible.sync="segmentVisible" :close-on-click-modal="false" width="600px"
:title="$t('trials:trialCfg:dialogTitle:addSegment')" custom-class="base-dialog-wrapper">
<div class="base-dialog-body">
<el-button @click.stop="addSegment" style="margin-bottom: 5px;" type="primary" size="small">{{
$t('common:button:new') }}</el-button>
<el-table :data="SegmentNameList" border style="width: 100%" size="small">
<el-table-column type="index" width="50" />
<el-table-column prop="name" :label="$t('trials:trialCfg:table:segmentName')" show-overflow-tooltip />
<el-table-column :label="$t('common:action:action')" align="left" fixed="right">
<template slot-scope="scope">
<el-button circle icon="el-icon-top" :title="$t('dictionary:template:keyDocList:button:up')"
@click.stop="sortSegment(scope, 'up')" :disabled="scope.$index === 0" />
<el-button circle icon="el-icon-bottom" :title="$t('dictionary:template:keyDocList:button:down')"
@click.stop="sortSegment(scope, 'down')" :disabled="scope.$index === SegmentNameList.length - 1" />
<el-button circle icon="el-icon-edit-outline" :title="$t('dictionary:template:keyDocList:button:update')"
@click.stop="updateSegment(scope)" />
<el-button circle icon="el-icon-delete" :title="$t('dictionary:template:keyDocList:button:del')"
@click.stop="delSegment(scope)" />
</template>
</el-table-column>
</el-table>
</div>
<div class="base-dialog-footer" style="text-align: right; margin-top: 10px">
<el-button size="small" type="primary" @click="segmentVisible = false">
{{ $t('trials:trialCfg:button:confirmCfg') }}
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
@ -420,6 +474,7 @@ export default {
},
data() {
return {
segmentVisible: false,
perview_visible: false,
ExternalList: [],
keyDocVisible: false,
@ -432,6 +487,11 @@ export default {
ReadingVersionEnum: null,
ReadingToolList: [],
SegmentToolList: [],
DefaultSegmentName: {
SegmentationName: null,
SegmentNameList: []
},
SegmentNameListStr: null,
ReadingTaskViewEnum: null,
IsImageLabeled: null,
IsReadingShowSubjectInfo: null,
@ -460,7 +520,8 @@ export default {
ImageUploadEnum: null,
IsImageFilter: false,
KeyFileListStr: '',
KeyFileList: []
KeyFileList: [],
CircleRadius: null
},
rules: {
IsAutoCreate: [
@ -596,6 +657,21 @@ export default {
trigger: ['blur', 'change'],
},
],
'DefaultSegmentName.SegmentationName': [
{
required: true,
message: this.$t('common:ruleMessage:specify'),
trigger: ['blur', 'change'],
},
],
'DefaultSegmentName.SegmentNameList': [
{
required: true,
type: 'array',
message: this.$t('common:ruleMessage:select'),
trigger: ['blur', 'change'],
},
],
CriterionModalitys: [
{
required: true,
@ -613,6 +689,10 @@ export default {
trigger: ['blur', 'change'],
},
],
CircleRadius: [
{ required: true, message: this.$t('common:ruleMessage:specify'), trigger: ['blur', 'change'], },
{ pattern: /^(0|[1-9]\d*)(\.\d{1,2})?$/, message: '请输入正确的数值格式,最多两位小数', trigger: 'blur' }
]
// IsReadingTaskViewInOrder: [
// { required: true, message: this.$t('common:ruleMessage:select'), trigger: ['blur', 'change'] }
// ]
@ -642,6 +722,16 @@ export default {
isUSA() {
return process.env.NODE_ENV === 'usa'
},
SegmentNameList() {
let arr = []
this.form.DefaultSegmentName.SegmentNameList.forEach(item => {
let obj = {
name: item
}
arr.push(obj)
})
return arr
}
},
watch: {
CriterionModalitys: {
@ -652,6 +742,76 @@ export default {
},
},
methods: {
sortSegment(row, key) {
let index = row.$index
let name = row.row.name
this.form.DefaultSegmentName.SegmentNameList.splice(index, 1)
if (key === 'up') {
this.form.DefaultSegmentName.SegmentNameList.splice(index - 1, 0, name)
} else {
this.form.DefaultSegmentName.SegmentNameList.splice(index + 1, 0, name)
}
},
async updateSegment(row) {
let name = await this.customPrompt(row.row.name)
if (!name) return false
let index = row.$index
this.form.DefaultSegmentName.SegmentNameList.splice(index, 1, name)
this.form.SegmentNameListStr = this.form.DefaultSegmentName.SegmentNameList.join("|")
},
delSegment(row) {
console.log(row)
let index = row.$index
this.form.DefaultSegmentName.SegmentNameList.splice(index, 1)
this.form.SegmentNameListStr = this.form.DefaultSegmentName.SegmentNameList.join("|")
},
async addSegment() {
let name = await this.customPrompt()
if (!name) return false
this.form.DefaultSegmentName.SegmentNameList.push(name)
this.form.SegmentNameListStr = this.form.DefaultSegmentName.SegmentNameList.join("|")
},
async customPrompt(name = null) {
try {
const that = this
//
let message = !name ? this.$t('trials:reading:Segmentations:message:add') : this.$t('trials:reading:Segmentations:message:upate')
const { value } = await this.$prompt(message, '', {
showClose: false,
cancelButtonText: this.$t('common:button:cancel'),
confirmButtonText: this.$t('common:button:save'),
showCancelButton: true,
closeOnClickModal: false,
closeOnPressEscape: false,
inputValue: name,
inputValidator: (value) => {
if (!value) {
return that.$t("trials:reading:Segmentations:message:notName")
} else if (value === name) {
return true
} else if (that.form.DefaultSegmentName.SegmentNameList.includes(value)) {
return that.$t("trials:reading:Segmentations:message:nameIsHas")
} else {
return true
}
},
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
// const value = instance.inputValue
done()
} else {
done()
}
}
})
return value
} catch (err) {
console.log(err)
return null
}
},
async viewManual() {
try {
if (this.form.KeyFileList.length <= 0) return this.$message.warning(this.$t("trials:researchRecord:ImageManual:message:noImageManual"))
@ -814,6 +974,7 @@ export default {
if (this.form.ReadingTool === 3) {
this.segmentTools = [...config.customizeStandardsSegmentDicom]
}
this.form.SegmentNameListStr = res.Result.DefaultSegmentName.SegmentNameList.join(",")
}
this.CriterionModalitys = this.form.CriterionModalitys
? this.form.CriterionModalitys.split('|')
@ -900,6 +1061,37 @@ export default {
})
})
},
handleReadingToolListChange(v) {
if (!v.includes('FixedRadiusCircleROI')) {
this.form.CircleRadius = null
}
},
handleNumberInput(value, field) {
if (!value) {
this.form[field] = ''
return
}
let val = value.toString()
// 1. .
val = val.replace(/[^\d.]/g, '')
// 2. .
val = val.replace(/^\./g, '')
// 3. .
val = val.replace(/\.{2,}/g, '.')
val = val.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.')
// 4.
val = val.replace(/^(\-)*(\d+)\.(\d{2}).*$/, '$1$2.$3')
// 5. 01 1
if (val.length > 1 && val[0] === '0' && val[1] !== '.') {
val = val.replace(/^0+/, '')
if (val === '') val = '0'
}
this.$nextTick(() => {
this.ruleForm[field] = val
});
},
},
}
</script>

View File

@ -3,6 +3,24 @@
<!-- 配置信息表单 -->
<el-form ref="processConfigForm" v-loading="loading" :model="form" label-width="300px" style="width: 800px"
:rules="rules" size="small">
<!-- 数据存储 -->
<el-form-item
:label="$t('trials:processCfg:form:trialDataStoreType')"
prop="TrialDataStoreType"
>
<el-radio-group
v-model="form.TrialDataStoreType"
:disabled="form.IsTrialProcessConfirmed && !isEdit"
>
<el-radio
v-for="item of $d.TrialDataStoreType"
:key="item.id"
:label="item.value"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 阅片方式 -->
<el-form-item :label="$t('trials:processCfg:form:readingMode')" prop="ReadingMode">
<el-radio-group v-model="form.ReadingMode" :disabled="form.IsTrialProcessConfirmed && !isEdit">
@ -78,6 +96,7 @@
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 修约小数位数 -->
<!-- <el-form-item
:label="$t('trials:processCfg:form:digitPlaces')"
@ -724,6 +743,7 @@ export default {
VUE_IS_TEST: process.env.VUE_APP_IS_TEST,
form: {
TrialId: '',
TrialDataStoreType: 0,
ClinicalInformationTransmissionEnum: 1,
ClinicalDataSetNames: [],
ClinicalDataTrialSetIds: [],
@ -750,6 +770,13 @@ export default {
TrialCriterionIdEnums: [],
},
rules: {
TrialDataStoreType: [
{
required: true,
message: this.$t('common:ruleMessage:select'),
trigger: 'blur',
},
],
ClinicalDataSetNamesStr: [
{
required: true,
@ -1005,6 +1032,11 @@ export default {
// initialCriterions.push(this.$fd('ReadingStandard', id, 'id'))
// })
this.confirmData = [
{
Name: this.$t('trials:processCfg:form:trialDataStoreType'), //
NewVal: this.$fd('TrialDataStoreType', this.form.TrialDataStoreType),
OldVal: this.$fd('TrialDataStoreType', this.initialForm.TrialDataStoreType),
},
{
Name: this.$t('trials:processCfg:form:readingMode'), //
NewVal: this.$fd('ReadingMode', this.form.ReadingMode),

View File

@ -261,12 +261,23 @@ export default {
uploadFilesAndSave() {
return new Promise(async (resolve, reject) => {
this.form.AddFileList = []
let uploadBatchId = this.$guid()
for (var i = 0; i < this.pendingUploadList.length; ++i) {
// const file = await this.convertBase64ToBlob(this.pendingUploadList[i])
const file = await this.fileToBlob(this.pendingUploadList[i])
const res = await this.OSSclient.put(
`/${this.data.TrialId}/ClinicalData/${this.pendingUploadList[i].name}`,
file
file,
{
fileName: this.pendingUploadList[i].name,
fileSize: this.pendingUploadList[i].size,
fileType: this.pendingUploadList[i].type,
uploadBatchId: uploadBatchId,
batchDataType: 4,
trialId: this.data.TrialId,
subjectId: this.data.SubjectId,
subjectVisitId: this.data.SubjectVisitId
}
)
this.form.AddFileList.push({
fileName: this.pendingUploadList[i].name,

View File

@ -551,6 +551,7 @@ export default {
IsVisit: this.data.IsVisit,
SubjectId: this.data.SubjectId,
IsBaseLine: this.data.IsBaseLine,
SubjectVisitId: this.data.SubjectVisitId
}
)
this.addOrUpdateCD.title = this.$t('common:button:new')
@ -565,6 +566,7 @@ export default {
}
this.currentData.IsVisit = this.data.IsVisit
this.currentData.IsBaseLine = this.data.IsBaseLine
this.currentData.SubjectVisitId = this.data.SubjectVisitId
this.addOrUpdateCD.title = this.$t('common:button:edit')
this.currentOption = [
{

View File

@ -46,20 +46,49 @@ import {
getTrialCriterionList,
getReportsChartSummary
} from '@/api/trials'
let echarts = require('echarts/lib/echarts');
import * as echarts from 'echarts/core';
import { LineChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
DataZoomComponent,
LegendComponent,
DatasetComponent,
// (filter, sort)
TransformComponent
} from 'echarts/components';
//
import { LabelLayout, UniversalTransition } from 'echarts/features';
// Canvas CanvasRenderer SVGRenderer
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
DataZoomComponent,
LegendComponent,
LineChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
// let echarts = require('echarts/lib/echarts');
//
// require('echarts/lib/chart/bar');
require('echarts/lib/chart/line');
// require('echarts/lib/chart/line');
// require('echarts/lib/chart/pie');
// require('echarts/lib/chart/scatter');
//
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
require('echarts/lib/component/legend');
require('echarts/lib/component/grid');
require('echarts/lib/component/dataZoom');
// require('echarts/lib/component/tooltip');
// require('echarts/lib/component/title');
// require('echarts/lib/component/legend');
// require('echarts/lib/component/grid');
// require('echarts/lib/component/dataZoom');
export default {
name: "SubjectChart",
props: {
@ -259,12 +288,19 @@ export default {
tooltip: {
trigger: 'axis',
},
grid: {
left: 30,
bottom: 30
},
xAxis: {
data: obj.visitName,
},
yAxis: {
name: obj.unit,
type: 'value',
axisLine: {
show: true
}
},
series: obj.series
};

View File

@ -0,0 +1,442 @@
<template>
<BaseContainer>
<template slot="search-container">
<el-form :inline="true">
<!-- 文件名称 -->
<el-form-item label="文件名称" prop="FileName">
<el-input v-model="searchData.FileName" size="small" clearable style="width: 120px" />
</el-form-item>
<!-- 文件类型 -->
<el-form-item label="文件类型" prop="FileType">
<el-input v-model="searchData.FileType" size="small" clearable style="width: 120px" />
</el-form-item>
<!-- 源区域 -->
<el-form-item label="源区域" prop="UploadRegion">
<el-select v-model="searchData.UploadRegion" style="width: 120px">
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<!-- 源可用时间 -->
<el-form-item label="源可用时间">
<el-date-picker
v-model="uploadTimeRange"
:default-time="['00:00:00', '23:59:59']"
value-format="yyyy-MM-dd HH:mm:ss"
@change="handleUploadtimeChange"
style="width: 300px"
type="datetimerange">
</el-date-picker>
</el-form-item>
<!-- 目标区域 -->
<el-form-item label="目标区域" prop="TargetRegion">
<el-select v-model="searchData.TargetRegion" style="width: 120px">
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<!-- 目标可用时间 -->
<el-form-item label="目标可用时间">
<el-date-picker
v-model="SyncTimeRange"
:default-time="['00:00:00', '23:59:59']"
value-format="yyyy-MM-dd HH:mm:ss"
@change="handleSynctimeChange"
style="width: 300px"
type="datetimerange">
</el-date-picker>
</el-form-item>
<!-- 优先级 -->
<el-form-item label="优先级">
<el-input v-model="searchData.Priority" clearable style="width: 120px"></el-input>
</el-form-item>
<!-- 是否同步完成 -->
<el-form-item label="是否同步完成" prop="IsSync">
<el-select v-model="searchData.IsSync" clearable style="width: 120px">
<el-option
v-for="item of $d.YesOrNo"
:key="'IsSync' + item.label"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
{{ $t('common:button:search') }}
</el-button>
<!-- 重置 -->
<el-button type="primary" icon="el-icon-refresh-left" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
<!-- 批量编辑 -->
<el-button type="primary" icon="el-icon-edit" :disabled="selectedRows.length === 0" @click="handleBatchEdit">
批量编辑
</el-button>
</el-form-item>
</el-form>
</template>
<template slot="main-container">
<el-table v-loading="loading" v-adaptive="{ bottomOffset: 70 }" height="100" :data="list"
class="table" @sort-change="handleSortByColumn" :default-sort="{ prop: 'CreateTime', order: 'descending' }" @selection-change="handleSelectionChange">
<el-table-column type="index" width="50" />
<el-table-column type="selection" width="50" :selectable="selectableRow"/>
<el-table-column label="文件名称" prop="FileName" min-width="90" show-overflow-tooltip sortable="custom">
</el-table-column>
<el-table-column label="文件大小" prop="FileSize" min-width="90" show-overflow-tooltip>
<template slot-scope="scope">
{{ fileSizeFormatter(scope.row.FileSize) }}
</template>
</el-table-column>
<el-table-column label="文件类型" prop="FileType" min-width="90" show-overflow-tooltip sortable="custom">
</el-table-column>
<el-table-column label="源区域" prop="UploadRegion" min-width="60" show-overflow-tooltip sortable="custom" />
<el-table-column label="源可用时间" prop="CreateTime" min-width="90" show-overflow-tooltip sortable="custom" />
<el-table-column label="路径" prop="Path" min-width="90" show-overflow-tooltip sortable="custom" />
<el-table-column label="是否需要同步" prop="IsNeedSync" min-width="90" show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsNeedSync" type="success">
{{ $fd('YesOrNo', scope.row.IsNeedSync) }}
</el-tag>
<el-tag v-else type="danger">
{{ $fd('YesOrNo', scope.row.IsNeedSync) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="目标区域" prop="TargetRegion" min-width="80" show-overflow-tooltip sortable="custom" />
<el-table-column label="目标可用时间" prop="SyncFinishedTime" min-width="90" show-overflow-tooltip sortable="custom" />
<el-table-column label="优先级" prop="Priority" min-width="60" show-overflow-tooltip sortable="custom" />
<el-table-column label="是否同步完成" prop="IsSync" min-width="90" show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsSync" type="success">
{{ $fd('YesOrNo', scope.row.IsSync) }}
</el-tag>
<el-tag v-else type="danger">
{{ $fd('YesOrNo', scope.row.IsSync) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="更新时间" prop="UpdateTime" min-width="90" show-overflow-tooltip sortable="custom" />
<el-table-column label="操作" width="240" show-overflow-tooltip fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleOpenTaskTable(scope.row)">
详情
</el-button>
<el-button type="primary" size="mini" :disabled="!scope.row.IsNeedSync || !scope.row.IsSync" @click="execute(scope.row)">
重新同步
</el-button>
<el-button type="primary" size="mini" :disabled="scope.row.IsSync" @click="handleEdit(scope.row)">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
</template>
<el-dialog v-if="editDialogVisible" title="编辑" :visible.sync="editDialogVisible" width="600px" :close-on-click-modal="false" append-to-body>
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="110px" v-loading="formLoading">
<el-form-item label="文件名称">
<el-input v-model="editForm.FileName" disabled />
</el-form-item>
<el-form-item label="文件大小">
<el-input :value="fileSizeFormatter(editForm.FileSize)" disabled />
</el-form-item>
<el-form-item label="文件类型">
<el-input v-model="editForm.FileType" disabled />
</el-form-item>
<el-form-item label="源区域">
<el-input v-model="editForm.UploadRegion" disabled />
</el-form-item>
<el-form-item label="目标区域">
<el-input v-model="editForm.TargetRegion" disabled />
</el-form-item>
<el-form-item label="是否需要同步" prop="IsNeedSync">
<el-select v-model="editForm.IsNeedSync" style="width: 100%">
<el-option
v-for="item of $d.YesOrNo"
:key="'IsNeedSync' + item.label"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<el-form-item label="更新时间">
<el-input v-model="editForm.UpdateTime" disabled />
</el-form-item>
<el-form-item label="路径">
<el-input v-model="editForm.Path" type="textarea" :rows="2" disabled />
</el-form-item>
<el-form-item label="优先级" prop="Priority">
<el-input-number v-model="editForm.Priority" :min="0" :controls="true" style="width: 100%" />
</el-form-item>
<el-form-item label="是否同步完成" prop="IsSync">
<el-select v-model="editForm.IsSync" style="width: 100%" disabled>
<el-option
v-for="item of $d.YesOrNo"
:key="'IsSync' + item.label"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveEdit"></el-button>
</span>
</el-dialog>
<el-dialog v-if="batchEditDialogVisible" title="批量编辑" :visible.sync="batchEditDialogVisible" width="300px" :close-on-click-modal="false" append-to-body>
<el-form ref="batchEditFormRef" :model="batchEditForm" :rules="batchEditRules" label-width="80px" v-loading="formLoading">
<el-form-item label="优先级" prop="Priority">
<el-input-number v-model="batchEditForm.Priority" :min="0" :controls="true" style="width: 100%" />
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="batchEditDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveBatchEdit"></el-button>
</span>
</el-dialog>
</BaseContainer>
</template>
<script>
import { getFileUploadRecordList, addOrUpdateFileUploadRecord, batchAddSyncFileTask } from '@/api/file'
import BaseContainer from '@/components/BaseContainer'
import Pagination from '@/components/Pagination'
import moment from 'moment'
const searchDataDefault = () => {
return {
TrialId: '',
DataFileType: null,
SubjectCode: '',
VisitName: '',
StudyCode: '',
FileName: '',
UploadRegion: null,
TargetRegion: null,
IsSync: null,
UploadStartTime: null,
UploadEndTime: null,
SyncFinishedStartTime: null,
SyncFinishedEndTime: null,
Priority: null,
Asc: false,
SortField: 'UpdateTime',
PageIndex: 1,
PageSize: 20,
}
}
export default {
name: 'FileList',
components: { BaseContainer, Pagination },
props: {
rowInfo: {
type: Object,
required: true,
},
dataFileType: {
type: Number,
required: true,
},
},
data() {
return {
moment,
searchData: searchDataDefault(),
list: [],
total: 0,
loading: false,
uploadTimeRange: null,
SyncTimeRange: null,
regionOptions: [
{
value: 'CN',
label: 'CN'
}, {
value: 'US',
label: 'US'
}
],
editDialogVisible: false,
editForm: {
// Priority: null,
// IsSync: null,
},
editRules: {
Priority: [{ required: true, message: '请输入', trigger: 'change' }],
IsSync: [{ required: true, message: '请选择', trigger: 'change' }],
},
formLoading: false,
selectedRows: [],
batchEditDialogVisible: false,
batchEditForm: {
Priority: null,
},
batchEditRules: {
Priority: [{ required: true, message: '请输入', trigger: 'change' }],
},
}
},
mounted() {
this.getList()
},
methods: {
async getList() {
try {
this.loading = true
this.searchData.TrialId = this.$route.query.trialId
this.searchData.SubjectCode = this.rowInfo.SubjectCode
this.searchData.VisitName = this.rowInfo.VisitName
this.searchData.StudyCode = this.rowInfo.StudyCode
this.searchData.DataFileType = this.dataFileType
let res = await getFileUploadRecordList(this.searchData)
this.loading = false
this.list = res.Result.CurrentPageData
this.total = res.Result.TotalCount
} catch(e) {
this.loading = false
console.log(e)
}
},
handleOpenTaskTable(row) {
this.$emit('openTaskTable', row)
},
handleEdit(row) {
this.editForm = {...row}
this.editDialogVisible = true
this.$nextTick(() => {
if (this.$refs.editFormRef) this.$refs.editFormRef.clearValidate()
})
},
async handleSaveEdit() {
try {
let valid = await this.$refs.editFormRef.validate()
if (!valid) return
this.formLoading = true
let res = await addOrUpdateFileUploadRecord(this.editForm)
this.formLoading = false
if (res.IsSuccess) {
this.editDialogVisible = false
this.getList()
}
} catch(e) {
console.log(e)
this.formLoading = false
}
},
async execute(row) {
try {
this.loading = true
let params = {
fileUploadRecordIdList: [row.Id]
}
let res = await batchAddSyncFileTask(params)
if (res.IsSuccess) {
this.$message.success('执行成功!')
}
this.loading = false
this.getList()
} catch(e) {
this.loading = false
console.log(e)
}
},
handleSelectionChange(rows) {
this.selectedRows = rows
},
selectableRow(row) {
return !row.IsSync
},
handleBatchEdit() {
if (!this.selectedRows.length) return
this.batchEditForm = {
Priority: null,
}
this.batchEditDialogVisible = true
this.$nextTick(() => {
if (this.$refs.batchEditFormRef) this.$refs.batchEditFormRef.clearValidate()
})
},
async handleSaveBatchEdit() {
try {
let valid = await this.$refs.batchEditFormRef.validate()
if (!valid) return
this.formLoading = true
let params = {
fileUploadRecordIdList: this.selectedRows,
priority: this.editForm.Priority
}
let res = await batchAddSyncFileTask(params)
this.formLoading = false
if (res.IsSuccess) {
this.batchEditDialogVisible = false
this.getList()
}
} catch(e) {
console.log(e)
this.formLoading = false
}
},
fileSizeFormatter(size) {
if (!size) return
return (size / Math.pow(1024, 2)).toFixed(3) + 'MB'
},
handleUploadtimeChange(val) {
if (val) {
this.searchData.UploadStartTime = val[0]
this.searchData.UploadEndTime = val[1]
} else {
this.searchData.UploadStartTime = ''
this.searchData.UploadEndTime = ''
}
},
handleSynctimeChange(val) {
if (val) {
this.searchData.SyncFinishedStartTime = val[0]
this.searchData.SyncFinishedEndTime = val[1]
} else {
this.searchData.SyncFinishedStartTime = ''
this.searchData.SyncFinishedEndTime = ''
}
},
handleSearch() {
this.searchData.PageIndex = 1
this.getList()
},
//
handleReset() {
this.uploadTimeRange = null
this.handleUploadtimeChange()
this.SyncTimeRange = null
this.handleSynctimeChange()
this.searchData = searchDataDefault()
this.getList()
},
//
handleSortByColumn(column) {
if (column.order === 'ascending') {
this.searchData.Asc = true
} else {
this.searchData.Asc = false
}
this.searchData.SortField = column.prop
this.searchData.PageIndex = 1
this.getList()
},
},
}
</script>

View File

@ -0,0 +1,351 @@
<template>
<BaseContainer>
<template slot="search-container">
<el-form :inline="true">
<!-- 受试者编号 -->
<el-form-item label="受试者编号" prop="SubjectCode">
<el-input v-model="searchData.SubjectCode" size="small" clearable style="width: 120px" />
</el-form-item>
<!-- 访视名称 -->
<el-form-item label="访视名称">
<el-select v-model="searchData.VisitName" style="width: 140px" clearable>
<el-option v-for="(item, index) of visitPlanOptions" :key="index" :label="item.VisitName"
:value="item.VisitNum">
<span style="float: left">{{ item.VisitName }}</span>
</el-option>
<!-- <el-option key="Other" label="Out of Plan" value="1.11" /> -->
</el-select>
</el-form-item>
<!-- 检查编号 -->
<el-form-item label="检查编号" prop="StudyCode">
<el-input v-model="searchData.StudyCode" size="small" clearable style="width: 120px" />
</el-form-item>
<!-- 源区域 -->
<el-form-item label="源区域" prop="UploadRegion">
<el-select v-model="searchData.UploadRegion" style="width: 120px">
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<!-- 源可用时间 -->
<!-- <el-form-item label="源可用时间">
<el-date-picker
v-model="uploadTimeRange"
:default-time="['00:00:00', '23:59:59']"
value-format="yyyy-MM-dd HH:mm:ss"
@change="handleUploadtimeChange"
style="width: 250px"
type="datetimerange">
</el-date-picker>
</el-form-item> -->
<!-- 目标区域 -->
<el-form-item label="目标区域" prop="TargetRegion">
<el-select v-model="searchData.TargetRegion" style="width: 120px">
<el-option
v-for="item in regionOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<!-- 目标可用时间 -->
<!-- <el-form-item label="目标可用时间">
<el-date-picker
v-model="SyncTimeRange"
:default-time="['00:00:00', '23:59:59']"
value-format="yyyy-MM-dd HH:mm:ss"
@change="handleSynctimeChange"
style="width: 250px"
type="datetimerange">
</el-date-picker>
</el-form-item> -->
<!-- 是否同步完成 -->
<el-form-item label="是否同步完成" prop="IsSync">
<el-select v-model="searchData.IsSync" clearable style="width: 120px">
<el-option
v-for="item of $d.YesOrNo"
:key="'IsSync' + item.label"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
{{ $t('common:button:search') }}
</el-button>
<!-- 重置 -->
<el-button type="primary" icon="el-icon-refresh-left" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
</el-form-item>
</el-form>
</template>
<template slot="main-container">
<el-table v-loading="loading" v-adaptive="{ bottomOffset: 60 }" height="100" :data="list"
class="table" @sort-change="handleSortByColumn" :default-sort="{ prop: 'CreateTime', order: 'descending' }">
<el-table-column type="index" width="50" />
<el-table-column label="受试者编号" prop="SubjectCode" min-width="90" show-overflow-tooltip sortable="custom"/>
<el-table-column label="访视名称" prop="VisitName" min-width="90" show-overflow-tooltip sortable="custom"/>
<el-table-column label="检查编号" prop="StudyCode" min-width="90" show-overflow-tooltip sortable="custom"/>
<el-table-column label="文件数" prop="FileCount" min-width="90" show-overflow-tooltip/>
<el-table-column label="源区域" prop="UploadRegion" min-width="60" show-overflow-tooltip />
<el-table-column label="源可用时间" prop="CreateTime" min-width="90" show-overflow-tooltip />
<el-table-column label="目标区域" prop="TargetRegion" min-width="60" show-overflow-tooltip />
<el-table-column label="目标可用时间" prop="SyncFinishedTime" min-width="90" show-overflow-tooltip/>
<el-table-column label="是否同步完成" prop="IsSync" min-width="90" show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.IsSync" type="success">
{{ $fd('YesOrNo', scope.row.IsSync) }}
</el-tag>
<el-tag v-else type="danger">
{{ $fd('YesOrNo', scope.row.IsSync) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="80" show-overflow-tooltip>
<template slot-scope="scope">
<el-button type="primary" size="small" @click="openDetailTable(scope.row)">
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
</template>
<el-dialog
v-if="detailDialog.visible"
:visible.sync="detailDialog.visible"
fullscreen
append-to-body
:close-on-click-modal="false"
class="detail-dialog"
>
<span slot="title">{{ detailDialog.title }}</span>
<span v-if="detailDialog.currentRow">{{`${detailDialog.currentRow.SubjectCode} / ${detailDialog.currentRow.VisitName} ${detailDialog.currentRow.StudyCode ? ' / ' + detailDialog.currentRow.StudyCode : ''} ${detailDialog.currentRow.UploadRegion} -> 目标${detailDialog.currentRow.TargetRegion}`}}</span>
<el-tabs class="detail-tabs" v-model="detailDialog.activeTab" @tab-click="handleDetailTabClick">
<el-tab-pane label="文件级别" name="file">
<FileList
v-if="detailDialog.activeTab === 'file'"
:dataFileType="1"
:rowInfo="detailDialog.currentRow"
@openTaskTable="openTaskTable"
/>
</el-tab-pane>
<el-tab-pane label="任务级别" name="task">
<TaskList
v-if="detailDialog.activeTab === 'task'"
:rowInfo="detailDialog.currentRow"
:fileUploadRecordId="fileUploadRecordId"
:path="path"
/>
</el-tab-pane>
</el-tabs>
</el-dialog>
</BaseContainer>
</template>
<script>
import { getSubjectUploadRecordList } from '@/api/file'
import { getTrialVisitStageSelect } from '@/api/trials'
import BaseContainer from '@/components/BaseContainer'
import Pagination from '@/components/Pagination'
import FileList from './FileList'
import TaskList from './TaskList'
import moment from 'moment'
const searchDataDefault = () => {
return {
TrialId: '',
SubjectCode: '',
VisitName: null,
StudyCode: '',
UploadRegion: '',
TargetRegion: '',
IsSync: null,
// UploadStartTime: null,
// UploadEndTime: null,
// SyncFinishedStartTime: null,
// SyncFinishedEndTime: null,
PageIndex: 1,
PageSize: 20,
Asc: true,
SortField: 'StudyCode'
}
}
export default {
components: { BaseContainer, Pagination, FileList, TaskList },
data() {
return {
trialId: '',
moment,
searchData: searchDataDefault(),
list: [],
total: 0,
loading: false,
visitPlanOptions: [],
detailDialog: {
visible: false,
title: '详情',
activeTab: 'file',
currentRow: null
},
regionOptions: [
{
value: 'CN',
label: 'CN'
}, {
value: 'US',
label: 'US'
}
],
fileUploadRecordId: '',
path: '',
uploadTimeRange: null,
SyncTimeRange: null,
}
},
mounted() {
this.trialId = this.$route.query.trialId
this.getList()
this.getVisitPlanOptions()
},
methods: {
async getList() {
try {
this.loading = true
this.searchData.TrialId = this.trialId
let res = await getSubjectUploadRecordList(this.searchData)
this.loading = false
this.list = res.Result.CurrentPageData
this.total = res.Result.TotalCount
} catch(e) {
this.loading = false
console.log(e)
}
},
openDetailTable(row) {
this.detailDialog.currentRow = row || null
this.detailDialog.activeTab = 'file'
this.detailDialog.visible = true
},
openTaskTable(row) {
this.fileUploadRecordId = row.Id
this.path = row.Path
this.detailDialog.activeTab = 'task'
},
handleDetailTabClick(tab) {
const name = tab.name
if (name === 'file') {
this.fileUploadRecordId = ''
this.path = ''
}
// if (name !== 'upload' && name !== 'sync') return
// if (this.detailDialog[name].list && this.detailDialog[name].list.length) return
// this.fetchDetailList(name)
},
// 访
async getVisitPlanOptions() {
try {
let res = await getTrialVisitStageSelect(this.trialId)
this.visitPlanOptions = res.Result
} catch(e) {
console.log(e)
}
},
handleUploadtimeChange(val) {
if (val) {
this.searchData.UploadStartTime = val[0]
this.searchData.UploadEndTime = val[1]
} else {
this.searchData.UploadStartTime = ''
this.searchData.UploadEndTime = ''
}
},
handleSynctimeChange(val) {
if (val) {
this.searchData.SyncFinishedStartTime = val[0]
this.searchData.SyncFinishedEndTime = val[1]
} else {
this.searchData.SyncFinishedStartTime = ''
this.searchData.SyncFinishedEndTime = ''
}
},
handleSearch() {
this.searchData.PageIndex = 1
this.getList()
},
//
handleReset() {
// this.uploadTimeRange = null
// this.handleUploadtimeChange()
// this.SyncTimeRange = null
// this.handleSynctimeChange()
this.searchData = searchDataDefault()
this.getList()
},
//
handleSortByColumn(column) {
if (column.order === 'ascending') {
this.searchData.Asc = true
} else {
this.searchData.Asc = false
}
this.searchData.SortField = column.prop
this.searchData.PageIndex = 1
this.getList()
},
},
}
</script>
<style lang="scss">
.detail-dialog {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
.el-dialog__header {
flex: 0 0 auto;
padding-bottom: 0px;
}
.el-dialog__body {
padding-top: 10px;
flex: 1 1 auto;
overflow: hidden;
}
&.is-fullscreen {
.el-dialog__body {
margin-top: 10px;
height: calc(100% - 80px);
}
}
}
.detail-tabs {
height: 100%;
display: flex;
flex-direction: column;
.el-tabs__header {
flex: 0 0 auto;
margin: 0 0 8px;
}
.el-tabs__content {
flex: 1 1 auto;
overflow: hidden;
}
.el-tab-pane {
height: 100%;
}
}
</style>

View File

@ -0,0 +1,249 @@
<template>
<BaseContainer>
<template slot="search-container">
<el-form :inline="true">
<!-- 文件名称 -->
<el-form-item label="文件名称" prop="FileName">
<el-input v-model="searchData.FileName" size="small" clearable style="width: 120px" />
</el-form-item>
<!-- 文件路径 -->
<el-form-item label="文件路径" prop="Path">
<el-input v-model="searchData.Path" size="small" clearable />
</el-form-item>
<!-- 任务状态 -->
<el-form-item label="任务状态" prop="JobState">
<el-select v-model="searchData.JobState" clearable style="width: 120px">
<el-option
v-for="item of $d.JobState"
:key="'JobState' + item.label"
:value="item.value"
:label="item.label"
/>
</el-select>
</el-form-item>
<!-- 任务开始日期 -->
<el-form-item label="任务开始日期">
<el-date-picker
v-model="searchData.StartTime"
type="date"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
:picker-options="beginPickerOption"
style="width:140px;"
/>
</el-form-item>
<!-- 任务结束日期 -->
<el-form-item label="任务结束日期">
<el-date-picker
v-model="searchData.EndTime"
type="date"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
:picker-options="endpickerOption"
style="width:140px;"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
{{ $t('common:button:search') }}
</el-button>
<!-- 重置 -->
<el-button type="primary" icon="el-icon-refresh-left" @click="handleReset">
{{ $t('common:button:reset') }}
</el-button>
</el-form-item>
</el-form>
</template>
<template slot="main-container">
<el-table v-loading="loading" v-adaptive="{ bottomOffset: 70 }" height="100" :data="list"
class="table" @sort-change="handleSortByColumn" :default-sort="{ prop: 'CreateTime', order: 'descending' }">
<el-table-column type="index" width="50" />
<el-table-column label="Job编号" prop="Id" min-width="90" show-overflow-tooltip/>
<el-table-column label="文件名称" prop="FileName" min-width="90" show-overflow-tooltip sortable="custom">
</el-table-column>
<el-table-column label="路径" prop="Path" min-width="90" show-overflow-tooltip/>
<el-table-column label="任务状态" prop="JobState" min-width="90" show-overflow-tooltip sortable="custom">
<template slot-scope="scope">
<el-tag v-if="scope.row.JobState === 1" type="infoinf0">
{{ $fd('JobState', scope.row.JobState) }}
</el-tag>
<el-tag v-else-if="scope.row.JobState === 1" type="warning">
{{ $fd('JobState', scope.row.JobState) }}
</el-tag>
<el-tag v-else-if="scope.row.JobState === 2" type="success">
{{ $fd('JobState', scope.row.JobState) }}
</el-tag>
<el-tag v-else-if="scope.row.JobState === 3" type="danger">
{{ $fd('JobState', scope.row.JobState) }}
</el-tag>
<el-tag v-else>{{ $fd('JobState', scope.row.JobState) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="任务开始时间" prop="StartTime" min-width="90" show-overflow-tooltip sortable="custom" />
<el-table-column label="任务结束时间" prop="EndTime" min-width="90" show-overflow-tooltip sortable="custom" />
<el-table-column label="创建时间" prop="CreateTime" min-width="90" show-overflow-tooltip sortable="custom" />
<el-table-column label="操作" min-width="80" show-overflow-tooltip>
<template slot-scope="scope">
<el-button type="primary" size="mini" :disabled="scope.row.JobState !== 3" @click="execute(scope.row)">
再次执行
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination class="page" :total="total" :page.sync="searchData.PageIndex" :limit.sync="searchData.PageSize"
@pagination="getList" />
</template>
</BaseContainer>
</template>
<script>
import { getUploadFileSyncRecordList, batchAddSyncFileTask } from '@/api/file'
import BaseContainer from '@/components/BaseContainer'
import Pagination from '@/components/Pagination'
import moment from 'moment'
const searchDataDefault = () => {
return {
TrialId: '',
StudyCode: '',
SubjectCode: '',
VisitName: '',
FileUploadRecordId: '',
JobState: null,
FileName: '',
Path: '',
StartTime: '',
EndTime: '',
Asc: false,
SortField: '',
PageIndex: 1,
PageSize: 20,
}
}
export default {
name: 'TaskList',
components: { BaseContainer, Pagination },
props: {
fileUploadRecordId: {
type: String,
default: '',
},
path: {
type: String,
default: '',
},
rowInfo: {
type: Object,
required: true,
},
},
data() {
return {
moment,
searchData: searchDataDefault(),
list: [],
total: 0,
loading: false,
datetimerange: [],
beginPickerOption: {
disabledDate: time => {
if (this.searchData.EndTime) {
return time.getTime() >= new Date(this.searchData.EndTime).getTime()
} else {
return time.getTime() > Date.now()
}
}
},
endpickerOption: {
disabledDate: time => {
if (this.searchData.StartTime) {
return time.getTime() > Date.now() || time.getTime() <= new Date(this.searchData.StartTime).getTime() - 86400000
} else {
return time.getTime() > Date.now()
}
}
}
}
},
mounted() {
this.$nextTick(()=>{
this.searchData.Path = this.path
this.searchData.FileUploadRecordId = this.fileUploadRecordId
this.getList()
})
},
methods: {
async getList() {
try {
this.loading = true
this.searchData.TrialId = this.$route.query.trialId
this.searchData.StudyCode = this.rowInfo.StudyCode
this.searchData.SubjectCode = this.rowInfo.SubjectCode
this.searchData.VisitName = this.rowInfo.VisitName
let res = await getUploadFileSyncRecordList(this.searchData)
this.loading = false
this.list = res.Result.CurrentPageData
this.total = res.Result.TotalCount
} catch(e) {
this.loading = false
console.log(e)
}
},
async execute(row) {
try {
this.loading = true
let params = {
fileUploadRecordIdList: [row.Id]
}
let res = await batchAddSyncFileTask(params)
if (res.IsSuccess) {
this.$message.success('执行成功!')
}
this.loading = false
this.getList()
} catch(e) {
this.loading = false
console.log(e)
}
},
fileSizeFormatter(size) {
if (!size) return
return (size / Math.pow(1024, 2)).toFixed(3) + 'MB'
},
handleDatetimeChange(val) {
if (val) {
this.searchData.BeginDate = val[0]
this.searchData.EndDate = val[1]
} else {
this.searchData.BeginDate = ''
this.searchData.EndDate = ''
}
},
handleSearch() {
this.searchData.PageIndex = 1
this.getList()
},
//
handleReset() {
this.datetimerange = null
this.searchData = searchDataDefault()
this.getList()
},
//
handleSortByColumn(column) {
if (column.order === 'ascending') {
this.searchData.Asc = true
} else {
this.searchData.Asc = false
}
this.searchData.SortField = column.prop
this.searchData.PageIndex = 1
this.getList()
},
},
}
</script>

View File

@ -0,0 +1,38 @@
<template>
<el-tabs class="data-sync-tabs" type="border-card" tab-position="left" v-model="activeTab" >
<el-tab-pane label="检查列表" name="study">
<StudyList />
</el-tab-pane>
<el-tab-pane label="其他" name="other">其他</el-tab-pane>
</el-tabs>
</template>
<script>
import StudyList from './components/StudyList'
export default {
name: 'DataSync',
components: {
StudyList
},
data(){
return {
activeTab: 'study'
}
}
}
</script>
<style lang="scss">
.data-sync-tabs{
height: 100%;
background-color: #fff;
.el-tabs__header {
height: 100%;
}
.el-tabs__content {
height: 100%;
overflow: auto;
}
.el-tab-pane {
height: 100%;
}
}
</style>

View File

@ -36,17 +36,49 @@
</template>
<script>
import BaseContainer from '@/components/BaseContainer'
let echarts = require('echarts/lib/echarts');
import * as echarts from 'echarts/core';
import { BarChart, FunnelChart } from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
GridComponent,
DataZoomComponent,
LegendComponent,
MarkLineComponent,
DatasetComponent,
// (filter, sort)
TransformComponent
} from 'echarts/components';
//
import { LabelLayout, UniversalTransition } from 'echarts/features';
// Canvas CanvasRenderer SVGRenderer
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
DataZoomComponent,
MarkLineComponent,
LegendComponent,
BarChart,
FunnelChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
// let echarts = require('echarts/lib/echarts');
require('echarts/lib/component/markLine');
require('echarts/lib/chart/funnel');
require('echarts/lib/chart/bar');
// require('echarts/lib/component/markLine');
// require('echarts/lib/chart/funnel');
// require('echarts/lib/chart/bar');
//
require('echarts/lib/component/tooltip');
require('echarts/lib/component/title');
require('echarts/lib/component/legend');
require('echarts/lib/component/grid');
require('echarts/lib/component/dataZoom');
// require('echarts/lib/component/tooltip');
// require('echarts/lib/component/title');
// require('echarts/lib/component/legend');
// require('echarts/lib/component/grid');
// require('echarts/lib/component/dataZoom');
import {
getTrialCriterionList,
getTrialVisitFinishedStatList,
@ -178,8 +210,11 @@ export default {
this.chart_left = echarts.init(this.$refs.chartContainer_left);
// ...
const option = {
richInheritPlainLabel: false,
title: {
text: obj.titleText
text: obj.titleText,
left: 0,
top: 0
},
color: this.color,
tooltip: {
@ -316,7 +351,13 @@ export default {
// ...
const option = {
title: {
text: obj.titleText
text: obj.titleText,
left: 0,
top: 0
},
grid: {
left: 80,
bottom: 50
},
toolbox: {
show: false
@ -339,6 +380,9 @@ export default {
],
yAxis: [
{
axisLine: {
show: true
},
name: obj.unit,
type: 'value'
}
@ -424,12 +468,18 @@ export default {
// ...
const option = {
title: {
text: obj.titleText
text: obj.titleText,
left: 0,
top: 0
},
toolbox: {
show: false
},
color: this.color,
grid: {
left: 80,
bottom: 50
},
tooltip: {
trigger: 'axis',
axisPointer: {
@ -441,6 +491,9 @@ export default {
data: obj.xAxisData
},
yAxis: {
axisLine: {
show: true
},
name: obj.unit,
type: 'value',
minInterval: 1
@ -474,11 +527,17 @@ export default {
// ...
const option = {
title: {
text: obj.titleText
text: obj.titleText,
left: 0,
top: 0
},
toolbox: {
show: false
},
grid: {
left: 80,
bottom: 50
},
color: this.color,
tooltip: {
trigger: 'item',
@ -498,8 +557,11 @@ export default {
yAxis: {
name: obj.unit,
type: 'value',
axisLine: {
show: true
},
axisLabel: {
formatter: '{value}%'
formatter: '{value}%',
}
},
series: {
@ -531,7 +593,13 @@ export default {
// ...
const option = {
title: {
text: obj.titleText
text: obj.titleText,
left: 0,
top: 0
},
grid: {
left: 80,
bottom: 50
},
toolbox: {
show: false
@ -550,7 +618,10 @@ export default {
},
yAxis: {
name: obj.unit,
type: 'value'
type: 'value',
axisLine: {
show: true
}
},
series: {
markLine: {

View File

@ -382,6 +382,7 @@ export default {
size: files[i].size,
type: extendName.split('.')[1],
file: files[i],
fileType: files[i].type
}
this.fileList.push(obj);
}
@ -401,7 +402,17 @@ export default {
var timestamp = Date.now();
const res = await this.OSSclient.put(
`/${this.trialId}/ClinicalData/${timestamp}_${this.fileList[i].file.name}`,
file
file,
{
fileName: `${this.fileList[i].file.name}`,
fileSize: file.size,
fileType: this.fileList[i].fileType,
uploadBatchId: this.$guid(),
batchDataType: 4,
trialId: this.trialId,
subjectId: this.data.SubjectId,
subjectVisitId: this.subjectVisitId
}
);
this.addFileList.push({
fileName: this.fileList[i].file.name,

View File

@ -1520,6 +1520,7 @@ export default {
},
}
let arr = []
let uploadBatchId = scope.$guid()
for (let i = 0; i < seriesList.length; i++) {
let v = seriesList[i]
let instanceList = []
@ -1587,6 +1588,7 @@ export default {
params.trialId
)}`
if (scope.isClose) return
console.log(o.file)
let res = await dcmUpload(
{
path: path,
@ -1607,6 +1609,16 @@ export default {
) {
dicomInfo.uploadFileSize = dicomInfo.fileSize
}
},
{
fileName: o.file.name,
fileSize: o.file.size,
fileType: 'application/dicom',
uploadBatchId: uploadBatchId,
batchDataType: 1,
trialId: params.trialId,
subjectId: params.subjectId,
subjectVisitId: params.subjectVisitId
}
)
if (!res || !res.url) {
@ -1623,11 +1635,22 @@ export default {
o.imageColumns,
o.imageRows
)
let thumbnailPath = `/${params.trialId}/Image/${params.subjectId}/${params.subjectVisitId}/${dicomInfo.studyUid}/${v.seriesUid}.jpg`
let OSSclient = scope.OSSclient
let seriesRes = await OSSclient.put(
thumbnailPath,
blob
blob,
{
fileName: `${v.seriesUid}.jpg`,
fileSize: blob.size,
fileType: 'image/jpeg',
uploadBatchId: uploadBatchId,
batchDataType: 2,
trialId: params.trialId,
subjectId: params.subjectId,
subjectVisitId: params.subjectVisitId
}
)
if (seriesRes && seriesRes.url) {
ImageResizePath = scope.$getObjectName(
@ -1762,7 +1785,20 @@ export default {
let thumbnailPath = `/${params.trialId}/Image/${params.trialSiteId}/${params.subjectId}/${params.subjectVisitId}/${dicomInfo.studyUid}/${v.seriesUid}.jpg`
let OSSclient = scope.OSSclient
try {
let seriesRes = await OSSclient.put(thumbnailPath, blob)
let seriesRes = await OSSclient.put(
thumbnailPath,
blob,
{
fileName: `${v.seriesUid}.jpg`,
fileSize: blob.size,
fileType: 'image/jpeg',
uploadBatchId: uploadBatchId,
batchDataType: 2,
trialId: params.trialId,
subjectId: params.subjectId,
subjectVisitId: params.subjectVisitId
}
)
if (seriesRes && seriesRes.url) {
o.ImageResizePath = scope.$getObjectName(seriesRes.url)
}
@ -1775,7 +1811,8 @@ export default {
params.study.instanceCount = dicomInfo.failedFileCount
params.RecordPath = scope.$getObjectName(logRes.url)
if (scope.isClose) return false
console.log(params)
params.UploadBatchId = uploadBatchId
addOrUpdateArchiveStudy(params)
.then((res) => {
if (dicomInfo.failedFileCount === dicomInfo.fileCount) {

View File

@ -756,6 +756,7 @@ export default {
.substring(fileName.lastIndexOf('.'))
.toLocaleLowerCase()
if (this.faccept.indexOf(extendName) !== -1) {
files[i].fileName = files[i].name
files[i].id = `${files[i].lastModified}${files[i].name}`
this.fileList.push(files[i])
}
@ -809,8 +810,9 @@ export default {
})
if (res.IsSuccess) {
this.studyMonitorId = res.Result
let uploadBatchId = this.$guid()
for (let i = 0; i < num; i++) {
funArr.push(this.handleUploadTask(this.selectArr, i))
funArr.push(this.handleUploadTask(this.selectArr, i, uploadBatchId))
}
if (funArr.length > 0) {
let res = await Promise.all(funArr)
@ -821,17 +823,16 @@ export default {
}
},
//
async handleUploadTask(arr, index) {
async handleUploadTask(arr, index, uploadBatchId) {
if (!this.uploadVisible) return
let file = this.fileList.filter((item) => item.id === arr[index].id)[0]
file.status = 1
let fileName = `${this.$guid()}${file.name.substring(file.name.lastIndexOf('.')).toLocaleLowerCase()}`
let path = `/${this.trialId}/Image/${this.data.SubjectId}/${this.data.Id
}/${this.$guid()}${file.name
.substring(file.name.lastIndexOf('.'))
.toLocaleLowerCase()}`
}/${fileName}`
file.curPath = path
const fileData = await this.fileToBlob(file.file)
let res = await this.fileToOss(path, fileData, file)
let res = await this.fileToOss(path, fileData, file, uploadBatchId)
if (res) {
file.status = 2
this.successFileList.push({
@ -865,13 +866,13 @@ export default {
}
let ind = arr.findIndex((item) => item.status === 0)
if (ind >= 0) {
return this.handleUploadTask(arr, ind)
return this.handleUploadTask(arr, ind, uploadBatchId)
} else {
return false
}
},
// fileoss
async fileToOss(path, file, item) {
async fileToOss(path, file, item, uploadBatchId) {
try {
let res = await this.OSSclient.multipartUpload(
{
@ -884,6 +885,18 @@ export default {
if (item.uploadFileSize > file.fileSize) {
item.uploadFileSize = file.fileSize > 0 ? file.fileSize : 1
}
},
{
fileName: item.name,
fileSize: item.size,
fileType: item.fileType,
uploadBatchId: uploadBatchId,
batchDataType: 3,
trialId: this.trialId,
subjectId: this.data.SubjectId,
subjectVisitId: this.subjectVisitId,
studyCode: this.currentRow.CodeView
}
)
if (res) {

View File

@ -361,6 +361,7 @@ export default {
size: files[i].size,
type: extendName.split('.')[1],
file: files[i],
fileType: files[i].type
}
this.fileList.push(obj)
}
@ -380,7 +381,17 @@ export default {
var timestamp = Date.now()
const res = await this.OSSclient.put(
`/${this.trialId}/ClinicalData/${timestamp}_${this.fileList[i].file.name}`,
file
file,
{
fileName: `${this.fileList[i].file.name}`,
fileSize: this.fileList[i].size,
fileType: this.fileList[i].fileType,
uploadBatchId: this.$guid(),
batchDataType: 4,
trialId: this.trialId,
subjectId: this.data.SubjectId,
subjectVisitId: this.subjectVisitId
}
)
this.addFileList.push({
fileName: this.fileList[i].file.name,