Compare commits
No commits in common. "d69d8802cdf1018e25775d3af4e5e2a444306225" and "b453eede31d7c9b1c21178ffc46097a4cc2c9ef2" have entirely different histories.
d69d8802cd
...
b453eede31
|
@ -48,7 +48,6 @@ const getters = {
|
||||||
TotalNeedSignSystemDocCount: state => state.user.TotalNeedSignSystemDocCount,
|
TotalNeedSignSystemDocCount: state => state.user.TotalNeedSignSystemDocCount,
|
||||||
TotalNeedSignTrialDocCount: state => state.user.TotalNeedSignTrialDocCount,
|
TotalNeedSignTrialDocCount: state => state.user.TotalNeedSignTrialDocCount,
|
||||||
IsFirstSysDocNeedSign: state => state.user.IsFirstSysDocNeedSign,
|
IsFirstSysDocNeedSign: state => state.user.IsFirstSysDocNeedSign,
|
||||||
TrialStatusStr: state => state.user.TrialStatusStr,
|
TrialStatusStr: state => state.user.TrialStatusStr
|
||||||
lastViewportTaskId: state => state.noneDicomReview.lastViewportTaskId
|
|
||||||
}
|
}
|
||||||
export default getters
|
export default getters
|
||||||
|
|
|
@ -12,7 +12,7 @@ import trials from './modules/trials'
|
||||||
import financials from './modules/financials'
|
import financials from './modules/financials'
|
||||||
import reading from './modules/reading'
|
import reading from './modules/reading'
|
||||||
import lang from './modules/lang'
|
import lang from './modules/lang'
|
||||||
import noneDicomReview from './modules/noneDicomReview'
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
|
@ -27,8 +27,7 @@ const store = new Vuex.Store({
|
||||||
trials,
|
trials,
|
||||||
financials,
|
financials,
|
||||||
reading,
|
reading,
|
||||||
lang,
|
lang
|
||||||
noneDicomReview
|
|
||||||
},
|
},
|
||||||
getters
|
getters
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
const getDefaultState = () => {
|
|
||||||
return {
|
|
||||||
lastViewportTaskId: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const state = getDefaultState
|
|
||||||
|
|
||||||
const mutations = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
setLastViewportTaskId({ state }, id) {
|
|
||||||
state.lastViewportTaskId = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
namespaced: true,
|
|
||||||
state,
|
|
||||||
mutations,
|
|
||||||
actions
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
volumeLoader,
|
volumeLoader,
|
||||||
cornerstoneStreamingImageVolumeLoader,
|
cornerstoneStreamingImageVolumeLoader
|
||||||
cornerstoneStreamingDynamicImageVolumeLoader
|
|
||||||
} from '@cornerstonejs/core';
|
} from '@cornerstonejs/core';
|
||||||
|
|
||||||
export default function initVolumeLoader() {
|
export default function initVolumeLoader() {
|
||||||
|
@ -12,8 +11,4 @@ export default function initVolumeLoader() {
|
||||||
'cornerstoneStreamingImageVolume',
|
'cornerstoneStreamingImageVolume',
|
||||||
cornerstoneStreamingImageVolumeLoader
|
cornerstoneStreamingImageVolumeLoader
|
||||||
);
|
);
|
||||||
volumeLoader.registerVolumeLoader(
|
|
||||||
'cornerstoneStreamingDynamicImageVolume',
|
|
||||||
cornerstoneStreamingDynamicImageVolumeLoader
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-loading="loading" class="reading-viewer-container">
|
|
||||||
<!-- 访视阅片 -->
|
|
||||||
<visit-review v-if="taskInfo && taskInfo.ReadingCategory=== 1" />
|
|
||||||
<!-- 全局阅片 -->
|
|
||||||
<global-review v-else-if="taskInfo && taskInfo.ReadingCategory=== 2" />
|
|
||||||
<!-- 裁判阅片 -->
|
|
||||||
<ad-review v-else-if="taskInfo && taskInfo.ReadingCategory=== 4" />
|
|
||||||
<!-- 肿瘤学阅片 -->
|
|
||||||
<oncology-review v-else-if="taskInfo && taskInfo.ReadingCategory=== 5" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { getNextTask } from '@/api/trials'
|
|
||||||
import store from '@/store'
|
|
||||||
import VisitReview from '@/views/trials/trials-panel/reading/visit-review'
|
|
||||||
import GlobalReview from '@/views/trials/trials-panel/reading/global-review'
|
|
||||||
import AdReview from '@/views/trials/trials-panel/reading/ad-review'
|
|
||||||
import OncologyReview from '@/views/trials/trials-panel/reading/oncology-review'
|
|
||||||
export default {
|
|
||||||
name: 'ReadingViewer',
|
|
||||||
components: {
|
|
||||||
VisitReview,
|
|
||||||
GlobalReview,
|
|
||||||
AdReview,
|
|
||||||
OncologyReview
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
taskInfo: null,
|
|
||||||
loading: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.getTaskInfo()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async getTaskInfo() {
|
|
||||||
this.loading = true
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
subjectId: this.$route.query.subjectId,
|
|
||||||
trialId: this.$route.query.trialId,
|
|
||||||
subjectCode: this.$route.query.subjectCode,
|
|
||||||
visitTaskId: this.$route.query.visitTaskId,
|
|
||||||
trialReadingCriterionId: this.$route.query.TrialReadingCriterionId
|
|
||||||
}
|
|
||||||
const res = await getNextTask(params)
|
|
||||||
this.taskInfo = res.Result
|
|
||||||
localStorage.setItem('taskInfo', JSON.stringify(res.Result))
|
|
||||||
// if (res.Result.IsExistsClinicalData && res.Result.IsNeedReadClinicalData && !res.Result.IsReadClinicalData) {
|
|
||||||
// this.isFullscreen = false
|
|
||||||
// this.dialogVisible = true
|
|
||||||
// this.cdVisitTaskId = res.Result.visitTaskId
|
|
||||||
// }
|
|
||||||
// this.$nextTick(() => {
|
|
||||||
// if (res.Result.IsFirstChangeTask && res.Result.ReadingTaskState === 0) {
|
|
||||||
// this.tipVisible = true
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
this.loading = false
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
store.dispatch('reading/setCurrentReadingTaskState', 2)
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.reading-viewer-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,302 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-loading="loading" class="ecrf-list-container">
|
|
||||||
<el-form
|
|
||||||
ref="questions"
|
|
||||||
size="small"
|
|
||||||
:model="questionForm"
|
|
||||||
class="ecrf-form"
|
|
||||||
>
|
|
||||||
<FormItem
|
|
||||||
v-for="question of questions"
|
|
||||||
:key="question.Id"
|
|
||||||
:question="question"
|
|
||||||
:question-form="questionForm"
|
|
||||||
:reading-task-state="readingTaskState"
|
|
||||||
:visit-task-id="visitTaskId"
|
|
||||||
:calculation-list="calculationList"
|
|
||||||
@setFormItemData="setFormItemData"
|
|
||||||
@resetFormItemData="resetFormItemData"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-form-item v-if="readingTaskState < 2">
|
|
||||||
<div style="text-align:center;">
|
|
||||||
<el-button v-if="taskInfo && taskInfo.IseCRFShowInDicomReading" type="primary" @click="skipTask">
|
|
||||||
{{ $t('trials:readingReport:button:skip') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button type="primary" @click="handleSave">
|
|
||||||
{{ $t('common:button:save') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button v-if="taskInfo && taskInfo.IseCRFShowInDicomReading" type="primary" @click="handleSubmit">
|
|
||||||
{{ $t('common:button:submit') }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 签名框 -->
|
|
||||||
<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" />
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import { getTrialReadingQuestion, saveVisitTaskQuestions, submitVisitTaskQuestionsInDto, getQuestionCalculateRelation } from '@/api/trials'
|
|
||||||
import { setSkipReadingCache } from '@/api/reading'
|
|
||||||
import const_ from '@/const/sign-code'
|
|
||||||
import FormItem from './FormItem'
|
|
||||||
import SignForm from '@/views/trials/components/newSignForm'
|
|
||||||
export default {
|
|
||||||
name: 'EcrfList',
|
|
||||||
components: {
|
|
||||||
FormItem,
|
|
||||||
SignForm
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
visitTaskInfo: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
trialId: '',
|
|
||||||
criterionId: '',
|
|
||||||
visitTaskId: '',
|
|
||||||
loading: false,
|
|
||||||
questions: [],
|
|
||||||
questionForm: {},
|
|
||||||
publicQuestions: [],
|
|
||||||
signVisible: false,
|
|
||||||
signCode: null,
|
|
||||||
currentUser: zzSessionStorage.getItem('userName'),
|
|
||||||
readingTaskState: 2,
|
|
||||||
activeName: 0,
|
|
||||||
classArr: [],
|
|
||||||
calculationList: [],
|
|
||||||
taskInfo: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'visitTaskInfo.VisitTaskId': {
|
|
||||||
immediate: true,
|
|
||||||
handler(id) {
|
|
||||||
this.trialId = this.$route.query.trialId
|
|
||||||
this.criterionId = this.$route.query.TrialReadingCriterionId
|
|
||||||
if (!id) return
|
|
||||||
this.visitTaskId = id
|
|
||||||
this.getQuestionCalculateRelation()
|
|
||||||
this.getQuestions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async getQuestions() {
|
|
||||||
this.loading = true
|
|
||||||
try {
|
|
||||||
const param = {
|
|
||||||
readingQuestionCriterionTrialId: this.criterionId,
|
|
||||||
visitTaskId: this.visitTaskId
|
|
||||||
}
|
|
||||||
const res = await getTrialReadingQuestion(param)
|
|
||||||
if (res.IsSuccess) {
|
|
||||||
this.readingTaskState = res.OtherInfo.readingTaskState
|
|
||||||
res.Result.SinglePage.map((v) => {
|
|
||||||
if (v.Type === 'group' && v.Childrens.length === 0) return
|
|
||||||
if (!v.IsPage && v.Type !== 'group' && v.Type !== 'summary') {
|
|
||||||
this.$set(this.questionForm, v.Id, v.Answer ? v.Answer : null)
|
|
||||||
}
|
|
||||||
if (v.Type === 'class') {
|
|
||||||
this.classArr.push({ triggerId: v.ClassifyQuestionId, classId: v.Id, classifyAlgorithms: v.ClassifyAlgorithms, classifyType: v.ClassifyType })
|
|
||||||
}
|
|
||||||
if (v.Childrens.length > 0) {
|
|
||||||
this.setChild(v.Childrens)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.questions = res.Result.SinglePage
|
|
||||||
}
|
|
||||||
this.loading = false
|
|
||||||
} catch (e) {
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setChild(obj) {
|
|
||||||
obj.forEach(i => {
|
|
||||||
if (i.Type !== 'group' && i.Type !== 'summary' && i.Id) {
|
|
||||||
this.$set(this.questionForm, i.Id, i.Answer ? i.Answer : null)
|
|
||||||
}
|
|
||||||
if (i.Type === 'class') {
|
|
||||||
this.classArr.push({ triggerId: i.ClassifyQuestionId, classId: i.Id, classifyAlgorithms: i.ClassifyAlgorithms, classifyType: i.ClassifyType })
|
|
||||||
}
|
|
||||||
if (i.Childrens && i.Childrens.length > 0) {
|
|
||||||
this.setChild(i.Childrens)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async getQuestionCalculateRelation() {
|
|
||||||
try {
|
|
||||||
const res = await getQuestionCalculateRelation({ TrialReadingCriterionId: this.criterionId })
|
|
||||||
this.calculationList = res.Result
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async handleSave() {
|
|
||||||
const valid = await this.$refs['questions'].validate()
|
|
||||||
if (!valid) return
|
|
||||||
this.loading = true
|
|
||||||
const answers = []
|
|
||||||
for (const k in this.questionForm) {
|
|
||||||
answers.push({ readingQuestionTrialId: k, answer: this.questionForm[k] })
|
|
||||||
}
|
|
||||||
const params = {
|
|
||||||
trialId: this.trialId,
|
|
||||||
visitTaskId: this.visitTaskId,
|
|
||||||
readingQuestionCriterionTrialId: this.criterionId,
|
|
||||||
answerList: answers
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await saveVisitTaskQuestions(params)
|
|
||||||
if (res.IsSuccess) {
|
|
||||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
|
||||||
}
|
|
||||||
this.loading = false
|
|
||||||
} catch (e) {
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async handleSubmit() {
|
|
||||||
const valid = await this.$refs['questions'].validate()
|
|
||||||
if (!valid) return
|
|
||||||
const { ImageAssessmentReportConfirmation } = const_.processSignature
|
|
||||||
this.signCode = ImageAssessmentReportConfirmation
|
|
||||||
this.signVisible = true
|
|
||||||
},
|
|
||||||
// 关闭签名框
|
|
||||||
closeSignDialog(isSign, signInfo) {
|
|
||||||
if (isSign) {
|
|
||||||
this.signConfirm(signInfo)
|
|
||||||
} else {
|
|
||||||
this.signVisible = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 签名并确认
|
|
||||||
async signConfirm(signInfo) {
|
|
||||||
this.loading = true
|
|
||||||
var answers = []
|
|
||||||
for (const k in this.questionForm) {
|
|
||||||
answers.push({ readingQuestionTrialId: k, answer: this.questionForm[k] })
|
|
||||||
}
|
|
||||||
var params = {
|
|
||||||
data: {
|
|
||||||
trialId: this.trialId,
|
|
||||||
visitTaskId: this.visitTaskId,
|
|
||||||
readingQuestionCriterionTrialId: this.criterionId,
|
|
||||||
answerList: answers
|
|
||||||
},
|
|
||||||
signInfo: signInfo
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await submitVisitTaskQuestionsInDto(params)
|
|
||||||
this.loading = false
|
|
||||||
if (res.IsSuccess) {
|
|
||||||
this.$message.success(this.$t('common:message:savedSuccessfully'))
|
|
||||||
this.isEdit = false
|
|
||||||
this.$refs['signForm'].btnLoading = false
|
|
||||||
this.signVisible = false
|
|
||||||
this.readingTaskState = 2
|
|
||||||
// window.opener.postMessage('refreshTaskList', window.location)
|
|
||||||
const confirm = await this.$confirm(
|
|
||||||
this.$t('trials:noneDicoms:message:msg1'),
|
|
||||||
{
|
|
||||||
type: 'warning',
|
|
||||||
distinguishCancelAndClose: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (confirm !== 'confirm') return
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.loading = false
|
|
||||||
this.$refs['signForm'].btnLoading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async skipTask() {
|
|
||||||
try {
|
|
||||||
// 是否确认跳过?
|
|
||||||
const confirm = await this.$confirm(
|
|
||||||
this.$t('trials:readingReport:message:skipConfirm'),
|
|
||||||
{
|
|
||||||
type: 'warning',
|
|
||||||
distinguishCancelAndClose: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (confirm !== 'confirm') return
|
|
||||||
this.loading = true
|
|
||||||
const res = await setSkipReadingCache({ visitTaskId: this.visitTaskId })
|
|
||||||
this.loading = false
|
|
||||||
if (res.IsSuccess) {
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.loading = false
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetFormItemData(v) {
|
|
||||||
this.questionForm[v] = null
|
|
||||||
},
|
|
||||||
setFormItemData(obj) {
|
|
||||||
this.$set(this.questionForm, obj.key, JSON.parse(JSON.stringify(obj.val)))
|
|
||||||
this.classArr.map(i => {
|
|
||||||
if (i.triggerId === obj.key) {
|
|
||||||
let answer = null
|
|
||||||
const list = JSON.parse(i.classifyAlgorithms)
|
|
||||||
if (i.classifyType === 0) {
|
|
||||||
const o = list.find(v => {
|
|
||||||
return (
|
|
||||||
parseFloat(obj.val) >= parseFloat(v.gt) &&
|
|
||||||
parseFloat(obj.val) < parseFloat(v.lt)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
answer = o ? o.label : null
|
|
||||||
} else if (i.classifyType === 1) {
|
|
||||||
const o = list.find(v => {
|
|
||||||
return v.val.includes(obj.val)
|
|
||||||
})
|
|
||||||
answer = o ? o.label : null
|
|
||||||
}
|
|
||||||
this.$set(this.questionForm, i.classId, answer)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ecrf-list-container{
|
|
||||||
min-height:400px;
|
|
||||||
color: #ddd;
|
|
||||||
.ecrf-form{
|
|
||||||
::v-deep .el-form-item__label{
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,588 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<!-- <div
|
|
||||||
v-if="!!question.GroupName && (questionForm[question.ParentId] === question.ParentTriggerValue) || question.ParentId===''||question.ParentId===null"
|
|
||||||
style="font-weight: bold;font-size: 16px;margin: 5px 0px;"
|
|
||||||
>
|
|
||||||
{{ question.GroupName }}
|
|
||||||
</div> -->
|
|
||||||
<div
|
|
||||||
v-if="!!question.GroupName && question.Type==='group'"
|
|
||||||
style="font-weight: bold;font-size: 16px;margin: 5px 0px;"
|
|
||||||
>
|
|
||||||
{{ question.GroupName }}
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<el-form-item
|
|
||||||
v-if="(question.ShowQuestion===1 && questionForm[question.ParentId] === question.ParentTriggerValue) || question.ShowQuestion===0"
|
|
||||||
:label="`${question.QuestionName}`"
|
|
||||||
:prop="question.Id"
|
|
||||||
:rules="[
|
|
||||||
{ required: (question.IsRequired === 0 || (question.IsRequired ===1 && question.RelevanceId && (question.RelevanceValueList.includes(isNaN(parseFloat(questionForm[question.RelevanceId])) ? questionForm[question.RelevanceId] : questionForm[question.RelevanceId].toString())))) && question.Type!=='group' && question.Type!=='summary',
|
|
||||||
message: $t('common:ruleMessage:specify'), trigger: ['blur', 'change']},
|
|
||||||
]"
|
|
||||||
:class="[question.Type==='group'?'mb':question.Type==='upload'?'uploadWrapper':'']"
|
|
||||||
>
|
|
||||||
|
|
||||||
<!-- 输入框 -->
|
|
||||||
<el-input
|
|
||||||
v-if="question.Type==='input'"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
:disabled="readingTaskState >= 2"
|
|
||||||
/>
|
|
||||||
<!-- 多行文本输入框 -->
|
|
||||||
<el-input
|
|
||||||
v-if="question.Type==='textarea'"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
type="textarea"
|
|
||||||
:autosize="{ minRows: 2, maxRows: 4}"
|
|
||||||
:disabled="readingTaskState >= 2"
|
|
||||||
/>
|
|
||||||
<!-- 下拉框 -->
|
|
||||||
<!-- <el-select-->
|
|
||||||
<!-- v-if="question.Type==='select'"-->
|
|
||||||
<!-- v-model="questionForm[question.Id]"-->
|
|
||||||
<!-- :disabled="readingTaskState >= 2"-->
|
|
||||||
<!-- clearable-->
|
|
||||||
<!-- @change="((val)=>{formItemChange(val, question)})"-->
|
|
||||||
<!-- >-->
|
|
||||||
<!-- <el-option-->
|
|
||||||
<!-- v-for="val in question.TypeValue.split('|')"-->
|
|
||||||
<!-- :key="val"-->
|
|
||||||
<!-- :label="val"-->
|
|
||||||
<!-- :value="val"-->
|
|
||||||
<!-- />-->
|
|
||||||
<!-- </el-select>-->
|
|
||||||
<el-select
|
|
||||||
v-if="question.Type==='select'"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
filterable
|
|
||||||
:placeholder="$t('common:placeholder:select')"
|
|
||||||
:disabled="readingTaskState >= 2"
|
|
||||||
@change="((val)=>{formItemChange(val, question)})"
|
|
||||||
>
|
|
||||||
<template v-if="question.DictionaryCode">
|
|
||||||
<el-option
|
|
||||||
v-for="item of $d[question.DictionaryCode]"
|
|
||||||
:key="item.id"
|
|
||||||
:value="item.value.toString()"
|
|
||||||
:label="item.label"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="question.TypeValue">
|
|
||||||
<el-option
|
|
||||||
v-for="val in question.TypeValue.split('|')"
|
|
||||||
:key="val.trim()"
|
|
||||||
:label="val.trim()"
|
|
||||||
:value="val.trim()"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-select>
|
|
||||||
<!-- 单选 -->
|
|
||||||
<el-radio-group
|
|
||||||
v-if="question.Type==='radio'"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
:disabled="readingTaskState >= 2"
|
|
||||||
@change="((val)=>{formItemChange(val, question)})"
|
|
||||||
>
|
|
||||||
<template v-if="question.DictionaryCode">
|
|
||||||
<el-radio
|
|
||||||
v-for="item of $d[question.DictionaryCode]"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.value.toString()"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</el-radio>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="question.TypeValue">
|
|
||||||
<el-radio
|
|
||||||
v-for="val in question.TypeValue.split('|')"
|
|
||||||
:key="val.trim()"
|
|
||||||
:label="val.trim()"
|
|
||||||
>
|
|
||||||
{{ val.trim() }}
|
|
||||||
</el-radio>
|
|
||||||
</template>
|
|
||||||
</el-radio-group>
|
|
||||||
<!-- 复选框 -->
|
|
||||||
<el-checkbox-group
|
|
||||||
v-if="question.Type==='checkbox'"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
:disabled="readingTaskState >= 2"
|
|
||||||
>
|
|
||||||
<el-checkbox
|
|
||||||
v-for="val in question.TypeValue.split('|')"
|
|
||||||
:key="val.trim()"
|
|
||||||
:label="val.trim()"
|
|
||||||
>
|
|
||||||
{{ val.trim() }}
|
|
||||||
</el-checkbox>
|
|
||||||
</el-checkbox-group>
|
|
||||||
<!-- 数值 -->
|
|
||||||
<!-- :precision="2" :step="0.1" :max="10" -->
|
|
||||||
<template v-if="question.Type==='number'">
|
|
||||||
<!-- 数值 -->
|
|
||||||
<el-select
|
|
||||||
v-if="question.TypeValue"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
clearable
|
|
||||||
@change="(val) => { formItemNumberChange(val, question) }"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="val in question.TypeValue.split('|')"
|
|
||||||
:key="val"
|
|
||||||
:label="val.trim()"
|
|
||||||
:value="val.trim()"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-input
|
|
||||||
v-if="question.DataSource !== 1"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
type="number"
|
|
||||||
onblur="value=parseFloat(value).toFixed(parseInt(localStorage.getItem('digitPlaces')));"
|
|
||||||
@change="(val) => { formItemNumberChange(val, question) }"
|
|
||||||
@input="limitInput($event, questionForm, question.Id)"
|
|
||||||
>
|
|
||||||
<template v-if="question.Unit !== 0" slot="append">{{ question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit }}</template>
|
|
||||||
<template v-else-if="question.ValueType === 2" slot="append">%</template>
|
|
||||||
</el-input>
|
|
||||||
<el-input
|
|
||||||
v-if="question.DataSource === 1"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
type="number"
|
|
||||||
onblur="value=parseFloat(value).toFixed(parseInt(localStorage.getItem('digitPlaces')));"
|
|
||||||
:disabled="question.DataSource === 1"
|
|
||||||
@input="limitInput($event, questionForm, question.Id)"
|
|
||||||
>
|
|
||||||
<template v-if="question.Unit !== 0" slot="append">{{ question.Unit !== 4 ? $fd('ValueUnit', question.Unit) : question.CustomUnit }}</template>
|
|
||||||
<template v-else-if="question.ValueType === 2" slot="append">%</template>
|
|
||||||
</el-input>
|
|
||||||
</template>
|
|
||||||
<!-- 自动分类 -->
|
|
||||||
<el-input
|
|
||||||
v-if="question.Type === 'class' && question.ClassifyShowType === 1"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
:disabled="!question.ClassifyEditType"
|
|
||||||
/>
|
|
||||||
<el-select
|
|
||||||
v-if="question.Type === 'class' && question.ClassifyShowType === 2"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
:disabled="!question.ClassifyEditType"
|
|
||||||
@change="(val) => { formItemChange(val, question) }"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="val in question.TypeValue.split('|')"
|
|
||||||
:key="val"
|
|
||||||
:label="val.trim()"
|
|
||||||
:value="val.trim()"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-radio-group
|
|
||||||
v-if="question.Type === 'class' && question.ClassifyShowType === 3"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
:disabled="!question.ClassifyEditType"
|
|
||||||
@change="(val) => { formItemChange(val, question) }"
|
|
||||||
>
|
|
||||||
<el-radio
|
|
||||||
v-for="item of question.TypeValue.split('|')"
|
|
||||||
:key="item.trim()"
|
|
||||||
:label="item.trim()"
|
|
||||||
>
|
|
||||||
{{ item.trim() }}
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
<el-input
|
|
||||||
v-if="question.Type === 'class' && question.ClassifyShowType === 4"
|
|
||||||
v-model="questionForm[question.Id]"
|
|
||||||
type="number"
|
|
||||||
:disabled="!question.ClassifyEditType"
|
|
||||||
@change="(val) => { formItemNumberChange(val, question) }"
|
|
||||||
/>
|
|
||||||
<!-- 上传图像 -->
|
|
||||||
<el-upload
|
|
||||||
v-if="question.Type==='upload'"
|
|
||||||
action
|
|
||||||
:accept="accept"
|
|
||||||
:limit="question.ImageCount"
|
|
||||||
:on-preview="handlePictureCardPreview"
|
|
||||||
:before-upload="handleBeforeUpload"
|
|
||||||
:http-request="uploadScreenshot"
|
|
||||||
list-type="picture-card"
|
|
||||||
:on-remove="handleRemove"
|
|
||||||
:file-list="fileList"
|
|
||||||
:class="{disabled:fileList.length >= question.ImageCount}"
|
|
||||||
:disabled="readingTaskState >= 2"
|
|
||||||
>
|
|
||||||
<i slot="default" class="el-icon-plus" />
|
|
||||||
<div slot="file" slot-scope="{file}">
|
|
||||||
<img
|
|
||||||
class="el-upload-list__item-thumbnail"
|
|
||||||
:src="OSSclientConfig.basePath + file.url"
|
|
||||||
alt=""
|
|
||||||
>
|
|
||||||
<span class="el-upload-list__item-actions">
|
|
||||||
<span
|
|
||||||
class="el-upload-list__item-preview"
|
|
||||||
@click="handlePictureCardPreview(file)"
|
|
||||||
>
|
|
||||||
<i class="el-icon-zoom-in" />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
v-if="readingTaskState < 2"
|
|
||||||
class="el-upload-list__item-delete"
|
|
||||||
@click="handleRemove(file)"
|
|
||||||
>
|
|
||||||
<i class="el-icon-delete" />
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</el-upload>
|
|
||||||
<el-dialog
|
|
||||||
append-to-body
|
|
||||||
:visible.sync="imgVisible"
|
|
||||||
width="600px"
|
|
||||||
>
|
|
||||||
<el-image :src="OSSclientConfig.basePath + imageUrl" width="100%">
|
|
||||||
<div slot="placeholder" class="image-slot">
|
|
||||||
{{ $t('trials:readingUnit:qsList:message:loading') }}<span class="dot">...</span>
|
|
||||||
</div>
|
|
||||||
</el-image>
|
|
||||||
</el-dialog>
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<FormItem
|
|
||||||
v-for="(item) in question.Childrens"
|
|
||||||
:key="item.Id"
|
|
||||||
:question="item"
|
|
||||||
:reading-task-state="readingTaskState"
|
|
||||||
:question-form="questionForm"
|
|
||||||
:visit-task-id="visitTaskId"
|
|
||||||
:calculation-list="calculationList"
|
|
||||||
@setFormItemData="setFormItemData"
|
|
||||||
@resetFormItemData="resetFormItemData"
|
|
||||||
@formItemNumberChange="formItemNumberChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
// import { uploadReadingAnswerImage } from '@/api/trials'
|
|
||||||
export default {
|
|
||||||
name: 'FormItem',
|
|
||||||
props: {
|
|
||||||
questionForm: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
question: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
readingTaskState: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
visitTaskId: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
calculationList: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
fileList: [],
|
|
||||||
accept: '.png,.jpg,.jpeg',
|
|
||||||
imgVisible: false,
|
|
||||||
imageUrl: '',
|
|
||||||
urls: [],
|
|
||||||
digitPlaces: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
questionForm: {
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
handler(v, oldv) {
|
|
||||||
try {
|
|
||||||
if (!v || !v[this.question.Id] || !oldv || !oldv[this.question.Id]) { return }
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e, v)
|
|
||||||
}
|
|
||||||
if (this.question.Type === 'class') {
|
|
||||||
this.$emit('setFormItemData', { key: this.question.Id, val: v[this.question.Id], question: v })
|
|
||||||
}
|
|
||||||
this.formItemNumberChange(this.question.Id, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (this.question.Type === 'upload') {
|
|
||||||
if (this.questionForm[this.question.Id]) {
|
|
||||||
this.urls = this.questionForm[this.question.Id].split('|')
|
|
||||||
this.fileList = []
|
|
||||||
|
|
||||||
this.urls.map(url => {
|
|
||||||
this.fileList.push({ name: '', url: `${url}` })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.digitPlaces = localStorage.getItem('digitPlaces') ? parseInt(localStorage.getItem('digitPlaces')) : 0
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
limitInput(value, a, b) {
|
|
||||||
if (value.indexOf('.') > -1) {
|
|
||||||
if (value.split('.')[1].length >= this.digitPlaces) {
|
|
||||||
this.$set(a, b, parseFloat(value).toFixed(this.digitPlaces))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
logic(rules, num = 0) {
|
|
||||||
try {
|
|
||||||
if (rules.CalculateQuestionList.length === 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const dataArr = []
|
|
||||||
rules.CalculateQuestionList.forEach((o, i) => {
|
|
||||||
if (i === 0) {
|
|
||||||
if (rules.CustomCalculateMark > 4 && rules.CustomCalculateMark < 10) {
|
|
||||||
switch (rules.CustomCalculateMark) {
|
|
||||||
case 5:
|
|
||||||
this.questionForm[o.QuestionId].forEach((q, qi) => {
|
|
||||||
if (qi === 0) {
|
|
||||||
num = parseFloat(q[o.TableQuestionId])
|
|
||||||
} else {
|
|
||||||
num *= parseFloat(q[o.TableQuestionId])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case 6:
|
|
||||||
this.questionForm[o.QuestionId].forEach((q, qi) => {
|
|
||||||
if (qi === 0) {
|
|
||||||
num = parseFloat(q[o.TableQuestionId])
|
|
||||||
} else {
|
|
||||||
num += parseFloat(q[o.TableQuestionId])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case 7:
|
|
||||||
this.questionForm[o.QuestionId].forEach((q, qi) => {
|
|
||||||
if (qi === 0) {
|
|
||||||
num = parseFloat(q[o.TableQuestionId])
|
|
||||||
} else {
|
|
||||||
num += parseFloat(q[o.TableQuestionId])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
num = this.questionForm[o.QuestionId].length === 0 ? 0 : num / this.questionForm[o.QuestionId].length
|
|
||||||
break
|
|
||||||
case 8:
|
|
||||||
const arr = []
|
|
||||||
this.questionForm[o.QuestionId].forEach(q => {
|
|
||||||
arr.push(q[o.TableQuestionId])
|
|
||||||
})
|
|
||||||
num = arr.length === 0 ? 0 : Math.max(...arr)
|
|
||||||
break
|
|
||||||
case 9:
|
|
||||||
const arr1 = []
|
|
||||||
this.questionForm[o.QuestionId].forEach(q => {
|
|
||||||
arr1.push(q[o.TableQuestionId])
|
|
||||||
})
|
|
||||||
num = arr1.length === 0 ? 0 : Math.min(...arr1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
num = parseFloat(this.questionForm[o.TableQuestionId])
|
|
||||||
if (!isNaN(num)) {
|
|
||||||
dataArr.push(num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (rules.CustomCalculateMark) {
|
|
||||||
case 1:
|
|
||||||
num += parseFloat(this.questionForm[o.TableQuestionId])
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
num -= parseFloat(this.questionForm[o.TableQuestionId])
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
num *= parseFloat(this.questionForm[o.TableQuestionId])
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
if (parseFloat(this.questionForm[o.TableQuestionId]) === 0) {
|
|
||||||
num = 0
|
|
||||||
} else {
|
|
||||||
num /= parseFloat(this.questionForm[o.TableQuestionId])
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 10:
|
|
||||||
if (!isNaN(parseFloat(this.questionForm[o.TableQuestionId]))) {
|
|
||||||
dataArr.push(parseFloat(this.questionForm[o.TableQuestionId]))
|
|
||||||
}
|
|
||||||
num = dataArr.length === 0 ? 0 : dataArr.reduce((acc, curr) => {
|
|
||||||
return acc + (typeof curr === 'number' ? curr : 0)
|
|
||||||
}, 0) / dataArr.length
|
|
||||||
break
|
|
||||||
case 11:
|
|
||||||
if (!isNaN(parseFloat(this.questionForm[o.TableQuestionId]))) {
|
|
||||||
dataArr.push(parseFloat(this.questionForm[o.TableQuestionId]))
|
|
||||||
}
|
|
||||||
num = Math.max(...dataArr)
|
|
||||||
break
|
|
||||||
case 12:
|
|
||||||
if (!isNaN(parseFloat(this.questionForm[o.TableQuestionId]))) {
|
|
||||||
dataArr.push(parseFloat(this.questionForm[o.TableQuestionId]))
|
|
||||||
}
|
|
||||||
num = Math.min(...dataArr)
|
|
||||||
break
|
|
||||||
case 13:
|
|
||||||
if (!isNaN(parseFloat(this.questionForm[o.TableQuestionId]))) {
|
|
||||||
dataArr.push(parseFloat(this.questionForm[o.TableQuestionId]))
|
|
||||||
}
|
|
||||||
num = dataArr.length === 0 ? 0 : dataArr.reduce((acc, curr) => acc && curr) ? 1 : 0
|
|
||||||
break
|
|
||||||
case 14:
|
|
||||||
if (!isNaN(parseFloat(this.questionForm[o.TableQuestionId]))) {
|
|
||||||
dataArr.push(parseFloat(this.questionForm[o.TableQuestionId]))
|
|
||||||
}
|
|
||||||
num = dataArr.length === 0 ? 0 : dataArr.reduce((acc, curr) => acc || curr, 0) ? 1 : 0
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
var digitPlaces = parseInt(localStorage.getItem('digitPlaces'))
|
|
||||||
if (rules.ValueType === 2) {
|
|
||||||
num = num * 100
|
|
||||||
}
|
|
||||||
if (rules.CustomCalculateMark === 13 || rules.CustomCalculateMark === 14) {
|
|
||||||
return num
|
|
||||||
} else {
|
|
||||||
return num.toFixed(digitPlaces)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
formItemNumberChange(questionId, isTable) {
|
|
||||||
if (isTable) {
|
|
||||||
this.calculationList.forEach((v, i) => {
|
|
||||||
var find = v.CalculateQuestionList.filter(o => {
|
|
||||||
return o.QuestionId === questionId
|
|
||||||
})
|
|
||||||
// find的自动计算值number
|
|
||||||
if (find) {
|
|
||||||
var num = this.logic(v)
|
|
||||||
if (num !== false) {
|
|
||||||
this.$emit('setFormItemData', { key: v.QuestionId, val: num, question: v })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.calculationList.forEach(v => {
|
|
||||||
var find = v.CalculateQuestionList.filter(o => {
|
|
||||||
return o.TableQuestionId === questionId
|
|
||||||
})
|
|
||||||
// find的自动计算值number
|
|
||||||
if (find) {
|
|
||||||
var num = this.logic(v)
|
|
||||||
if (num !== false) {
|
|
||||||
this.$emit('setFormItemData', { key: v.QuestionId, val: num, question: v })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
formItemChange(v, question) {
|
|
||||||
if (question.Childrens.length > 0) {
|
|
||||||
this.resetChild(question.Childrens)
|
|
||||||
} else {
|
|
||||||
this.$emit('setFormItemData', { key: question.Id, val: v, question: question })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetChild(obj) {
|
|
||||||
obj.forEach(i => {
|
|
||||||
this.$emit('resetFormItemData', i.Id)
|
|
||||||
if (i.Childrens && i.Childrens.length > 0) {
|
|
||||||
this.resetChild(i.Childrens)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
resetFormItemData(v) {
|
|
||||||
this.$emit('resetFormItemData', v)
|
|
||||||
},
|
|
||||||
setFormItemData(obj) {
|
|
||||||
this.$emit('setFormItemData', obj)
|
|
||||||
},
|
|
||||||
async uploadScreenshot(param) {
|
|
||||||
if (!this.visitTaskId) return
|
|
||||||
const loading = this.$loading({
|
|
||||||
target: document.querySelector('.ecrf-wrapper'),
|
|
||||||
fullscreen: false,
|
|
||||||
lock: true,
|
|
||||||
text: 'Loading',
|
|
||||||
spinner: 'el-icon-loading'
|
|
||||||
})
|
|
||||||
var file = await this.fileToBlob(param.file)
|
|
||||||
const res = await this.OSSclient.put(`/${this.trialId}/ReadAttachment/${this.subjectId}/${this.visitTaskId}/${param.file.name}`, file)
|
|
||||||
this.fileList.push({ name: param.file.name, url: this.$getObjectName(res.url) })
|
|
||||||
this.urls.push(this.$getObjectName(res.url))
|
|
||||||
this.$emit('setFormItemData', { key: this.question.Id, val: this.urls.length > 0 ? this.urls.join('|') : '' })
|
|
||||||
loading.close()
|
|
||||||
},
|
|
||||||
handleBeforeUpload(file) {
|
|
||||||
// 检测文件类型是否符合要求
|
|
||||||
if (this.checkFileSuffix(file.name)) {
|
|
||||||
// this.fileList = []
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
this.$alert(`必须是 ${this.accept} 格式`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checkFileSuffix(fileName) {
|
|
||||||
var index = fileName.lastIndexOf('.')
|
|
||||||
var suffix = fileName.substring(index + 1, fileName.length)
|
|
||||||
if (this.accept.toLocaleLowerCase().search(suffix.toLocaleLowerCase()) === -1) {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 预览图片
|
|
||||||
handlePictureCardPreview(file) {
|
|
||||||
this.imageUrl = this.OSSclientConfig.basePath + file.url
|
|
||||||
this.imgVisible = true
|
|
||||||
},
|
|
||||||
// 删除图片
|
|
||||||
handleRemove(file, fileList) {
|
|
||||||
this.imageUrl = ''
|
|
||||||
this.fileList.splice(this.fileList.findIndex(f => f.url === file.url), 1)
|
|
||||||
this.urls.splice(this.fileList.findIndex(f => f === file.url), 1)
|
|
||||||
this.$emit('setFormItemData', { key: this.question.Id, val: this.urls.length > 0 ? this.urls.join('|') : '' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.mb{
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
.disabled{
|
|
||||||
::v-deep .el-upload--picture-card {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.uploadWrapper{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,509 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="none-dicom-viewer">
|
|
||||||
<!-- tools -->
|
|
||||||
<div class="tools-wrapper">
|
|
||||||
<el-dropdown @command="handleCommand">
|
|
||||||
<span class="el-dropdown-link">
|
|
||||||
<i class="el-icon-menu" /><i class="el-icon-arrow-down el-icon--right" />
|
|
||||||
</span>
|
|
||||||
<el-dropdown-menu slot="dropdown">
|
|
||||||
<el-dropdown-item command="1*1">1*1</el-dropdown-item>
|
|
||||||
<el-dropdown-item command="1*2">1*2</el-dropdown-item>
|
|
||||||
<el-dropdown-item command="2*2">2*2</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</el-dropdown>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- viewports -->
|
|
||||||
<div class="viewports-wrapper">
|
|
||||||
<div class="grid-container" :style="gridStyle">
|
|
||||||
<div
|
|
||||||
v-for="(v, index) in viewportInfos"
|
|
||||||
v-show="index < cells.length"
|
|
||||||
:key="index"
|
|
||||||
:style="cellStyle"
|
|
||||||
:class="['grid-cell', index === activeCanvasIndex?'cell_active':'']"
|
|
||||||
@click="activeCanvas(index)"
|
|
||||||
@dblclick="toggleFullScreen(index)"
|
|
||||||
>
|
|
||||||
<div :ref="`canvas-${index}`" class="content">
|
|
||||||
<div class="left-top-text">
|
|
||||||
<div
|
|
||||||
v-if="v.taskInfo.IsExistsClinicalData"
|
|
||||||
class="cd-info"
|
|
||||||
:title="$t('trials:reading:button:clinicalData')"
|
|
||||||
@click.stop="viewCD($event)"
|
|
||||||
>
|
|
||||||
<svg-icon icon-class="documentation" class="svg-icon" />
|
|
||||||
</div>
|
|
||||||
<h2
|
|
||||||
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo && v.taskInfo"
|
|
||||||
class="subject-info"
|
|
||||||
>
|
|
||||||
{{ `${taskInfo.SubjectCode} ${v.taskInfo.TaskBlindName} ` }}
|
|
||||||
</h2>
|
|
||||||
<div v-if="v.currentFileName">{{ v.currentFileName }}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="taskInfo && taskInfo.IsReadingTaskViewInOrder === 1 && v.taskInfo"
|
|
||||||
class="top-center-tool"
|
|
||||||
>
|
|
||||||
<div class="toggle-visit-container">
|
|
||||||
<div
|
|
||||||
class="arrw_icon"
|
|
||||||
:style="{ cursor: v.taskInfo.VisitTaskNum !== 0 ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum !== 0 ? '#fff': '#6b6b6b' }"
|
|
||||||
@click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, -1)"
|
|
||||||
@dblclick.stop="preventDefault($event)"
|
|
||||||
>
|
|
||||||
<i class="el-icon-caret-left" />
|
|
||||||
</div>
|
|
||||||
<div class="arrow_text">
|
|
||||||
{{ v.taskInfo.TaskBlindName }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="arrw_icon"
|
|
||||||
:style="{ cursor: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? 'pointer' : 'not-allowed', color: v.taskInfo.VisitTaskNum < taskInfo.VisitNum ? '#fff': '#6b6b6b' }"
|
|
||||||
@click.stop.prevent="toggleTask($event, v.taskInfo.VisitTaskNum, 1)"
|
|
||||||
@dblclick.stop="preventDefault($event)"
|
|
||||||
>
|
|
||||||
<i class="el-icon-caret-right" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
RenderingEngine,
|
|
||||||
Enums,
|
|
||||||
imageLoader,
|
|
||||||
metaData,
|
|
||||||
getRenderingEngine
|
|
||||||
} from '@cornerstonejs/core'
|
|
||||||
import * as cornerstoneTools from '@cornerstonejs/tools'
|
|
||||||
import initLibraries from '@/views/trials/trials-panel/reading/dicoms/components/Fusion/js/initLibraries'
|
|
||||||
import hardcodedMetaDataProvider from './../js/hardcodedMetaDataProvider'
|
|
||||||
import registerWebImageLoader from './../js/registerWebImageLoader'
|
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
import store from '@/store'
|
|
||||||
import { readUint16 } from '../../../../../../../static/pdfjs/build/pdf.worker'
|
|
||||||
const { ViewportType } = Enums
|
|
||||||
const renderingEngineId = 'myRenderingEngine'
|
|
||||||
const { ToolGroupManager, Enums: csToolsEnums, StackScrollTool } = cornerstoneTools
|
|
||||||
const { MouseBindings } = csToolsEnums
|
|
||||||
export default {
|
|
||||||
name: 'ImageViewer',
|
|
||||||
props: {
|
|
||||||
relatedStudyInfo: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
rows: 1,
|
|
||||||
cols: 1,
|
|
||||||
fullScreenIndex: null,
|
|
||||||
imageIds: [],
|
|
||||||
activeCanvasIndex: 0,
|
|
||||||
layout: '1*2',
|
|
||||||
cellsMax: 4,
|
|
||||||
viewportInfos: [],
|
|
||||||
taskInfo: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
gridStyle() {
|
|
||||||
return {
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateRows: `repeat(${this.rows}, 1fr)`,
|
|
||||||
gridTemplateColumns: `repeat(${this.cols}, 1fr)`,
|
|
||||||
height: '100%',
|
|
||||||
width: '100%'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cellStyle() {
|
|
||||||
return {
|
|
||||||
border: '1px dashed #ccc',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cells() {
|
|
||||||
return Array(this.rows * this.cols).fill(0)
|
|
||||||
},
|
|
||||||
...mapGetters(['lastViewportTaskId'])
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
relatedStudyInfo: {
|
|
||||||
immediate: true,
|
|
||||||
handler(obj) {
|
|
||||||
if (!obj || Object.keys(obj).length === 0) return
|
|
||||||
this.updateViewportInfos(0, obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
|
|
||||||
if (this.taskInfo.VisitNum > 0) {
|
|
||||||
this.rows = 1
|
|
||||||
this.cols = 2
|
|
||||||
this.activeCanvasIndex = 1
|
|
||||||
}
|
|
||||||
this.viewportInfos = Array.from({ length: this.cellsMax }, (_, index) => ({
|
|
||||||
index: index,
|
|
||||||
taskInfo: '',
|
|
||||||
currentImageIdIndex: 0,
|
|
||||||
viewportId: `canvas-${index}`,
|
|
||||||
currentFileName: '',
|
|
||||||
imageIds: []
|
|
||||||
}))
|
|
||||||
this.initLoader()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 初始化加载器
|
|
||||||
async initLoader() {
|
|
||||||
registerWebImageLoader(imageLoader)
|
|
||||||
await initLibraries()
|
|
||||||
|
|
||||||
let renderingEngine = getRenderingEngine(renderingEngineId)
|
|
||||||
if (!renderingEngine) {
|
|
||||||
renderingEngine = new RenderingEngine(renderingEngineId)
|
|
||||||
}
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
|
||||||
if (renderingEngine) {
|
|
||||||
renderingEngine.resize(true, false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const element1 = this.$refs['canvas-0'][0]
|
|
||||||
const element2 = this.$refs['canvas-1'][0]
|
|
||||||
const element3 = this.$refs['canvas-2'][0]
|
|
||||||
const element4 = this.$refs['canvas-3'][0]
|
|
||||||
const elements = [
|
|
||||||
element1,
|
|
||||||
element2,
|
|
||||||
element3,
|
|
||||||
element4
|
|
||||||
]
|
|
||||||
elements.forEach((element, i) => {
|
|
||||||
element.oncontextmenu = (e) => e.preventDefault()
|
|
||||||
resizeObserver.observe(element)
|
|
||||||
element.addEventListener('CORNERSTONE_STACK_NEW_IMAGE', this.stackNewImage)
|
|
||||||
})
|
|
||||||
const viewportInputArray = [
|
|
||||||
{
|
|
||||||
viewportId: 'canvas-0',
|
|
||||||
type: ViewportType.STACK,
|
|
||||||
element: element1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
viewportId: 'canvas-1',
|
|
||||||
type: ViewportType.STACK,
|
|
||||||
element: element2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
viewportId: 'canvas-2',
|
|
||||||
type: ViewportType.STACK,
|
|
||||||
element: element3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
viewportId: 'canvas-3',
|
|
||||||
type: ViewportType.STACK,
|
|
||||||
element: element4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
const viewportIds = ['canvas-0', 'canvas-1', 'canvas-2', 'canvas-3']
|
|
||||||
renderingEngine.setViewports(viewportInputArray)
|
|
||||||
cornerstoneTools.addTool(StackScrollTool)
|
|
||||||
viewportIds.forEach((viewportId, i) => {
|
|
||||||
const toolGroupId = `canvas-${i}`
|
|
||||||
const toolGroup = ToolGroupManager.createToolGroup(toolGroupId)
|
|
||||||
toolGroup.addViewport(viewportId, renderingEngineId)
|
|
||||||
toolGroup.addTool(StackScrollTool.toolName)
|
|
||||||
toolGroup.setToolActive(StackScrollTool.toolName, {
|
|
||||||
bindings: [{ mouseButton: MouseBindings.Wheel }]
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 加载图片回调
|
|
||||||
stackNewImage(e) {
|
|
||||||
const { detail } = e
|
|
||||||
const i = this.viewportInfos.findIndex(i => i.viewportId === detail.viewportId)
|
|
||||||
if (i === -1) return
|
|
||||||
this.viewportInfos[i].currentImageIdIndex = detail.imageIdIndex
|
|
||||||
const obj = this.viewportInfos[i].fileList[detail.imageIdIndex]
|
|
||||||
if (!obj) return
|
|
||||||
this.viewportInfos[i].currentFileName = obj.FileName
|
|
||||||
},
|
|
||||||
// 渲染图片
|
|
||||||
async renderImage(imageIds, canvasIndex, sliceIndex) {
|
|
||||||
metaData.addProvider((type, imageId) => hardcodedMetaDataProvider(type, imageId, imageIds), 10000)
|
|
||||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
|
||||||
const viewport = renderingEngine.getViewport(`canvas-${canvasIndex}`)
|
|
||||||
await viewport.setStack(imageIds)
|
|
||||||
viewport.setImageIdIndex(sliceIndex)
|
|
||||||
viewport.render()
|
|
||||||
// this.updateViewportInfos()
|
|
||||||
},
|
|
||||||
setActiveCanvasImages(obj) {
|
|
||||||
if (!obj || Object.keys(obj).length === 0) return
|
|
||||||
const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
|
|
||||||
if (i === -1) return
|
|
||||||
if (obj.visitTaskInfo.VisitTaskId === this.viewportInfos[i].taskInfo.VisitTaskId) {
|
|
||||||
this.sliceIndex(obj.fileIndex)
|
|
||||||
} else {
|
|
||||||
this.updateViewportInfos(this.activeCanvasIndex, obj)
|
|
||||||
store.dispatch('noneDicomReview/setLastViewportTaskId', obj.visitTaskInfo.VisitTaskId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 激活视图
|
|
||||||
activeCanvas(index) {
|
|
||||||
if (this.activeCanvasIndex === index) return
|
|
||||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
|
||||||
if (i === -1) return
|
|
||||||
store.dispatch('noneDicomReview/setLastViewportTaskId', this.viewportInfos[i].taskInfo.VisitTaskId)
|
|
||||||
this.activeCanvasIndex = index
|
|
||||||
// this.$emit('toggleTask', this.viewportInfos[i].taskInfo)
|
|
||||||
},
|
|
||||||
// 更新视图信息
|
|
||||||
updateViewportInfos(index, obj) {
|
|
||||||
const i = this.viewportInfos.findIndex(i => i.index === index)
|
|
||||||
if (i === -1) return
|
|
||||||
this.viewportInfos[i].taskInfo = obj.visitTaskInfo
|
|
||||||
this.viewportInfos[i].currentImageIdIndex = obj.fileIndex
|
|
||||||
this.viewportInfos[i].currentFileName = obj.fileInfo.FileName
|
|
||||||
this.viewportInfos[i].fileList = obj.fileList
|
|
||||||
const imageIds = []
|
|
||||||
for (let i = 0; i < obj.fileList.length; i++) {
|
|
||||||
const path = obj.fileList[i].Path
|
|
||||||
imageIds.push(`web:${this.OSSclientConfig.basePath}${path}`)
|
|
||||||
}
|
|
||||||
this.viewportInfos[i].imageIds = imageIds
|
|
||||||
if (imageIds.length > 0) {
|
|
||||||
this.renderImage(imageIds, index, obj.fileIndex)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 切换图片
|
|
||||||
sliceIndex(index) {
|
|
||||||
const i = this.viewportInfos.findIndex(i => i.index === this.activeCanvasIndex)
|
|
||||||
if (i === -1) return
|
|
||||||
if (index < 0 || index >= this.viewportInfos[i].imageIds.length) return
|
|
||||||
const renderingEngine = getRenderingEngine(renderingEngineId)
|
|
||||||
const viewport = renderingEngine.getViewport(
|
|
||||||
this.viewportInfos[i].viewportId
|
|
||||||
)
|
|
||||||
viewport.setImageIdIndex(index)
|
|
||||||
viewport.render()
|
|
||||||
},
|
|
||||||
// 更改视图布局
|
|
||||||
handleCommand(command) {
|
|
||||||
this.layout = command
|
|
||||||
this.rows = parseInt(command.split('*')[0])
|
|
||||||
this.cols = parseInt(command.split('*')[1])
|
|
||||||
// 有序阅片 1*1 显示当前图片 1*2 显示基线*当前图片 2*2 显示当前图片
|
|
||||||
// const obj = this.viewportInfos.find(i => i.index === this.activeCanvasIndex)
|
|
||||||
// if (obj && this.rows === 1 && this.cols === 1) {
|
|
||||||
// this.viewportInfos = this.viewportInfos.map((v, i) => {
|
|
||||||
// if (i === 0) {
|
|
||||||
// v.taskInfo = obj.taskInfo
|
|
||||||
// v.currentImageIdIndex === obj.currentImageIdIndex
|
|
||||||
// v.currentFileName === obj.currentFileName
|
|
||||||
// v.imageIds === obj.imageIds
|
|
||||||
// } else {
|
|
||||||
// v.taskInfo = ''
|
|
||||||
// v.currentImageIdIndex === 0
|
|
||||||
// v.currentFileName === ''
|
|
||||||
// v.imageIds === []
|
|
||||||
// }
|
|
||||||
// return v
|
|
||||||
// })
|
|
||||||
// this.activeCanvasIndex = 0
|
|
||||||
// } else if (obj && this.rows === 1 && this.cols === 2 && this.taskInfo.IsReadingTaskViewInOrder === 1) {
|
|
||||||
// this.viewportInfos = this.viewportInfos.map((v, i) => {
|
|
||||||
// if (i === 0) {
|
|
||||||
// v.taskInfo = this.relatedStudyInfo.visitTaskInfo
|
|
||||||
// v.currentImageIdIndex === this.relatedStudyInfo.fileIndex
|
|
||||||
// v.currentFileName === this.relatedStudyInfo.fileInfo.FileName
|
|
||||||
// const imageIds = []
|
|
||||||
// for (let i = 0; i < this.relatedStudyInfo.fileList.length; i++) {
|
|
||||||
// const path = this.relatedStudyInfo.fileList[i].Path
|
|
||||||
// imageIds.push(`web:${this.OSSclientConfig.basePath}${path}`)
|
|
||||||
// }
|
|
||||||
// v.imageIds === imageIds
|
|
||||||
// } else if (i === 1) {
|
|
||||||
// v.taskInfo = obj.taskInfo
|
|
||||||
// v.currentImageIdIndex === obj.currentImageIdIndex
|
|
||||||
// v.currentFileName === obj.currentFileName
|
|
||||||
// v.imageIds === obj.imageIds
|
|
||||||
// } else {
|
|
||||||
// v.taskInfo = ''
|
|
||||||
// v.currentImageIdIndex === 0
|
|
||||||
// v.currentFileName === ''
|
|
||||||
// v.imageIds === []
|
|
||||||
// }
|
|
||||||
// return v
|
|
||||||
// })
|
|
||||||
// this.activeCanvasIndex = 1
|
|
||||||
// } else if (obj && this.rows === 1 && this.cols === 2 && this.taskInfo.IsReadingTaskViewInOrder !== 1) {
|
|
||||||
// this.viewportInfos = this.viewportInfos.map((v, i) => {
|
|
||||||
// if (i === 0 || i === 1) {
|
|
||||||
// v.taskInfo = obj.taskInfo
|
|
||||||
// v.currentImageIdIndex === obj.currentImageIdIndex
|
|
||||||
// v.currentFileName === obj.currentFileName
|
|
||||||
// v.imageIds === obj.imageIds
|
|
||||||
// } else {
|
|
||||||
// v.taskInfo = ''
|
|
||||||
// v.currentImageIdIndex === 0
|
|
||||||
// v.currentFileName === ''
|
|
||||||
// v.imageIds === []
|
|
||||||
// }
|
|
||||||
// return v
|
|
||||||
// })
|
|
||||||
// this.activeCanvasIndex = 1
|
|
||||||
// } else if (obj && (this.rows === 2 && this.cols === 2)) {
|
|
||||||
// this.viewportInfos = this.viewportInfos.map(v => {
|
|
||||||
// v.taskInfo = obj.taskInfo
|
|
||||||
// v.currentImageIdIndex === obj.currentImageIdIndex
|
|
||||||
// v.currentFileName === obj.currentFileName
|
|
||||||
// v.imageIds === obj.imageIds
|
|
||||||
// return v
|
|
||||||
// })
|
|
||||||
// this.activeCanvasIndex = 3
|
|
||||||
// }
|
|
||||||
// this.$nextTick(() => {
|
|
||||||
// this.viewportInfos.forEach(v => {
|
|
||||||
// if (v.imageIds.length > 0) {
|
|
||||||
// this.renderImage(v.imageIds, v.index, v.currentImageIdIndex)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
},
|
|
||||||
// 切换全屏
|
|
||||||
toggleFullScreen(index) {
|
|
||||||
this.fullScreenIndex = this.fullScreenIndex === index ? null : index
|
|
||||||
},
|
|
||||||
// 切换任务
|
|
||||||
toggleTask(evt, visitTaskNum, i) {
|
|
||||||
const num = visitTaskNum + i
|
|
||||||
if (num >= 0 && num <= this.taskInfo.VisitNum) {
|
|
||||||
this.$emit('toggleTaskByViewer', num)
|
|
||||||
}
|
|
||||||
|
|
||||||
evt.stopImmediatePropagation()
|
|
||||||
evt.stopPropagation()
|
|
||||||
evt.preventDefault()
|
|
||||||
},
|
|
||||||
preventDefault(e) {
|
|
||||||
e.stopImmediatePropagation()
|
|
||||||
e.stopPropagation()
|
|
||||||
e.preventDefault()
|
|
||||||
},
|
|
||||||
// 查看临床数据
|
|
||||||
viewCD(e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.none-dicom-viewer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width:100%;
|
|
||||||
height: 100%;
|
|
||||||
user-select: none;
|
|
||||||
.tools-wrapper {
|
|
||||||
height: 60px;
|
|
||||||
border-bottom: 1px solid #727272;
|
|
||||||
}
|
|
||||||
.viewports-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
.grid-container {
|
|
||||||
display: grid;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-cell {
|
|
||||||
border: 1px dashed #ccc;;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.cell_active {
|
|
||||||
border-color: #fafa00!important;
|
|
||||||
}
|
|
||||||
.full-screen {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: white;
|
|
||||||
z-index: 1000;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
.left-top-text {
|
|
||||||
position: absolute;
|
|
||||||
left: 5px;
|
|
||||||
top: 5px;
|
|
||||||
color: #ddd;
|
|
||||||
z-index: 1;
|
|
||||||
font-size: 12px;
|
|
||||||
.cd-info {
|
|
||||||
color: #ddd;
|
|
||||||
font-size: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.subject-info {
|
|
||||||
color:#f44336;
|
|
||||||
padding: 5px 0px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.top-center-tool {
|
|
||||||
position: absolute;
|
|
||||||
left:50%;
|
|
||||||
top: 5px;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 1;
|
|
||||||
.toggle-visit-container {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.arrw_icon{
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background-color: #3f3f3f;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 20px;
|
|
||||||
border-radius: 10%;
|
|
||||||
}
|
|
||||||
.arrow_text{
|
|
||||||
height: 20px;
|
|
||||||
line-height: 20px;
|
|
||||||
background-color: #00000057;
|
|
||||||
color: #fff;
|
|
||||||
padding:0 10px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,299 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-loading="loading" class="read-page-container">
|
|
||||||
<!-- 检查列表 -->
|
|
||||||
<div class="left-panel">
|
|
||||||
<div class="task-container">
|
|
||||||
<div class="task-info">
|
|
||||||
<div
|
|
||||||
v-for="s in visitTaskList"
|
|
||||||
:key="s.VisitTaskId"
|
|
||||||
class="task-item"
|
|
||||||
:class="{'task-item-active': activeTaskVisitId==s.VisitTaskId}"
|
|
||||||
|
|
||||||
@click.prevent="toggleTask(s)"
|
|
||||||
>{{ s.TaskBlindName }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="study-info">
|
|
||||||
<div
|
|
||||||
v-for="s in visitTaskList"
|
|
||||||
v-show="activeTaskVisitId === 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"
|
|
||||||
@selectFile="selectFile"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 图像 -->
|
|
||||||
<div class="middle-panel">
|
|
||||||
<image-viewer
|
|
||||||
ref="imageViewer"
|
|
||||||
:related-study-info="relatedStudyInfo"
|
|
||||||
@toggleTaskByViewer="toggleTaskByViewer"
|
|
||||||
@toggleTask="toggleTask"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- 表单 -->
|
|
||||||
<div class="right-panel">
|
|
||||||
<div
|
|
||||||
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
|
|
||||||
class="text-info"
|
|
||||||
>
|
|
||||||
<h3>
|
|
||||||
<span
|
|
||||||
v-if="currentVisitInfo && currentVisitInfo.SubjectCode"
|
|
||||||
>
|
|
||||||
{{ currentVisitInfo.SubjectCode }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="currentVisitInfo && currentVisitInfo.TaskBlindName"
|
|
||||||
style="margin-left:5px;"
|
|
||||||
>
|
|
||||||
{{ currentVisitInfo.TaskBlindName }}
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<ecrf-list
|
|
||||||
v-if="currentVisitInfo"
|
|
||||||
:visit-task-info="currentVisitInfo"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { getRelatedVisitTask, getReadingImageFile } from '@/api/trials'
|
|
||||||
import StudyList from './StudyList'
|
|
||||||
import ImageViewer from './ImageViewer'
|
|
||||||
import EcrfList from './EcrfList'
|
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
export default {
|
|
||||||
name: 'ReadPage',
|
|
||||||
components: {
|
|
||||||
StudyList,
|
|
||||||
ImageViewer,
|
|
||||||
EcrfList
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
// 当前任务信息
|
|
||||||
taskInfo: null,
|
|
||||||
// 当前激活的任务Id
|
|
||||||
activeTaskVisitId: '',
|
|
||||||
// 表单数据
|
|
||||||
formData: {
|
|
||||||
name: '',
|
|
||||||
description: ''
|
|
||||||
},
|
|
||||||
// 当前任务关联的任务信息
|
|
||||||
visitTaskList: [],
|
|
||||||
selectArr: [],
|
|
||||||
fileType: 'image',
|
|
||||||
currentStudyInfo: null,
|
|
||||||
currentVisitInfo: null,
|
|
||||||
relatedStudyInfo: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapGetters(['lastViewportTaskId'])
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
lastViewportTaskId: {
|
|
||||||
immediate: true,
|
|
||||||
handler(id) {
|
|
||||||
if (!id) return
|
|
||||||
const idx = this.visitTaskList.findIndex(i => i.VisitTaskId === id)
|
|
||||||
if (idx === -1) return
|
|
||||||
this.currentVisitInfo = this.visitTaskList[idx]
|
|
||||||
this.activeTaskVisitId = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
|
|
||||||
this.getRelatedTask()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 获取关联任务信息
|
|
||||||
async getRelatedTask() {
|
|
||||||
this.loading = true
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
visitTaskId: this.taskInfo.VisitTaskId
|
|
||||||
}
|
|
||||||
const res = await getRelatedVisitTask(params)
|
|
||||||
this.visitTaskList = res.Result.map((item) => ({
|
|
||||||
...item,
|
|
||||||
StudyList: []
|
|
||||||
}))
|
|
||||||
const idx = res.Result.findIndex(i => i.IsCurrentTask)
|
|
||||||
if (idx > -1) {
|
|
||||||
await this.setActiveTaskVisitId(res.Result[idx].VisitTaskId)
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs[res.Result[idx].VisitTaskId][0].setInitActiveFile()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.taskInfo.IsReadingTaskViewInOrder === 1 && res.Result.length > 1) {
|
|
||||||
const i = this.visitTaskList.findIndex(i => i.IsBaseLineTask)
|
|
||||||
if (i > -1) {
|
|
||||||
await this.getReadingImageFile(res.Result[i].VisitTaskId, i)
|
|
||||||
const studyList = this.visitTaskList[i].StudyList
|
|
||||||
if (studyList.length > 0) {
|
|
||||||
const fileInfo = studyList[0].NoneDicomStudyFileList[0]
|
|
||||||
this.relatedStudyInfo = { fileInfo, visitTaskInfo: this.visitTaskList[i], fileList: studyList[0].NoneDicomStudyFileList, fileIndex: 0, studyId: studyList[0].Id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.loading = false
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 获取初始化渲染检查信息
|
|
||||||
getInitStudyList() {
|
|
||||||
// 有序阅片且非基线
|
|
||||||
},
|
|
||||||
// 获取任务关联的文件信息
|
|
||||||
getReadingImageFile(visitTaskId, visitTaskIdx) {
|
|
||||||
return new Promise(async(resolve, reject) => {
|
|
||||||
this.loading = true
|
|
||||||
try {
|
|
||||||
const params = {
|
|
||||||
subjectId: this.taskInfo.SubjectId,
|
|
||||||
trialId: this.$route.query.trialId,
|
|
||||||
visistTaskId: visitTaskId
|
|
||||||
}
|
|
||||||
const res = await getReadingImageFile(params)
|
|
||||||
this.$set(this.visitTaskList[visitTaskIdx], 'StudyList', res.Result)
|
|
||||||
this.loading = false
|
|
||||||
resolve()
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
this.loading = false
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 切换任务
|
|
||||||
toggleTask(taskInfo) {
|
|
||||||
this.setActiveTaskVisitId(taskInfo.VisitTaskId)
|
|
||||||
},
|
|
||||||
// 设置激活的访视
|
|
||||||
async setActiveTaskVisitId(id, isInitActiveFile = false) {
|
|
||||||
if (!id) return
|
|
||||||
if (!this.selectArr.includes(id)) {
|
|
||||||
this.selectArr.push(id)
|
|
||||||
}
|
|
||||||
const idx = this.visitTaskList.findIndex(i => i.VisitTaskId === id)
|
|
||||||
if (idx === -1) return
|
|
||||||
if (this.visitTaskList[idx].StudyList.length === 0) {
|
|
||||||
await this.getReadingImageFile(id, idx)
|
|
||||||
}
|
|
||||||
this.activeTaskVisitId = id
|
|
||||||
if (isInitActiveFile) {
|
|
||||||
this.$refs[id][0].setInitActiveFile()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 视图触发任务切换
|
|
||||||
async toggleTaskByViewer(visitTaskNum) {
|
|
||||||
const i = this.visitTaskList.findIndex(v => v.VisitTaskNum === visitTaskNum)
|
|
||||||
if (i === -1) return
|
|
||||||
const visistTaskId = this.visitTaskList[i].VisitTaskId
|
|
||||||
this.setActiveTaskVisitId(visistTaskId, true)
|
|
||||||
},
|
|
||||||
selectFile(obj) {
|
|
||||||
this.$refs['imageViewer'].setActiveCanvasImages(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.read-page-container {
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #d0d0d0;
|
|
||||||
}
|
|
||||||
.left-panel {
|
|
||||||
display: flex;
|
|
||||||
width: 200px;
|
|
||||||
border: 1px solid #727272;
|
|
||||||
color: #fff;
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 3px;
|
|
||||||
height: 3px;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #d0d0d0;
|
|
||||||
}
|
|
||||||
.task-container {
|
|
||||||
position: relative;
|
|
||||||
width: 25px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.task-info {
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
right: 20px;
|
|
||||||
transform-origin: right top;
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
display: flex;
|
|
||||||
.task-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 0px 4px;
|
|
||||||
border: 1px solid #999999;
|
|
||||||
border-bottom:none ;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #4e4e4e;
|
|
||||||
color: #d5d5d5;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.task-item-active {
|
|
||||||
background-color: #607d8b;
|
|
||||||
border: 1px solid #607d8b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.study-info {
|
|
||||||
width: 170px;
|
|
||||||
border-left: 1px solid #727272;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.middle-panel {
|
|
||||||
flex: 1;
|
|
||||||
border: 1px solid #727272;
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
.right-panel {
|
|
||||||
width: 400px;
|
|
||||||
border: 1px solid #727272;
|
|
||||||
padding: 0 10px;
|
|
||||||
.text-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,3 +0,0 @@
|
||||||
<template>
|
|
||||||
<div>报告页</div>
|
|
||||||
</template>
|
|
|
@ -1,184 +0,0 @@
|
||||||
<template>
|
|
||||||
<div v-loading="loading" class="study-wrapper">
|
|
||||||
<div class="study-info">
|
|
||||||
<div
|
|
||||||
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
|
|
||||||
:title="taskInfo.SubjectCode"
|
|
||||||
>
|
|
||||||
{{ taskInfo.SubjectCode }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="taskInfo && taskInfo.IsReadingShowSubjectInfo"
|
|
||||||
:title="visitTaskInfo.TaskBlindName"
|
|
||||||
>
|
|
||||||
{{ visitTaskInfo.TaskBlindName }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ps">
|
|
||||||
<el-collapse v-model="activeNames">
|
|
||||||
<el-collapse-item v-for="(study, index) in studyList" :key="`${study.Id}`" :name="`${study.Id}`">
|
|
||||||
<template slot="title">
|
|
||||||
<div
|
|
||||||
class="dicom-desc"
|
|
||||||
>
|
|
||||||
<div>{{ study.CodeView }}</div>
|
|
||||||
<div>
|
|
||||||
<span :title="study.BodyPart">{{ study.BodyPart }}</span>
|
|
||||||
<span style="margin-left: 5px;" :title="study.Modality">{{ study.Modality }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div class="series">
|
|
||||||
<div
|
|
||||||
v-for="(k, i) in study.NoneDicomStudyFileList"
|
|
||||||
:key="i"
|
|
||||||
style="position:relative;margin-top:1px;"
|
|
||||||
series-type="current"
|
|
||||||
@click="selectFile(study, index, i)"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:class="{'series-active': index === activeStudyIndex && i === activeFileIndex}"
|
|
||||||
class="series-wrapper"
|
|
||||||
:title="k.FileName"
|
|
||||||
>
|
|
||||||
{{ k.FileName }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-collapse-item>
|
|
||||||
</el-collapse>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'StudyList',
|
|
||||||
props: {
|
|
||||||
visitTaskInfo: {
|
|
||||||
type: Object,
|
|
||||||
default() {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
activeNames: [],
|
|
||||||
activeStudyIndex: -1,
|
|
||||||
activeFileIndex: -1,
|
|
||||||
taskInfo: null,
|
|
||||||
studyList: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
|
|
||||||
this.studyList = this.visitTaskInfo.StudyList
|
|
||||||
if (this.studyList.length === 0) return
|
|
||||||
this.activeNames.push(this.studyList[0].Id)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 设置初始化激活文件
|
|
||||||
setInitActiveFile() {
|
|
||||||
if (this.studyList.length === 0) return
|
|
||||||
this.activeNames.push(this.studyList[0].Id)
|
|
||||||
this.selectFile(this.studyList[0].NoneDicomStudyFileList, 0, 0)
|
|
||||||
},
|
|
||||||
// 切换文件
|
|
||||||
selectFile(study, studyIndex, fileIndex) {
|
|
||||||
this.activeStudyIndex = studyIndex
|
|
||||||
this.activeFileIndex = fileIndex
|
|
||||||
let fileList = study.NoneDicomStudyFileList
|
|
||||||
this.$emit('selectFile', { fileInfo: fileList[fileIndex], fileList, visitTaskInfo: this.visitTaskInfo, fileIndex: fileIndex, studyId: study.Id})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.study-wrapper{
|
|
||||||
width:100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: hidden;
|
|
||||||
overflow-x: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
.study-info {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #ddd;
|
|
||||||
padding: 5px 0px;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #4c4c4c;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
.dicom-desc{
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: left;
|
|
||||||
color: #d0d0d0;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ps {
|
|
||||||
flex: 1;
|
|
||||||
overflow-anchor: none;
|
|
||||||
touch-action: auto;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
.series-active {
|
|
||||||
background-color: #607d8b!important;
|
|
||||||
border: 1px solid #607d8b!important;
|
|
||||||
}
|
|
||||||
::v-deep.el-progress__text{
|
|
||||||
color: #ccc;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.series{
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
.series-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #3a3a3a;
|
|
||||||
color: #ddd;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
.el-progress__text{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.el-progress-bar{
|
|
||||||
padding-right:0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
::v-deep.el-collapse{
|
|
||||||
border: none;
|
|
||||||
.el-collapse-item{
|
|
||||||
background-color: #000!important;
|
|
||||||
color: #ddd;
|
|
||||||
|
|
||||||
}
|
|
||||||
.el-collapse-item__content{
|
|
||||||
padding-bottom:0px;
|
|
||||||
background-color: #000!important;
|
|
||||||
}
|
|
||||||
.el-collapse-item__header{
|
|
||||||
background-color: #000!important;
|
|
||||||
color: #ddd;
|
|
||||||
border-bottom-color:#5a5a5a;
|
|
||||||
padding-left: 5px;
|
|
||||||
height: 60px;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
::v-deep .el-progress-bar__inner{
|
|
||||||
transition: width 0s ease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,79 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="visit-review-container">
|
|
||||||
<el-tabs
|
|
||||||
v-model="activeName"
|
|
||||||
>
|
|
||||||
<!-- 阅片 -->
|
|
||||||
<el-tab-pane
|
|
||||||
v-if="taskInfo"
|
|
||||||
:label="$t('trials:reading:tabTitle:review')"
|
|
||||||
name="read"
|
|
||||||
>
|
|
||||||
<read-page />
|
|
||||||
</el-tab-pane>
|
|
||||||
<!-- 报告 -->
|
|
||||||
<el-tab-pane
|
|
||||||
v-if="taskInfo && !taskInfo.IseCRFShowInDicomReading"
|
|
||||||
:label="$t('trials:reading:tabTitle:report')"
|
|
||||||
name="report"
|
|
||||||
>
|
|
||||||
<report-page />
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import ReadPage from './components/ReadPage'
|
|
||||||
import ReportPage from './components/ReportPage'
|
|
||||||
export default {
|
|
||||||
name: 'VisitReview',
|
|
||||||
components: {
|
|
||||||
ReadPage,
|
|
||||||
ReportPage
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
activeName: 'read',
|
|
||||||
taskInfo: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.taskInfo = JSON.parse(localStorage.getItem('taskInfo'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.visit-review-container {
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: #000;
|
|
||||||
padding: 5px;
|
|
||||||
::v-deep .el-tabs {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
.el-tabs__item {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.el-tabs__item.is-active {
|
|
||||||
color: #428bca;
|
|
||||||
}
|
|
||||||
.el-tabs__item:hover {
|
|
||||||
color: #428bca;
|
|
||||||
}
|
|
||||||
.el-tabs__header {
|
|
||||||
height: 50px;
|
|
||||||
margin:0px;
|
|
||||||
}
|
|
||||||
.el-tabs__content {
|
|
||||||
flex: 1;
|
|
||||||
margin:0px;
|
|
||||||
}
|
|
||||||
.el-tab-pane {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,68 +0,0 @@
|
||||||
// Add hardcoded meta data provider for color images
|
|
||||||
export default function hardcodedMetaDataProvider(type, imageId, imageIds) {
|
|
||||||
const colonIndex = imageId.indexOf(':')
|
|
||||||
const scheme = imageId.substring(0, colonIndex)
|
|
||||||
if (scheme !== 'web') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'imagePixelModule') {
|
|
||||||
const imagePixelModule = {
|
|
||||||
pixelRepresentation: 0,
|
|
||||||
bitsAllocated: 24,
|
|
||||||
bitsStored: 24,
|
|
||||||
highBit: 24,
|
|
||||||
photometricInterpretation: 'RGB',
|
|
||||||
samplesPerPixel: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
return imagePixelModule
|
|
||||||
} else if (type === 'generalSeriesModule') {
|
|
||||||
const generalSeriesModule = {
|
|
||||||
modality: 'SC',
|
|
||||||
seriesNumber: 1,
|
|
||||||
seriesDescription: 'Color',
|
|
||||||
seriesDate: '20190201',
|
|
||||||
seriesTime: '120000',
|
|
||||||
seriesInstanceUID: '1.2.276.0.7230010.3.1.4.83233.20190201120000.1'
|
|
||||||
}
|
|
||||||
|
|
||||||
return generalSeriesModule
|
|
||||||
} else if (type === 'imagePlaneModule') {
|
|
||||||
const index = imageIds.indexOf(imageId)
|
|
||||||
// console.warn(index);
|
|
||||||
const imagePlaneModule = {
|
|
||||||
imageOrientationPatient: [1, 0, 0, 0, 1, 0],
|
|
||||||
imagePositionPatient: [0, 0, index * 5],
|
|
||||||
pixelSpacing: [1, 1],
|
|
||||||
columnPixelSpacing: 1,
|
|
||||||
rowPixelSpacing: 1,
|
|
||||||
frameOfReferenceUID: 'FORUID',
|
|
||||||
columns: 2048,
|
|
||||||
rows: 1216,
|
|
||||||
rowCosines: [1, 0, 0],
|
|
||||||
columnCosines: [0, 1, 0],
|
|
||||||
// setting useDefaultValues to true signals the calibration values above cannot be trusted
|
|
||||||
// and units should be displayed in pixels
|
|
||||||
usingDefaultValues: true
|
|
||||||
}
|
|
||||||
|
|
||||||
return imagePlaneModule
|
|
||||||
} else if (type === 'voiLutModule') {
|
|
||||||
return {
|
|
||||||
// According to the DICOM standard, the width is the number of samples
|
|
||||||
// in the input, so 256 samples.
|
|
||||||
windowWidth: [256],
|
|
||||||
// The center is offset by 0.5 to allow for an integer value for even
|
|
||||||
// sample counts
|
|
||||||
windowCenter: [128]
|
|
||||||
}
|
|
||||||
} else if (type === 'modalityLutModule') {
|
|
||||||
return {
|
|
||||||
rescaleSlope: 1,
|
|
||||||
rescaleIntercept: 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,244 +0,0 @@
|
||||||
import * as cornerstone from '@cornerstonejs/core'
|
|
||||||
const canvas = document.createElement('canvas')
|
|
||||||
let lastImageIdDrawn
|
|
||||||
|
|
||||||
// Todo: this loader should exist in a separate package in the same monorepo
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a cornerstone Image object for the specified Image and imageId
|
|
||||||
*
|
|
||||||
* @param image - An Image
|
|
||||||
* @param imageId - the imageId for this image
|
|
||||||
* @returns Cornerstone Image Object
|
|
||||||
*/
|
|
||||||
function createImage(image, imageId) {
|
|
||||||
// extract the attributes we need
|
|
||||||
const rows = image.naturalHeight
|
|
||||||
const columns = image.naturalWidth
|
|
||||||
|
|
||||||
function getPixelData(targetBuffer) {
|
|
||||||
const imageData = getImageData()
|
|
||||||
|
|
||||||
let targetArray
|
|
||||||
|
|
||||||
// Check if targetBuffer is provided for volume viewports
|
|
||||||
if (targetBuffer) {
|
|
||||||
targetArray = new Uint8Array(
|
|
||||||
targetBuffer.arrayBuffer,
|
|
||||||
targetBuffer.offset,
|
|
||||||
targetBuffer.length
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
targetArray = new Uint8Array(imageData.width * imageData.height * 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// modify original image data and remove alpha channel (RGBA to RGB)
|
|
||||||
convertImageDataToRGB(imageData, targetArray)
|
|
||||||
|
|
||||||
return targetArray
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertImageDataToRGB(imageData, targetArray) {
|
|
||||||
for (let i = 0, j = 0; i < imageData.data.length; i += 4, j += 3) {
|
|
||||||
targetArray[j] = imageData.data[i]
|
|
||||||
targetArray[j + 1] = imageData.data[i + 1]
|
|
||||||
targetArray[j + 2] = imageData.data[i + 2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImageData() {
|
|
||||||
let context
|
|
||||||
|
|
||||||
if (lastImageIdDrawn === imageId) {
|
|
||||||
context = canvas.getContext('2d')
|
|
||||||
} else {
|
|
||||||
canvas.height = image.naturalHeight
|
|
||||||
canvas.width = image.naturalWidth
|
|
||||||
context = canvas.getContext('2d')
|
|
||||||
context.drawImage(image, 0, 0)
|
|
||||||
lastImageIdDrawn = imageId
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.getImageData(0, 0, image.naturalWidth, image.naturalHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCanvas() {
|
|
||||||
if (lastImageIdDrawn === imageId) {
|
|
||||||
return canvas
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.height = image.naturalHeight
|
|
||||||
canvas.width = image.naturalWidth
|
|
||||||
const context = canvas.getContext('2d')
|
|
||||||
|
|
||||||
context.drawImage(image, 0, 0)
|
|
||||||
lastImageIdDrawn = imageId
|
|
||||||
|
|
||||||
return canvas
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the various attributes we need
|
|
||||||
return {
|
|
||||||
imageId,
|
|
||||||
minPixelValue: 0,
|
|
||||||
maxPixelValue: 255,
|
|
||||||
slope: 1,
|
|
||||||
intercept: 0,
|
|
||||||
windowCenter: 128,
|
|
||||||
windowWidth: 255,
|
|
||||||
getPixelData,
|
|
||||||
getCanvas,
|
|
||||||
getImage: () => image,
|
|
||||||
rows,
|
|
||||||
columns,
|
|
||||||
height: rows,
|
|
||||||
width: columns,
|
|
||||||
color: true,
|
|
||||||
// we converted the canvas rgba already to rgb above
|
|
||||||
rgba: false,
|
|
||||||
columnPixelSpacing: 1, // for web it's always 1
|
|
||||||
rowPixelSpacing: 1, // for web it's always 1
|
|
||||||
invert: false,
|
|
||||||
sizeInBytes: rows * columns * 3,
|
|
||||||
numberOfComponents: 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayBufferToImage(arrayBuffer) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const image = new Image()
|
|
||||||
const arrayBufferView = new Uint8Array(arrayBuffer)
|
|
||||||
const blob = new Blob([arrayBufferView])
|
|
||||||
const urlCreator = window.URL || window.webkitURL
|
|
||||||
const imageUrl = urlCreator.createObjectURL(blob)
|
|
||||||
|
|
||||||
image.src = imageUrl
|
|
||||||
image.onload = () => {
|
|
||||||
resolve(image)
|
|
||||||
urlCreator.revokeObjectURL(imageUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
image.onerror = (error) => {
|
|
||||||
urlCreator.revokeObjectURL(imageUrl)
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// This is a cornerstone image loader for web images such as PNG and JPEG
|
|
||||||
//
|
|
||||||
const options = {
|
|
||||||
// callback allowing customization of the xhr (e.g. adding custom auth headers, cors, etc)
|
|
||||||
beforeSend: (xhr) => {
|
|
||||||
// xhr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loads an image given a url to an image
|
|
||||||
function loadImage(uri, imageId) {
|
|
||||||
const xhr = new XMLHttpRequest()
|
|
||||||
|
|
||||||
xhr.open('GET', uri, true)
|
|
||||||
xhr.responseType = 'arraybuffer'
|
|
||||||
options.beforeSend(xhr)
|
|
||||||
|
|
||||||
xhr.onprogress = function(oProgress) {
|
|
||||||
if (oProgress.lengthComputable) {
|
|
||||||
// evt.loaded the bytes browser receive
|
|
||||||
// evt.total the total bytes set by the header
|
|
||||||
const loaded = oProgress.loaded
|
|
||||||
const total = oProgress.total
|
|
||||||
const percentComplete = Math.round((loaded / total) * 100)
|
|
||||||
|
|
||||||
const eventDetail = {
|
|
||||||
imageId,
|
|
||||||
loaded,
|
|
||||||
total,
|
|
||||||
percentComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
cornerstone.triggerEvent(
|
|
||||||
cornerstone.eventTarget,
|
|
||||||
'cornerstoneimageloadprogress',
|
|
||||||
eventDetail
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
|
||||||
xhr.onload = function() {
|
|
||||||
const imagePromise = arrayBufferToImage(this.response)
|
|
||||||
|
|
||||||
imagePromise
|
|
||||||
.then((image) => {
|
|
||||||
const imageObject = createImage(image, imageId)
|
|
||||||
|
|
||||||
resolve(imageObject)
|
|
||||||
}, reject)
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
xhr.onerror = function(error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
xhr.send()
|
|
||||||
})
|
|
||||||
|
|
||||||
const cancelFn = () => {
|
|
||||||
xhr.abort()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
promise,
|
|
||||||
cancelFn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerWebImageLoader(imageLoader) {
|
|
||||||
imageLoader.registerImageLoader('web', _loadImageIntoBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Small stripped down loader from cornerstoneDICOMImageLoader
|
|
||||||
* Which doesn't create cornerstone images that we don't need
|
|
||||||
*/
|
|
||||||
function _loadImageIntoBuffer(imageId, options) {
|
|
||||||
const uri = imageId.replace('web:', '')
|
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
|
||||||
// get the pixel data from the server
|
|
||||||
loadImage(uri, imageId)
|
|
||||||
.promise.then(
|
|
||||||
(image) => {
|
|
||||||
if (
|
|
||||||
!options?.targetBuffer?.length ||
|
|
||||||
!options?.targetBuffer?.offset
|
|
||||||
) {
|
|
||||||
resolve(image)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
image.getPixelData(options.targetBuffer)
|
|
||||||
|
|
||||||
resolve(true)
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
promise,
|
|
||||||
cancelFn: undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default registerWebImageLoader
|
|
Loading…
Reference in New Issue