1082 lines
38 KiB
Plaintext
1082 lines
38 KiB
Plaintext
<template>
|
||
<div class="upload-dicom-files-wrapper">
|
||
<!-- 已上传的dicom影像记录 -->
|
||
<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"
|
||
>
|
||
<el-table-column
|
||
type="selection"
|
||
width="55"
|
||
/>
|
||
<!-- 检查编号 -->
|
||
<el-table-column
|
||
prop="StudyCode"
|
||
:label="$t('trials:uploadedDicoms:table:studyId')"
|
||
min-width="80"
|
||
show-overflow-tooltip
|
||
/>
|
||
<!-- 检查类型 -->
|
||
<el-table-column
|
||
prop="Modalities"
|
||
:label="$t('trials:uploadedDicoms:table:modality')"
|
||
min-width="80"
|
||
show-overflow-tooltip
|
||
/>
|
||
<!-- 检查部位 -->
|
||
<el-table-column
|
||
prop="BodyPartExamined"
|
||
:label="$t('trials:uploadedDicoms:table:bodyPart')"
|
||
min-width="100"
|
||
show-overflow-tooltip
|
||
/>
|
||
<!-- 序列数量 -->
|
||
<el-table-column
|
||
prop="SeriesCount"
|
||
:label="$t('trials:uploadedDicoms:table:seriesCount')"
|
||
min-width="100"
|
||
show-overflow-tooltip
|
||
/>
|
||
<!-- 图像数量 -->
|
||
<el-table-column
|
||
prop="InstanceCount"
|
||
:label="$t('trials:uploadedDicoms:table:instanceCount')"
|
||
min-width="100"
|
||
show-overflow-tooltip
|
||
/>
|
||
<!-- 检查日期 -->
|
||
<el-table-column
|
||
prop="StudyTime"
|
||
:label="$t('trials:uploadedDicoms:table:studyDate')"
|
||
min-width="120"
|
||
show-overflow-tooltip
|
||
>
|
||
<template slot-scope="scope">
|
||
{{ scope.row.StudyTime ? moment(scope.row.StudyTime).format('YYYY-MM-DD'):'' }}
|
||
</template>
|
||
</el-table-column>
|
||
<!-- 上传时间 -->
|
||
<el-table-column
|
||
prop="UploadedTime"
|
||
:label="$t('trials:uploadedDicoms:table:uploadedTime')"
|
||
min-width="80"
|
||
show-overflow-tooltip
|
||
/>
|
||
<el-table-column :label="$t('common:action:action')" min-width="100" fixed="right">
|
||
<template slot-scope="scope">
|
||
<!-- 预览 -->
|
||
<el-button
|
||
icon="el-icon-view"
|
||
:disabled="scope.row.SeriesCount === 0 || scope.row.IsDeleted"
|
||
:title="$t('trials:uploadedDicoms:action:preview')"
|
||
circle
|
||
@click="handleViewStudy(scope.row)"
|
||
/>
|
||
<!-- 删除 -->
|
||
<el-button
|
||
icon="el-icon-delete"
|
||
:disabled="scope.row.IsDeleted"
|
||
:title="$t('trials:uploadedDicoms:action:delete')"
|
||
circle
|
||
@click="handleDeleteStudy(scope.row)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 多文件上传 -->
|
||
<form id="inputForm" ref="uploadForm">
|
||
<el-divider content-position="left">{{ $t('trials:uploadedDicoms:title:dicomFilesOnly') }}</el-divider>
|
||
<div class="form-group">
|
||
<div id="directoryInputWrapper" class="btn btn-link file-input">
|
||
<el-button size="small">{{ $t('trials:uploadedDicomsicom:button:selectFolder') }}</el-button>
|
||
<input
|
||
type="file"
|
||
name="file"
|
||
webkitdirectory
|
||
multiple
|
||
@change="beginScanFiles($event)"
|
||
>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- 文件列表 -->
|
||
<el-table
|
||
ref="dicomFilesTable"
|
||
:data="uploadQueues"
|
||
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">
|
||
<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:status')" min-width="120" show-overflow-tooltip>
|
||
<template slot-scope="scope">
|
||
<el-progress v-show="scope.row.uploadState.progressValue && scope.row.uploadState.progressValue!= 100" :percentage="scope.row.uploadState.progressValue" />
|
||
<el-progress v-show="scope.row.uploadState.UpdataValue && scope.row.uploadState.UpdataValue!= 100" color="#409eff" :format="format(scope.row.uploadState.UpdataValue)" :percentage="scope.row.uploadState.UpdataValue" />
|
||
<el-tooltip v-if="scope.row.uploadState.stateCode === 'Uploading not allowed'" class="item" effect="dark" :content="scope.row.uploadState.errorMessage" placement="bottom">
|
||
<span>{{ scope.row.uploadState.stateText }}</span>
|
||
</el-tooltip>
|
||
|
||
<span v-else>{{ scope.row.uploadState.stateText }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column
|
||
:label="$t('trials:uploadDicomList:table:stats')"
|
||
min-width="140"
|
||
show-overflow-tooltip
|
||
>
|
||
<template slot-scope="scope">
|
||
<div style="display: flex;justify-content: flex-start;align-items: center">
|
||
<div
|
||
v-if="scope.row.uploadState.totalCount"
|
||
style="text-align: center"
|
||
>
|
||
{{
|
||
scope.row.uploadState.totalCount +
|
||
' / ' +
|
||
scope.row.uploadState.successCount +
|
||
' / ' +
|
||
scope.row.uploadState.failedCount
|
||
}}
|
||
</div>
|
||
<div v-if="scope.row.uploadState.failedCount > 0">
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="exportFailedFiles(scope.row.uploadState.ErrorFiles)"
|
||
>
|
||
{{ $t('trials:uploadDicomList:button:exportFailed') }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
|
||
<el-table-column v-show="isScan" :label="$t('common:action:action')" width="200">
|
||
<template slot-scope="scope">
|
||
<!-- 预览 -->
|
||
<el-button
|
||
icon="el-icon-view"
|
||
circle
|
||
: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 !== ''"
|
||
@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> -->
|
||
<!-- 上传 -->
|
||
<el-button
|
||
size="small"
|
||
type="primary"
|
||
:disabled="selectArr.length == 0 || !isScan"
|
||
:loading="btnLoading || uploadCount !== uploadedCount"
|
||
@click="beginUploadQueues"
|
||
>
|
||
{{ $t('trials:uploadDicomList:button:upload') }}
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 预览影像模态框 -->
|
||
<el-dialog
|
||
v-if="previewVisible"
|
||
:fullscreen="true"
|
||
:show-close="true"
|
||
:visible.sync="previewVisible"
|
||
append-to-body
|
||
>
|
||
<DicomPreview :uid="uid" />
|
||
</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>
|
||
</div>
|
||
|
||
</template>
|
||
<script>
|
||
import {
|
||
batchVerifyStudyAllowUpload, getSubjectVisitUploadedStudyList, deleteStudyList
|
||
} from '@/api/trials'
|
||
import store from '@/store'
|
||
import * as dicomParser from 'dicom-parser'
|
||
import * as signalR from '@microsoft/signalr'
|
||
import DicomPreview from '@/views/dicom-show/dicom-preview'
|
||
import dicomStore from '@/utils/dicom-store'
|
||
import { getToken } from '@/utils/auth'
|
||
import moment from 'moment'
|
||
export default {
|
||
name: 'UploadDicomFiles',
|
||
components: { DicomPreview },
|
||
props: {
|
||
data: {
|
||
type: Object,
|
||
default() {
|
||
return {}
|
||
}
|
||
},
|
||
subjectVisitId: {
|
||
type: String,
|
||
required: true
|
||
},
|
||
subjectId: {
|
||
type: String,
|
||
required: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
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: []
|
||
}
|
||
},
|
||
mounted() {
|
||
this.trialId = this.$route.query.trialId
|
||
if (Object.keys(this.data).length) {
|
||
this.form = { ...this.data }
|
||
}
|
||
let connection = new signalR.HubConnectionBuilder()
|
||
.withUrl("/UploadHub", { accessTokenFactory: () => getToken() })
|
||
.configureLogging(signalR.LogLevel.Information)
|
||
.withAutomaticReconnect()
|
||
.build()
|
||
connection.start()
|
||
connection.on("ReceivProgressAsync", (id, num) => {
|
||
this.onUploadProgress2(id, num)
|
||
});
|
||
this.getStudyInfo()
|
||
},
|
||
methods: {
|
||
format(v) {
|
||
return v
|
||
},
|
||
// 获取已上传的dicom影像
|
||
getStudyInfo() {
|
||
this.studyLoading = true
|
||
getSubjectVisitUploadedStudyList(this.subjectVisitId).then(res => {
|
||
this.studyList = res.Result
|
||
this.studyLoading = false
|
||
}).catch(() => { this.studyLoading = 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')
|
||
},
|
||
// 预览所有影像
|
||
handlePreviewAllFiles() {
|
||
var token = getToken()
|
||
const routeData = this.$router.resolve({
|
||
path: `/showvisitdicoms?trialId=${this.data.TrialId}&visitInfo=${this.data.VisitName}(${this.data.VisitNum})&subjectVisitId=${this.data.Id}&TokenKey=${token}`
|
||
})
|
||
window.open(routeData.href, '_blank')
|
||
},
|
||
// 扫描待上传文件
|
||
beginScanFiles(e) {
|
||
var files = e.target.files
|
||
if (files.length > 0) {
|
||
console.log(files)
|
||
var checkFiles = [...files]
|
||
var isHasDicom = checkFiles.find(v => {
|
||
return v.name.split('.')[v.name.split('.').length - 1] === 'DCM' || v.name.split('.')[v.name.split('.').length - 1] === 'dcm'
|
||
})
|
||
if (!isHasDicom) {
|
||
this.$message.error(this.$t('trials:trials-panel:message:notIsHasDicom'))
|
||
return
|
||
}
|
||
var scope = this
|
||
scope.scanState = ''
|
||
scope.isScan = false
|
||
var p = new Promise(function(resolve) {
|
||
resolve()
|
||
})
|
||
for (var i = 0; i < files.length; ++i) {
|
||
(function(index) {
|
||
p = p.then(function() {
|
||
return scope.parseDicomFile(files[index])
|
||
})
|
||
})(i)
|
||
}
|
||
p.then(function(result) {
|
||
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 studyUid = data.string('x0020000d')
|
||
var studyIndex = 0
|
||
while (
|
||
studyIndex < scope.uploadQueues.length &&
|
||
scope.uploadQueues[studyIndex].dicomInfo.studyUid !== studyUid
|
||
) {
|
||
++studyIndex
|
||
}
|
||
if (studyIndex >= scope.uploadQueues.length) {
|
||
var date = data.string('x00080020')
|
||
var time = data.string('x00080030')
|
||
if (date) {
|
||
// date = `${date.substr(0, 4)}-${date.substr(4, 2)}-${date.substr(6, 2)}`
|
||
date = `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6, 8)}`
|
||
}
|
||
if (time) {
|
||
// time = `${time.substr(0, 2)}:${time.substr(2, 2)}:${time.substr(4, 2)}`
|
||
time = `${time.substring(0, 2)}:${time.substring(2, 4)}:${time.substring(4, 6)}`
|
||
}
|
||
var studyTime = time ? `${date} ${time}` : `${date} 00:00:00`
|
||
scope.uploadQueues.push({
|
||
studyIndex: studyIndex,
|
||
seriesList: [],
|
||
fileList: [],
|
||
dicomInfo: {
|
||
studyUid: studyUid,
|
||
patientId: data.string('x00100020'),
|
||
patientName: data.string('x00100010') ? data.string('x00100010') : '',
|
||
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'),
|
||
studyTime: studyTime,
|
||
VisitInfo: {},
|
||
SubjectInfo: {}
|
||
},
|
||
|
||
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
|
||
) {
|
||
++instanceIndex
|
||
}
|
||
if (instanceIndex >= fileList.length) {
|
||
fileList.push({
|
||
instanceUid: instanceUid,
|
||
file: file
|
||
})
|
||
}
|
||
|
||
var seriesUid = data.string('x0020000e')
|
||
var seriesList = scope.uploadQueues[studyIndex].seriesList
|
||
var seriesItem = seriesList.find(function(item) {
|
||
return item.seriesUid === seriesUid
|
||
})
|
||
if (!seriesItem) {
|
||
seriesItem = {
|
||
seriesUid: seriesUid,
|
||
seriesNumber: data.intString('x00200011') || 1,
|
||
modality: data.string('x00080060'),
|
||
description: data.string('x0008103e'),
|
||
instanceList: []
|
||
}
|
||
seriesList.push(seriesItem)
|
||
}
|
||
var instanceList = seriesItem.instanceList
|
||
var instanceItem = instanceList.find(function(item) {
|
||
return item.instanceUid === instanceUid
|
||
})
|
||
if (!instanceItem) {
|
||
instanceItem = {
|
||
instanceUid: instanceUid,
|
||
instanceNumber: data.intString('x00200013') || 1,
|
||
frameCount: data.intString('x00280008') || 1,
|
||
file: file
|
||
}
|
||
instanceList.push(instanceItem)
|
||
}
|
||
|
||
resolve()
|
||
} catch (error) {
|
||
console.log(error)
|
||
resolve()
|
||
}
|
||
}
|
||
reader.onerror = function(e) {
|
||
resolve()
|
||
}
|
||
reader.readAsArrayBuffer(file)
|
||
})
|
||
},
|
||
// 上传之前校验基本信息及检查是否已上传
|
||
beginUploadQueues() {
|
||
this.scanState = 'uploading'
|
||
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
|
||
} else {
|
||
this.verifyStudy()
|
||
}
|
||
},
|
||
// 校验影像中检查时间、性别是否与受试者匹配
|
||
verifyBasicInfos(index) {
|
||
// const studyTime = moment(this.uploadQueues[index].dicomInfo.studyTime).format('YYYY-MM-DD')
|
||
var warnings = []
|
||
// if (studyTime && studyTime < this.form.SVSTDTC) {
|
||
// warnings.push('Study Date earlier than Visit Start Date.')
|
||
// }
|
||
// if (studyTime && studyTime > this.form.SVSTDTC) {
|
||
// if (this.compare(studyTime, this.form.SVSTDTC) > 7) { warnings.push('Study Date later than Visit Start Date for more than one week.') }
|
||
// }
|
||
// const patientAge = this.uploadQueues[index].dicomInfo.patientAge
|
||
// if (patientAge && this.form.SubjectAge) {
|
||
// var age = patientAge.match(/\d+(.\d+)?/g)
|
||
// if (Math.abs(parseInt(age[0]) - parseInt(this.form.SubjectAge)) >= 1) {
|
||
// warnings.push('Patient Age not consistent with Subject Age.')
|
||
// }
|
||
// }
|
||
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 => {
|
||
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((res) => {
|
||
var messageArr = []
|
||
res.Result.forEach((item) => {
|
||
// 不能上传或不能重传时,收集校验结果提示,并取消勾选,设置上传状态
|
||
if (item.ErrorMesseage) {
|
||
const i = this.uploadQueues.findIndex((value) => value.dicomInfo.studyUid === item.StudyInstanceUid)
|
||
// Uploading not allowed
|
||
this.uploadQueues[i].uploadState.stateText = this.$t('trials:uploadDicomList:label:uploadingNotAllowed')
|
||
this.uploadQueues[i].uploadState.selected = true
|
||
this.uploadQueues[i].uploadState.stateCode = 'Uploading not allowed'
|
||
this.uploadQueues[i].uploadState.errorMessage = item.ErrorMesseage
|
||
this.$refs.dicomFilesTable.toggleRowSelection(this.uploadQueues[i])
|
||
// const msg = `ACC:${this.uploadQueues[i].dicomInfo.accNumber}, ${item.ErrorMesseage}`
|
||
const msg = `${item.ErrorMesseage}`
|
||
messageArr.push(msg)
|
||
}
|
||
|
||
// 可以重传时,记录需要被覆盖的检查ID
|
||
if (item.AllowReUpload) {
|
||
const i = this.uploadQueues.findIndex((value) => value.dicomInfo.studyUid === item.StudyInstanceUid)
|
||
this.uploadQueues[i].AbandonStudyId = item.StudyInfo ? item.StudyInfo.Id : ''
|
||
}
|
||
})
|
||
|
||
if (messageArr.length > 0) {
|
||
var li = messageArr.map(msg => { return `<li>${msg}</li>` })
|
||
const content = `
|
||
<div>
|
||
<ol>
|
||
${li.join('')}
|
||
</ol>
|
||
</div>`
|
||
this.$alert(content, this.$t('trials:uploadDicomList:label:prompt'),
|
||
{
|
||
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
|
||
results.push(this.archiveStudy(index))
|
||
}
|
||
Promise.all(results).then(_ => {
|
||
this.btnLoading = false
|
||
}).catch(_ => {
|
||
this.btnLoading = false
|
||
})
|
||
}).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
|
||
}, '')
|
||
},
|
||
// 上传影像并归档
|
||
archiveStudy(index) {
|
||
var scope = this
|
||
return new Promise(function(resolve, reject) {
|
||
var data = new FormData()
|
||
var dicomInfo = scope.uploadQueues[index].dicomInfo
|
||
var seriesNum = scope.uploadQueues[index].seriesList.length
|
||
var fileNum = scope.uploadQueues[index].fileList.length
|
||
dicomInfo.seriesNum = seriesNum
|
||
dicomInfo.fileNum = fileNum
|
||
dicomInfo.siteId = scope.data.SiteId
|
||
dicomInfo.subjectId = scope.data.SubjectId
|
||
dicomInfo.subjectVisitId = scope.subjectVisitId
|
||
var params = {
|
||
trialId: scope.trialId,
|
||
subjectVisitId: scope.subjectVisitId,
|
||
studyInstanceUid: scope.uploadQueues[index].dicomInfo.studyUid
|
||
}
|
||
// data.append('subjectVisitId', scope.subjectVisitId)
|
||
// data.append('studyInstanceUid', scope.uploadQueues[index].dicomInfo.studyUid)
|
||
if (scope.uploadQueues[index].AbandonStudyId) {
|
||
params.abandonStudyId = scope.uploadQueues[index].AbandonStudyId
|
||
// data.append('abandonStudyId', scope.uploadQueues[index].AbandonStudyId)
|
||
}
|
||
for (var i = 0; i < scope.uploadQueues[index].fileList.length; ++i) {
|
||
data.append('file', scope.uploadQueues[index].fileList[i].file)
|
||
}
|
||
scope.btnLoading = true
|
||
var xhr = new XMLHttpRequest()
|
||
xhr.open('POST', `/api/study/archiveStudy${scope.objectToQuery(params)}`)
|
||
xhr.setRequestHeader('Authorization', `Bearer ${store.getters.token}`)
|
||
// xhr.setRequestHeader('Content-Type', 'octet-stream')
|
||
xhr.timeout = 2 * 360000 // 超时时间,单位是毫秒
|
||
xhr.responseType = 'text'
|
||
xhr.onload = function(e) {
|
||
scope.onUploadCompleted(e, index)
|
||
resolve()
|
||
}
|
||
xhr.onerror = function(e) {
|
||
scope.onUploadError(e, index)
|
||
resolve()
|
||
}
|
||
xhr.upload.onprogress = function(e) {
|
||
scope.onUploadProgress(e, index)
|
||
}
|
||
// xhr.onprogress = function(e) {
|
||
// console.log('请求完成')
|
||
// scope.onUploadProgress(e, index)
|
||
// }
|
||
xhr.send(data)
|
||
var uploadState = scope.uploadQueues[index].uploadState
|
||
// 'Uploading...'
|
||
uploadState.stateText = `${this.$t('trials:uploadDicomList:label:uploading')}...`
|
||
|
||
uploadState.beginUploadTime = new Date()
|
||
uploadState.progressValue = 0
|
||
})
|
||
},
|
||
// 上传成功回调
|
||
onUploadCompleted: function(e, index) {
|
||
this.uploadedCount++
|
||
var uploadState = this.uploadQueues[index].uploadState
|
||
// this.btnLoading = false
|
||
|
||
if (e.target.status === 200) {
|
||
const responseObj = JSON.parse(e.target.responseText)
|
||
uploadState.stateCode = 'Finished'
|
||
|
||
if (responseObj.IsSuccess) {
|
||
this.uploadQueues[index].uploadState.selected = true
|
||
const i = this.selectArr.findIndex(item => { item.studyIndex === index })
|
||
this.selectArr.splice(i, 1)
|
||
this.$refs.dicomFilesTable.toggleRowSelection(this.uploadQueues[index])
|
||
uploadState.totalCount = responseObj.Result.ReceivedFileCount
|
||
|
||
uploadState.failedCount = responseObj.Result.ErrorFiles.length
|
||
uploadState.successCount = uploadState.totalCount - uploadState.failedCount
|
||
uploadState.ErrorFiles = responseObj.Result.ErrorFiles
|
||
if (responseObj.Result.ArchivedDicomStudies.length > 0) {
|
||
// Uploaded
|
||
uploadState.stateText = this.$t('trials:uploadDicomList:label:uploaded')
|
||
this.scanState = 'Uploaded'
|
||
this.getStudyInfo()
|
||
this.$emit('getList')
|
||
}
|
||
} else {
|
||
// Failed
|
||
this.uploadQueues[index].uploadState.stateText = this.$t('trials:uploadDicomList:label:uploadFailed')
|
||
this.scanState = 'Failed'
|
||
if (responseObj.ErrorMessage) {
|
||
this.$message.error(responseObj.ErrorMessage)
|
||
}
|
||
}
|
||
this.wariningMes = ''
|
||
} else {
|
||
uploadState.stateCode = 'Failed'
|
||
// Failed
|
||
uploadState.stateText = this.$t('trials:uploadDicomList:label:uploadFailed')
|
||
this.scanState = 'Failed'
|
||
}
|
||
this.$forceUpdate()
|
||
},
|
||
// 上传失败回调
|
||
onUploadError: function(e, index) {
|
||
this.uploadedCount++
|
||
// this.btnLoading = false
|
||
var uploadState = this.uploadQueues[index].uploadState
|
||
uploadState.stateCode = 'Failed'
|
||
uploadState.stateText = this.$t('trials:uploadDicomList:label:uploadFailed')
|
||
this.scanState = 'Failed'
|
||
},
|
||
onUploadProgress2(id, num) {
|
||
let info = this.uploadQueues.find(v => {
|
||
return v.dicomInfo.studyUid === id
|
||
})
|
||
if (info) {
|
||
// fileNum 总数
|
||
this.$set(info.uploadState, 'UpdataValue', Math.floor(num / info.dicomInfo.fileNum * 100))
|
||
this.$set(info.uploadState, 'stateText', `影像上传中`)
|
||
console.log(info.uploadState.UpdataValue)
|
||
}
|
||
},
|
||
// 上传进程
|
||
onUploadProgress: function(e, index) {
|
||
if (e.lengthComputable) {
|
||
var percent = Math.floor(e.loaded / e.total * 100)
|
||
console.log(e.loaded, e.total, percent)
|
||
// Uploading
|
||
// this.uploadQueues[index].uploadState.stateText = `${this.$t('trials:uploadDicomList:label:uploading')}...`
|
||
// this.uploadQueues[index].uploadState.progressValue = percent
|
||
this.$set(this.uploadQueues[index].uploadState, 'stateText', `影像处理中`)
|
||
|
||
this.$set(this.uploadQueues[index].uploadState, 'progressValue', percent)
|
||
// if (percent === 100) {
|
||
// this.uploadQueues[index].uploadState.progressValue = 100
|
||
// }
|
||
// this.$forceUpdate()
|
||
}
|
||
},
|
||
// 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.selectArr = []
|
||
this.uploadQueues.splice(index, 1)
|
||
})
|
||
},
|
||
// 当选择项发生变化时收集待上传文件数据
|
||
handleSelectionChange(selection) {
|
||
selection.sort((n1, n2) => {
|
||
return n1.studyIndex - n2.studyIndex
|
||
})
|
||
this.selectArr = selection
|
||
},
|
||
// 根据是否上传状态决定CheckBox 是否可以勾选
|
||
handleSelectable(row) {
|
||
if (row.uploadState.selected) {
|
||
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()
|
||
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()
|
||
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 ''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</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: #33dac1;
|
||
}
|
||
#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>
|