irc_web/src/views/trials/trials-panel/visit/crc-upload/components/uploadDicomFiles2.vue

1925 lines
75 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="upload-dicom-files-wrapper">
<!-- 已上传的dicom影像记录 -->
<!-- <el-divider content-position="left">{{
$t('trials:uploadedDicoms:title:dicomUploaded')
}}</el-divider> -->
<div style="margin: 10px 0">
{{ $t('trials:uploadedDicoms:title:dicomUploaded') }}
</div>
<div class="functions" style="text-align: right">
<!-- //批量删除已上传的影像 -->
<el-button :disabled="deleteArr.length === 0" type="primary" size="small" icon="el-icon-delete"
@click="handleBatchDelete">
{{ $t('trials:uploadedDicoms:action:delete') }}
</el-button>
<!-- 预览 -->
<el-button type="primary" icon="el-icon-view" size="small" :disabled="studyList.length === 0"
@click="handlePreviewAllFiles">
{{ $t('trials:uploadedDicoms:action:preview') }}
</el-button>
</div>
<el-table v-loading="studyLoading" :data="studyList" style="width: 100%" :row-class-name="tableRowClassName"
max-height="250" @selection-change="handleUploadedSelectionChange"
:default-sort="{ prop: 'UploadedTime', order: 'ascending' }">
<el-table-column type="selection" width="55" />
<!-- 检查编号 -->
<el-table-column prop="StudyCode" :label="$t('trials:uploadedDicoms:table:studyId')" min-width="80"
show-overflow-tooltip sortable>
<template slot-scope="scope">
<el-tooltip placement="top" v-if="
(() => {
var r = false
if (scope.row.IsHaveUploadFailed) {
uploadQueues.forEach((v) => {
if (
v.dicomInfo.studyUid === scope.row.StudyInstanceUid &&
v.uploadState.record &&
v.uploadState.record.Failed.length
) {
r = true
}
})
}
return r
})()
">
<div slot="content">
{{ $t('trials:uploadDicomList:table:status4') }}
</div>
<span class="el-icon-warning" style="color: #cbb024; cursor: pointer"
v-if="scope.row.IsHaveUploadFailed"></span>
</el-tooltip>
<el-tooltip placement="top" v-if="!scope.row.IsCompleteClinicalData">
<div slot="content">
{{ $t('trials:crc-upload:confirm:message') }}
</div>
<span class="el-icon-warning" style="color: red; cursor: pointer"></span>
</el-tooltip>
{{ scope.row.StudyCode }}
</template>
</el-table-column>
<!-- 检查名称 -->
<el-table-column v-if="relationInfo.IsShowStudyName" prop="StudyName" :label="$t('trials:audit:table:StudyName')"
sortable />
<!-- 检查类型 -->
<el-table-column prop="ModalityForEdit" :label="$t('trials:audit:table:modality')" sortable />
<!-- 检查设备 -->
<el-table-column prop="Modalities" :label="$t('trials:audit:table:modality1')" sortable />
<!-- 检查部位 -->
<el-table-column prop="BodyPartForEdit" :label="$t('trials:uploadedDicoms:table:bodyPart')" min-width="100"
show-overflow-tooltip sortable>
<template slot-scope="scope">
{{ getBodyPart(scope.row.BodyPartForEdit) }}
</template>
</el-table-column>
<!-- 序列数量 -->
<el-table-column prop="SeriesCount" :label="$t('trials:uploadedDicoms:table:seriesCount')" min-width="100"
show-overflow-tooltip sortable />
<!-- 图像数量 -->
<el-table-column prop="InstanceCount" :label="$t('trials:uploadedDicoms:table:instanceCount')" min-width="100"
show-overflow-tooltip sortable />
<!-- 检查日期 -->
<el-table-column prop="StudyTime" :label="$t('trials:uploadedDicoms:table:studyDate')" min-width="120"
show-overflow-tooltip sortable>
<template slot-scope="scope">
{{
scope.row.StudyTime
? moment(scope.row.StudyTime).format('YYYY-MM-DD')
: ''
}}
</template>
</el-table-column>
<!-- 更新时间 -->
<el-table-column prop="UpdateTime" :label="$t('trials:uploadedDicoms:table:UpdateTime')" min-width="120"
show-overflow-tooltip sortable />
<!-- 上传时间 -->
<el-table-column prop="UploadedTime" :label="$t('trials:uploadedDicoms:table:uploadedTime')" min-width="120"
show-overflow-tooltip sortable />
<el-table-column :label="$t('common:action:action')" min-width="260" fixed="right">
<template slot-scope="scope">
<!-- 预览 -->
<el-button icon="el-icon-view" :disabled="scope.row.SeriesCount === 0"
:title="$t('trials:uploadedDicoms:action:preview')" circle @click="handleViewStudy(scope.row)" />
<!-- 上传临床数据 -->
<el-button icon="el-icon-upload2" v-if="
['PT、CT', 'CT、PT', 'PET-CT'].includes(scope.row.Modalities) &&
relationInfo.IsHaveStudyClinicalData
" :title="$t('trials:workbench:title:UploadClinicalData')" circle @click="handleUploadPetData(scope.row)" />
<!-- 编辑 -->
<el-button icon="el-icon-edit-outline" v-hasPermi="['trials:trials-panel:visit:crc-upload:edit']"
:title="$t('common:button:edit')" circle @click="handleEditStudy(scope.row)" />
<!-- 删除 :disabled="scope.row.IsDeleted"-->
<el-button icon="el-icon-delete" :title="$t('trials:uploadedDicoms:action:delete')" circle
@click="handleDeleteStudy(scope.row)" />
<!-- <el-button-->
<!-- icon="el-icon-toilet-paper"-->
<!-- circle-->
<!-- :title="$t('trials:uploadDicomFiles:button:historical')"-->
<!-- @click="handleHistorical(scope.row)"-->
<!-- />-->
</template>
</el-table-column>
</el-table>
<!-- 多文件上传 -->
<!-- <el-divider content-position="left">{{
$t('trials:uploadedDicoms:title:dicomFilesOnly')
}}</el-divider> -->
<div style="margin: 30px 0 10px 0">
{{ $t('trials:uploadedDicoms:title:dicomFilesOnly') }}
</div>
<el-tabs v-model="uploadActiveName">
<!-- 本地上传 -->
<el-tab-pane :label="$t('trials:uploadedDicoms:tab:uploadFile')" name="file">
<form id="inputForm" ref="uploadForm" enctype="multipart/form-data">
<div class="form-group">
<div id="directoryInputWrapper" class="btn btn-link file-input">
<el-button type="primary" :disabled="btnLoading" :loading="btnLoading" size="small">{{
$t('trials:uploadedDicomsicom:button:selectFolder')
}}</el-button>
<input type="file" name="file" ref="pathClear" :disabled="btnLoading" webkitdirectory multiple title=""
@change="beginScanFiles($event)" />
</div>
</div>
</form>
<!-- 文件列表 -->
<el-table ref="dicomFilesTable" :data="uploadQueues" :row-key="(row) => row.studyIndex" class="dicomFiles-table"
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" :selectable="handleSelectable" />
<el-table-column type="index" width="40" />
<el-table-column min-width="200" show-overflow-tooltip>
<template slot="header">
<el-tooltip placement="top-start">
<div slot="content">
{{ $t('trials:uploadDicomList:table:studyDetail1') }}<br />
{{ $t('trials:uploadDicomList:table:studyDetail2') }}<br />
{{ $t('trials:uploadedDicoms:table:studyDate') }}
</div>
<span>{{ $t('trials:uploadDicomList:table:studyInfo') }}</span>
</el-tooltip>
</template>
<template slot-scope="scope">
<div style="line-height: 15px">
<div>
<div>
<span v-if="scope.row.dicomInfo.accNumber"><span style="font-weight: 500">Acc:</span>
{{ scope.row.dicomInfo.accNumber }}</span>
<span v-else style="color: #f44336">N/A</span>
</div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.dicomInfo.modality.length > 0">
{{ scope.row.dicomInfo.modality.join('、') }},</span>
<span v-else style="color: #f44336">N/A,</span>
</div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.seriesList.length">{{ scope.row.seriesList.length }} Series,</span>
<span v-else style="color: #f44336">N/A,</span>
</div>
<div style="display: inline-block">
<span v-if="scope.row.fileList.length">{{ scope.row.fileList.length }} Instances</span>
<span v-else style="color: #f44336">N/A</span>
</div>
</div>
<div>
<div style="display: inline-block; margin-right: 2px">
<span v-if="scope.row.dicomInfo.bodyPart">
{{ scope.row.dicomInfo.bodyPart }},
</span>
<span v-else style="color: #f44336">N/A, </span>
</div>
<div style="display: inline-block">
<span v-if="scope.row.dicomInfo.description">
{{ scope.row.dicomInfo.description }}</span>
<span v-else style="color: #f44336">N/A</span>
</div>
</div>
<div>
{{ scope.row.dicomInfo.studyTime }}
</div>
</div>
</template>
</el-table-column>
<el-table-column min-width="130" show-overflow-tooltip>
<template slot="header">
<el-tooltip placement="top">
<div slot="content">
{{ $t('trials:uploadDicomList:table:pId') }}<br />
{{ $t('trials:uploadDicomList:table:patientName') }}<br />
{{ $t('trials:uploadDicomList:table:pInfo') }}
</div>
<span>{{
$t('trials:uploadDicomList:table:patientInfo')
}}</span>
</el-tooltip>
</template>
<template slot-scope="scope">
<div style="line-height: 15px">
<div>
<span v-if="scope.row.dicomInfo.patientId"><span style="font-weight: 500">PID: </span>{{
scope.row.dicomInfo.patientId }}</span>
<span v-else style="color: #f44336">N/A</span>
</div>
<div>
<span :class="[
scope.row.dicomInfo.patientName ? '' : 'colorOfRed',
]">
{{
scope.row.dicomInfo.patientName
? scope.row.dicomInfo.patientName
: 'N/A'
}}
</span>
</div>
<div>
<span :class="[
scope.row.dicomInfo.patientSex ? '' : 'colorOfRed',
]">
{{
scope.row.dicomInfo.patientSex
? scope.row.dicomInfo.patientSex
: 'N/A'
}},
</span>
<span :class="[
scope.row.dicomInfo.patientAge ? '' : 'colorOfRed',
]">
{{
scope.row.dicomInfo.patientAge
? scope.row.dicomInfo.patientAge
: 'N/A'
}},
</span>
<span :class="[
scope.row.dicomInfo.patientBirthDate ? '' : 'colorOfRed',
]">
{{
scope.row.dicomInfo.patientBirthDate
? scope.row.dicomInfo.patientBirthDate
: 'N/A'
}}
</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('trials:uploadDicomList:table:failedFileCount')" min-width="150"
show-overflow-tooltip>
<!--:percentage="
(scope.row.dicomInfo.failedFileCount * 100) /
scope.row.dicomInfo.fileCount
"
:show-text="false"--->
<template slot-scope="scope">
<el-progress color="#409eff" :percentage="(
(scope.row.dicomInfo.uploadFileSize * 100) /
scope.row.dicomInfo.fileSize
).toFixed(2) * 1
" />
<span>
{{ $t('trials:uploadDicomList:table:uploadNow')
}}{{ scope.row.dicomInfo.failedFileCount }}/{{
scope.row.dicomInfo.fileCount
}}
({{
(scope.row.dicomInfo.uploadFileSize / 1024 / 1024).toFixed(3)
}}MB/{{
(scope.row.dicomInfo.fileSize / 1024 / 1024).toFixed(3)
}}MB)
</span>
</template>
</el-table-column>
<el-table-column :label="$t('trials:uploadDicomList:table:status')" min-width="140" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="
!scope.row.dicomInfo.failedFileCount &&
!scope.row.dicomInfo.isInit
">
{{ $t('trials:uploadDicomList:table:status1') }}</span>
<span style="color: #409eff" v-else-if="
!scope.row.dicomInfo.failedFileCount &&
scope.row.dicomInfo.isInit &&
btnLoading
">{{ $t('trials:uploadDicomList:table:status2') }}</span>
<span style="color: #409eff" v-else-if="
scope.row.dicomInfo.failedFileCount <
scope.row.dicomInfo.fileCount &&
!scope.row.uploadState.record
">{{ $t('trials:uploadDicomList:table:status2') }}</span>
<span style="color: #2cc368" v-else-if="
scope.row.dicomInfo.failedFileCount ===
scope.row.dicomInfo.fileCount
">{{ $t('trials:uploadDicomList:table:status3') }}</span>
<span style="color: #f66" v-else-if="
scope.row.uploadState.record &&
scope.row.uploadState.record.fileCount === 0
">{{ $t('trials:uploadDicomList:table:status5') }}</span>
<span style="color: #f66" v-else>{{
$t('trials:uploadDicomList:table:Failed')
}}</span>
</template>
</el-table-column>
<el-table-column :label="$t('trials:uploadDicomList:table:record')" min-width="140" show-overflow-tooltip>
<template slot-scope="scope">
<el-tooltip placement="top" v-if="scope.row.uploadState.record">
<div slot="content">
<div style="max-height: 500px; overflow-y: auto">
{{ $t('trials:uploadDicomList:table:Existed') }}:
<div v-if="scope.row.uploadState.record.Existed.length">
<div v-for="item of scope.row.uploadState.record.Existed" :key="item"
style="font-size: 12px; color: #baa72a">
{{ item }}
</div>
</div>
<div v-else>&nbsp;</div>
{{ $t('trials:uploadDicomList:table:Uploaded') }}:
<div v-if="scope.row.uploadState.record.Uploaded.length">
<div v-for="item of scope.row.uploadState.record.Uploaded" :key="item"
style="font-size: 12px; color: #24b837">
{{ item }}
</div>
</div>
<div v-else>&nbsp;</div>
<br />
{{ $t('trials:uploadDicomList:table:Failed') }}:
<div v-if="scope.row.uploadState.record.Failed.length">
<div v-for="item of scope.row.uploadState.record.Failed" :key="item"
style="font-size: 12px; color: #f66">
{{ item }}
</div>
</div>
<div v-else>&nbsp;</div>
</div>
</div>
<el-button size="mini" style="cursor: pointer">
<span style="font-size: 12px; color: #baa72a">{{
scope.row.uploadState.record.Existed.length
}}</span>
/
<span style="font-size: 12px; color: #24b837">{{
scope.row.uploadState.record.Uploaded.length
}}</span>
/
<span style="font-size: 12px; color: #f66">{{
scope.row.uploadState.record.Failed.length
}}</span>
</el-button>
</el-tooltip>
</template>
</el-table-column>
<el-table-column v-show="isScan" :label="$t('common:action:action')" width="200">
<template slot-scope="scope">
<!-- 预览 -->
<!-- :disabled="scope.row.dicomInfo.failedFileCount < scope.row.dicomInfo.fileCount && scope.row.dicomInfo.failedFileCount !== 0"-->
<el-button icon="el-icon-view" circle :disabled="(scope.row.uploadState.stateCode !== '' &&
scope.row.dicomInfo.failedFileCount <
scope.row.dicomInfo.fileCount &&
!scope.row.uploadState.record) ||
btnLoading
" :title="$t('trials:uploadedDicoms:action:preview')" size="small"
@click="handlePreview(scope.row.dicomInfo.studyUid)" />
<!-- 移除 -->
<el-button icon="el-icon-delete" circle :title="$t('trials:uploadedDicoms:action:delete')" size="small"
:disabled="(scope.row.uploadState.stateCode !== '' &&
scope.row.dicomInfo.failedFileCount <
scope.row.dicomInfo.fileCount &&
!scope.row.uploadState.record) ||
btnLoading
" @click="handleDelete(scope.$index, scope.row)" />
</template>
</el-table-column>
</el-table>
<div style="text-align: right; padding: 10px 0px">
<!-- 关闭 -->
<!-- <el-button :disabled="btnLoading" size="small" type="primary" @click="cancel">
{{ $t('trials:uploadDicomList:button:close') }}
</el-button> -->
<span style="margin-right: 10px">{{
$store.state.trials.uploadTip
}}</span>
<!-- 上传 -->
<el-button size="small" type="primary" :disabled="selectArr.length == 0 || !isScan" :loading="btnLoading"
@click="beginUploadQueues">
{{ $t('trials:uploadDicomList:button:upload') }}
</el-button>
</div>
</el-tab-pane>
<!-- pacs上传 -->
<el-tab-pane :label="$t('trials:uploadNonDicoms:tab:uploadPacs')" name="pacs" :disabled="btnLoading"
v-if="relationInfo.IsPacsConnectConfiged">
<uploadDicomPacs v-if="uploadActiveName === 'pacs'" ref="dicomPacs" :subjectVisitId="subjectVisitId"
:relationInfo="relationInfo" :subjectId="subjectId" @getList="getParentList" @petDataTip="petDataTip" />
</el-tab-pane>
</el-tabs>
<!-- 预览影像模态框 -->
<el-dialog v-if="previewVisible" :fullscreen="true" :show-close="true" :visible.sync="previewVisible"
append-to-body>
<DicomPreview :uid="uid" :studyList="uploadQueues" />
</el-dialog>
<!--pet-ct临床数据上传-->
<el-dialog v-if="petVisible" :show-close="true" :visible.sync="petVisible" append-to-body>
<uploadPetClinicalData :subject-visit-id="data.Id" :data="data" :studyData="studyData" :allow-add-or-edit="true"
@getStudyInfo="getStudyInfo" />
</el-dialog>
<!-- 校验警告信息模态框 -->
<el-dialog v-if="warning_cfg.visible" :visible.sync="warning_cfg.visible" width="500px"
:close-on-click-modal="false" append-to-body title="Warning" custom-class="warning-dialog">
<div style="border: 1px solid #e0e0e0; padding: 10px">
<!-- Information from DICOM headers not consistent with that of this subject -->
<div style="color: red; font-size: 14px; margin-bottom: 10px">
{{ $t('trials:uploadDicomList:message:informationConsistent') }}:
</div>
<div v-for="(item, i) in warningArr" :key="item.index">
<div>{{ `(${i + 1}). ACC: ${item.accNumber}` }}</div>
<div v-for="(warning, index) in item.warnings" :key="index" style="margin: 10px 0px; font-size: 13px">
<ul>
<li>{{ warning }}</li>
</ul>
</div>
</div>
</div>
<div slot="footer" class="base-modal-footer">
<el-button size="small" type="primary" @click="handleCancelWarnVisible">
{{ $t('common:button:cancel') }}
</el-button>
<el-button size="small" type="primary" @click="handleContinueUpload">
{{ $t('trials:uploadDicomList:button:upload') }}
</el-button>
</div>
</el-dialog>
<el-dialog v-if="editStudyInfoVisible" :title="$t('trials:audit:action:edit')" :visible.sync="editStudyInfoVisible"
:close-on-click-modal="false" append-to-body custom-class="base-dialog-wrapper" width="600px">
<div style="
padding: 10px;
border: 1px solid #e0e0e0;
max-height: 650px;
overflow-y: auto;
">
<el-form ref="studyForm" :model="studyForm" label-width="100px">
<!-- 检查编号 -->
<el-form-item :label="$t('trials:audit:table:studyId')">
<el-input v-model="studyForm.StudyCode" disabled />
</el-form-item>
<!-- 检查名称 -->
<el-form-item v-if="relationInfo.IsShowStudyName" :label="$t('trials:audit:table:StudyName')" prop="StudyName"
:rules="[
{
required: true,
message: $t('common:ruleMessage:specify'),
trigger: 'blur',
},
]">
<el-radio-group v-model="studyForm.StudyName">
<template v-for="m in relationInfo.StudyNameList">
<el-radio v-if="m.IsChoose" :key="m.Name" :label="isEN ? m.EnName : m.Name"
style="margin-bottom: 15px" />
</template>
</el-radio-group>
</el-form-item>
<!-- 检查类型 -->
<el-form-item v-if="studyForm.IsDicomData" :label="$t('trials:audit:table:modality')">
<el-input v-model="studyForm.Modalities" disabled />
</el-form-item>
<!-- 检查类型 -->
<el-form-item v-else :label="$t('trials:audit:table:modality')" prop="Modalities" :rules="[
{
required: true,
message: $t('common:ruleMessage:specify'),
trigger: 'blur',
},
]">
<el-radio-group v-model="studyForm.Modality">
<el-radio v-for="m in trialModalitys" v-show="m !== ''" :key="m" :label="m" style="margin-bottom: 15px" />
</el-radio-group>
</el-form-item>
<!-- 检查部位 -->
<el-form-item :label="$t('trials:audit:table:bodyPart')" prop="BodyPartForEdit" :rules="[
{
required: true,
message: $t('common:ruleMessage:specify'),
trigger: 'blur',
},
]">
<el-checkbox-group v-model="studyForm.BodyPartForEdit">
<el-checkbox v-for="bodyPart in trialBodyPartTypes" :key="bodyPart" :label="bodyPart">{{
$fd('Bodypart', bodyPart, 'Code', BodyPart, 'Name')
}}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 序列数量 -->
<el-form-item :label="$t('trials:audit:table:seriesCount')">
<el-input v-model="studyForm.SeriesCount" disabled />
</el-form-item>
<!-- 图像数量 -->
<el-form-item v-if="studyForm.InstanceCount" :label="$t('trials:audit:table:instanceCount')">
<el-input v-model="studyForm.InstanceCount" disabled />
</el-form-item>
<!-- 检查日期 -->
<el-form-item :label="$t('trials:audit:table:studyDate')">
<el-date-picker v-model="studyForm.StudyTime" disabled type="date" value-format="yyyy-MM-dd"
format="yyyy-MM-dd" style="width: 100%" />
</el-form-item>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<el-button :disabled="btnLoading" size="small" type="primary" @click="editStudyInfoVisible = false">
{{ $t('common:button:cancel') }}
</el-button>
<el-button :loading="btnLoading" size="small" type="primary" @click="handleUpdateStudyInfo">
{{ $t('common:button:save') }}
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
dicomUploadInProgress,
batchVerifyStudyAllowUpload,
getSubjectVisitUploadedStudyList,
deleteStudyList,
updateModality,
preArchiveDicomStudy,
addOrUpdateArchiveStudy,
} from '@/api/trials'
import * as dicomParser from 'dicom-parser'
import * as cornerstone from 'cornerstone-core'
import * as cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'
var config = {
maxWebWorkers: 4,
startWebWorkersOnDemand: true,
taskConfiguration: {
decodeTask: {
initializeCodecsOnStartup: false,
},
},
}
cornerstoneWADOImageLoader.webWorkerManager.initialize(config)
cornerstoneWADOImageLoader.external.dicomParser = dicomParser
cornerstoneWADOImageLoader.external.cornerstone = cornerstone
import DicomPreview from '@/views/dicom-show/dicom-preview'
import uploadPetClinicalData from './uploadPetClinicalData.vue'
import dicomStore from '@/utils/dicom-store'
import { getToken } from '@/utils/auth'
import { dcmUpload } from '@/utils/dcmUpload/dcmUpload'
import { convertBytes } from '@/utils/dicom-character-set'
import moment from 'moment'
import store from '@/store'
import uploadDicomPacs from './uploadDiocmPacs.vue'
export default {
name: 'UploadDicomFiles',
components: { DicomPreview, uploadPetClinicalData, uploadDicomPacs },
props: {
data: {
type: Object,
default() {
return {}
},
},
subjectVisitId: {
type: String,
required: true,
},
subjectId: {
type: String,
required: true,
},
activeName: {
type: String,
required: true,
},
},
data() {
return {
uploadActiveName: 'file',
editStudyInfoVisible: false,
studyForm: {
StudyCode: '',
IsDicomData: true,
Modalities: '',
BodyPartForEdit: [],
SeriesCount: null,
StudyTime: '',
},
uploadQueues: dicomStore.studyList, // 上传队列
selectArr: [], // 已勾选待上传文件
scanState: '', // 当前上传队列上传状态
form: {
SiteName: '',
SubjectName: '',
VisitName: '',
SubjectId: '',
VisitNum: null,
},
btnLoading: false,
previewVisible: false,
warning_cfg: { visible: false },
warningArr: [],
isScan: false,
studyLoading: false,
studyList: [],
deleteArr: [],
trialId: '',
moment,
uploadCount: 0,
uploadedCount: 0,
wsList: [],
relationInfo: {},
trialBodyPartTypes: [],
trialModalitys: [],
myInterval: [],
studyErrorList: [],
confirmFlag: false,
// pet-ct临床数据上传
petVisible: false,
studyData: [],
BodyPart: {},
isClose: false,
}
},
computed: {
isEN() {
return this.$i18n.locale !== 'zh'
},
},
watch: {
btnLoading() {
store.dispatch('trials/setUnLock', this.btnLoading)
},
},
async mounted() {
this.trialId = this.$route.query.trialId
this.BodyPart.Bodypart = await this.$getBodyPart(this.$route.query.trialId)
if (Object.keys(this.data).length) {
this.form = { ...this.data }
}
this.getStudyInfo()
this.uploadQueues = []
this.OSSclient.close()
},
beforeDestroy() {
this.myInterval.forEach((v) => {
clearInterval(v)
})
this.myInterval = []
store.dispatch('trials/setUnLock', false)
this.isClose = true
this.OSSclient.close()
},
methods: {
getParentList() {
this.getStudyInfo()
this.$emit('getList')
},
handleHistorical(row) {
this.$router.push(
`/trials/trials-panel/trial-summary/upload-monitor?trialId=${this.trialId}&trialCode=${this.$route.query.trialCode}&${this.$route.query.researchProgramNo}&studyCode=${row.StudyCode}`
)
},
// 打开检查信息编辑框
handleEditStudy(row) {
this.editStudyInfoVisible = true
this.studyForm = { ...row }
var bodyPart = []
if (this.studyForm.BodyPartForEdit.indexOf('|') !== -1) {
bodyPart = this.studyForm.BodyPartForEdit.split('|')
} else if (this.studyForm.BodyPartForEdit !== '') {
bodyPart.push(this.studyForm.BodyPartForEdit)
}
this.$set(this.studyForm, 'Modality', row.ModalityForEdit)
this.studyForm.BodyPartForEdit = bodyPart
},
// 更新拍片部位/拍片类型信息
handleUpdateStudyInfo() {
this.$refs['studyForm'].validate((valid) => {
if (!valid) return
this.btnLoading = true
this.studyForm.BodyPart = this.studyForm.BodyPartForEdit.join('|')
// this.studyForm.Modality = this.studyForm.Modalities
var params = {
id: this.studyForm.StudyId,
subjectVisitId: this.data.Id,
type: 1,
modality: this.studyForm.Modality,
bodyPart: this.studyForm.BodyPart,
StudyName: this.studyForm.StudyName,
}
updateModality(this.trialId, params)
.then((res) => {
this.btnLoading = false
if (res.IsSuccess) {
this.getStudyInfo()
this.$message.success(this.$t('common:message:savedSuccessfully'))
this.editStudyInfoVisible = false
}
})
.catch(() => {
this.btnLoading = false
})
})
},
format(v) {
return v
},
// 获取已上传的dicom影像
getStudyInfo() {
this.studyLoading = true
getSubjectVisitUploadedStudyList(this.subjectVisitId)
.then((res) => {
// this.studyList = res.Result
this.relationInfo = res.OtherInfo
this.trialBodyPartTypes = this.relationInfo.BodyPartTypes
? this.relationInfo.BodyPartTypes.split('|')
: []
this.trialModalitys = this.relationInfo.Modalitys
? this.relationInfo.Modalitys.split('|')
: []
this.studyLoading = false
this.studyList = res.Result
})
.catch(() => {
this.studyLoading = false
})
},
// 弹框提示pet-ct临床数据未上传
petDataTip(isPacs = false) {
if (this.confirmFlag || isPacs) {
this.confirmFlag = false
this.$confirm(
this.$t('trials:crc-upload:confirm:message'),
this.$t('trials:uploadDicomList:label:prompt'),
{
confirmButtonText: this.$t('trials:reviewTrack:impactList:save'),
// cancelButtonText: this.$t("common:button:cancel"),
type: 'warning',
showCancelButton: false,
}
)
.then(() => {
this.confirmFlag = false
})
.catch(() => {
this.confirmFlag = false
})
}
},
// 预览单个检查影像
handleViewStudy(row) {
var token = getToken()
const routeData = this.$router.resolve({
path: `/showdicom?studyId=${row.StudyId}&TokenKey=${token}&type=Study`,
})
window.open(routeData.href, '_blank')
},
// 打开上传pet临床数据弹框
handleUploadPetData(row) {
this.studyData = row
this.studyData.SubjectId = this.subjectId
this.petVisible = true
},
// 预览所有影像
handlePreviewAllFiles() {
var token = getToken()
if (this.$route.path === '/trials/trials-panel/visit/crc-question') {
var routeData = this.$router.resolve({
path: `/showvisitdicoms?trialId=${this.trialId}&visitInfo=${this.data.VisitName}(${this.data.VisitNum})&subjectVisitId=${this.data.SubjectVisitId}&isFromCRCUpload=1&TokenKey=${token}`,
})
} else {
var routeData = this.$router.resolve({
path: `/showvisitdicoms?trialId=${this.trialId}&visitInfo=${this.data.VisitName}(${this.data.VisitNum})&subjectVisitId=${this.data.Id}&isFromCRCUpload=1&TokenKey=${token}`,
})
}
window.open(routeData.href, '_blank')
},
// 扫描待上传文件
beginScanFiles(e) {
var files = e.target.files
if (files.length > 0) {
var checkFiles = [...files]
let max = checkFiles.some((item) => item.size > 20 * 1024 * 1024 * 1024)
if (max)
return this.$message.warning(
this.$t('trials:uploadDicomList:message:maxFileSize')
)
var scope = this
scope.scanState = ''
scope.isScan = false
var p = new Promise(function (resolve) {
resolve()
})
var validFilesCount = 0
for (var i = 0; i < files.length; ++i) {
; (function (index) {
p = p.then(function () {
if (files[index].name.toUpperCase().indexOf('DICOMDIR') === -1) {
validFilesCount = validFilesCount + 1
return scope.parseDicomFile(files[index])
}
})
})(i)
}
p.then(function (result) {
scope.uploadQueues.forEach((v, i) => {
scope.$refs.dicomFilesTable.toggleRowSelection(v, true)
})
scope.scanState = 'finished'
scope.isScan = true
})
}
},
// 按序列UUID本地归档
parseDicomFile(file) {
var scope = this
return new Promise(function (resolve, reject) {
if (scope.scanState === 'cancelling') {
resolve()
return
}
var reader = new FileReader()
reader.onload = function (e) {
try {
var data = dicomParser.parseDicom(new Uint8Array(e.target.result))
var instanceUid = data.string('x00080018')
if (!instanceUid) return resolve()
var modality = data.string('x00080060') || ''
var studyUid = data.string('x0020000d')
if (!studyUid) return resolve()
var pixelDataElement = data.elements.x7fe00010
var studyIndex = 0
while (
studyIndex < scope.uploadQueues.length &&
scope.uploadQueues[studyIndex].dicomInfo.studyUid !== studyUid &&
(pixelDataElement || modality === 'SR') &&
modality != ''
) {
++studyIndex
}
if (studyIndex >= scope.uploadQueues.length) {
var date = data.string('x00080020')
var time = data.string('x00080030')
var studyTime = ''
if (date) {
date = `${date.substring(0, 4)}-${date.substring(
4,
6
)}-${date.substring(6, 8)}`
}
if (time) {
time = `${time.substring(0, 2)}:${time.substring(
2,
4
)}:${time.substring(4, 6)}`
}
if (date) {
studyTime = time ? `${date} ${time}` : `${date} 00:00:00`
}
const patientNameElement = data.elements.x00100010
const patientNameBytes = new Uint8Array(
data.byteArray.buffer,
patientNameElement ? patientNameElement.dataOffset : 0,
patientNameElement ? patientNameElement.length : 0
)
// 解析dicom中字符集字段与解析库不一致 2025.03.04
let SpecificCharacterSet = data.string('x00080005')
? data.string('x00080005').replace('ISO IR', 'ISO_IR')
: ''
const patientNameStr = convertBytes(
SpecificCharacterSet,
patientNameBytes
)
scope.uploadQueues.push({
studyIndex: studyIndex,
seriesList: [],
fileList: [],
dicomInfo: {
studyId: data.string('x00200010'),
studyUid: studyUid,
patientId: data.string('x00100020'),
patientName: patientNameStr,
patientAge: data.string('x00101010')
? data.string('x00101010')
: '',
patientSex: data.string('x00100040')
? data.string('x00100040')
: '',
patientBirthDate: data.string('x00100030'),
hospitalName: data.string('x00080080'),
accNumber: data.string('x00080050'),
bodyPart: data.string('x00180015') || '',
modality: [],
institutionName: data.string('x00080080'),
description: data.string('x00081030'),
//0008,0032
acquisitionTime: data.string('x00080032'),
acquisitionNumber: data.string('x00200012'),
triggerTime: data.string('x00181060'),
studyTime: studyTime,
VisitInfo: {},
SubjectInfo: {},
failedFileCount: 0,
uploadFileSize: 0,
fileSize: 0,
fileCount: 0,
isInit: false,
},
uploadState: {
selected: false,
stateCode: '',
beginUploadTime: {},
progressValue: 0,
},
})
}
var modality = scope.uploadQueues[studyIndex].dicomInfo.modality
var currentModality = data.string('x00080060')
if (!(modality.indexOf(currentModality) > -1)) {
modality.push(currentModality)
}
var fileList = scope.uploadQueues[studyIndex].fileList
var instanceUid = data.string('x00080018')
var instanceIndex = 0
while (
instanceIndex < fileList.length &&
fileList[instanceIndex].instanceUid !== instanceUid &&
(pixelDataElement || modality === 'SR')
) {
++instanceIndex
}
// if (instanceIndex >= fileList.length) {
// fileList.push({
// instanceUid: instanceUid,
// file: file,
// })
// }
// scope.uploadQueues[studyIndex].dicomInfo.fileCount = fileList.length
// scope.uploadQueues[studyIndex].dicomInfo.fileSize = fileList.reduce(
// (prev, next) => {
// return prev + next.file.size
// },
// 0
// )
var seriesUid = data.string('x0020000e')
var seriesList = scope.uploadQueues[studyIndex].seriesList
var seriesItem = seriesList.find(function (item) {
return item.seriesUid === seriesUid
})
if (!seriesItem && (pixelDataElement || modality === 'SR')) {
var date = data.string('x00080021')
var time = data.string('x00080031')
var seriesTime = ''
if (date) {
date = `${date.substring(0, 4)}-${date.substring(
4,
6
)}-${date.substring(6, 8)}`
}
if (time) {
time = `${time.substring(0, 2)}:${time.substring(
2,
4
)}:${time.substring(4, 6)}`
}
if (date) {
seriesTime = time ? `${date} ${time}` : `${date} 00:00:00`
}
const seriesDescriptionElement = data.elements.x0008103e
const seriesDescriptionBytes = new Uint8Array(
data.byteArray.buffer,
seriesDescriptionElement
? seriesDescriptionElement.dataOffset
: 0,
seriesDescriptionElement ? seriesDescriptionElement.length : 0
)
let SpecificCharacterSet = data.string('x00080005')
? data.string('x00080005').replace('ISO IR', 'ISO_IR')
: ''
const seriesDescriptionStr = convertBytes(
SpecificCharacterSet,
seriesDescriptionBytes
)
seriesItem = {
seriesUid: seriesUid,
seriesNumber: data.intString('x00200011') || 1,
modality: data.string('x00080060') || '',
description: seriesDescriptionStr,
seriesTime: seriesTime,
sliceThickness: data.string('x00180050') || '',
imagePositionPatient: data.string('x00200032') || '',
imageOrientationPatient: data.string('x00200037') || '',
sequenceName: data.string('x00180024') || '',
protocolName: data.string('x00181030') || '',
imagerPixelSpacing: data.string('x00181164') || '',
instanceList: [],
}
seriesList.push(seriesItem)
}
var instanceList = seriesItem.instanceList
var instanceItem = instanceList.find(function (item) {
return item.instanceUid === instanceUid
})
if (!instanceItem) {
var date = data.string('x00080023')
var time = data.string('x00080033')
var instanceTime = ''
if (date) {
date = `${date.substring(0, 4)}-${date.substring(
4,
6
)}-${date.substring(6, 8)}`
}
if (time) {
time = `${time.substring(0, 2)}:${time.substring(
2,
4
)}:${time.substring(4, 6)}`
}
if (date) {
instanceTime = time ? `${date} ${time}` : `${date} 00:00:00`
}
instanceItem = {
instanceUid: instanceUid,
instanceNumber: data.intString('x00200013') || 1,
frameCount: data.intString('x00280008') || 1,
instanceTime: instanceTime,
imageRows: data.uint16('x00280010') || 0,
imageColumns: data.uint16('x00280011') || 0,
sliceLocation: data.intString('x00201041') || 0,
sliceThickness: data.string('x00180050') || '',
numberOfFrames: data.intString('x00280008') || 0,
pixelSpacing: data.string('x00280030') || '',
imagerPixelSpacing: data.string('x00181164') || '',
frameOfReferenceUID: data.string('x00200052') || '',
windowCenter: data.string('x00281050') || '',
windowWidth: data.string('x00281051') || '',
modality: data.string('x00080060') || '',
file: file,
FileSize: file.size,
}
instanceList.push(instanceItem)
}
if (instanceIndex >= fileList.length) {
fileList.push({
instanceUid: instanceUid,
file: file,
})
}
scope.uploadQueues[studyIndex].dicomInfo.fileCount = fileList.length
scope.uploadQueues[studyIndex].dicomInfo.fileSize = fileList.reduce(
(prev, next) => {
return prev + next.file.size
},
0
)
resolve()
} catch (error) {
console.log(error)
resolve()
}
}
reader.onerror = function (e) {
resolve()
}
reader.readAsArrayBuffer(file)
})
},
// 上传之前校验基本信息及检查是否已上传
beginUploadQueues() {
this.scanState = 'uploading'
this.studyErrorList = []
// console.log(this.uploadQueues, this.selectArr)
this.warningArr = []
for (let i = 0; i < this.selectArr.length; ++i) {
const index = this.selectArr[i].studyIndex
this.uploadQueues[index].uploadState.stateCode = 'uploading'
this.verifyBasicInfos(index)
}
if (this.warningArr.length) {
this.warning_cfg.visible = true
this.btnLoading = false
} else {
this.verifyStudy()
}
},
// 校验影像中检查时间、性别是否与受试者匹配
verifyBasicInfos(index) {
var warnings = []
if (
this.uploadQueues[index].dicomInfo.patientSex &&
this.form.SubjectSex
) {
if (
this.uploadQueues[index].dicomInfo.patientSex.substr(0, 1) !==
this.form.SubjectSex.substr(0, 1)
) {
warnings.push(
this.$t('trials:uploadDicomList:message:genderConsistent')
)
}
}
if (warnings.length > 0) {
this.warningArr.push({
index: this.uploadQueues[index].studyIndex + 1,
accNumber: this.uploadQueues[index].dicomInfo.accNumber,
warnings: warnings,
})
}
},
// 校验该检查是否上传过
verifyStudy() {
this.btnLoading = true
var studyList = []
this.selectArr.forEach((item) => {
item.dicomInfo.uploadFileSize = 0
if (!item.uploadState.selected) {
studyList.push({
studyInstanceUid: item.dicomInfo.studyUid,
studyDate: item.dicomInfo.studyTime,
})
}
})
var param = {
trialId: this.trialId,
subjectId: this.subjectId,
subjectVisitId: this.subjectVisitId,
visitNum: this.form.VisitNum,
studyInstanceUidList: studyList,
}
batchVerifyStudyAllowUpload(param)
.then(async (res) => {
var messageArr = []
res.Result.forEach((item) => {
const i = this.uploadQueues.findIndex(
(value) => value.dicomInfo.studyUid === item.StudyInstanceUid
)
let instanceUidList = []
item.UploadedSeriesList.forEach((v) => {
v.SOPInstanceUIDList.forEach((v1) => {
instanceUidList.push(v1)
})
})
// isReUploadinstanceUidList1.includes(instanceUidList2)
try {
this.uploadQueues[i].seriesList.forEach((v) => {
let o = item.UploadedSeriesList.find((v1) => {
return v.seriesUid === v1.SeriesId
// v.instanceList.forEach(v2 => {
// let o2 = v1.SOPInstanceUIDList.find(v3 => {
// return v3 === v2.instanceUid
// })
// if (!!o2) {
// v2.isReUpload = true
// }
// })
})
if (!!o) {
v.isReUpload = true
}
})
this.uploadQueues[i].seriesList.forEach((v) => {
v.instanceList.forEach((v2) => {
let o2 = instanceUidList.find((v3) => {
return v3 === v2.instanceUid
})
if (!!o2) {
v2.isReUpload = true
}
})
})
} catch (e) {
console.log(e)
}
// 不能上传或不能重传时,收集校验结果提示,并取消勾选,设置上传状态
if (item.ErrorMesseage) {
this.uploadQueues[i].uploadState.AllowReUpload =
item.AllowReUpload
this.$refs.dicomFilesTable.toggleRowSelection(
this.uploadQueues[i]
)
this.uploadQueues[i].uploadState.selected = true
const msg = `${item.ErrorMesseage}`
messageArr.push(msg)
}
// 可以重传时记录需要被覆盖的检查ID
if (item.AllowReUpload) {
this.uploadQueues[i].AbandonStudyId = item.StudyInfo
? item.StudyInfo.Id
: ''
}
this.uploadQueues[i].uploadState.AllowReUpload = item.AllowReUpload
})
if (messageArr.length > 0) {
var li = messageArr.map((msg) => {
return `<li>${msg}</li>`
})
const content = `
<div>
<ol>
${li.join('')}
</ol>
</div>`
this.$alert(content, '', {
confirmButtonText: this.$t(
'trials:uploadDicomList:label:confirm'
),
dangerouslyUseHTMLString: true,
callback: (action) => { },
})
this.btnLoading = false
}
var results = []
this.uploadCount = this.selectArr.length
this.uploadedCount = 0
for (let i = 0; i < this.selectArr.length; i++) {
const index = this.selectArr[i].studyIndex
if (!this.uploadQueues[index].uploadState.selected) {
await this.archiveStudy(index, res.OtherInfo)
}
}
this.$set(this, 'uploadQueues', [
...this.uploadQueues.filter((v) => {
return (
!v.uploadState.record ||
(v.uploadState.record && !!v.uploadState.record.Failed.length)
)
}),
])
this.$nextTick(() => {
this.selectArr = []
this.uploadQueues.forEach((v, i) => {
if (v.uploadState.record) {
v.uploadState.selected = false
this.$refs.dicomFilesTable.toggleRowSelection(v, true)
}
v.studyIndex = i
})
this.$refs.pathClear.value = ''
this.btnLoading = false
// console.log(11111);
if (this.studyErrorList.length > 0) {
let msg = this.$t(
'trials:uploadDicomFiles2:message:studyErrorList'
)
msg = msg.replaceAll('xxx', this.studyErrorList.join('、'))
this.$confirm(msg, this.$t('trials:hotkeys:message:tip'), {
confirmButtonText: this.$t(
'trials:uploadDicomFiles2:button:reupload'
),
}).then(() => {
this.beginUploadQueues()
})
}
if (this.confirmFlag) {
this.petDataTip()
}
})
})
.catch((_) => {
this.btnLoading = false
})
},
objectToQuery() {
let obj = arguments[0]
let prefix = arguments[1]
if (typeof obj !== 'object') return ''
const attrs = Object.keys(obj)
return attrs.reduce((query, attr, index) => {
// 判断是否是第一层第一个循环
if (index === 0 && !prefix) query += '?'
if (typeof obj[attr] === 'object') {
const subPrefix = prefix ? `${prefix}[${attr}]` : attr
query += this.objectToQuery(obj[attr], subPrefix)
} else {
if (prefix) {
query += `${prefix}[${attr}]=${obj[attr]}`
} else {
query += `${attr}=${obj[attr]}`
}
}
// 判断是否是第一层最后一个循环
if (index !== attrs.length - 1) query += '&'
return query
}, '')
},
canvasToBlob(canvas) {
return new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(blob)
})
})
},
dicomToPng(imageId, width, height) {
return new Promise((resolve) => {
cornerstone.loadImage(imageId).then(async (image) => {
let canvas = document.createElement('canvas')
canvas.width = (width * 60) / height
canvas.height = 60
if (image) {
cornerstone.renderToCanvas(canvas, image)
// 将 Canvas 图像对象转换为 PNG 格式
let blob = await this.canvasToBlob(canvas)
resolve(blob)
} else {
resolve()
}
})
}).catch((reason) => {
reason()
})
},
// 上传影像并归档
archiveStudy(index, config) {
var scope = this
return new Promise(function (resolve, reject) {
try {
preArchiveDicomStudy({
trialId: scope.trialId,
trialSiteId: scope.data.TrialSiteId,
subjectId: scope.subjectId,
subjectVisitId: scope.subjectVisitId,
// failedFileCount: scope.uploadQueues[index].dicomInfo.failedFileCount,
fileSize: scope.uploadQueues[index].dicomInfo.fileSize,
fileCount: scope.uploadQueues[index].dicomInfo.fileCount,
IsDicomReUpload:
scope.uploadQueues[index].uploadState.AllowReUpload,
})
.then(async (res) => {
scope.uploadQueues[index].dicomInfo.failedFileCount = 0
scope.$set(scope.uploadQueues[index].dicomInfo, 'isInit', true)
let dicomInfo = scope.uploadQueues[index].dicomInfo
let seriesNum = scope.uploadQueues[index].seriesList.length
let fileNum = scope.uploadQueues[index].fileList.length
let seriesList = scope.uploadQueues[index].seriesList
let fileList = scope.uploadQueues[index].fileList
dicomInfo.seriesNum = seriesNum
dicomInfo.fileNum = fileNum
dicomInfo.trialSiteId = scope.data.TrialSiteId
dicomInfo.subjectId = scope.data.SubjectId
dicomInfo.subjectVisitId = scope.subjectVisitId
let t = setInterval(() => {
dicomUploadInProgress({
trialId: scope.trialId,
studyInstanceUid: dicomInfo.studyUid,
}).then((res) => { })
}, 5000)
scope.myInterval.push(t)
let Record = {
Failed: [],
Existed: [],
Uploaded: [],
FileCount: 0,
}
let params = {
trialId: scope.trialId,
trialSiteId: scope.data.TrialSiteId,
subjectId: scope.subjectId,
subjectVisitId: scope.subjectVisitId,
studyMonitorId: res.Result,
failedFileCount: 0,
RecordPath: null,
study: {
studyId: dicomInfo.studyId,
studyInstanceUid: dicomInfo.studyUid,
studyTime: dicomInfo.studyTime,
description: dicomInfo.description,
seriesCount: dicomInfo.seriesNum,
instanceCount: dicomInfo.fileNum,
institutionName: dicomInfo.institutionName,
patientId: config.DicomStoreInfo.SubjectCode,
patientName: '',
patientAge: '',
patientSex: config.DicomStoreInfo.SubjectSex,
accessionNumber: dicomInfo.accNumber,
patientBirthDate: '',
acquisitionTime: dicomInfo.acquisitionTime,
acquisitionNumber: dicomInfo.acquisitionNumber,
triggerTime: dicomInfo.triggerTime,
bodyPartExamined: '',
seriesList: [],
},
}
let arr = []
for (let i = 0; i < seriesList.length; i++) {
let v = seriesList[i]
let instanceList = []
let ImageResizePath
for (let ii = 0; ii < v.instanceList.length; ii++) {
arr.push(
(function () {
return new Promise(async (resolve1) => {
try {
let o = v.instanceList[ii]
let name = `${v.instanceList[ii].file.webkitRelativePath}_${v.instanceList[ii].instanceUid}`
if (o.isReUpload) {
dicomInfo.failedFileCount++
dicomInfo.uploadFileSize += o.file.size
Record.Existed.push(name)
Record.FileCount++
} else if (o.myPath) {
instanceList.push({
studyInstanceUid: dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid,
sopInstanceUid: o.instanceUid,
instanceNumber: o.instanceNumber,
instanceTime: o.instanceTime,
imageRows: o.imageRows,
imageColumns: o.imageColumns,
sliceLocation: o.sliceLocation,
sliceThickness: o.sliceThickness,
numberOfFrames: o.numberOfFrames,
pixelSpacing: o.pixelSpacing,
imagerPixelSpacing: o.imagerPixelSpacing,
frameOfReferenceUID: o.frameOfReferenceUID,
windowCenter: o.windowCenter,
windowWidth: o.windowWidth,
path: o.myPath,
FileSize: o.FileSize,
})
Record.Uploaded.push(name)
dicomInfo.failedFileCount++
Record.FileCount++
} else {
let path = `/${params.trialId}/Image/${params.subjectId
}/${params.subjectVisitId}/${dicomInfo.studyUid
}/${scope.getGuid(
dicomInfo.studyUid +
v.seriesUid +
o.instanceUid +
params.trialId
)}`
if (scope.isClose) return
let res = await dcmUpload(
{
path: path,
file: o.file,
speed: true,
},
config,
(percentage, checkpoint, lastPer) => {
dicomInfo.uploadFileSize +=
checkpoint.size * (percentage - lastPer)
if (
dicomInfo.uploadFileSize > dicomInfo.fileSize
) {
dicomInfo.uploadFileSize = dicomInfo.fileSize
}
if (
Math.abs(dicomInfo.uploadFileSize - dicomInfo.fileSize) < 5000
) {
dicomInfo.uploadFileSize = dicomInfo.fileSize
}
}
)
if (!res || !res.url) {
params.failedFileCount++
} else {
if (ii === 0 && o.modality !== 'SR') {
try {
let fileId =
cornerstoneWADOImageLoader.wadouri.fileManager.add(
o.file
)
let blob = await scope.dicomToPng(
fileId,
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
)
if (seriesRes && seriesRes.url) {
ImageResizePath = scope.$getObjectName(
seriesRes.url
)
}
} catch (e) {
console.log(e)
}
}
}
if (res && res.url) {
instanceList.push({
studyInstanceUid: dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid,
sopInstanceUid: o.instanceUid,
instanceNumber: o.instanceNumber,
instanceTime: o.instanceTime,
imageRows: o.imageRows,
imageColumns: o.imageColumns,
sliceLocation: o.sliceLocation,
sliceThickness: o.sliceThickness,
numberOfFrames: o.numberOfFrames,
pixelSpacing: o.pixelSpacing,
imagerPixelSpacing: o.imagerPixelSpacing,
frameOfReferenceUID: o.frameOfReferenceUID,
windowCenter: o.windowCenter,
windowWidth: o.windowWidth,
path: scope.$getObjectName(res.url),
FileSize: o.FileSize,
})
o.myPath = scope.$getObjectName(res.url)
Record.Uploaded.push(name)
dicomInfo.failedFileCount++
Record.FileCount++
} else {
Record.Failed.push(name)
Record.FileCount++
}
}
resolve1()
} catch (e) {
console.log(e)
resolve1()
}
})
})()
)
if (
(arr.length >= 10 || ii === v.instanceList.length - 1) &&
!scope.isClose
) {
await Promise.all(arr)
arr = []
}
}
params.study.seriesList.push({
studyInstanceUid: dicomInfo.studyUid,
seriesInstanceUid: v.seriesUid,
seriesNumber: v.seriesNumber,
seriesTime: v.seriesTime,
sliceThickness: v.sliceThickness,
imagePositionPatient: v.imagePositionPatient,
imageOrientationPatient: v.imageOrientationPatient,
sequenceName: v.sequenceName,
protocolName: v.protocolName,
imagerPixelSpacing: v.imagerPixelSpacing,
acquisitionTime: dicomInfo.acquisitionTime,
acquisitionNumber: dicomInfo.acquisitionNumber,
triggerTime: dicomInfo.triggerTime,
modality: v.modality,
description: v.description,
instanceCount: v.instanceList.length,
bodyPartExamined: dicomInfo.bodyPart,
instanceList: instanceList,
ImageResizePath: ImageResizePath,
})
}
let text = JSON.stringify(Record)
let logJsonBlob = scope.generateTxtFile(text)
let logJsonObjectName = `/${params.trialId}/Image/${params.trialSiteId}/${params.subjectId}/${params.subjectVisitId}/${dicomInfo.studyUid}/${params.studyMonitorId}.txt`
let logRes
try {
logRes = await scope.OSSclient.put(
logJsonObjectName,
logJsonBlob
)
} catch (e) {
scope.uploadQueues[index].uploadState.record = Record
scope.studyErrorList.push(dicomInfo.accNumber)
clearInterval(t)
resolve()
}
for (let i = 0; i < seriesList.length; i++) {
let v = seriesList[i]
let o = params.study.seriesList.find((o) => {
return o.seriesInstanceUid === v.seriesUid
})
if (o && !o.ImageResizePath) {
let fileId =
cornerstoneWADOImageLoader.wadouri.fileManager.add(
v.instanceList[0].file
)
let blob = await scope.dicomToPng(
fileId,
v.instanceList[0].imageColumns,
v.instanceList[0].imageRows
)
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)
if (seriesRes && seriesRes.url) {
o.ImageResizePath = scope.$getObjectName(seriesRes.url)
}
} catch (e) {
console.log(e)
}
}
}
if (logRes && logRes.url) {
params.study.instanceCount = dicomInfo.failedFileCount
params.RecordPath = scope.$getObjectName(logRes.url)
if (scope.isClose) return false
console.log(params)
addOrUpdateArchiveStudy(params)
.then((res) => {
if (dicomInfo.failedFileCount === dicomInfo.fileCount) {
scope.$message.success(
scope.$t('trials:uploadDicomList:label:uploaded')
)
} else {
scope.studyErrorList.push(dicomInfo.accNumber)
}
scope.uploadQueues[index].uploadState.record = Record
let flag =
res.Result === 'PET-CT' ||
res.Result === 'CT、PT' ||
res.Result === 'PT、CT'
// let flag = params.study.seriesList.some(item=>item.modality==='PT');
if (flag && scope.relationInfo.IsHaveStudyClinicalData) {
scope.confirmFlag = true
}
scope.getStudyInfo()
if (
scope.$route.path !==
'/trials/trials-panel/visit/crc-question'
) {
scope.$emit('getList')
}
clearInterval(t)
resolve()
})
.catch((res) => {
scope.uploadQueues[index].uploadState.record = Record
scope.studyErrorList.push(dicomInfo.accNumber)
clearInterval(t)
resolve()
})
} else {
scope.uploadQueues[index].uploadState.record = Record
scope.studyErrorList.push(dicomInfo.accNumber)
clearInterval(t)
resolve()
}
})
.catch(() => {
let Record = {
Failed: [],
Existed: [],
Uploaded: [],
FileCount: 0,
}
let fileList = scope.uploadQueues[index].fileList
fileList.forEach((v) => {
Record.Failed.push(v.webkitRelativePath)
})
scope.uploadQueues[index].uploadState.record = Record
scope.studyErrorList.push(dicomInfo.accNumber)
resolve()
})
} catch (e) {
console.log(e)
resolve()
}
})
},
generateTxtFile(text) {
let blob = new Blob(['\ufeff', text], { type: 'text/plain' })
return blob
},
// warning弹框取消按钮回调
handleCancelWarnVisible() {
this.warning_cfg.visible = false
for (var i = 0; i < this.selectArr.length; ++i) {
const index = this.selectArr[i].studyIndex
this.uploadQueues[index].uploadState.stateCode = ''
}
},
// 忽略warning继续上传
handleContinueUpload() {
this.warning_cfg.visible = false
this.verifyStudy()
},
// 预览影像
handlePreview(uid) {
this.previewVisible = true
this.uid = uid
},
// 删除影像列表中的某个影像
handleDelete(index, row) {
this.$confirm(this.$t('trials:uploadedDicoms:message:deleteMes'), {
type: 'warning',
distinguishCancelAndClose: true,
}).then(() => {
this.$refs.uploadForm.reset()
this.uploadQueues.splice(index, 1)
this.uploadQueues.forEach((v, i) => {
if (v.uploadState.record) {
v.uploadState.selected = false
this.$refs.dicomFilesTable.toggleRowSelection(v, true)
}
v.studyIndex = i
})
this.handleSelectionChange(this.uploadQueues)
})
},
// 当选择项发生变化时收集待上传文件数据
handleSelectionChange(selection) {
selection.sort((n1, n2) => {
return n1.studyIndex - n2.studyIndex
})
this.selectArr = selection
},
// 根据是否上传状态决定CheckBox 是否可以勾选
handleSelectable(row) {
if (row.uploadState.selected || this.btnLoading) {
return false
} else {
return true
}
},
// 收集已上传文件列表勾选项
handleUploadedSelectionChange(selection) {
this.deleteArr = []
selection.forEach((item) => {
this.deleteArr.push(item.StudyId)
})
},
// 批量删除已上传的影像
handleBatchDelete() {
this.$confirm(this.$t('trials:uploadedDicoms:message:deleteMes'), {
type: 'warning',
distinguishCancelAndClose: true,
})
.then(() => {
this.studyLoading = true
deleteStudyList(this.trialId, this.subjectVisitId, this.deleteArr)
.then((res) => {
if (res.IsSuccess) {
this.getStudyInfo()
if (
this.$route.path !== '/trials/trials-panel/visit/crc-question'
) {
this.$emit('getList')
}
this.$message.success(
this.$t('trials:uploadedDicoms:message:deleteSuccessfully')
)
}
})
.catch(() => {
this.studyLoading = true
})
})
.catch(() => { })
},
// 删除已上传的某个检查
handleDeleteStudy(row) {
this.$confirm(this.$t('trials:uploadedDicoms:message:deleteMes'), {
type: 'warning',
distinguishCancelAndClose: true,
})
.then(() => {
this.studyLoading = true
deleteStudyList(this.trialId, this.subjectVisitId, [row.StudyId])
.then((res) => {
if (res.IsSuccess) {
this.getStudyInfo()
if (this.uploadActiveName === 'pacs') {
this.$refs.dicomPacs.getList()
}
if (
this.$route.path !== '/trials/trials-panel/visit/crc-question'
) {
this.$emit('getList')
}
this.$message.success(
this.$t('trials:uploadedDicoms:message:deleteSuccessfully')
)
}
})
.catch(() => {
this.studyLoading = true
})
})
.catch(() => { })
},
// cancel按钮回调
cancel() {
this.scanState = 'cancelling'
// 关闭上传模态框
this.$emit('close')
},
compare(start, end) {
start = new Date(start)
start = start.getTime()
end = new Date(end)
end = end.getTime()
var time = 0
if (start > end) {
time = start - end
} else {
time = end - start
}
return Math.floor(time / 86400000)
},
exportFailedFiles(errorFiles) {
var elementA = document.createElement('a')
var str = ''
errorFiles.forEach((el) => {
str += el + '\n'
})
elementA.setAttribute('href', 'data:text/html;charset=utf-8,' + str)
elementA.setAttribute('download', +new Date() + '.txt')
elementA.style.display = 'none'
document.body.appendChild(elementA)
elementA.click()
document.body.removeChild(elementA)
},
// 设置已删除序列行样式
tableRowClassName({ row, rowIndex }) {
if (row.IsDeleted) {
return 'delete-row'
} else {
return ''
}
},
getBodyPart(bodyPart) {
if (!bodyPart) return ''
var separator = ','
if (bodyPart.indexOf('|') > -1) {
separator = '|'
} else if (bodyPart.indexOf(',') > -1) {
separator = ','
} else if (bodyPart.indexOf('') > -1) {
separator = ''
}
var arr = bodyPart.split(separator)
var newArr = arr.map((i) => {
return this.$fd('Bodypart', i.trim(), 'Code', this.BodyPart, 'Name')
})
return newArr.join(' | ')
},
},
}
</script>
<style lang="scss">
.upload-dicom-files-wrapper {
.dicomFiles-table {
.el-table__body-wrapper {
height: 300px;
overflow-y: auto;
}
}
.delete-row {
text-decoration-line: line-through;
color: #c0c4cc;
}
.previewActive:hover {
cursor: pointer;
color: #428bca;
}
#inputForm label {
font-weight: normal;
}
#inputForm .file-input {
position: relative;
overflow: hidden;
display: inline-block;
}
#inputForm .file-input input[type='file'] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
#inputForm #listWrapper {
margin-top: 10px;
margin-bottom: 50px;
}
#inputForm #listWrapper a {
text-decoration: none;
}
#inputForm .text-left {
text-align: left;
}
.colorOfRed {
color: #f44336;
}
}
</style>